๐ŸŽฎ g5se ๋ ˆํŠธ๋กœ ์•„์ผ€์ด๋“œ ํ•˜์ด์Šค์ฝ”์–ด ๋ฆฌ๋”๋ณด๋“œ - ์ „์„ค์˜ ๊ฒŒ์ด๋จธ ๋ช…์˜ˆ์˜ ์ „๋‹น - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
๊ณ ๊ธ‰ PHP g5se AI: Claude

๐ŸŽฎ g5se ๋ ˆํŠธ๋กœ ์•„์ผ€์ด๋“œ ํ•˜์ด์Šค์ฝ”์–ด ๋ฆฌ๋”๋ณด๋“œ - ์ „์„ค์˜ ๊ฒŒ์ด๋จธ ๋ช…์˜ˆ์˜ ์ „๋‹น

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… g5se-retro-arcade-leaderboard
์กฐํšŒ์ˆ˜ 61 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐Ÿ•น๏ธ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

80~90๋…„๋Œ€ ์˜ค๋ฝ์‹ค ๊ฐ์„ฑ์„ ์žฌํ˜„ํ•œ ๊ฒŒ์ž„ ํ•˜์ด์Šค์ฝ”์–ด ๋ฆฌ๋”๋ณด๋“œ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ ˆํŠธ๋กœ ๊ฒŒ์ž„์˜ ์ตœ๊ณ  ์ ์ˆ˜๋ฅผ ๊ธฐ๋กํ•˜๊ณ , ๊ฒŒ์ด๋จธ๋“ค์ด ์„œ๋กœ ๊ฒฝ์Ÿํ•˜๋ฉฐ ์ „์„ค์˜ ์ž๋ฆฌ๋ฅผ ์ฐจ์ง€ํ•˜๋Š” ๋ช…์˜ˆ์˜ ์ „๋‹น์„ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

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

  1. ๊ฒŒ์ž„ ๋ชฉ๋ก ๊ด€๋ฆฌ

    • ๋ ˆํŠธ๋กœ ๊ฒŒ์ž„ ์นดํ…Œ๊ณ ๋ฆฌ (์•ก์…˜, ์ŠˆํŒ…, ํผ์ฆ, ๋ ˆ์ด์‹ฑ ๋“ฑ)
    • ๊ฒŒ์ž„๋ณ„ ์ธ๋„ค์ผ, ์„ค๋ช…, ์ตœ๊ณ  ๊ธฐ๋ก ๋‚œ์ด๋„ ์ •๋ณด
    • ๊ฒŒ์ž„๋ณ„ ์ „์šฉ ๋ฆฌ๋”๋ณด๋“œ ํŽ˜์ด์ง€
  2. ํ•˜์ด์Šค์ฝ”์–ด ๋“ฑ๋ก ์‹œ์Šคํ…œ

    • ์ ์ˆ˜ ์ œ์ถœ ํผ (๊ฒŒ์ž„ ์„ ํƒ, ๋‹‰๋„ค์ž„, ์ ์ˆ˜, ์Šคํฌ๋ฆฐ์ƒท ์—…๋กœ๋“œ)
    • ์ค‘๋ณต ์ œ์ถœ ๋ฐฉ์ง€ (IP + ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์ฟจ๋‹ค์šด)
    • ๊ด€๋ฆฌ์ž ์Šน์ธ ๋Œ€๊ธฐ ์ƒํƒœ (์Šคํฌ๋ฆฐ์ƒท ๊ฒ€์ฆ)
  3. ์‹ค์‹œ๊ฐ„ ๋ฆฌ๋”๋ณด๋“œ

    • ๊ฒŒ์ž„๋ณ„ TOP 10 ๋žญํ‚น (๊ธˆ/์€/๋™ ๋ฉ”๋‹ฌ ํ‘œ์‹œ)
    • ์ „์ฒด ๊ฒŒ์ž„ ํ†ตํ•ฉ ๋žญํ‚น (์ด์  ๊ธฐ๋ฐ˜)
    • ์ฃผ๊ฐ„/์›”๊ฐ„/์—ญ๋Œ€ ๊ธฐ๋ก ํ•„ํ„ฐ
    • ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ˆœ์œ„ ๋ณ€๋™ ํšจ๊ณผ
  4. ๊ฒŒ์ด๋จธ ํ”„๋กœํ•„

    • ๋‹‰๋„ค์ž„๋ณ„ ์ „์  ํŽ˜์ด์ง€
    • ๋ณด์œ  ๋ฉ”๋‹ฌ ์ปฌ๋ ‰์…˜
    • ํ”Œ๋ ˆ์ด ํžˆ์Šคํ† ๋ฆฌ ํƒ€์ž„๋ผ์ธ
    • ๊ฒŒ์ž„๋ณ„ ์ตœ๊ณ  ๊ธฐ๋ก ์š”์•ฝ
  5. ๊ด€๋ฆฌ์ž ๊ฒ€์ฆ ์‹œ์Šคํ…œ

    • ์ œ์ถœ๋œ ์Šคํฌ๋ฆฐ์ƒท ๊ฒ€ํ†  ํŽ˜์ด์ง€
    • ์Šน์ธ/๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ (์‚ฌ์œ  ์ž…๋ ฅ)
    • ๋ถ€์ • ๊ธฐ๋ก ์‹ ๊ณ  ์ ‘์ˆ˜

๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

  • ๐Ÿ† ์—…์  ์‹œ์Šคํ…œ (์ฒซ 1์œ„ ๋‹ฌ์„ฑ, 3๊ด€์™•, 100๋งŒ์  ๋ŒํŒŒ ๋“ฑ)
  • ๐Ÿ“Š ๊ฒŒ์ž„๋ณ„ ์ ์ˆ˜ ๋ถ„ํฌ ์ฐจํŠธ
  • ๐ŸŽจ CRT ๋ชจ๋‹ˆํ„ฐ ์Šค์บ”๋ผ์ธ ํšจ๊ณผ (CSS ํ•„ํ„ฐ)
  • ๐Ÿ”” ์‹ ๊ธฐ๋ก ์•Œ๋ฆผ ๋ฐฐ๋„ˆ (์ตœ๊ทผ 24์‹œ๊ฐ„ ๋‚ด ๊ฐฑ์‹ ๋œ ๊ธฐ๋ก)
  • ๐ŸŽต 8๋น„ํŠธ ๋ฐฐ๊ฒฝ์Œ์•… ํ† ๊ธ€
  • ๐Ÿ’ฌ ๊ธฐ๋ก๋ณ„ ๋Œ“๊ธ€ (์ถ•ํ•˜ ๋ฉ”์‹œ์ง€, ๊ณต๋žต ํŒ ๊ณต์œ )

๐ŸŽฏ ๊ธฐ์ˆ  ์š”๊ตฌ์‚ฌํ•ญ

  • ๋ฐฑ์—”๋“œ: g5se (PHP 8.1+) ์ˆœ์ˆ˜ ๊ตฌํ˜„
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: SQLite (๋ชจ๋“ˆ ์ „์šฉ DB ํŒŒ์ผ)
  • ํ”„๋ก ํŠธ: Modern Shell + ์ˆœ์ˆ˜ JS (CDN ์˜์กด ์ตœ์†Œํ™”)
  • ํŒŒ์ผ ์—…๋กœ๋“œ: ์Šคํฌ๋ฆฐ์ƒท ์ด๋ฏธ์ง€ ์ €์žฅ (G5_DATA_PATH/modules/arcade-screenshots/)
  • ๋ณด์•ˆ: SQL Injection ๋ฐฉ์ง€ (prepared statement), XSS ๋ฐฉ์ง€ (htmlspecialchars)

