๐ฏ g5se ๋๋ค ํ ๋น๋ - ๊ณต์ ํ ํ ํธ์ฑ ์์ฑ๊ธฐ
g5se-random-team-builder
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
์ฒด์ก๋ํ, MT, ์ํฌ์, ์คํฐ๋ ๊ทธ๋ฃน ๋ฑ์์ ๊ณต์ ํ๊ณ ๋๋คํ๊ฒ ํ์ ๋๋๋ ์น ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ์ฐธ๊ฐ์ ์ด๋ฆ์ ์ ๋ ฅํ๋ฉด AI๊ฐ ์๋์ผ๋ก ๊ท ๋ฑํ ์ธ์์ ํ์ผ๋ก ๋๋์ด ์๊ฐ์ ์ผ๋ก ๋ณด์ฌ์ค๋๋ค.
์ฌ์ฉ์๋ ์ฐธ๊ฐ์ ๋ช ๋จ์ ์ ๋ ฅํ๊ณ , ์ํ๋ ํ ๊ฐ์๋ฅผ ์ ํํ๋ฉด ๋๋ค ์๊ณ ๋ฆฌ์ฆ์ด ํ์ ๊ตฌ์ฑํฉ๋๋ค. ๊ฒฐ๊ณผ๋ ์นด๋ ํํ๋ก ์์๊ฒ ํ์๋๋ฉฐ, ๋คํฌ๋ชจ๋์์๋ ์์ฐ์ค๋ฝ๊ฒ ๋์ํฉ๋๋ค.
ํ์ ๊ธฐ๋ฅ
- โ ์ฐธ๊ฐ์ ์ ๋ ฅ ํผ: ์ด๋ฆ์ ํ ์ค์ ํ๋์ฉ ์ ๋ ฅํ๊ฑฐ๋ ์ผํ๋ก ๊ตฌ๋ถํ์ฌ ์ ๋ ฅ
- โ ํ ๊ฐ์ ์ ํ: 2~10๊ฐ ํ ์ค ์ ํ (๋๋กญ๋ค์ด)
- โ ๋๋ค ํ ํธ์ฑ ๋ฒํผ: ํด๋ฆญ ์ ์ฐธ๊ฐ์๋ฅผ ๊ท ๋ฑํ๊ฒ ๋๋์ด ํ ์์ฑ
- โ ํ ๊ฒฐ๊ณผ ์นด๋: ๊ฐ ํ๋ณ๋ก ๊ตฌ๋ถ๋ ์นด๋์ ํ์ ์ด๋ฆ ๋์ด (ํ ์์ ์๋ ํ ๋น)
- โ ์ฌํธ์ฑ ๋ฒํผ: ๊ฐ์ ์ฐธ๊ฐ์๋ก ๋ค์ ๋๋ค ํธ์ฑ
- โ ๋คํฌ๋ชจ๋ ์๋ ๋์: modern shell ๋์์ธ ํ ํฐ ์ฌ์ฉ
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๐ ํ ์ด๋ฆ ์๋ ์์ฑ: "๐ฅ ํ์ดํ ํ", "โก ๋ฒ๊ฐ ํ" ๊ฐ์ ๋๋ค ํ๋ช ์๋ ๋ถ์ฌ
- ๐ ๊ฒฐ๊ณผ ๊ณต์ ๋ฒํผ: ํ ํธ์ฑ ๊ฒฐ๊ณผ๋ฅผ ํ ์คํธ๋ก ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ
- ๐ ์ฐธ๊ฐ์ ์ ์ ํ ์๋ด: ์ ๋ ฅ๋ ์ธ์์ด ํ ๊ฐ์๋ณด๋ค ์ ์ผ๋ฉด ๊ฒฝ๊ณ ๋ฉ์์ง
- ๐ ์ ๋๋ฉ์ด์ ํจ๊ณผ: ํ ํธ์ฑ ์ ์นด๋๊ฐ ๋ถ๋๋ฝ๊ฒ ๋ํ๋๋ fade-in ํจ๊ณผ
- ๐ ์ฐธ๊ฐ์ ํ์คํ ๋ฆฌ: localStorage์ ์ต๊ทผ ์ฌ์ฉํ ์ฐธ๊ฐ์ ๋ช ๋จ ์ ์ฅ (๋ค์ ๋ฐฉ๋ฌธ ์ ๋ถ๋ฌ์ค๊ธฐ ์ต์ )
๊ธฐ์ ์คํ
- ํ๋ ์์ํฌ: ๊ทธ๋๋ณด๋5 SE (g5se) - PHP ๊ธฐ๋ฐ ๊ฒฝ๋ CMS
- ๋์์ธ: modern shell ๋์์ธ ์์คํ (๋คํฌ๋ชจ๋ ์๋ ๋์)
- DB: ๋ถํ์ (๋ชจ๋ ๋ก์ง์ ํด๋ผ์ด์ธํธ JavaScript๋ก ์ฒ๋ฆฌ)
- ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ์์ (์์ HTML/CSS/JS)
ํ์ต ๋ชฉํ
- g5se์ ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ์ดํด
- modern shell์ ํ์ฉํ ์ผ๊ด๋ ์ฌ์ดํธ ๋์์ธ ์ ์ฉ
- ๋์์ธ ํ ํฐ์ ์ฌ์ฉํ ๋คํฌ๋ชจ๋ ๋์
- JavaScript๋ก ๋ฐฐ์ด ์ ํ ๋ฐ ๊ท ๋ฑ ๋ถ๋ฐฐ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ
- ์ฌ์ฉ์ ์ ๋ ฅ ๊ฒ์ฆ ๋ฐ ์๋ฌ ํธ๋ค๋ง
์ ์ฝ ์กฐ๊ฑด
- โข ๊ทธ๋๋ณด๋5 SE(g5se) ํ๋ ์์ํฌ ์ฌ์ฉ (Laravel/Composer/Vite/React/Vue ์ ๋ ๊ธ์ง)
- โข ์์ PHP + g5se ์ปจ๋ฒค์ ๋ง ์ฌ์ฉ (์ธ๋ถ ๋น๋ ๋๊ตฌ ๊ธ์ง)
- โข ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ํ์ฉ (app/modules/<name>/index.php โ /<name>)
- โข modern shell ํ์ ์ ์ฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php)
- โข ๋์์ธ ํ ํฐ ์ฌ์ฉ (--m-surface, --m-text, --m-shadow ๋ฑ) โ ๋คํฌ๋ชจ๋ ์๋ ๋์
- โข DB ์ฌ์ฉ ๊ธ์ง (๋ชจ๋ ๋ก์ง์ ํด๋ผ์ด์ธํธ JavaScript๋ก ์ฒ๋ฆฌ)
- โข ์ธ๋ถ CDN ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ธ์ง (์์ HTML/CSS/JS)
- โข ์ฌ์ฉ์ ์ ๋ ฅ์ htmlspecialchars()๋ก ์ด์ค์ผ์ดํ (XSS ๋ฐฉ์ง)
- โข ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ/ํ๋ธ๋ฆฟ/๋ฐ์คํฌํฑ ๋์)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
**๋ชฉํ**: ๋๋ค ํ ๋น๋ ํ์ด์ง์ ๊ธฐ๋ณธ ๊ณจ๊ฒฉ์ ๋ง๋ญ๋๋ค.
๋ค์ ํ์ผ์ ์์ฑํ์ธ์:
**ํ์ผ**: `app/modules/team-builder/index.php`
```php
<?php
include_once('./_common.php');
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 32px 20px 48px;">
<div style="max-width: 800px; margin: 0 auto;">
<h1 style="font-size: 28px; font-weight: 700; margin-bottom: 8px; color: var(--m-text);">๐ฏ ๋๋ค ํ ๋น๋</h1>
<p style="color: var(--m-text-muted); margin-bottom: 32px;">์ฐธ๊ฐ์๋ฅผ ๊ณต์ ํ๊ฒ ํ์ผ๋ก ๋๋์ด ๋๋ฆฝ๋๋ค</p>
<!-- ์
๋ ฅ ์น์
-->
<div style="background: var(--m-surface); padding: 24px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); margin-bottom: 24px;">
<label style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--m-text);">์ฐธ๊ฐ์ ๋ช
๋จ (ํ ์ค์ ํ ๋ช
์ฉ)</label>
<textarea id="participants" rows="8" placeholder="ํ๊ธธ๋ ๊น์ฒ ์ ์ด์ํฌ ๋ฐ๋ฏผ์" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; font-family: inherit; resize: vertical; background: var(--m-surface); color: var(--m-text);"></textarea>
<div style="margin-top: 16px; display: flex; gap: 16px; align-items: center;">
<div style="flex: 1;">
<label style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--m-text);">ํ ๊ฐ์</label>
<select id="teamCount" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; background: var(--m-surface); color: var(--m-text);">
<option value="2">2๊ฐ ํ</option>
<option value="3">3๊ฐ ํ</option>
<option value="4" selected>4๊ฐ ํ</option>
<option value="5">5๊ฐ ํ</option>
<option value="6">6๊ฐ ํ</option>
<option value="8">8๊ฐ ํ</option>
<option value="10">10๊ฐ ํ</option>
</select>
</div>
<div style="flex: 1; display: flex; align-items: flex-end;">
<button id="buildTeams" style="width: 100%; padding: 12px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.2s;">
๐ฒ ํ ํธ์ฑํ๊ธฐ
</button>
</div>
</div>
</div>
<!-- ๊ฒฐ๊ณผ ์น์
-->
<div id="result" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2 style="font-size: 20px; font-weight: 700; color: var(--m-text);">ํ ํธ์ฑ ๊ฒฐ๊ณผ</h2>
<button id="reshuffleBtn" style="padding: 8px 16px; background: var(--m-surface); color: var(--m-text); border: 1px solid #ddd; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer;">
๐ ์ฌํธ์ฑ
</button>
</div>
<div id="teams" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">
<!-- ํ ์นด๋๊ฐ ์ฌ๊ธฐ ๋ค์ด๊ฐ -->
</div>
</div>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<style>
#buildTeams:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
#reshuffleBtn:hover {
background: #f0f0f0;
}
@media (prefers-color-scheme: dark) {
#participants, #teamCount {
border-color: #444;
}
#reshuffleBtn:hover {
background: #333;
}
}
</style>
<script>
// JavaScript ๋ก์ง์ ๋ค์ ๋จ๊ณ์์ ์ถ๊ฐ
console.log('ํ ๋น๋ ํ์ด์ง ๋ก๋๋จ');
</script>
<?php include_once('./tail.sub.php'); ?>
```
**๊ธฐ๋ ๊ฒฐ๊ณผ**:
- `http://your-domain/team-builder` URL๋ก ์ ์ํ๋ฉด ํ์ด์ง๊ฐ ํ์๋จ
- modern shell์ด ์ ์ฉ๋์ด ์ฌ์ดํธ nav/footer/๋คํฌ๋ชจ๋ ํ ๊ธ์ด ์๋์ผ๋ก ๋ํ๋จ
- ์ฐธ๊ฐ์ ์
๋ ฅ ํผ๊ณผ ํ ๊ฐ์ ์ ํ ๋๋กญ๋ค์ด์ด ๋ณด์
- "ํ ํธ์ฑํ๊ธฐ" ๋ฒํผ์ด ์์ง๋ง ์์ง ๋์ํ์ง ์์
๊ธฐ๋ ๊ฒฐ๊ณผ: ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ์ผ๋ก `/team-builder` URL์ด ์์ฑ๋๊ณ , modern shell์ด ์ ์ฉ๋ ํ์ด์ง ๊ณจ๊ฒฉ์ด ํ์๋ฉ๋๋ค. ๋คํฌ๋ชจ๋ ํ ๊ธ์ ๋๋ฌ๋ณด๋ฉด ๋ฐฐ๊ฒฝ/ํ ์คํธ ์์์ด ์๋์ผ๋ก ์ ํ๋ฉ๋๋ค.
**๋ชฉํ**: ์ฐธ๊ฐ์ ์
๋ ฅ์ ํ์ฑํ๊ณ ๋๋ค ํ ํธ์ฑ ๋ก์ง์ JavaScript๋ก ๊ตฌํํฉ๋๋ค.
`app/modules/team-builder/index.php` ํ์ผ์ `<script>` ๋ธ๋ก์ ๋ค์๊ณผ ๊ฐ์ด ๊ต์ฒดํ์ธ์:
```javascript
<script>
const participantsInput = document.getElementById('participants');
const teamCountSelect = document.getElementById('teamCount');
const buildBtn = document.getElementById('buildTeams');
const reshuffleBtn = document.getElementById('reshuffleBtn');
const resultDiv = document.getElementById('result');
const teamsContainer = document.getElementById('teams');
let currentParticipants = [];
// ๋ฐฐ์ด ์
ํ (Fisher-Yates)
function shuffleArray(array) {
const arr = [...array];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// ํ ์์ ํ๋ ํธ
const teamColors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A',
'#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2',
'#F8B88B', '#A8E6CF'
];
// ํ ํธ์ฑ ํจ์
function buildTeams() {
const input = participantsInput.value.trim();
if (!input) {
alert('์ฐธ๊ฐ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์!');
return;
}
// ์ฐธ๊ฐ์ ํ์ฑ (์ค๋ฐ๊ฟ ๋๋ ์ผํ๋ก ๊ตฌ๋ถ)
const participants = input.split(/[\n,]+/)
.map(name => name.trim())
.filter(name => name.length > 0);
if (participants.length === 0) {
alert('์ ํจํ ์ฐธ๊ฐ์๊ฐ ์์ต๋๋ค!');
return;
}
const teamCount = parseInt(teamCountSelect.value);
if (participants.length < teamCount) {
alert(`์ฐธ๊ฐ์ ์(${participants.length}๋ช
)๊ฐ ํ ๊ฐ์(${teamCount}๊ฐ)๋ณด๋ค ์ ์ต๋๋ค!`);
return;
}
currentParticipants = participants;
distributeTeams(participants, teamCount);
}
// ํ ๋ถ๋ฐฐ ๋ฐ ๋ ๋๋ง
function distributeTeams(participants, teamCount) {
const shuffled = shuffleArray(participants);
const teams = Array.from({ length: teamCount }, () => []);
// ๊ท ๋ฑ ๋ถ๋ฐฐ
shuffled.forEach((participant, index) => {
teams[index % teamCount].push(participant);
});
renderTeams(teams);
resultDiv.style.display = 'block';
resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// ํ ์นด๋ ๋ ๋๋ง
function renderTeams(teams) {
teamsContainer.innerHTML = teams.map((team, index) => {
const color = teamColors[index % teamColors.length];
return `
<div style="background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); padding: 20px; border-top: 4px solid ${color}; animation: fadeIn 0.3s ease-out;">
<h3 style="font-size: 18px; font-weight: 700; margin-bottom: 12px; color: var(--m-text); display: flex; align-items: center; gap: 8px;">
<span style="width: 12px; height: 12px; border-radius: 50%; background: ${color};"></span>
ํ ${index + 1}
</h3>
<div style="color: var(--m-text-muted); font-size: 14px; line-height: 1.8;">
${team.map(name => `<div>โข ${name}</div>`).join('')}
</div>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee; font-size: 12px; color: var(--m-text-muted);">
๐ฅ ${team.length}๋ช
</div>
</div>
`;
}).join('');
}
// ์ฌํธ์ฑ
function reshuffle() {
const teamCount = parseInt(teamCountSelect.value);
distributeTeams(currentParticipants, teamCount);
}
buildBtn.addEventListener('click', buildTeams);
reshuffleBtn.addEventListener('click', reshuffle);
// Enter ํค๋ก๋ ํ ํธ์ฑ ๊ฐ๋ฅ
participantsInput.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
buildTeams();
}
});
</script>
<style>
/* ๊ธฐ์กด style ๋ธ๋ก์ ์ถ๊ฐ */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-color-scheme: dark) {
#teams > div {
border-top-width: 4px;
}
#teams > div > div:last-child {
border-top-color: #444;
}
}
</style>
```
**๊ธฐ๋ ๊ฒฐ๊ณผ**:
- ์ฐธ๊ฐ์๋ฅผ ์
๋ ฅํ๊ณ "ํ ํธ์ฑํ๊ธฐ" ๋ฒํผ์ ๋๋ฅด๋ฉด ๋๋คํ๊ฒ ํ์ด ๋๋จ
- ๊ฐ ํ์ ์์์ด ๋ค๋ฅธ ์นด๋๋ก ํ์๋๋ฉฐ, ๊ท ๋ฑํ๊ฒ ์ธ์์ด ๋ฐฐ๋ถ๋จ
- "์ฌํธ์ฑ" ๋ฒํผ์ ๋๋ฅด๋ฉด ๊ฐ์ ์ฐธ๊ฐ์๋ก ๋ค์ ๋๋ค ํธ์ฑ๋จ
- ์ฐธ๊ฐ์ ์๊ฐ ํ ๊ฐ์๋ณด๋ค ์ ์ผ๋ฉด ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ํ์๋จ
- ํ ์นด๋๊ฐ ๋ถ๋๋ฝ๊ฒ ๋ํ๋๋ ์ ๋๋ฉ์ด์
ํจ๊ณผ
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ฐธ๊ฐ์ ๋ช ๋จ์ ์ ๋ ฅํ๊ณ ํ ํธ์ฑ ๋ฒํผ์ ๋๋ฅด๋ฉด, ์ ๋ ฅ๋ ์ธ์์ด ์ ํํ ํ ๊ฐ์๋ก ๊ท ๋ฑํ๊ฒ ๋๋์ด ์์๋ณ ์นด๋๋ก ํ์๋ฉ๋๋ค. ์ฌํธ์ฑ ๋ฒํผ์ผ๋ก ๋ค์ ์์ ์ ์์ต๋๋ค.
**๋ชฉํ**: ๋ณด๋์ค ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค โ ํ ์ด๋ฆ ์๋ ์์ฑ, ๊ฒฐ๊ณผ ๋ณต์ฌ, localStorage ํ์คํ ๋ฆฌ.
`app/modules/team-builder/index.php` ํ์ผ์ `<script>` ๋ธ๋ก์ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํ์ธ์ (๊ธฐ์กด ์ฝ๋ ์ ์งํ๊ณ ์ถ๊ฐ):
```javascript
<script>
// ๊ธฐ์กด ์ฝ๋๋ ๊ทธ๋๋ก ๋๊ณ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐ
// ํ ์ด๋ฆ ํ
const teamNames = [
'๐ฅ ํ์ดํ
', 'โก ๋ฒ๊ฐ', '๐ ์คํ', '๐ ๋ก์ผ',
'๐ ๋ค์ด์๋ชฌ๋', '๐ฆ ๋ผ์ด์จ', '๐ ๋๋๊ณค', '๐ฆ
์ด๊ธ',
'๐ ์จ์ด๋ธ', '๐ฏ ํ๊ฒ'
];
// ํ ์ด๋ฆ ์
ํํด์ ํ ๋น
function getTeamNames(count) {
const shuffled = shuffleArray(teamNames);
return shuffled.slice(0, count);
}
// renderTeams ํจ์๋ฅผ ์์ (ํ ์ด๋ฆ ์ถ๊ฐ)
function renderTeams(teams) {
const names = getTeamNames(teams.length);
teamsContainer.innerHTML = teams.map((team, index) => {
const color = teamColors[index % teamColors.length];
const teamName = names[index];
return `
<div style="background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); padding: 20px; border-top: 4px solid ${color}; animation: fadeIn 0.3s ease-out;">
<h3 style="font-size: 18px; font-weight: 700; margin-bottom: 4px; color: var(--m-text); display: flex; align-items: center; gap: 8px;">
<span style="width: 12px; height: 12px; border-radius: 50%; background: ${color};"></span>
${teamName} ํ
</h3>
<div style="font-size: 12px; color: var(--m-text-muted); margin-bottom: 12px;">Team ${index + 1}</div>
<div style="color: var(--m-text-muted); font-size: 14px; line-height: 1.8;">
${team.map(name => `<div>โข ${name}</div>`).join('')}
</div>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee; font-size: 12px; color: var(--m-text-muted);">
๐ฅ ${team.length}๋ช
</div>
</div>
`;
}).join('');
}
// ๊ฒฐ๊ณผ ๋ณต์ฌ ๋ฒํผ ์ถ๊ฐ
const copyBtn = document.createElement('button');
copyBtn.id = 'copyResult';
copyBtn.textContent = '๐ ๊ฒฐ๊ณผ ๋ณต์ฌ';
copyBtn.style.cssText = 'padding: 8px 16px; background: var(--m-surface); color: var(--m-text); border: 1px solid #ddd; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; margin-left: 8px;';
reshuffleBtn.parentElement.appendChild(copyBtn);
// ๊ฒฐ๊ณผ๋ฅผ ํ
์คํธ๋ก ๋ณํ
function getResultText(teams) {
const names = getTeamNames(teams.length);
return teams.map((team, index) => {
return `${names[index]} ํ (${team.length}๋ช
):\n${team.map(name => ` โข ${name}`).join('\n')}`;
}).join('\n\n');
}
// ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ
copyBtn.addEventListener('click', () => {
const teamCount = parseInt(teamCountSelect.value);
const shuffled = shuffleArray(currentParticipants);
const teams = Array.from({ length: teamCount }, () => []);
shuffled.forEach((participant, index) => {
teams[index % teamCount].push(participant);
});
const text = getResultText(teams);
navigator.clipboard.writeText(text).then(() => {
const originalText = copyBtn.textContent;
copyBtn.textContent = 'โ
๋ณต์ฌ๋จ!';
setTimeout(() => {
copyBtn.textContent = originalText;
}, 2000);
}).catch(() => {
alert('๋ณต์ฌ ์คํจ! ๋ธ๋ผ์ฐ์ ๊ฐ ํด๋ฆฝ๋ณด๋ ์ ๊ทผ์ ์ง์ํ์ง ์์ต๋๋ค.');
});
});
// localStorage์ ์ฐธ๊ฐ์ ์ ์ฅ
function saveParticipants() {
if (currentParticipants.length > 0) {
localStorage.setItem('teamBuilderHistory', JSON.stringify(currentParticipants));
}
}
// ํ์ด์ง ๋ก๋ ์ ํ์คํ ๋ฆฌ ๋ถ๋ฌ์ค๊ธฐ
window.addEventListener('load', () => {
const history = localStorage.getItem('teamBuilderHistory');
if (history) {
const participants = JSON.parse(history);
participantsInput.value = participants.join('\n');
}
});
// buildTeams ํจ์ ์์ (์ ์ฅ ์ถ๊ฐ)
const originalBuildTeams = buildTeams;
function buildTeams() {
originalBuildTeams();
saveParticipants();
}
</script>
```
๋ํ `<style>` ๋ธ๋ก์ hover ํจ๊ณผ๋ฅผ ์ถ๊ฐํ์ธ์:
```css
#copyResult:hover {
background: #f0f0f0;
}
@media (prefers-color-scheme: dark) {
#copyResult:hover {
background: #333;
}
}
```
**๊ธฐ๋ ๊ฒฐ๊ณผ**:
- ๊ฐ ํ์ "๐ฅ ํ์ดํ
ํ", "โก ๋ฒ๊ฐ ํ" ๊ฐ์ ๋๋ค ์ด๋ฆ์ด ์๋ ๋ถ์ฌ๋จ
- "๐ ๊ฒฐ๊ณผ ๋ณต์ฌ" ๋ฒํผ์ ๋๋ฅด๋ฉด ํ ํธ์ฑ ๊ฒฐ๊ณผ๊ฐ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ๋จ
- ์ฐธ๊ฐ์ ๋ช
๋จ์ด localStorage์ ์๋ ์ ์ฅ๋์ด ๋ค์ ๋ฐฉ๋ฌธ ์ ๋ถ๋ฌ์์ง
- ๋คํฌ๋ชจ๋์์๋ ๋ชจ๋ ๋ฒํผ์ด ์์ฐ์ค๋ฝ๊ฒ ๋์ํจ
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ๋ง๋ค ๋๋ค ์ด๋ฆ์ด ๋ถ๊ณ , ๊ฒฐ๊ณผ ๋ณต์ฌ ๋ฒํผ์ผ๋ก ํ ์คํธ๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํ ์ ์์ต๋๋ค. ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํด๋ ์ด์ ์ ์ ๋ ฅํ ์ฐธ๊ฐ์ ๋ช ๋จ์ด ์๋์ผ๋ก ๋ถ๋ฌ์์ง๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!