๐ŸŽฏ g5se ์˜ค๋Š˜์˜ ์ƒ‰์ƒ ์‹ฌ๋ฆฌ ํ…Œ์ŠคํŠธ - ๋‚˜๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ปฌ๋Ÿฌ ์ฐพ๊ธฐ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ž…๋ฌธ PHP g5se AI: Claude

๐ŸŽฏ g5se ์˜ค๋Š˜์˜ ์ƒ‰์ƒ ์‹ฌ๋ฆฌ ํ…Œ์ŠคํŠธ - ๋‚˜๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ปฌ๋Ÿฌ ์ฐพ๊ธฐ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… g5se-color-psychology-quiz
์กฐํšŒ์ˆ˜ 75 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐ŸŽจ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ„๋‹จํ•œ ์งˆ๋ฌธ์— ๋‹ตํ•˜๋ฉด ์„ฑ๊ฒฉ์— ๋งž๋Š” ์ƒ‰์ƒ์„ ์ถ”์ฒœํ•ด์ฃผ๋Š” ์‹ฌ๋ฆฌ ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. g5se์˜ modern shell์„ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์ดํŠธ ์ „์ฒด ๋””์ž์ธ๊ณผ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉฐ, ๋‹คํฌ๋ชจ๋“œ์—์„œ๋„ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

โœจ ํ•„์ˆ˜ ๊ธฐ๋Šฅ

  • 5๊ฐ€์ง€ ์‹ฌ๋ฆฌ ์งˆ๋ฌธ: ์„ฑ๊ฒฉ, ์„ ํ˜ธ๋„, ๊ธฐ๋ถ„ ๋“ฑ์„ ๋ฌป๋Š” ๊ฐ๊ด€์‹ ์งˆ๋ฌธ (๊ฐ 4๊ฐœ ์„ ํƒ์ง€)
  • ๊ฒฐ๊ณผ ํŽ˜์ด์ง€: ์„ ํƒ์— ๋”ฐ๋ผ ์ถ”์ฒœ ์ƒ‰์ƒ ํ‘œ์‹œ (์ƒ‰์ƒ ๊ฒฌ๋ณธ + ์‹ฌ๋ฆฌ ํ•ด์„ ํ…์ŠคํŠธ)
  • ๊ฒฐ๊ณผ ๊ณต์œ ํ•˜๊ธฐ: ๊ฒฐ๊ณผ URL ๋ณต์‚ฌ ๋ฒ„ํŠผ (์ฟผ๋ฆฌ์ŠคํŠธ๋ง์œผ๋กœ ๊ฒฐ๊ณผ ์ „๋‹ฌ)
  • ๋‹ค์‹œ ํ•˜๊ธฐ ๋ฒ„ํŠผ: ํ…Œ์ŠคํŠธ ์ดˆ๊ธฐํ™”
  • ๊ฒฐ๊ณผ ํžˆ์Šคํ† ๋ฆฌ: SQLite์— ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์ €์žฅ (์ƒ‰์ƒ๋ช…, ํƒ€์ž„์Šคํƒฌํ”„, IP)
  • ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„: ๊ฐ€์žฅ ๋งŽ์ด ๋‚˜์˜จ ๊ฒฐ๊ณผ TOP 3 ํ‘œ์‹œ (๋ฉ”์ธ ํŽ˜์ด์ง€ ํ•˜๋‹จ)