๐ŸŽจ ๋””์ž์ธ ์ปจ์…‰

  • ๋„ค์˜จ ์‚ฌ์ธ ์Šคํƒ€์ผ ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ
  • ๊ทธ๋ฆฌ๋“œ ๋ฐฐ๊ฒฝ (80๋…„๋Œ€ ์‚ฌ์ด๋ฒ„ํŽ‘ํฌ ๊ฐ์„ฑ)
  • ํ”ฝ์…€ ์•„ํŠธ ์•„์ด์ฝ˜
  • ๋‹คํฌ๋ชจ๋“œ ๊ธฐ๋ณธ (Modern Shell ํ† ํฐ ํ™œ์šฉ)
  • ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ (๋ชจ๋ฐ”์ผ ์ตœ์ ํ™”)

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข g5se (PHP) ๊ธฐ๋ฐ˜ โ€” Laravel/Composer/npm ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€
  • โ€ข ํด๋” ์ž๋™ ๋ผ์šฐํŒ… ์‚ฌ์šฉ: app/modules/arcade/index.php โ†’ /arcade
  • โ€ข Modern Shell ํ•„์ˆ˜ ์ ์šฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php)
  • โ€ข SQLite ์ „์šฉ DB: G5_DATA_PATH/modules/arcade.sqlite
  • โ€ข ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ: G5_DATA_PATH/modules/arcade-screenshots/ ํด๋”
  • โ€ข prepared statement๋กœ SQL Injection ๋ฐฉ์ง€
  • โ€ข htmlspecialchars()๋กœ XSS ๋ฐฉ์ง€
  • โ€ข ์ˆœ์ˆ˜ HTML/CSS/JS ๊ตฌํ˜„ (์™ธ๋ถ€ CDN ์ตœ์†Œํ™”)
  • โ€ข ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋Œ€์‘ (CSS ๋ณ€์ˆ˜ ์‚ฌ์šฉ)
  • โ€ข IP ๊ธฐ๋ฐ˜ ์ค‘๋ณต ์ œ์ถœ ๋ฐฉ์ง€ (1์‹œ๊ฐ„ ์ฟจ๋‹ค์šด)
  • โ€ข ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์ฒดํฌ ($member['mb_level'] >= 10)
  • โ€ข ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ ์šฐ์„ )
  • โ€ข CRT ํšจ๊ณผ๋Š” CSS filter๋กœ๋งŒ ๊ตฌํ˜„ (Canvas ๊ธˆ์ง€)

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

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

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

## Step 1: ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์ƒ์„ฑ & ๋ฉ”์ธ ํŽ˜์ด์ง€ ๊ณจ๊ฒฉ

๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”:

### 1) ํด๋” ๊ตฌ์กฐ ์ƒ์„ฑ
```bash
mkdir -p app/modules/arcade
mkdir -p data/modules/arcade-screenshots
```

### 2) app/modules/arcade/index.php ์ƒ์„ฑ

์•„๋ž˜ ์ฝ”๋“œ๋กœ ๋ฉ”์ธ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ์„ธ์š”:

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

// SQLite DB ์—ฐ๊ฒฐ (์ฒซ ์ ‘๊ทผ ์‹œ ์ž๋™ ์ƒ์„ฑ)
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/arcade.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ์ž๋™ ์ƒ์„ฑ
$db->exec("
CREATE TABLE IF NOT EXISTS games (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    category TEXT NOT NULL,
    thumbnail TEXT,
    description TEXT,
    difficulty TEXT DEFAULT 'normal',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS scores (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    game_id INTEGER NOT NULL,
    nickname TEXT NOT NULL,
    score INTEGER NOT NULL,
    screenshot TEXT,
    ip TEXT,
    status TEXT DEFAULT 'pending',
    admin_note TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (game_id) REFERENCES games(id)
);

CREATE INDEX IF NOT EXISTS idx_scores_game ON scores(game_id, score DESC);
CREATE INDEX IF NOT EXISTS idx_scores_status ON scores(status);
");

// ์ƒ˜ํ”Œ ๊ฒŒ์ž„ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (์ตœ์ดˆ 1ํšŒ๋งŒ)
$count = $db->query("SELECT COUNT(*) FROM games")->fetchColumn();
if ($count == 0) {
    $stmt = $db->prepare("INSERT INTO games (title, category, description, difficulty) VALUES (?, ?, ?, ?)");
    $games = [
        ['๊ฐค๋Ÿฌ๊ทธ', '์ŠˆํŒ…', '์™ธ๊ณ„ ์นจ๋žต์ž๋ฅผ ๊ฒฉํŒŒํ•˜๋ผ! ํด๋ž˜์‹ ์ข…์Šคํฌ๋กค ์ŠˆํŒ…', 'hard'],
        ['ํŒฉ๋งจ', 'ํผ์ฆ', '๋ฏธ๋กœ๋ฅผ ๋ˆ„๋น„๋ฉฐ ์ ์„ ๋จน๊ณ  ์œ ๋ น์„ ํ”ผํ•˜๋ผ', 'normal'],
        ['์ŠคํŠธ๋ฆฌํŠธ ํŒŒ์ดํ„ฐ II', '๊ฒฉํˆฌ', '์„ธ๊ณ„ ์ตœ๊ฐ•์˜ ํŒŒ์ดํ„ฐ๋ฅผ ๊ฐ€๋ ค๋ผ', 'hard'],
        ['ํ…ŒํŠธ๋ฆฌ์Šค', 'ํผ์ฆ', '๋ธ”๋ก์„ ์Œ“์•„ ๋ผ์ธ์„ ์™„์„ฑํ•˜๋ผ', 'normal'],
        ['์•„์›ƒ๋Ÿฐ', '๋ ˆ์ด์‹ฑ', 'ํŽ˜๋ผ๋ฆฌ๋ฅผ ๋ชฐ๊ณ  ํ•ด์•ˆ๋„๋กœ๋ฅผ ์งˆ์ฃผํ•˜๋ผ', 'easy']
    ];
    foreach ($games as $g) {
        $stmt->execute($g);
    }
}

// ์ „์ฒด ๊ฒŒ์ž„ ๋ชฉ๋ก ์กฐํšŒ
$games = $db->query("SELECT * FROM games ORDER BY category, title")->fetchAll(PDO::FETCH_ASSOC);

// ์ตœ๊ทผ ์Šน์ธ๋œ ํ•˜์ด์Šค์ฝ”์–ด TOP 5
$recentScores = $db->query("
    SELECT s.*, g.title as game_title
    FROM scores s
    JOIN games g ON s.game_id = g.id
    WHERE s.status = 'approved'
    ORDER BY s.created_at DESC
    LIMIT 5
")->fetchAll(PDO::FETCH_ASSOC);

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.arcade-hero {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    padding: 60px 20px;
    text-align: center;
    margin: -32px -20px 32px;
    position: relative;
    overflow: hidden;
}
.arcade-hero::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: repeating-linear-gradient(
        0deg,
        transparent,
        transparent 2px,
        rgba(0,0,0,0.1) 2px,
        rgba(0,0,0,0.1) 4px
    );
    pointer-events: none;
}
.arcade-hero h1 {
    font-size: 3rem;
    font-weight: 900;
    color: #fff;
    text-shadow: 0 0 20px rgba(255,255,255,0.5),
                 0 0 40px rgba(255,0,255,0.3);
    margin: 0 0 16px;
    position: relative;
}
.arcade-hero p {
    color: rgba(255,255,255,0.9);
    font-size: 1.1rem;
    position: relative;
}
.game-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 24px;
    margin-bottom: 48px;
}
.game-card {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 20px;
    box-shadow: var(--m-shadow);
    transition: transform 0.2s;
}
.game-card:hover {
    transform: translateY(-4px);
}
.game-title {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--m-text);
    margin-bottom: 8px;
}
.game-category {
    display: inline-block;
    padding: 4px 12px;
    background: var(--m-primary);
    color: white;
    border-radius: 12px;
    font-size: 0.85rem;
    margin-bottom: 12px;
}
.recent-scores {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 24px;
    box-shadow: var(--m-shadow);
}
.score-item {
    display: flex;
    justify-content: space-between;
    padding: 12px 0;
    border-bottom: 1px solid var(--m-border, rgba(0,0,0,0.1));
}
.score-item:last-child {
    border-bottom: none;
}
</style>

<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <div class="arcade-hero">
            <h1>๐Ÿ•น๏ธ ๋ ˆํŠธ๋กœ ์•„์ผ€์ด๋“œ</h1>
            <p>์ „์„ค์˜ ๊ฒŒ์ด๋จธ๊ฐ€ ๋˜์–ด ๋ช…์˜ˆ์˜ ์ „๋‹น์— ์ด๋ฆ„์„ ์ƒˆ๊ธฐ์„ธ์š”</p>
        </div>

        <h2 style="margin-bottom: 24px; color: var(--m-text);">๐ŸŽฎ ๊ฒŒ์ž„ ๋ชฉ๋ก</h2>
        <div class="game-grid">
            <?php foreach ($games as $game): ?>
            <div class="game-card">
                <div class="game-title"><?php echo htmlspecialchars($game['title']); ?></div>
                <span class="game-category"><?php echo htmlspecialchars($game['category']); ?></span>
                <p style="color: var(--m-text-muted); margin: 12px 0;">
                    <?php echo htmlspecialchars($game['description']); ?>
                </p>
                <a href="/arcade/game/<?php echo $game['id']; ?>" 
                   style="display: inline-block; padding: 8px 16px; background: var(--m-primary); color: white; border-radius: 8px; text-decoration: none;">
                    ๋ฆฌ๋”๋ณด๋“œ ๋ณด๊ธฐ
                </a>
            </div>
            <?php endforeach; ?>
        </div>

        <h2 style="margin-bottom: 24px; color: var(--m-text);">๐Ÿ† ์ตœ๊ทผ ๋“ฑ๋ก๋œ ํ•˜์ด์Šค์ฝ”์–ด</h2>
        <div class="recent-scores">
            <?php if (empty($recentScores)): ?>
                <p style="color: var(--m-text-muted); text-align: center;">์•„์ง ๋“ฑ๋ก๋œ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
            <?php else: ?>
                <?php foreach ($recentScores as $score): ?>
                <div class="score-item">
                    <div>
                        <strong style="color: var(--m-text);"><?php echo htmlspecialchars($score['game_title']); ?></strong>
                        <span style="color: var(--m-text-muted); margin-left: 12px;">
                            <?php echo htmlspecialchars($score['nickname']); ?>
                        </span>
                    </div>
                    <div style="font-weight: 700; color: var(--m-primary);">
                        <?php echo number_format($score['score']); ?>์ 
                    </div>
                </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

