๐ŸŽฏ g5se ๋žœ๋ค ํŒ€ ๋นŒ๋” - ๊ณต์ •ํ•œ ํŒ€ ํŽธ์„ฑ ์ƒ์„ฑ๊ธฐ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ž…๋ฌธ PHP g5se AI: Claude

๐ŸŽฏ g5se ๋žœ๋ค ํŒ€ ๋นŒ๋” - ๊ณต์ •ํ•œ ํŒ€ ํŽธ์„ฑ ์ƒ์„ฑ๊ธฐ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… g5se-random-team-builder
์กฐํšŒ์ˆ˜ 51 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์ฒด์œก๋Œ€ํšŒ, 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์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
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="ํ™๊ธธ๋™&#10;๊น€์ฒ ์ˆ˜&#10;์ด์˜ํฌ&#10;๋ฐ•๋ฏผ์ˆ˜" 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์ด ์ ์šฉ๋œ ํŽ˜์ด์ง€ ๊ณจ๊ฒฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€์„ ๋ˆŒ๋Ÿฌ๋ณด๋ฉด ๋ฐฐ๊ฒฝ/ํ…์ŠคํŠธ ์ƒ‰์ƒ์ด ์ž๋™์œผ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.

2
Step 2
**๋ชฉํ‘œ**: ์ฐธ๊ฐ€์ž ์ž…๋ ฅ์„ ํŒŒ์‹ฑํ•˜๊ณ  ๋žœ๋ค ํŒ€ ํŽธ์„ฑ ๋กœ์ง์„ 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>
```

**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ**:
- ์ฐธ๊ฐ€์ž๋ฅผ ์ž…๋ ฅํ•˜๊ณ  "ํŒ€ ํŽธ์„ฑํ•˜๊ธฐ" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋žœ๋คํ•˜๊ฒŒ ํŒ€์ด ๋‚˜๋‰จ
- ๊ฐ ํŒ€์€ ์ƒ‰์ƒ์ด ๋‹ค๋ฅธ ์นด๋“œ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ๊ท ๋“ฑํ•˜๊ฒŒ ์ธ์›์ด ๋ฐฐ๋ถ„๋จ
- "์žฌํŽธ์„ฑ" ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๊ฐ™์€ ์ฐธ๊ฐ€์ž๋กœ ๋‹ค์‹œ ๋žœ๋ค ํŽธ์„ฑ๋จ
- ์ฐธ๊ฐ€์ž ์ˆ˜๊ฐ€ ํŒ€ ๊ฐœ์ˆ˜๋ณด๋‹ค ์ ์œผ๋ฉด ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋จ
- ํŒ€ ์นด๋“œ๊ฐ€ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋‚˜ํƒ€๋‚˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฐธ๊ฐ€์ž ๋ช…๋‹จ์„ ์ž…๋ ฅํ•˜๊ณ  ํŒ€ ํŽธ์„ฑ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, ์ž…๋ ฅ๋œ ์ธ์›์ด ์„ ํƒํ•œ ํŒ€ ๊ฐœ์ˆ˜๋กœ ๊ท ๋“ฑํ•˜๊ฒŒ ๋‚˜๋‰˜์–ด ์ƒ‰์ƒ๋ณ„ ์นด๋“œ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์žฌํŽธ์„ฑ ๋ฒ„ํŠผ์œผ๋กœ ๋‹ค์‹œ ์„ž์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3
Step 3
**๋ชฉํ‘œ**: ๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค โ€” ํŒ€ ์ด๋ฆ„ ์ž๋™ ์ƒ์„ฑ, ๊ฒฐ๊ณผ ๋ณต์‚ฌ, 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)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!