๐ŸŽ ์„ ํƒ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (๋ณด๋„ˆ์Šค)

  • ๊ฒฐ๊ณผ ์ƒ‰์ƒ์œผ๋กœ ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ ์šฉ
  • SNS ๊ณต์œ  ๋ฉ”ํƒ€ํƒœ๊ทธ (Open Graph) ์ถ”๊ฐ€
  • ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ (HTML2Canvas)
  • ์ƒ‰์ƒ๋ณ„ ์–ด์šธ๋ฆฌ๋Š” ์Œ์•… ์žฅ๋ฅด ์ถ”์ฒœ
  • ๊ณ„์ ˆ๋ณ„ ์ถ”์ฒœ ์ƒ‰์ƒ ํ•„ํ„ฐ๋ง

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • g5se ํด๋” ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ… ์ดํ•ด (app/modules/color-quiz/index.php)
  • modern shell ์ ์šฉ์œผ๋กœ ์‚ฌ์ดํŠธ ์ผ๊ด€์„ฑ ์œ ์ง€
  • SQLite๋ฅผ ํ™œ์šฉํ•œ ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ
  • PHP ์กฐ๊ฑด๋ฌธ์œผ๋กœ ์งˆ๋ฌธ-๊ฒฐ๊ณผ ๋กœ์ง ๊ตฌํ˜„
  • CSS ๋ณ€์ˆ˜๋ฅผ ํ™œ์šฉํ•œ ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘
  • ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ๊ธฐ๋ฐ˜ ๊ฒฐ๊ณผ ๊ณต์œ  ๊ตฌํ˜„

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข g5se ์„ค์น˜ ํ•„์ˆ˜ (https://github.com/gnuboard/g5se main ๋ธŒ๋žœ์น˜)
  • โ€ข app/modules/color-quiz/index.php ๋‹จ์ผ ํŒŒ์ผ๋กœ ๊ตฌํ˜„
  • โ€ข modern shell (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php) ํ•„์ˆ˜ ์‚ฌ์šฉ
  • โ€ข SQLite DB๋Š” G5_DATA_PATH.'/modules/color-quiz.sqlite'์— ์ €์žฅ
  • โ€ข ๋ผ์šฐํŠธ ์ˆ˜๋™ ๋“ฑ๋ก ๊ธˆ์ง€ (ํด๋” ์ž๋™ ๋งคํ•‘ ํ™œ์šฉ)
  • โ€ข ์™ธ๋ถ€ ๋นŒ๋“œ ๋„๊ตฌ(npm/webpack) ์‚ฌ์šฉ ๊ธˆ์ง€
  • โ€ข CDN ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ์ตœ์†Œํ™” (์ˆœ์ˆ˜ HTML/CSS/JS ๊ถŒ์žฅ)
  • โ€ข ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘์„ ์œ„ํ•ด var(--m-surface), var(--m-text) ๋“ฑ CSS ๋ณ€์ˆ˜ ์‚ฌ์šฉ
  • โ€ข XSS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด htmlspecialchars() ํ•„์ˆ˜ ์ ์šฉ
  • โ€ข prepared statement๋กœ SQL ์ธ์ ์…˜ ๋ฐฉ์ง€

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
g5se๊ฐ€ ์„ค์น˜๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ(๋ฃจํŠธ์— `index.php`ยท`app/`์ด ์žˆ๋Š” ๊ณณ)์—์„œ ์ž‘์—…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. (g5se ์„ค์น˜๋Š” https://github.com/gnuboard/g5se ์ฐธ๊ณ )

**1๋‹จ๊ณ„: ์ƒ‰์ƒ ์‹ฌ๋ฆฌ ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€ ๊ธฐ๋ณธ ๊ณจ๊ฒฉ ์ƒ์„ฑ**

`app/modules/color-quiz/index.php` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

// SQLite DB ์ดˆ๊ธฐํ™”
$db_dir = G5_DATA_PATH.'/modules';
@mkdir($db_dir, 0707, true);
$db_path = $db_dir.'/color-quiz.sqlite';
$pdo = new PDO('sqlite:'.$db_path);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ…Œ์ด๋ธ” ์ƒ์„ฑ (์ตœ์ดˆ 1ํšŒ)
$pdo->exec("
    CREATE TABLE IF NOT EXISTS quiz_results (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        color_name TEXT NOT NULL,
        ip_address TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
");

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: 700px; margin: 0 auto;">
            <h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">๐ŸŽจ ์˜ค๋Š˜์˜ ์ƒ‰์ƒ ์‹ฌ๋ฆฌ ํ…Œ์ŠคํŠธ</h1>
            <p style="color: var(--m-text-muted); margin-bottom: 32px;">5๊ฐ€์ง€ ์งˆ๋ฌธ์œผ๋กœ ๋‚˜๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ƒ‰์ƒ์„ ์ฐพ์•„๋ณด์„ธ์š”!</p>
            
            <div id="quiz-container">
                <!-- ์งˆ๋ฌธ์ด ์—ฌ๊ธฐ์— ๋™์ ์œผ๋กœ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค -->
            </div>
            
            <div id="result-container" style="display: none;">
                <!-- ๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค -->
            </div>
            
            <div style="margin-top: 48px; padding: 24px; background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
                <h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 16px; color: var(--m-text);">๐Ÿ† ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„</h3>
                <div id="popular-colors">
                    <!-- ์ธ๊ธฐ ์ƒ‰์ƒ TOP 3์ด ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค -->
                </div>
            </div>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

์ด ํŒŒ์ผ์„ ์ €์žฅํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ `/color-quiz`๋กœ ์ ‘์†ํ•˜์—ฌ ํŽ˜์ด์ง€๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋กœ๋“œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. modern shell์ด ์ ์šฉ๋˜์–ด ๋„ค๋น„๊ฒŒ์ด์…˜, ํ‘ธํ„ฐ, ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€์ด ์ž๋™์œผ๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - `/color-quiz` URL๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ - modern shell์ด ์ ์šฉ๋˜์–ด ์‚ฌ์ดํŠธ ์ „์ฒด ๋””์ž์ธ๊ณผ ์ผ๊ด€์„ฑ ์œ ์ง€ - SQLite DB ํŒŒ์ผ์ด `data/modules/color-quiz.sqlite`์— ์ž๋™ ์ƒ์„ฑ - quiz_results ํ…Œ์ด๋ธ”์ด ์ž๋™ ์ƒ์„ฑ - ํŽ˜์ด์ง€ ์ œ๋ชฉ๊ณผ ์„ค๋ช…์ด ํ‘œ์‹œ๋จ - ๋‹คํฌ๋ชจ๋“œ ์ „ํ™˜ ์‹œ ๋ฐฐ๊ฒฝ/ํ…์ŠคํŠธ ์ƒ‰์ƒ์ด ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝ๋จ

2
Step 2
**2๋‹จ๊ณ„: ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋ฐ ๋ Œ๋”๋ง ๋กœ์ง ๊ตฌํ˜„**

`app/modules/color-quiz/index.php` ํŒŒ์ผ์˜ ์ƒ๋‹จ(SQLite ์ดˆ๊ธฐํ™” ์งํ›„)์— ์งˆ๋ฌธ ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ , ํ•˜๋‹จ์— JavaScript๋กœ ์งˆ๋ฌธ์„ ๋™์  ๋ Œ๋”๋งํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:

PHP ๋ถ€๋ถ„ (ํŒŒ์ผ ์ƒ๋‹จ, `$pdo->exec()` ์งํ›„์— ์ถ”๊ฐ€):
```php
// ์งˆ๋ฌธ ๋ฐ์ดํ„ฐ (5๊ฐœ ์งˆ๋ฌธ, ๊ฐ 4๊ฐœ ์„ ํƒ์ง€)
$questions = [
    [
        'id' => 1,
        'question' => '์ฃผ๋ง์— ๊ฐ€์žฅ ํ•˜๊ณ  ์‹ถ์€ ํ™œ๋™์€?',
        'options' => [
            ['text' => '์กฐ์šฉํ•œ ์นดํŽ˜์—์„œ ์ฑ… ์ฝ๊ธฐ', 'point' => 'blue'],
            ['text' => '์นœ๊ตฌ๋“ค๊ณผ ํ™œ๊ธฐ์ฐฌ ํŒŒํ‹ฐ', 'point' => 'red'],
            ['text' => '์ž์—ฐ ์† ์‚ฐ์ฑ…', 'point' => 'green'],
            ['text' => '์ง‘์—์„œ ์˜ํ™” ๋งˆ๋ผํ†ค', 'point' => 'purple']
        ]
    ],
    [
        'id' => 2,
        'question' => '์ŠคํŠธ๋ ˆ์Šค ๋ฐ›์„ ๋•Œ ์–ด๋–ป๊ฒŒ ํ•ด์†Œํ•˜๋‚˜์š”?',
        'options' => [
            ['text' => '์šด๋™์œผ๋กœ ๋•€ ํ˜๋ฆฌ๊ธฐ', 'point' => 'red'],
            ['text' => '๋ช…์ƒ์ด๋‚˜ ์š”๊ฐ€', 'point' => 'blue'],
            ['text' => '๋ง›์žˆ๋Š” ์Œ์‹ ๋จน๊ธฐ', 'point' => 'orange'],
            ['text' => '์Œ์•… ๋“ฃ๊ธฐ', 'point' => 'purple']
        ]
    ],
    [
        'id' => 3,
        'question' => '๊ฐ€์žฅ ์ข‹์•„ํ•˜๋Š” ๊ณ„์ ˆ์€?',
        'options' => [
            ['text' => '๋ด„ (๋”ฐ๋œปํ•˜๊ณ  ํ™”์‚ฌํ•œ)', 'point' => 'pink'],
            ['text' => '์—ฌ๋ฆ„ (๋œจ๊ฒ๊ณ  ์—ด์ •์ ์ธ)', 'point' => 'orange'],
            ['text' => '๊ฐ€์„ (์ฐจ๋ถ„ํ•˜๊ณ  ๊ฐ์„ฑ์ ์ธ)', 'point' => 'brown'],
            ['text' => '๊ฒจ์šธ (๊ณ ์š”ํ•˜๊ณ  ํ‰ํ™”๋กœ์šด)', 'point' => 'blue']
        ]
    ],
    [
        'id' => 4,
        'question' => '์นœ๊ตฌ๋“ค์ด ๋‚˜๋ฅผ ์–ด๋–ป๊ฒŒ ํ‘œํ˜„ํ• ๊นŒ์š”?',
        'options' => [
            ['text' => '๋ฐ๊ณ  ๊ธ์ •์ ์ธ ์‚ฌ๋žŒ', 'point' => 'yellow'],
            ['text' => '์นจ์ฐฉํ•˜๊ณ  ์‹ ์ค‘ํ•œ ์‚ฌ๋žŒ', 'point' => 'blue'],
            ['text' => '์—ด์ •์ ์ด๊ณ  ํ™œ๋ฐœํ•œ ์‚ฌ๋žŒ', 'point' => 'red'],
            ['text' => '์ฐฝ์˜์ ์ด๊ณ  ๋…ํŠนํ•œ ์‚ฌ๋žŒ', 'point' => 'purple']
        ]
    ],
    [
        'id' => 5,
        'question' => '์ด์ƒ์ ์ธ ํœด๊ฐ€์ง€๋Š”?',
        'options' => [
            ['text' => 'ํ•œ์ ํ•œ ์‹œ๊ณจ ๋งˆ์„', 'point' => 'green'],
            ['text' => '๋ถ์ ์ด๋Š” ๋Œ€๋„์‹œ', 'point' => 'red'],
            ['text' => '๊ณ ์š”ํ•œ ํ•ด๋ณ€', 'point' => 'blue'],
            ['text' => '์—ญ์‚ฌ์ ์ธ ์œ ์ ์ง€', 'point' => 'brown']
        ]
    ]
];

// ์ƒ‰์ƒ๋ณ„ ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ
$color_results = [
    'blue' => ['name' => '๋ธ”๋ฃจ (ํ‰ํ™”)', 'hex' => '#4A90E2', 'desc' => '์ฐจ๋ถ„ํ•˜๊ณ  ์‹ ์ค‘ํ•œ ๋‹น์‹ ์€ ํ‰ํ™”๋กœ์šด ๋ธ”๋ฃจ์ž…๋‹ˆ๋‹ค. ๊นŠ์€ ์ƒ๊ฐ๊ณผ ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ์‚ฌ์ƒ‰๊ฐ€ ํƒ€์ž…์ด์—์š”.'],
    'red' => ['name' => '๋ ˆ๋“œ (์—ด์ •)', 'hex' => '#E74C3C', 'desc' => '์—๋„ˆ์ง€ ๋„˜์น˜๊ณ  ํ™œ๋ฐœํ•œ ๋‹น์‹ ์€ ์—ด์ •์ ์ธ ๋ ˆ๋“œ์ž…๋‹ˆ๋‹ค. ๋„์ „์„ ์ฆ๊ธฐ๊ณ  ๋ฆฌ๋”์‹ญ์ด ๊ฐ•ํ•œ ํƒ€์ž…์ด์—์š”.'],
    'green' => ['name' => '๊ทธ๋ฆฐ (์ž์—ฐ)', 'hex' => '#27AE60', 'desc' => '์ž์—ฐ์„ ์‚ฌ๋ž‘ํ•˜๊ณ  ๊ท ํ˜•์„ ์ค‘์‹œํ•˜๋Š” ๋‹น์‹ ์€ ๊ทธ๋ฆฐ์ž…๋‹ˆ๋‹ค. ์กฐํ™”๋กญ๊ณ  ์•ˆ์ •์ ์ธ ์„ฑ๊ฒฉ์˜ ์†Œ์œ ์ž์˜ˆ์š”.'],
    'purple' => ['name' => 'ํผํ”Œ (์ฐฝ์˜)', 'hex' => '#9B59B6', 'desc' => '์ฐฝ์˜์ ์ด๊ณ  ๋…์ฐฝ์ ์ธ ๋‹น์‹ ์€ ์‹ ๋น„๋กœ์šด ํผํ”Œ์ž…๋‹ˆ๋‹ค. ์˜ˆ์ˆ ์  ๊ฐ๊ฐ๊ณผ ์ƒ์ƒ๋ ฅ์ด ํ’๋ถ€ํ•œ ํƒ€์ž…์ด์—์š”.'],
    'yellow' => ['name' => '์˜๋กœ์šฐ (ํ™œ๊ธฐ)', 'hex' => '#F39C12', 'desc' => '๋ฐ๊ณ  ๊ธ์ •์ ์ธ ๋‹น์‹ ์€ ํ–‰๋ณตํ•œ ์˜๋กœ์šฐ์ž…๋‹ˆ๋‹ค. ์ฃผ๋ณ€์„ ์ฆ๊ฒ๊ฒŒ ๋งŒ๋“œ๋Š” ์—๋„ˆ์ง€๋ฅผ ๊ฐ€์ง„ ํƒ€์ž…์ด์—์š”.'],
    'orange' => ['name' => '์˜ค๋ Œ์ง€ (์—ด์ •)', 'hex' => '#E67E22', 'desc' => '๋”ฐ๋œปํ•˜๊ณ  ์‚ฌ๊ต์ ์ธ ๋‹น์‹ ์€ ํ™œ๊ธฐ์ฐฌ ์˜ค๋ Œ์ง€์ž…๋‹ˆ๋‹ค. ์‚ฌ๋žŒ๋“ค๊ณผ ์–ด์šธ๋ฆฌ๊ธธ ์ข‹์•„ํ•˜๋Š” ํƒ€์ž…์ด์—์š”.'],
    'pink' => ['name' => 'ํ•‘ํฌ (์˜จํ™”)', 'hex' => '#EC87C0', 'desc' => '๋ถ€๋“œ๋Ÿฝ๊ณ  ๋ฐฐ๋ ค์‹ฌ ๊นŠ์€ ๋‹น์‹ ์€ ์‚ฌ๋ž‘์Šค๋Ÿฌ์šด ํ•‘ํฌ์ž…๋‹ˆ๋‹ค. ๊ฐ์„ฑ์ ์ด๊ณ  ๋”ฐ๋œปํ•œ ์„ฑ๊ฒฉ์˜ ์†Œ์œ ์ž์˜ˆ์š”.'],
    'brown' => ['name' => '๋ธŒ๋ผ์šด (์•ˆ์ •)', 'hex' => '#8B6F47', 'desc' => 'ํ˜„์‹ค์ ์ด๊ณ  ๋ฏฟ์Œ์งํ•œ ๋‹น์‹ ์€ ์•ˆ์ •์ ์ธ ๋ธŒ๋ผ์šด์ž…๋‹ˆ๋‹ค. ์ฑ…์ž„๊ฐ์ด ๊ฐ•ํ•˜๊ณ  ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด์—์š”.']
];

// JSON์œผ๋กœ ์ „๋‹ฌ
$questions_json = json_encode($questions, JSON_UNESCAPED_UNICODE);
$colors_json = json_encode($color_results, JSON_UNESCAPED_UNICODE);
```

JavaScript ๋ถ€๋ถ„ (ํŒŒ์ผ ํ•˜๋‹จ, `</main>` ์ง์ „์— ์ถ”๊ฐ€):
```php
<script>
const questions = <?= $questions_json ?>;
const colorResults = <?= $colors_json ?>;
let currentQuestion = 0;
let answers = [];

function renderQuestion() {
    const container = document.getElementById('quiz-container');
    const q = questions[currentQuestion];
    
    container.innerHTML = `
        <div style="background: var(--m-surface); padding: 32px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
            <div style="margin-bottom: 24px;">
                <span style="color: var(--m-text-muted); font-size: 0.875rem;">์งˆ๋ฌธ ${currentQuestion + 1} / ${questions.length}</span>
                <div style="width: 100%; height: 4px; background: var(--m-text-muted); opacity: 0.2; border-radius: 2px; margin-top: 8px;">
                    <div style="width: ${((currentQuestion + 1) / questions.length) * 100}%; height: 100%; background: #4A90E2; border-radius: 2px; transition: width 0.3s;"></div>
                </div>
            </div>
            <h2 style="font-size: 1.5rem; font-weight: 600; margin-bottom: 24px; color: var(--m-text);">${q.question}</h2>
            <div style="display: grid; gap: 12px;">
                ${q.options.map((opt, idx) => `
                    <button onclick="selectAnswer('${opt.point}')" 
                        style="padding: 16px 20px; background: var(--m-surface); border: 2px solid var(--m-text-muted); opacity: 0.3; border-radius: 8px; text-align: left; cursor: pointer; transition: all 0.2s; color: var(--m-text); font-size: 1rem;"
                        onmouseover="this.style.borderColor='#4A90E2'; this.style.transform='translateX(4px)';"
                        onmouseout="this.style.borderColor='var(--m-text-muted)'; this.style.opacity='0.3'; this.style.transform='translateX(0)';">
                        ${opt.text}
                    </button>
                `).join('')}
            </div>
        </div>
    `;
}

function selectAnswer(point) {
    answers.push(point);
    currentQuestion++;
    
    if (currentQuestion < questions.length) {
        renderQuestion();
    } else {
        showResult();
    }
}

function showResult() {
    // ๊ฐ€์žฅ ๋งŽ์ด ์„ ํƒ๋œ ์ƒ‰์ƒ ์ฐพ๊ธฐ
    const count = {};
    answers.forEach(c => count[c] = (count[c] || 0) + 1);
    const resultColor = Object.keys(count).reduce((a, b) => count[a] > count[b] ? a : b);
    const result = colorResults[resultColor];
    
    document.getElementById('quiz-container').style.display = 'none';
    const resultContainer = document.getElementById('result-container');
    resultContainer.style.display = 'block';
    resultContainer.innerHTML = `
        <div style="background: var(--m-surface); padding: 40px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); text-align: center;">
            <div style="width: 120px; height: 120px; background: ${result.hex}; border-radius: 50%; margin: 0 auto 24px; box-shadow: 0 8px 24px rgba(0,0,0,0.15);"></div>
            <h2 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">${result.name}</h2>
            <p style="font-size: 1.125rem; color: var(--m-text-muted); line-height: 1.6; margin-bottom: 32px;">${result.desc}</p>
            <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
                <button onclick="copyResult()" style="padding: 12px 24px; background: #4A90E2; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">๐Ÿ”— ๊ฒฐ๊ณผ ๊ณต์œ ํ•˜๊ธฐ</button>
                <button onclick="location.reload()" style="padding: 12px 24px; background: var(--m-surface); color: var(--m-text); border: 2px solid var(--m-text-muted); opacity: 0.5; border-radius: 8px; cursor: pointer; font-weight: 600;">๐Ÿ”„ ๋‹ค์‹œ ํ•˜๊ธฐ</button>
            </div>
        </div>
    `;
    
    // ๊ฒฐ๊ณผ ์ €์žฅ
    fetch('/color-quiz?action=save', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({color: resultColor})
    });
}

function copyResult() {
    const url = window.location.origin + window.location.pathname + '?result=' + answers.join(',');
    navigator.clipboard.writeText(url).then(() => {
        alert('โœ… ๊ฒฐ๊ณผ URL์ด ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
    });
}

// ์ดˆ๊ธฐ ๋ Œ๋”๋ง
renderQuestion();
</script>
```

์ €์žฅ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—์„œ `/color-quiz`๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์—ฌ ์งˆ๋ฌธ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - 5๊ฐœ์˜ ์งˆ๋ฌธ์ด ์ˆœ์ฐจ์ ์œผ๋กœ ํ‘œ์‹œ๋จ - ๊ฐ ์งˆ๋ฌธ๋งˆ๋‹ค 4๊ฐœ์˜ ์„ ํƒ์ง€ ๋ฒ„ํŠผ์ด ํ‘œ์‹œ๋จ - ์ง„ํ–‰๋ฅ  ๋ฐ”๊ฐ€ ์ƒ๋‹จ์— ํ‘œ์‹œ๋˜๊ณ  ์งˆ๋ฌธ์ด ์ง„ํ–‰๋ ์ˆ˜๋ก ์ฑ„์›Œ์ง - ๋ฒ„ํŠผ์— ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ํ˜ธ๋ฒ„ ํšจ๊ณผ ์ž‘๋™ - ๋ชจ๋“  ์งˆ๋ฌธ์— ๋‹ตํ•˜๋ฉด ๊ฒฐ๊ณผ ํ™”๋ฉด์œผ๋กœ ์ „ํ™˜๋จ - ๊ฒฐ๊ณผ ํ™”๋ฉด์— ์ƒ‰์ƒ ์›ํ˜•, ์ƒ‰์ƒ๋ช…, ์‹ฌ๋ฆฌ ํ•ด์„์ด ํ‘œ์‹œ๋จ - '๊ฒฐ๊ณผ ๊ณต์œ ํ•˜๊ธฐ' ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ URL์ด ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋จ - '๋‹ค์‹œ ํ•˜๊ธฐ' ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ๋จ

3
Step 3
**3๋‹จ๊ณ„: ๊ฒฐ๊ณผ ์ €์žฅ ๋ฐ ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„ ํ‘œ์‹œ**

`app/modules/color-quiz/index.php` ํŒŒ์ผ์˜ ์ƒ๋‹จ(์งˆ๋ฌธ ๋ฐฐ์—ด ์ •์˜ ์ด์ „)์— AJAX ์š”์ฒญ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ณ , ํ•˜๋‹จ์— ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:

PHP ๋ถ€๋ถ„ (ํŒŒ์ผ ์ƒ๋‹จ, `$pdo->exec()` ์งํ›„์— ์ถ”๊ฐ€):
```php
// AJAX ์š”์ฒญ ์ฒ˜๋ฆฌ: ๊ฒฐ๊ณผ ์ €์žฅ
if (isset($_GET['action']) && $_GET['action'] === 'save') {
    $input = json_decode(file_get_contents('php://input'), true);
    if (isset($input['color'])) {
        $color = htmlspecialchars($input['color']);
        $ip = $_SERVER['REMOTE_ADDR'];
        
        $stmt = $pdo->prepare("INSERT INTO quiz_results (color_name, ip_address) VALUES (?, ?)");
        $stmt->execute([$color, $ip]);
        
        echo json_encode(['success' => true]);
        exit;
    }
}

// AJAX ์š”์ฒญ ์ฒ˜๋ฆฌ: ์ธ๊ธฐ ์ƒ‰์ƒ ์กฐํšŒ
if (isset($_GET['action']) && $_GET['action'] === 'popular') {
    $stmt = $pdo->query("
        SELECT color_name, COUNT(*) as count 
        FROM quiz_results 
        GROUP BY color_name 
        ORDER BY count DESC 
        LIMIT 3
    ");
    $popular = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
    echo json_encode($popular);
    exit;
}
```

JavaScript ๋ถ€๋ถ„ (ํŒŒ์ผ ํ•˜๋‹จ, ๊ธฐ์กด ์Šคํฌ๋ฆฝํŠธ ๋์— ์ถ”๊ฐ€):
```javascript
// ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„ ๋กœ๋“œ
function loadPopularColors() {
    fetch('/color-quiz?action=popular')
        .then(r => r.json())
        .then(data => {
            const container = document.getElementById('popular-colors');
            if (data.length === 0) {
                container.innerHTML = '<p style="color: var(--m-text-muted); text-align: center;">์•„์ง ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ฐธ์—ฌ์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!</p>';
                return;
            }
            
            container.innerHTML = data.map((item, idx) => {
                const colorInfo = colorResults[item.color_name];
                if (!colorInfo) return '';
                
                const medals = ['๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰'];
                return `
                    <div style="display: flex; align-items: center; gap: 16px; padding: 16px; background: var(--m-surface); border-radius: 8px; margin-bottom: 12px; border: 2px solid var(--m-text-muted); opacity: 0.2;">
                        <span style="font-size: 2rem;">${medals[idx]}</span>
                        <div style="width: 40px; height: 40px; background: ${colorInfo.hex}; border-radius: 50%; flex-shrink: 0;"></div>
                        <div style="flex: 1;">
                            <div style="font-weight: 600; color: var(--m-text);">${colorInfo.name}</div>
                            <div style="font-size: 0.875rem; color: var(--m-text-muted);">${item.count}๋ช…์ด ์„ ํƒ</div>
                        </div>
                    </div>
                `;
            }).join('');
        });
}

// ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ธ๊ธฐ ์ƒ‰์ƒ ๋กœ๋“œ
loadPopularColors();
```

์ €์žฅ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—์„œ `/color-quiz`๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์™„๋ฃŒํ•˜์—ฌ ๊ฒฐ๊ณผ๊ฐ€ ์ €์žฅ๋˜๋Š”์ง€, ์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: - ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ์‹œ ๊ฒฐ๊ณผ๊ฐ€ SQLite DB์— ์ž๋™ ์ €์žฅ๋จ (IP ์ฃผ์†Œ์™€ ํ•จ๊ป˜) - ํŽ˜์ด์ง€ ํ•˜๋‹จ์— '์ธ๊ธฐ ์ƒ‰์ƒ ์ˆœ์œ„' ์„น์…˜์ด ํ‘œ์‹œ๋จ - TOP 3 ์ƒ‰์ƒ์ด ๋ฉ”๋‹ฌ(๐Ÿฅ‡๐Ÿฅˆ๐Ÿฅ‰)๊ณผ ํ•จ๊ป˜ ํ‘œ์‹œ๋จ - ๊ฐ ์ƒ‰์ƒ๋งˆ๋‹ค ์ƒ‰์ƒ ์›ํ˜•, ์ด๋ฆ„, ์„ ํƒ ํšŸ์ˆ˜๊ฐ€ ํ‘œ์‹œ๋จ - ์•„์ง ๊ฒฐ๊ณผ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ '์•„์ง ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค' ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ - ์—ฌ๋Ÿฌ ๋ฒˆ ํ…Œ์ŠคํŠธ๋ฅผ ์™„๋ฃŒํ•˜๋ฉด ์ˆœ์œ„๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋จ - ๋‹คํฌ๋ชจ๋“œ ์ „ํ™˜ ์‹œ์—๋„ ๋ชจ๋“  ์š”์†Œ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•จ

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

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

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

๋Œ“๊ธ€ (0)

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