### 3) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

๋ธŒ๋ผ์šฐ์ €์—์„œ `/arcade`๋กœ ์ ‘์†ํ•˜์—ฌ:
- ๋„ค์˜จ ๊ทธ๋ผ๋ฐ์ด์…˜ ํžˆ์–ด๋กœ ์„น์…˜
- 5๊ฐœ ์ƒ˜ํ”Œ ๊ฒŒ์ž„ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ
- ๋นˆ ์ตœ๊ทผ ํ•˜์ด์Šค์ฝ”์–ด ์„น์…˜

์ด ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฉ”์ธ ํŽ˜์ด์ง€ `/arcade`๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , SQLite DB์— games/scores ํ…Œ์ด๋ธ”๊ณผ ์ƒ˜ํ”Œ ๊ฒŒ์ž„ 5๊ฐœ๊ฐ€ ์ž๋™ ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค. Modern Shell์ด ์ ์šฉ๋˜์–ด ์‚ฌ์ดํŠธ ๋„ค๋น„๊ฒŒ์ด์…˜๊ณผ ํ‘ธํ„ฐ๊ฐ€ ํ‘œ์‹œ๋˜๋ฉฐ, ๋ ˆํŠธ๋กœ ์•„์ผ€์ด๋“œ ๊ฐ์„ฑ์˜ ํžˆ์–ด๋กœ ์„น์…˜๊ณผ ๊ฒŒ์ž„ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ๊ฐ€ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

2
Step 2
## Step 2: ๊ฒŒ์ž„๋ณ„ ๋ฆฌ๋”๋ณด๋“œ ํŽ˜์ด์ง€ & ํ•˜์ด์Šค์ฝ”์–ด ์ œ์ถœ ํผ

### 1) app/modules/arcade/game/[id]/index.php ์ƒ์„ฑ

๋™์  segment๋ฅผ ํ™œ์šฉํ•œ ๊ฒŒ์ž„๋ณ„ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ์„ธ์š”:

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

$game_id = (int)$_GET['id'];
if ($game_id <= 0) {
    alert('์ž˜๋ชป๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค.');
    goto_url('/arcade');
}

$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/arcade.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ๊ฒŒ์ž„ ์ •๋ณด ์กฐํšŒ
$stmt = $db->prepare("SELECT * FROM games WHERE id = ?");
$stmt->execute([$game_id]);
$game = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$game) {
    alert('์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒŒ์ž„์ž…๋‹ˆ๋‹ค.');
    goto_url('/arcade');
}

// TOP 10 ๋ฆฌ๋”๋ณด๋“œ (์Šน์ธ๋œ ๊ธฐ๋ก๋งŒ)
$stmt = $db->prepare("
    SELECT * FROM scores
    WHERE game_id = ? AND status = 'approved'
    ORDER BY score DESC
    LIMIT 10
");
$stmt->execute([$game_id]);
$leaderboard = $stmt->fetchAll(PDO::FETCH_ASSOC);

// ์ ์ˆ˜ ์ œ์ถœ ์ฒ˜๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_score'])) {
    $nickname = trim($_POST['nickname']);
    $score = (int)$_POST['score'];
    $ip = $_SERVER['REMOTE_ADDR'];

    // ์œ ํšจ์„ฑ ๊ฒ€์ฆ
    if (empty($nickname) || $score <= 0) {
        alert('๋‹‰๋„ค์ž„๊ณผ ์ ์ˆ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜์„ธ์š”.');
    } else {
        // IP ๊ธฐ๋ฐ˜ ์ค‘๋ณต ์ œ์ถœ ๋ฐฉ์ง€ (1์‹œ๊ฐ„ ์ฟจ๋‹ค์šด)
        $stmt = $db->prepare("
            SELECT COUNT(*) FROM scores
            WHERE game_id = ? AND ip = ? AND created_at > datetime('now', '-1 hour')
        ");
        $stmt->execute([$game_id, $ip]);
        if ($stmt->fetchColumn() > 0) {
            alert('1์‹œ๊ฐ„์— ํ•œ ๋ฒˆ๋งŒ ์ œ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.');
        } else {
            // ์Šคํฌ๋ฆฐ์ƒท ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ
            $screenshot = null;
            if (!empty($_FILES['screenshot']['tmp_name'])) {
                $upload_dir = G5_DATA_PATH.'/modules/arcade-screenshots';
                @mkdir($upload_dir, 0707, true);
                $ext = pathinfo($_FILES['screenshot']['name'], PATHINFO_EXTENSION);
                $filename = uniqid('screenshot_').'.'.$ext;
                if (move_uploaded_file($_FILES['screenshot']['tmp_name'], $upload_dir.'/'.$filename)) {
                    $screenshot = $filename;
                }
            }

            // DB ์‚ฝ์ž…
            $stmt = $db->prepare("
                INSERT INTO scores (game_id, nickname, score, screenshot, ip)
                VALUES (?, ?, ?, ?, ?)
            ");
            $stmt->execute([$game_id, $nickname, $score, $screenshot, $ip]);

            alert('ํ•˜์ด์Šค์ฝ”์–ด๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž ์Šน์ธ ํ›„ ๋ฆฌ๋”๋ณด๋“œ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.');
            goto_url('/arcade/game/'.$game_id);
        }
    }
}

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.game-header {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    padding: 40px 20px;
    border-radius: var(--m-radius-lg);
    margin-bottom: 32px;
    color: white;
}
.leaderboard-table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 32px;
}
.leaderboard-table th {
    background: var(--m-primary);
    color: white;
    padding: 12px;
    text-align: left;
}
.leaderboard-table td {
    padding: 12px;
    border-bottom: 1px solid var(--m-border, rgba(0,0,0,0.1));
    color: var(--m-text);
}
.rank-medal {
    font-size: 1.5rem;
    margin-right: 8px;
}
.submit-form {
    background: var(--m-surface);
    padding: 24px;
    border-radius: var(--m-radius-lg);
    box-shadow: var(--m-shadow);
}
.form-group {
    margin-bottom: 16px;
}
.form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
    color: var(--m-text);
}
.form-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid var(--m-border, #ddd);
    border-radius: 8px;
    background: var(--m-surface);
    color: var(--m-text);
}
.submit-btn {
    background: var(--m-primary);
    color: white;
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    width: 100%;
}
</style>

<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <div class="game-header">
            <h1 style="margin: 0 0 8px;"><?php echo htmlspecialchars($game['title']); ?></h1>
            <p style="margin: 0; opacity: 0.9;"><?php echo htmlspecialchars($game['description']); ?></p>
        </div>

        <h2 style="margin-bottom: 16px; color: var(--m-text);">๐Ÿ† ๋ฆฌ๋”๋ณด๋“œ TOP 10</h2>
        <table class="leaderboard-table">
            <thead>
                <tr>
                    <th>์ˆœ์œ„</th>
                    <th>๋‹‰๋„ค์ž„</th>
                    <th>์ ์ˆ˜</th>
                    <th>๋“ฑ๋ก์ผ</th>
                </tr>
            </thead>
            <tbody>
                <?php if (empty($leaderboard)): ?>
                    <tr><td colspan="4" style="text-align: center; color: var(--m-text-muted);">์•„์ง ๋“ฑ๋ก๋œ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.</td></tr>
                <?php else: ?>
                    <?php foreach ($leaderboard as $idx => $row): ?>
                    <tr>
                        <td>
                            <?php
                            $medals = ['๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰'];
                            echo $idx < 3 ? '<span class="rank-medal">'.$medals[$idx].'</span>' : ($idx + 1).'์œ„';
                            ?>
                        </td>
                        <td><?php echo htmlspecialchars($row['nickname']); ?></td>
                        <td style="font-weight: 700; color: var(--m-primary);"><?php echo number_format($row['score']); ?>์ </td>
                        <td style="color: var(--m-text-muted);"><?php echo date('Y-m-d', strtotime($row['created_at'])); ?></td>
                    </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tbody>
        </table>

        <h2 style="margin-bottom: 16px; color: var(--m-text);">๐Ÿ“ ํ•˜์ด์Šค์ฝ”์–ด ๋“ฑ๋ก</h2>
        <form method="post" enctype="multipart/form-data" class="submit-form">
            <div class="form-group">
                <label>๋‹‰๋„ค์ž„</label>
                <input type="text" name="nickname" required maxlength="20" placeholder="๊ฒŒ์ด๋จธ ํƒœ๊ทธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”">
            </div>
            <div class="form-group">
                <label>์ ์ˆ˜</label>
                <input type="number" name="score" required min="1" placeholder="ํš๋“ํ•œ ์ ์ˆ˜">
            </div>
            <div class="form-group">
                <label>์Šคํฌ๋ฆฐ์ƒท (์„ ํƒ)</label>
                <input type="file" name="screenshot" accept="image/*">
                <small style="color: var(--m-text-muted);">๊ด€๋ฆฌ์ž ๊ฒ€์ฆ์„ ์œ„ํ•ด ๊ฒŒ์ž„ ํ™”๋ฉด ์บก์ฒ˜๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”</small>
            </div>
            <button type="submit" name="submit_score" class="submit-btn">๐Ÿš€ ํ•˜์ด์Šค์ฝ”์–ด ๋“ฑ๋ก</button>
        </form>

        <div style="margin-top: 24px; text-align: center;">
            <a href="/arcade" style="color: var(--m-primary);">โ† ๊ฒŒ์ž„ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

### 2) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- `/arcade/game/1` ์ ‘์† ์‹œ ๊ฐค๋Ÿฌ๊ทธ ๋ฆฌ๋”๋ณด๋“œ ํ‘œ์‹œ
- ํ•˜์ด์Šค์ฝ”์–ด ์ œ์ถœ ํผ์—์„œ ๋‹‰๋„ค์ž„/์ ์ˆ˜/์Šคํฌ๋ฆฐ์ƒท ์ž…๋ ฅ ๊ฐ€๋Šฅ
- ์ œ์ถœ ํ›„ "๊ด€๋ฆฌ์ž ์Šน์ธ ๋Œ€๊ธฐ" ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
- 1์‹œ๊ฐ„ ์ด๋‚ด ์žฌ์ œ์ถœ ์‹œ ์ฟจ๋‹ค์šด ๊ฒฝ๊ณ 

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋™์  ๋ผ์šฐํŒ…์œผ๋กœ ๊ฒŒ์ž„๋ณ„ ํŽ˜์ด์ง€๊ฐ€ ์ƒ์„ฑ๋˜๋ฉฐ, ๋ฆฌ๋”๋ณด๋“œ ํ…Œ์ด๋ธ”๊ณผ ํ•˜์ด์Šค์ฝ”์–ด ์ œ์ถœ ํผ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฐ์ƒท ์—…๋กœ๋“œ๊ฐ€ ์ฒ˜๋ฆฌ๋˜๊ณ , IP ๊ธฐ๋ฐ˜ ์ค‘๋ณต ์ œ์ถœ ๋ฐฉ์ง€ ๋กœ์ง์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ œ์ถœ๋œ ์ ์ˆ˜๋Š” pending ์ƒํƒœ๋กœ DB์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

3
Step 3
## Step 3: ๊ด€๋ฆฌ์ž ๊ฒ€์ฆ ์‹œ์Šคํ…œ & ์Šน์ธ ๋Œ€์‹œ๋ณด๋“œ

### 1) app/modules/arcade/admin/index.php ์ƒ์„ฑ

๊ด€๋ฆฌ์ž ์ „์šฉ ๊ฒ€์ฆ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ์„ธ์š”:

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

// ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์ฒดํฌ
if (!$is_member || $member['mb_level'] < 10) {
    alert('๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.');
    goto_url('/arcade');
}

$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/arcade.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ์Šน์ธ/๊ฑฐ๋ถ€ ์ฒ˜๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $score_id = (int)$_POST['score_id'];
    $action = $_POST['action'];
    $note = trim($_POST['admin_note'] ?? '');

    if (in_array($action, ['approved', 'rejected'])) {
        $stmt = $db->prepare("UPDATE scores SET status = ?, admin_note = ? WHERE id = ?");
        $stmt->execute([$action, $note, $score_id]);
        alert($action === 'approved' ? '์Šน์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' : '๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
        goto_url('/arcade/admin');
    }
}

// ๋Œ€๊ธฐ ์ค‘์ธ ์ ์ˆ˜ ๋ชฉ๋ก
$pendingScores = $db->query("
    SELECT s.*, g.title as game_title
    FROM scores s
    JOIN games g ON s.game_id = g.id
    WHERE s.status = 'pending'
    ORDER BY s.created_at DESC
")->fetchAll(PDO::FETCH_ASSOC);

// ์ตœ๊ทผ ์ฒ˜๋ฆฌ๋œ ๊ธฐ๋ก
$recentProcessed = $db->query("
    SELECT s.*, g.title as game_title
    FROM scores s
    JOIN games g ON s.game_id = g.id
    WHERE s.status IN ('approved', 'rejected')
    ORDER BY s.created_at DESC
    LIMIT 20
")->fetchAll(PDO::FETCH_ASSOC);

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.admin-header {
    background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
    padding: 40px 20px;
    border-radius: var(--m-radius-lg);
    margin-bottom: 32px;
    color: white;
}
.pending-card {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 20px;
    margin-bottom: 16px;
    box-shadow: var(--m-shadow);
}
.screenshot-preview {
    max-width: 100%;
    max-height: 300px;
    border-radius: 8px;
    margin: 12px 0;
}
.action-buttons {
    display: flex;
    gap: 12px;
    margin-top: 16px;
}
.approve-btn {
    flex: 1;
    padding: 10px;
    background: #10b981;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.reject-btn {
    flex: 1;
    padding: 10px;
    background: #ef4444;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.status-badge {
    display: inline-block;
    padding: 4px 12px;
    border-radius: 12px;
    font-size: 0.85rem;
    font-weight: 600;
}
.status-approved { background: #d1fae5; color: #065f46; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.status-pending { background: #fef3c7; color: #92400e; }
</style>

<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <div class="admin-header">
            <h1 style="margin: 0 0 8px;">๐Ÿ›ก๏ธ ๊ด€๋ฆฌ์ž ๊ฒ€์ฆ ๋Œ€์‹œ๋ณด๋“œ</h1>
            <p style="margin: 0; opacity: 0.9;">ํ•˜์ด์Šค์ฝ”์–ด ์Šน์ธ ๋ฐ ๋ถ€์ • ๊ธฐ๋ก ๊ด€๋ฆฌ</p>
        </div>

        <h2 style="margin-bottom: 16px; color: var(--m-text);">โณ ์Šน์ธ ๋Œ€๊ธฐ ์ค‘ (<?php echo count($pendingScores); ?>๊ฑด)</h2>
        <?php if (empty($pendingScores)): ?>
            <p style="color: var(--m-text-muted); text-align: center; padding: 40px 0;">๋Œ€๊ธฐ ์ค‘์ธ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
        <?php else: ?>
            <?php foreach ($pendingScores as $score): ?>
            <div class="pending-card">
                <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
                    <div>
                        <h3 style="margin: 0 0 4px; color: var(--m-text);"><?php echo htmlspecialchars($score['game_title']); ?></h3>
                        <p style="margin: 0; color: var(--m-text-muted);">
                            ๋‹‰๋„ค์ž„: <strong><?php echo htmlspecialchars($score['nickname']); ?></strong> |
                            ์ ์ˆ˜: <strong style="color: var(--m-primary);"><?php echo number_format($score['score']); ?>์ </strong>
                        </p>
                    </div>
                    <span class="status-badge status-pending">๋Œ€๊ธฐ์ค‘</span>
                </div>

                <?php if ($score['screenshot']): ?>
                    <img src="/data/modules/arcade-screenshots/<?php echo htmlspecialchars($score['screenshot']); ?>" 
                         class="screenshot-preview" alt="์Šคํฌ๋ฆฐ์ƒท">
                <?php else: ?>
                    <p style="color: var(--m-text-muted); font-style: italic;">์Šคํฌ๋ฆฐ์ƒท ์—†์Œ</p>
                <?php endif; ?>

                <form method="post" style="margin-top: 16px;">
                    <input type="hidden" name="score_id" value="<?php echo $score['id']; ?>">
                    <input type="text" name="admin_note" placeholder="๊ด€๋ฆฌ์ž ๋ฉ”๋ชจ (์„ ํƒ)" 
                           style="width: 100%; padding: 8px; margin-bottom: 12px; border: 1px solid var(--m-border, #ddd); border-radius: 8px; background: var(--m-surface); color: var(--m-text);">
                    <div class="action-buttons">
                        <button type="submit" name="action" value="approved" class="approve-btn">โœ… ์Šน์ธ</button>
                        <button type="submit" name="action" value="rejected" class="reject-btn">โŒ ๊ฑฐ๋ถ€</button>
                    </div>
                </form>
            </div>
            <?php endforeach; ?>
        <?php endif; ?>

        <h2 style="margin: 48px 0 16px; color: var(--m-text);">๐Ÿ“‹ ์ตœ๊ทผ ์ฒ˜๋ฆฌ ๋‚ด์—ญ</h2>
        <table class="leaderboard-table">
            <thead>
                <tr>
                    <th>๊ฒŒ์ž„</th>
                    <th>๋‹‰๋„ค์ž„</th>
                    <th>์ ์ˆ˜</th>
                    <th>์ƒํƒœ</th>
                    <th>์ฒ˜๋ฆฌ์ผ</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($recentProcessed as $row): ?>
                <tr>
                    <td><?php echo htmlspecialchars($row['game_title']); ?></td>
                    <td><?php echo htmlspecialchars($row['nickname']); ?></td>
                    <td><?php echo number_format($row['score']); ?>์ </td>
                    <td>
                        <span class="status-badge status-<?php echo $row['status']; ?>">
                            <?php echo $row['status'] === 'approved' ? '์Šน์ธ' : '๊ฑฐ๋ถ€'; ?>
                        </span>
                    </td>
                    <td style="color: var(--m-text-muted);"><?php echo date('Y-m-d H:i', strtotime($row['created_at'])); ?></td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>

        <div style="margin-top: 24px; text-align: center;">
            <a href="/arcade" style="color: var(--m-primary);">โ† ๋ฉ”์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

### 2) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- ๊ด€๋ฆฌ์ž ๊ณ„์ •์œผ๋กœ `/arcade/admin` ์ ‘์†
- ๋Œ€๊ธฐ ์ค‘์ธ ์ ์ˆ˜ ์นด๋“œ์— ์Šคํฌ๋ฆฐ์ƒท ๋ฏธ๋ฆฌ๋ณด๊ธฐ
- ์Šน์ธ/๊ฑฐ๋ถ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒํƒœ ๋ณ€๊ฒฝ
- ์ตœ๊ทผ ์ฒ˜๋ฆฌ ๋‚ด์—ญ ํ…Œ์ด๋ธ”์— ํžˆ์Šคํ† ๋ฆฌ ํ‘œ์‹œ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๊ด€๋ฆฌ์ž ์ „์šฉ ๊ฒ€์ฆ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉฐ, pending ์ƒํƒœ์˜ ์ ์ˆ˜ ๋ชฉ๋ก์ด ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฐ์ƒท์ด ์žˆ์„ ๊ฒฝ์šฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ๋ Œ๋”๋ง๋˜๊ณ , ์Šน์ธ/๊ฑฐ๋ถ€ ๋ฒ„ํŠผ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜๋ฆฌ๋œ ๊ธฐ๋ก์€ ํ•˜๋‹จ ํ…Œ์ด๋ธ”์— ํžˆ์Šคํ† ๋ฆฌ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

4
Step 4
## Step 4: ๊ฒŒ์ด๋จธ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ & ์ „์  ํ†ต๊ณ„

### 1) app/modules/arcade/profile/[nickname]/index.php ์ƒ์„ฑ

๋‹‰๋„ค์ž„๋ณ„ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ์„ธ์š”:

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

$nickname = trim($_GET['nickname'] ?? '');
if (empty($nickname)) {
    alert('์ž˜๋ชป๋œ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค.');
    goto_url('/arcade');
}

$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/arcade.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ•ด๋‹น ๋‹‰๋„ค์ž„์˜ ์ „์ฒด ์Šน์ธ๋œ ๊ธฐ๋ก
$stmt = $db->prepare("
    SELECT s.*, g.title as game_title, g.category
    FROM scores s
    JOIN games g ON s.game_id = g.id
    WHERE s.nickname = ? AND s.status = 'approved'
    ORDER BY s.created_at DESC
");
$stmt->execute([$nickname]);
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);

if (empty($records)) {
    alert('ํ•ด๋‹น ๊ฒŒ์ด๋จธ์˜ ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.');
    goto_url('/arcade');
}

// ํ†ต๊ณ„ ๊ณ„์‚ฐ
$totalScore = array_sum(array_column($records, 'score'));
$totalGames = count(array_unique(array_column($records, 'game_id')));
$avgScore = $totalGames > 0 ? round($totalScore / $totalGames) : 0;

// ๋ฉ”๋‹ฌ ์ง‘๊ณ„
$medals = ['gold' => 0, 'silver' => 0, 'bronze' => 0];
foreach ($records as $record) {
    $stmt = $db->prepare("
        SELECT COUNT(*) as rank FROM scores
        WHERE game_id = ? AND status = 'approved' AND score > ?
    ");
    $stmt->execute([$record['game_id'], $record['score']]);
    $rank = $stmt->fetchColumn() + 1;
    
    if ($rank === 1) $medals['gold']++;
    elseif ($rank === 2) $medals['silver']++;
    elseif ($rank === 3) $medals['bronze']++;
}

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.profile-header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    padding: 40px 20px;
    border-radius: var(--m-radius-lg);
    margin-bottom: 32px;
    color: white;
    text-align: center;
}
.profile-avatar {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    margin: 0 auto 16px;
}
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
    margin-bottom: 32px;
}
.stat-card {
    background: var(--m-surface);
    padding: 20px;
    border-radius: var(--m-radius-lg);
    text-align: center;
    box-shadow: var(--m-shadow);
}
.stat-value {
    font-size: 2rem;
    font-weight: 700;
    color: var(--m-primary);
    margin-bottom: 8px;
}
.stat-label {
    color: var(--m-text-muted);
    font-size: 0.9rem;
}
.record-timeline {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 24px;
    box-shadow: var(--m-shadow);
}
.timeline-item {
    padding: 16px 0;
    border-bottom: 1px solid var(--m-border, rgba(0,0,0,0.1));
}
.timeline-item:last-child {
    border-bottom: none;
}
</style>

<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <div class="profile-header">
            <div class="profile-avatar">๐ŸŽฎ</div>
            <h1 style="margin: 0 0 8px;"><?php echo htmlspecialchars($nickname); ?></h1>
            <p style="margin: 0; opacity: 0.9;">๋ ˆํŠธ๋กœ ๊ฒŒ์ด๋จธ</p>
        </div>

        <div class="stats-grid">
            <div class="stat-card">
                <div class="stat-value"><?php echo number_format($totalScore); ?></div>
                <div class="stat-label">์ด ๋ˆ„์  ์ ์ˆ˜</div>
            </div>
            <div class="stat-card">
                <div class="stat-value"><?php echo $totalGames; ?>๊ฐœ</div>
                <div class="stat-label">ํ”Œ๋ ˆ์ดํ•œ ๊ฒŒ์ž„</div>
            </div>
            <div class="stat-card">
                <div class="stat-value"><?php echo number_format($avgScore); ?></div>
                <div class="stat-label">ํ‰๊ท  ์ ์ˆ˜</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">
                    ๐Ÿฅ‡<?php echo $medals['gold']; ?>
                    ๐Ÿฅˆ<?php echo $medals['silver']; ?>
                    ๐Ÿฅ‰<?php echo $medals['bronze']; ?>
                </div>
                <div class="stat-label">ํš๋“ ๋ฉ”๋‹ฌ</div>
            </div>
        </div>

        <h2 style="margin-bottom: 16px; color: var(--m-text);">๐Ÿ“œ ํ”Œ๋ ˆ์ด ํžˆ์Šคํ† ๋ฆฌ</h2>
        <div class="record-timeline">
            <?php foreach ($records as $record): ?>
            <div class="timeline-item">
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <div>
                        <strong style="color: var(--m-text); font-size: 1.1rem;">
                            <?php echo htmlspecialchars($record['game_title']); ?>
                        </strong>
                        <span style="margin-left: 12px; padding: 2px 8px; background: var(--m-primary); color: white; border-radius: 8px; font-size: 0.8rem;">
                            <?php echo htmlspecialchars($record['category']); ?>
                        </span>
                    </div>
                    <div style="text-align: right;">
                        <div style="font-weight: 700; color: var(--m-primary); font-size: 1.2rem;">
                            <?php echo number_format($record['score']); ?>์ 
                        </div>
                        <div style="color: var(--m-text-muted); font-size: 0.85rem;">
                            <?php echo date('Y-m-d', strtotime($record['created_at'])); ?>
                        </div>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>

        <div style="margin-top: 24px; text-align: center;">
            <a href="/arcade" style="color: var(--m-primary);">โ† ๋ฉ”์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

### 2) ๊ฒŒ์ž„๋ณ„ ๋ฆฌ๋”๋ณด๋“œ์— ํ”„๋กœํ•„ ๋งํฌ ์ถ”๊ฐ€

`app/modules/arcade/game/[id]/index.php`์˜ ๋ฆฌ๋”๋ณด๋“œ ํ…Œ์ด๋ธ” ๋ถ€๋ถ„์„ ์ˆ˜์ •:

```php
<td>
    <a href="/arcade/profile/<?php echo urlencode($row['nickname']); ?>" 
       style="color: var(--m-primary); text-decoration: none; font-weight: 600;">
        <?php echo htmlspecialchars($row['nickname']); ?>
    </a>
</td>
```

### 3) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- ๋ฆฌ๋”๋ณด๋“œ์—์„œ ๋‹‰๋„ค์ž„ ํด๋ฆญ ์‹œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ์ด๋™
- ์ด ์ ์ˆ˜, ํ”Œ๋ ˆ์ด ๊ฒŒ์ž„ ์ˆ˜, ํ‰๊ท  ์ ์ˆ˜ ํ†ต๊ณ„ ํ‘œ์‹œ
- ๋ฉ”๋‹ฌ ์ง‘๊ณ„ (1์œ„=๊ธˆ, 2์œ„=์€, 3์œ„=๋™)
- ํ”Œ๋ ˆ์ด ํžˆ์Šคํ† ๋ฆฌ ํƒ€์ž„๋ผ์ธ ๋ Œ๋”๋ง

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋‹‰๋„ค์ž„๋ณ„ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๊ฐ€ ์ƒ์„ฑ๋˜๋ฉฐ, ํ•ด๋‹น ๊ฒŒ์ด๋จธ์˜ ์ „์ฒด ์Šน์ธ๋œ ๊ธฐ๋ก์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ†ต๊ณ„๊ฐ€ ๊ณ„์‚ฐ๋ฉ๋‹ˆ๋‹ค. ๋ฉ”๋‹ฌ ์ง‘๊ณ„ ๋กœ์ง์ด ๊ฐ ๊ฒŒ์ž„๋ณ„ ์ˆœ์œ„๋ฅผ ์‹ค์‹œ๊ฐ„ ๊ณ„์‚ฐํ•˜์—ฌ ํ‘œ์‹œํ•˜๊ณ , ํ”Œ๋ ˆ์ด ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ํƒ€์ž„๋ผ์ธ ํ˜•ํƒœ๋กœ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

5
Step 5
## Step 5: ์ „์ฒด ๋žญํ‚น ํ†ตํ•ฉ ํŽ˜์ด์ง€ & ํ•„ํ„ฐ ์‹œ์Šคํ…œ

### 1) app/modules/arcade/rankings/index.php ์ƒ์„ฑ

์ „์ฒด ๊ฒŒ์ด๋จธ ํ†ตํ•ฉ ๋žญํ‚น์„ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ์„ธ์š”:

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

$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/arcade.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ•„ํ„ฐ ํŒŒ๋ผ๋ฏธํ„ฐ
$period = $_GET['period'] ?? 'all';
$category = $_GET['category'] ?? 'all';

// ๊ธฐ๊ฐ„ ํ•„ํ„ฐ SQL
$periodSql = '';
if ($period === 'week') {
    $periodSql = "AND s.created_at > datetime('now', '-7 days')";
} elseif ($period === 'month') {
    $periodSql = "AND s.created_at > datetime('now', '-30 days')";
}

// ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ SQL
$categorySql = '';
if ($category !== 'all') {
    $categorySql = "AND g.category = ".$db->quote($category);
}

// ๋‹‰๋„ค์ž„๋ณ„ ์ด์  ์ง‘๊ณ„
$rankings = $db->query("
    SELECT 
        s.nickname,
        SUM(s.score) as total_score,
        COUNT(DISTINCT s.game_id) as games_played,
        MAX(s.created_at) as last_played
    FROM scores s
    JOIN games g ON s.game_id = g.id
    WHERE s.status = 'approved' {$periodSql} {$categorySql}
    GROUP BY s.nickname
    ORDER BY total_score DESC
    LIMIT 50
")->fetchAll(PDO::FETCH_ASSOC);

// ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก
$categories = $db->query("SELECT DISTINCT category FROM games ORDER BY category")->fetchAll(PDO::FETCH_COLUMN);

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.filter-bar {
    background: var(--m-surface);
    padding: 20px;
    border-radius: var(--m-radius-lg);
    margin-bottom: 24px;
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
    box-shadow: var(--m-shadow);
}
.filter-group {
    display: flex;
    gap: 8px;
    align-items: center;
}
.filter-btn {
    padding: 8px 16px;
    border: 2px solid var(--m-border, #ddd);
    background: var(--m-surface);
    color: var(--m-text);
    border-radius: 8px;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
    transition: all 0.2s;
}
.filter-btn:hover {
    border-color: var(--m-primary);
}
.filter-btn.active {
    background: var(--m-primary);
    color: white;
    border-color: var(--m-primary);
}
.ranking-card {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 16px 20px;
    margin-bottom: 12px;
    box-shadow: var(--m-shadow);
    display: flex;
    align-items: center;
    gap: 20px;
}
.rank-number {
    font-size: 2rem;
    font-weight: 900;
    color: var(--m-text-muted);
    min-width: 60px;
    text-align: center;
}
.rank-number.top3 {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}
.player-info {
    flex: 1;
}
.player-name {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--m-text);
    margin-bottom: 4px;
}
.player-stats {
    color: var(--m-text-muted);
    font-size: 0.9rem;
}
.total-score {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--m-primary);
    text-align: right;
}
</style>

<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <h1 style="margin-bottom: 24px; color: var(--m-text);">๐Ÿ† ์ „์ฒด ๋žญํ‚น</h1>

        <div class="filter-bar">
            <div class="filter-group">
                <span style="color: var(--m-text); font-weight: 600;">๊ธฐ๊ฐ„:</span>
                <a href="?period=all&category=<?php echo $category; ?>" 
                   class="filter-btn <?php echo $period === 'all' ? 'active' : ''; ?>">์ „์ฒด</a>
                <a href="?period=week&category=<?php echo $category; ?>" 
                   class="filter-btn <?php echo $period === 'week' ? 'active' : ''; ?>">์ฃผ๊ฐ„</a>
                <a href="?period=month&category=<?php echo $category; ?>" 
                   class="filter-btn <?php echo $period === 'month' ? 'active' : ''; ?>">์›”๊ฐ„</a>
            </div>
            <div class="filter-group">
                <span style="color: var(--m-text); font-weight: 600;">์นดํ…Œ๊ณ ๋ฆฌ:</span>
                <a href="?period=<?php echo $period; ?>&category=all" 
                   class="filter-btn <?php echo $category === 'all' ? 'active' : ''; ?>">์ „์ฒด</a>
                <?php foreach ($categories as $cat): ?>
                    <a href="?period=<?php echo $period; ?>&category=<?php echo urlencode($cat); ?>" 
                       class="filter-btn <?php echo $category === $cat ? 'active' : ''; ?>">
                        <?php echo htmlspecialchars($cat); ?>
                    </a>
                <?php endforeach; ?>
            </div>
        </div>

        <?php if (empty($rankings)): ?>
            <p style="text-align: center; color: var(--m-text-muted); padding: 40px 0;">๋žญํ‚น ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</p>
        <?php else: ?>
            <?php foreach ($rankings as $idx => $player): ?>
            <div class="ranking-card">
                <div class="rank-number <?php echo $idx < 3 ? 'top3' : ''; ?>">
                    <?php
                    if ($idx === 0) echo '๐Ÿฅ‡';
                    elseif ($idx === 1) echo '๐Ÿฅˆ';
                    elseif ($idx === 2) echo '๐Ÿฅ‰';
                    else echo ($idx + 1);
                    ?>
                </div>
                <div class="player-info">
                    <div class="player-name">
                        <a href="/arcade/profile/<?php echo urlencode($player['nickname']); ?>" 
                           style="color: var(--m-text); text-decoration: none;">
                            <?php echo htmlspecialchars($player['nickname']); ?>
                        </a>
                    </div>
                    <div class="player-stats">
                        <?php echo $player['games_played']; ?>๊ฐœ ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด |
                        ์ตœ๊ทผ ํ™œ๋™: <?php echo date('Y-m-d', strtotime($player['last_played'])); ?>
                    </div>
                </div>
                <div class="total-score">
                    <?php echo number_format($player['total_score']); ?>์ 
                </div>
            </div>
            <?php endforeach; ?>
        <?php endif; ?>

        <div style="margin-top: 32px; text-align: center;">
            <a href="/arcade" style="color: var(--m-primary); font-weight: 600;">โ† ๋ฉ”์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</a>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```

### 2) ๋ฉ”์ธ ํŽ˜์ด์ง€์— ๋žญํ‚น ๋งํฌ ์ถ”๊ฐ€

`app/modules/arcade/index.php`์˜ ํžˆ์–ด๋กœ ์„น์…˜ ์•„๋ž˜์— ์ถ”๊ฐ€:

```php
<div style="text-align: center; margin-bottom: 32px;">
    <a href="/arcade/rankings" 
       style="display: inline-block; padding: 12px 24px; background: var(--m-primary); color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">
        ๐Ÿ† ์ „์ฒด ๋žญํ‚น ๋ณด๊ธฐ
    </a>
</div>
```

### 3) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- `/arcade/rankings` ์ ‘์† ์‹œ ์ „์ฒด ๊ฒŒ์ด๋จธ ์ด์  ๋žญํ‚น ํ‘œ์‹œ
- ๊ธฐ๊ฐ„ ํ•„ํ„ฐ (์ „์ฒด/์ฃผ๊ฐ„/์›”๊ฐ„) ๋™์ž‘ ํ™•์ธ
- ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ (์ „์ฒด/์ŠˆํŒ…/ํผ์ฆ/๊ฒฉํˆฌ/๋ ˆ์ด์‹ฑ) ๋™์ž‘ ํ™•์ธ
- TOP 3 ๋ฉ”๋‹ฌ ํ‘œ์‹œ ๋ฐ ๊ทธ๋ผ๋ฐ์ด์…˜ ํšจ๊ณผ
- ๋‹‰๋„ค์ž„ ํด๋ฆญ ์‹œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ์ด๋™

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ „์ฒด ๋žญํ‚น ํŽ˜์ด์ง€๊ฐ€ ์ƒ์„ฑ๋˜๋ฉฐ, ๋‹‰๋„ค์ž„๋ณ„ ์ด์ ์„ ์ง‘๊ณ„ํ•˜์—ฌ ์ˆœ์œ„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๊ฐ„ ๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๊ฐ€ URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋™์ž‘ํ•˜๊ณ , SQL ์ฟผ๋ฆฌ์— ๋™์ ์œผ๋กœ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. TOP 3๋Š” ๋ฉ”๋‹ฌ ์ด๋ชจ์ง€์™€ ๊ทธ๋ผ๋ฐ์ด์…˜ ํ…์ŠคํŠธ๋กœ ๊ฐ•์กฐ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

6
Step 6
## Step 6: ์‹ ๊ธฐ๋ก ์•Œ๋ฆผ ๋ฐฐ๋„ˆ & ์—…์  ์‹œ์Šคํ…œ

### 1) ์‹ ๊ธฐ๋ก ์•Œ๋ฆผ ๋ฐฐ๋„ˆ ์ถ”๊ฐ€

`app/modules/arcade/index.php`์˜ ๊ฒŒ์ž„ ๋ชฉ๋ก ์œ„์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€:

```php
// ์ตœ๊ทผ 24์‹œ๊ฐ„ ๋‚ด ๊ฐฑ์‹ ๋œ ์‹ ๊ธฐ๋ก ์กฐํšŒ
$newRecords = $db->query("
    WITH RankedScores AS (
        SELECT 
            s.*,
            g.title as game_title,
            ROW_NUMBER() OVER (PARTITION BY s.game_id ORDER BY s.score DESC) as rank
        FROM scores s
        JOIN games g ON s.game_id = g.id
        WHERE s.status = 'approved'
    )
    SELECT * FROM RankedScores
    WHERE rank = 1 AND created_at > datetime('now', '-24 hours')
    ORDER BY created_at DESC
    LIMIT 3
")->fetchAll(PDO::FETCH_ASSOC);
?>

<!-- ํžˆ์–ด๋กœ ์„น์…˜ ๋ฐ”๋กœ ์•„๋ž˜์— ์ถ”๊ฐ€ -->
<?php if (!empty($newRecords)): ?>
<div style="background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%); padding: 20px; border-radius: var(--m-radius-lg); margin-bottom: 32px; box-shadow: var(--m-shadow);">
    <h3 style="margin: 0 0 12px; color: #2d3436; display: flex; align-items: center; gap: 8px;">
        ๐Ÿ”ฅ ์ตœ๊ทผ 24์‹œ๊ฐ„ ์‹ ๊ธฐ๋ก!
    </h3>
    <?php foreach ($newRecords as $record): ?>
    <div style="background: rgba(255,255,255,0.9); padding: 12px 16px; border-radius: 8px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
        <div>
            <strong style="color: #2d3436;"><?php echo htmlspecialchars($record['game_title']); ?></strong>
            <span style="color: #636e72; margin-left: 12px;">
                <?php echo htmlspecialchars($record['nickname']); ?>๋‹˜์ด 1์œ„ ๋‹ฌ์„ฑ!
            </span>
        </div>
        <span style="font-weight: 700; color: #d63031; font-size: 1.1rem;">
            <?php echo number_format($record['score']); ?>์ 
        </span>
    </div>
    <?php endforeach; ?>
</div>
<?php endif; ?>
```

### 2) ์—…์  ์‹œ์Šคํ…œ ๊ตฌํ˜„

`app/modules/arcade/profile/[nickname]/index.php`์— ์—…์  ๊ณ„์‚ฐ ๋กœ์ง ์ถ”๊ฐ€:

```php
// ๊ธฐ์กด ํ†ต๊ณ„ ๊ณ„์‚ฐ ์•„๋ž˜์— ์ถ”๊ฐ€

// ์—…์  ์ฒดํฌ
$achievements = [];

// ์ฒซ 1์œ„ ๋‹ฌ์„ฑ
if ($medals['gold'] >= 1) {
    $achievements[] = [
        'icon' => '๐Ÿ†',
        'title' => '์ฒซ ์Šน๋ฆฌ',
        'description' => '์ฒซ 1์œ„ ๊ธฐ๋ก ๋‹ฌ์„ฑ',
        'rarity' => 'common'
    ];
}

// 3๊ด€์™•
if ($medals['gold'] >= 3) {
    $achievements[] = [
        'icon' => '๐Ÿ‘‘',
        'title' => '3๊ด€์™•',
        'description' => '3๊ฐœ ๊ฒŒ์ž„์—์„œ 1์œ„ ๋‹ฌ์„ฑ',
        'rarity' => 'rare'
    ];
}

// 100๋งŒ์  ๋ŒํŒŒ
if ($totalScore >= 1000000) {
    $achievements[] = [
        'icon' => '๐Ÿ’Ž',
        'title' => '๋ฐ€๋ฆฌ์–ธ ๊ฒŒ์ด๋จธ',
        'description' => '์ด ๋ˆ„์  ์ ์ˆ˜ 100๋งŒ์  ๋ŒํŒŒ',
        'rarity' => 'epic'
    ];
}

// ์˜ฌ๋ผ์šด๋” (5๊ฐœ ์ด์ƒ ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด)
if ($totalGames >= 5) {
    $achievements[] = [
        'icon' => '๐ŸŽฎ',
        'title' => '์˜ฌ๋ผ์šด๋”',
        'description' => '5๊ฐœ ์ด์ƒ์˜ ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด',
        'rarity' => 'common'
    ];
}

// ํผํŽ™ํŠธ ๊ฒŒ์ด๋จธ (๋ชจ๋“  ๊ฒŒ์ž„ 1์œ„)
$allGamesCount = $db->query("SELECT COUNT(*) FROM games")->fetchColumn();
if ($medals['gold'] >= $allGamesCount && $allGamesCount > 0) {
    $achievements[] = [
        'icon' => 'โญ',
        'title' => 'ํผํŽ™ํŠธ ๊ฒŒ์ด๋จธ',
        'description' => '๋ชจ๋“  ๊ฒŒ์ž„์—์„œ 1์œ„ ๋‹ฌ์„ฑ',
        'rarity' => 'legendary'
    ];
}
```

ํ†ต๊ณ„ ๊ทธ๋ฆฌ๋“œ ์•„๋ž˜์— ์—…์  ์„น์…˜ ์ถ”๊ฐ€:

```php
<?php if (!empty($achievements)): ?>
<h2 style="margin-bottom: 16px; color: var(--m-text);">๐Ÿ… ํš๋“ ์—…์ </h2>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; margin-bottom: 32px;">
    <?php foreach ($achievements as $ach): ?>
    <div style="background: var(--m-surface); padding: 16px; border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); text-align: center;
                border: 2px solid <?php 
                    echo $ach['rarity'] === 'legendary' ? '#ffd700' : 
                        ($ach['rarity'] === 'epic' ? '#a855f7' : 
                        ($ach['rarity'] === 'rare' ? '#3b82f6' : '#94a3b8')); 
                ?>;">
        <div style="font-size: 3rem; margin-bottom: 8px;"><?php echo $ach['icon']; ?></div>
        <div style="font-weight: 700; color: var(--m-text); margin-bottom: 4px;"><?php echo $ach['title']; ?></div>
        <div style="color: var(--m-text-muted); font-size: 0.9rem;"><?php echo $ach['description']; ?></div>
    </div>
    <?php endforeach; ?>
</div>
<?php endif; ?>
```

### 3) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ตœ๊ทผ 24์‹œ๊ฐ„ ์‹ ๊ธฐ๋ก ๋ฐฐ๋„ˆ ํ‘œ์‹œ
- ํ”„๋กœํ•„ ํŽ˜์ด์ง€์— ์—…์  ์นด๋“œ ๋ Œ๋”๋ง
- ์—…์  ๋“ฑ๊ธ‰๋ณ„ ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ (์ „์„ค=๊ธˆ์ƒ‰, ์—ํ”ฝ=๋ณด๋ผ, ๋ ˆ์–ด=ํŒŒ๋ž‘, ์ผ๋ฐ˜=ํšŒ์ƒ‰)
- ์กฐ๊ฑด ์ถฉ์กฑ ์‹œ ์—…์  ์ž๋™ ํ•ด์ œ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ตœ๊ทผ 24์‹œ๊ฐ„ ๋‚ด ๊ฐฑ์‹ ๋œ 1์œ„ ๊ธฐ๋ก์„ ๋ณด์—ฌ์ฃผ๋Š” ์•Œ๋ฆผ ๋ฐฐ๋„ˆ๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ํ”„๋กœํ•„ ํŽ˜์ด์ง€์— ์—…์  ์‹œ์Šคํ…œ์ด ๊ตฌํ˜„๋˜์–ด, ํŠน์ • ์กฐ๊ฑด(์ฒซ 1์œ„, 3๊ด€์™•, 100๋งŒ์ , ์˜ฌ๋ผ์šด๋”, ํผํŽ™ํŠธ ๊ฒŒ์ด๋จธ)์„ ์ถฉ์กฑํ•˜๋ฉด ์—…์  ์นด๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

7
Step 7
## Step 7: CRT ํšจ๊ณผ & ๋ ˆํŠธ๋กœ UI ํด๋ฆฌ์‹ฑ

### 1) CRT ์Šค์บ”๋ผ์ธ ํšจ๊ณผ ์ถ”๊ฐ€

๋ชจ๋“  ํŽ˜์ด์ง€์˜ `<style>` ๋ธ”๋ก์— ๋‹ค์Œ CSS๋ฅผ ์ถ”๊ฐ€:

```css
/* CRT ํšจ๊ณผ ํ† ๊ธ€ */
.crt-mode {
    position: relative;
}
.crt-mode::before {
    content: '';
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: repeating-linear-gradient(
        0deg,
        rgba(0, 0, 0, 0) 0px,
        rgba(0, 0, 0, 0) 2px,
        rgba(0, 0, 0, 0.1) 2px,
        rgba(0, 0, 0, 0.1) 4px
    );
    pointer-events: none;
    z-index: 9999;
    animation: scanline 8s linear infinite;
}
.crt-mode::after {
    content: '';
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: radial-gradient(
        ellipse at center,
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0.3) 100%
    );
    pointer-events: none;
    z-index: 9998;
}
@keyframes scanline {
    0% { transform: translateY(0); }
    100% { transform: translateY(100vh); }
}

/* ํ”ฝ์…€ ํฐํŠธ ์ ์šฉ */
.pixel-text {
    font-family: 'Courier New', monospace;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
}

/* ๋„ค์˜จ ๊ธ€๋กœ์šฐ ํšจ๊ณผ */
.neon-glow {
    text-shadow: 
        0 0 10px currentColor,
        0 0 20px currentColor,
        0 0 30px currentColor,
        0 0 40px currentColor;
    animation: neon-flicker 3s infinite alternate;
}
@keyframes neon-flicker {
    0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
        opacity: 1;
    }
    20%, 24%, 55% {
        opacity: 0.8;
    }
}
```

### 2) CRT ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ ์ถ”๊ฐ€

๋ชจ๋“  ํŽ˜์ด์ง€์˜ `<main>` ์‹œ์ž‘ ๋ถ€๋ถ„์— ์ถ”๊ฐ€:

```php
<div style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
    <button onclick="toggleCRT()" 
            style="background: var(--m-primary); color: white; border: none; padding: 12px 20px; border-radius: 50px; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-weight: 600;">
        ๐Ÿ“บ CRT ๋ชจ๋“œ
    </button>
</div>

<script>
function toggleCRT() {
    document.body.classList.toggle('crt-mode');
    const isEnabled = document.body.classList.contains('crt-mode');
    localStorage.setItem('crtMode', isEnabled ? '1' : '0');
}

// ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ €์žฅ๋œ ์„ค์ • ๋ณต์›
if (localStorage.getItem('crtMode') === '1') {
    document.body.classList.add('crt-mode');
}
</script>
```

### 3) ํžˆ์–ด๋กœ ์„น์…˜์— ๋„ค์˜จ ํšจ๊ณผ ์ ์šฉ

`app/modules/arcade/index.php`์˜ ํžˆ์–ด๋กœ h1 ํƒœ๊ทธ๋ฅผ ์ˆ˜์ •:

```php
<h1 class="pixel-text neon-glow" style="font-size: 3rem; font-weight: 900; color: #fff; margin: 0 0 16px; position: relative;">
    ๐Ÿ•น๏ธ RETRO ARCADE
</h1>
```

### 4) ๊ฒŒ์ž„ ์นด๋“œ์— ํ˜ธ๋ฒ„ ํšจ๊ณผ ๊ฐ•ํ™”

`.game-card` ์Šคํƒ€์ผ์— ์ถ”๊ฐ€:

```css
.game-card {
    background: var(--m-surface);
    border-radius: var(--m-radius-lg);
    padding: 20px;
    box-shadow: var(--m-shadow);
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    overflow: hidden;
}
.game-card::before {
    content: '';
    position: absolute;
    top: -50%; left: -50%;
    width: 200%; height: 200%;
    background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
    opacity: 0;
    transition: opacity 0.3s;
}
.game-card:hover::before {
    opacity: 1;
}
.game-card:hover {
    transform: translateY(-8px) scale(1.02);
    box-shadow: 0 12px 24px rgba(102, 126, 234, 0.3);
}
```

### 5) ๋ฆฌ๋”๋ณด๋“œ ํ…Œ์ด๋ธ” ์• ๋‹ˆ๋ฉ”์ด์…˜

`.leaderboard-table tbody tr`์— ์ถ”๊ฐ€:

```css
.leaderboard-table tbody tr {
    animation: fadeInUp 0.5s ease-out backwards;
}
.leaderboard-table tbody tr:nth-child(1) { animation-delay: 0.1s; }
.leaderboard-table tbody tr:nth-child(2) { animation-delay: 0.2s; }
.leaderboard-table tbody tr:nth-child(3) { animation-delay: 0.3s; }
.leaderboard-table tbody tr:nth-child(4) { animation-delay: 0.4s; }
.leaderboard-table tbody tr:nth-child(5) { animation-delay: 0.5s; }

@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
```

### 6) ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ

- ์šฐ์ธก ํ•˜๋‹จ CRT ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์Šค์บ”๋ผ์ธ ํšจ๊ณผ ํ™œ์„ฑํ™”
- ํžˆ์–ด๋กœ ์„น์…˜ ์ œ๋ชฉ์— ๋„ค์˜จ ๊ธ€๋กœ์šฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
- ๊ฒŒ์ž„ ์นด๋“œ ํ˜ธ๋ฒ„ ์‹œ ๋ถ€๋“œ๋Ÿฌ์šด ํ™•๋Œ€ + ๊ทธ๋ฆผ์ž ํšจ๊ณผ
- ๋ฆฌ๋”๋ณด๋“œ ํ…Œ์ด๋ธ” ํ–‰์ด ์ˆœ์ฐจ์ ์œผ๋กœ ํŽ˜์ด๋“œ์ธ
- localStorage๋กœ CRT ๋ชจ๋“œ ์„ค์ • ์œ ์ง€

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ ˆํŠธ๋กœ ์•„์ผ€์ด๋“œ ๊ฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ์‹œ๊ฐ ํšจ๊ณผ๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. CRT ๋ชจ๋“œ ํ† ๊ธ€ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ „์ฒด์— ์Šค์บ”๋ผ์ธ๊ณผ ๋น„๋„คํŒ… ํšจ๊ณผ๊ฐ€ ์ ์šฉ๋˜๋ฉฐ, ๋„ค์˜จ ๊ธ€๋กœ์šฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํžˆ์–ด๋กœ ์„น์…˜์— ์ƒ๋™๊ฐ์„ ๋”ํ•ฉ๋‹ˆ๋‹ค. ๊ฒŒ์ž„ ์นด๋“œ์™€ ๋ฆฌ๋”๋ณด๋“œ์˜ ์ธํ„ฐ๋ž™์…˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

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

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

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

๋Œ“๊ธ€ (0)

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