๐ŸŽฎArcadeLab

๐ŸŒ Social Change Champions

by GoldenCobra39
1289 lines41.6 KB
โ–ถ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>๐ŸŒ Social Change Champions</title>
    <style>
        /* ===== RESET & BASE ===== */
        *,
        *::before,
        *::after {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
            background: linear-gradient(145deg, #f7f0e6 0%, #e8ddd0 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        .game-container {
            max-width: 1100px;
            width: 100%;
            background: rgba(255, 248, 240, 0.85);
            backdrop-filter: blur(12px);
            border-radius: 56px;
            padding: 30px 35px 40px;
            box-shadow: 0 25px 60px rgba(0, 0, 0, 0.20), 0 8px 24px rgba(0, 0, 0, 0.08);
            border: 1px solid rgba(255, 255, 255, 0.35);
            transition: all 0.25s;
        }

        /* ===== TYPOGRAPHY ===== */
        h1,
        h2,
        h3 {
            font-weight: 700;
            letter-spacing: -0.02em;
        }
        h1 {
            font-size: 2.6rem;
            background: linear-gradient(135deg, #2b5a4c, #1d3e33);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            display: inline-flex;
            align-items: center;
            gap: 10px;
        }
        h2 {
            font-size: 1.8rem;
            color: #1d3e33;
        }
        h3 {
            font-size: 1.3rem;
            color: #2b5a4c;
        }
        p {
            color: #3d3d3d;
            line-height: 1.5;
        }
        .subtitle {
            font-size: 1.05rem;
            color: #5a4f44;
            margin-top: 2px;
        }

        /* ===== BUTTONS ===== */
        .btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            padding: 12px 30px;
            border: none;
            border-radius: 50px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: 0.2s;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
            background: #fff;
            color: #1d3e33;
        }
        .btn-primary {
            background: #2b5a4c;
            color: #fff;
            box-shadow: 0 6px 18px rgba(43, 90, 76, 0.35);
        }
        .btn-primary:hover:not(:disabled) {
            background: #1d3e33;
            transform: translateY(-2px);
            box-shadow: 0 10px 28px rgba(43, 90, 76, 0.40);
        }
        .btn-primary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }
        .btn-secondary {
            background: #e8ddd0;
            color: #1d3e33;
        }
        .btn-secondary:hover {
            background: #d5c8b8;
        }
        .btn-danger {
            background: #c0392b;
            color: #fff;
        }
        .btn-success {
            background: #27ae60;
            color: #fff;
        }
        .btn-gold {
            background: #f1c40f;
            color: #1d3e33;
            box-shadow: 0 6px 18px rgba(241, 196, 15, 0.35);
        }
        .btn-gold:hover:not(:disabled) {
            background: #d4ac0e;
            transform: translateY(-2px);
        }
        .btn-sm {
            padding: 8px 18px;
            font-size: 0.9rem;
        }

        /* ===== SETUP SCREEN ===== */
        .setup-screen {
            display: block;
        }
        .setup-screen.hidden {
            display: none;
        }
        .setup-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            margin-bottom: 28px;
        }
        .player-count-control {
            display: flex;
            align-items: center;
            gap: 16px;
            background: #f0ebe4;
            padding: 8px 20px 8px 24px;
            border-radius: 60px;
        }
        .player-count-control label {
            font-weight: 600;
            color: #1d3e33;
        }
        .player-count-control input {
            width: 60px;
            padding: 6px 8px;
            border: 2px solid #cdbdb0;
            border-radius: 30px;
            font-size: 1.1rem;
            font-weight: 600;
            text-align: center;
            background: #fff;
            color: #1d3e33;
            outline: none;
        }
        .player-count-control input:focus {
            border-color: #2b5a4c;
        }
        .player-name-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 14px;
            margin: 24px 0 30px;
        }
        .player-name-grid .name-input-wrap {
            display: flex;
            align-items: center;
            gap: 10px;
            background: #f8f3ed;
            padding: 6px 16px 6px 20px;
            border-radius: 40px;
            border: 2px solid transparent;
            transition: 0.15s;
        }
        .player-name-grid .name-input-wrap:focus-within {
            border-color: #2b5a4c;
            background: #fff;
        }
        .player-name-grid .name-input-wrap .num-badge {
            font-weight: 700;
            color: #2b5a4c;
            min-width: 24px;
        }
        .player-name-grid .name-input-wrap input {
            border: none;
            background: transparent;
            padding: 10px 0;
            font-size: 1rem;
            font-weight: 500;
            color: #1d3e33;
            outline: none;
            width: 100%;
        }
        .player-name-grid .name-input-wrap input::placeholder {
            color: #b0a69a;
            font-weight: 400;
        }
        .setup-actions {
            display: flex;
            gap: 16px;
            justify-content: flex-end;
            flex-wrap: wrap;
        }

        /* ===== GAME SCREEN ===== */
        .game-screen {
            display: none;
        }
        .game-screen.active {
            display: block;
        }

        .game-top-bar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 14px;
            margin-bottom: 20px;
            padding-bottom: 14px;
            border-bottom: 2px solid rgba(43, 90, 76, 0.12);
        }
        .player-status-list {
            display: flex;
            flex-wrap: wrap;
            gap: 8px 16px;
        }
        .player-status-item {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 4px 14px 4px 10px;
            border-radius: 30px;
            background: #f0ebe4;
            font-size: 0.9rem;
            font-weight: 500;
            color: #1d3e33;
            transition: 0.2s;
        }
        .player-status-item .dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            display: inline-block;
        }
        .player-status-item .dot.active {
            background: #27ae60;
        }
        .player-status-item .dot.eliminated {
            background: #c0392b;
        }
        .player-status-item.eliminated {
            opacity: 0.5;
            text-decoration: line-through;
        }
        .player-status-item.current {
            background: #2b5a4c;
            color: #fff;
            box-shadow: 0 4px 14px rgba(43, 90, 76, 0.30);
        }
        .player-status-item.current .dot.active {
            background: #f1c40f;
        }

        .game-main {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 28px;
            padding: 10px 0 6px;
        }

        /* ===== WHEEL ===== */
        .wheel-section {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 16px;
        }
        .wheel-wrapper {
            position: relative;
            width: 260px;
            height: 260px;
        }
        .wheel-wrapper canvas {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18), inset 0 2px 8px rgba(255, 255, 255, 0.4);
            transition: transform 0.1s;
            cursor: pointer;
        }
        .wheel-wrapper .spin-btn-overlay {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 70px;
            height: 70px;
            border-radius: 50%;
            background: #fff;
            border: 3px solid #2b5a4c;
            font-weight: 700;
            font-size: 0.85rem;
            color: #1d3e33;
            cursor: pointer;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
            transition: 0.15s;
            display: flex;
            align-items: center;
            justify-content: center;
            letter-spacing: 0.03em;
        }
        .wheel-wrapper .spin-btn-overlay:hover:not(:disabled) {
            background: #2b5a4c;
            color: #fff;
            transform: translate(-50%, -50%) scale(1.04);
        }
        .wheel-wrapper .spin-btn-overlay:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .turn-indicator {
            font-size: 1.1rem;
            font-weight: 600;
            color: #1d3e33;
            background: #f0ebe4;
            padding: 6px 28px;
            border-radius: 40px;
            display: inline-block;
        }
        .turn-indicator span {
            color: #2b5a4c;
        }

        /* ===== CARD ===== */
        .card-section {
            width: 100%;
            max-width: 620px;
            margin: 0 auto;
        }
        .card {
            background: #fffcf8;
            border-radius: 36px;
            padding: 28px 32px 32px;
            box-shadow: 0 10px 32px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.6);
            border: 1px solid rgba(205, 189, 176, 0.25);
            transition: 0.25s;
            min-height: 240px;
            display: flex;
            flex-direction: column;
        }
        .card .card-badge {
            font-size: 0.75rem;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: #8a7a6a;
            margin-bottom: 6px;
        }
        .card .card-question {
            font-size: 1.25rem;
            font-weight: 600;
            color: #1d3e33;
            margin-bottom: 18px;
            line-height: 1.4;
        }
        .card .card-minigame-area {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 14px;
            padding: 8px 0 4px;
            border-top: 2px dashed rgba(43, 90, 76, 0.12);
            padding-top: 18px;
        }
        .card .card-minigame-area .mg-title {
            font-weight: 600;
            color: #2b5a4c;
            font-size: 0.95rem;
        }
        .card .card-minigame-area .mg-desc {
            font-size: 0.95rem;
            color: #4d4d4d;
            text-align: center;
        }
        .card .card-minigame-area .mg-controls {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            justify-content: center;
            margin-top: 4px;
        }
        .card .card-minigame-area .mg-controls .mg-btn {
            padding: 10px 28px;
            border-radius: 40px;
            border: 2px solid #cdbdb0;
            background: #fff;
            font-weight: 600;
            font-size: 1rem;
            cursor: pointer;
            transition: 0.15s;
            color: #1d3e33;
            min-width: 80px;
        }
        .card .card-minigame-area .mg-controls .mg-btn:hover:not(:disabled) {
            background: #2b5a4c;
            color: #fff;
            border-color: #2b5a4c;
        }
        .card .card-minigame-area .mg-controls .mg-btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }
        .card .card-minigame-area .mg-controls .mg-btn.correct {
            background: #27ae60;
            border-color: #27ae60;
            color: #fff;
        }
        .card .card-minigame-area .mg-controls .mg-btn.wrong {
            background: #c0392b;
            border-color: #c0392b;
            color: #fff;
        }
        .card .card-minigame-area .mg-feedback {
            font-weight: 600;
            font-size: 1.05rem;
            margin-top: 4px;
            min-height: 2.2rem;
        }
        .card .card-minigame-area .mg-feedback.success {
            color: #27ae60;
        }
        .card .card-minigame-area .mg-feedback.fail {
            color: #c0392b;
        }
        .card .card-minigame-area .mg-progress {
            font-size: 0.9rem;
            color: #5a4f44;
        }

        /* mini-game specific */
        .mg-tap-target {
            width: 90px;
            height: 90px;
            border-radius: 50%;
            background: #2b5a4c;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #fff;
            font-weight: 700;
            font-size: 1.2rem;
            cursor: pointer;
            transition: 0.1s;
            box-shadow: 0 6px 20px rgba(43, 90, 76, 0.30);
            user-select: none;
        }
        .mg-tap-target:hover:not(:disabled) {
            transform: scale(1.04);
            background: #1d3e33;
        }
        .mg-tap-target:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .mg-reaction-circle {
            width: 120px;
            height: 120px;
            border-radius: 50%;
            background: #e74c3c;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #fff;
            font-weight: 700;
            font-size: 1.1rem;
            cursor: pointer;
            transition: 0.08s;
            user-select: none;
            box-shadow: 0 6px 24px rgba(231, 76, 60, 0.30);
        }
        .mg-reaction-circle.go {
            background: #27ae60;
            box-shadow: 0 6px 24px rgba(39, 174, 96, 0.35);
        }
        .mg-reaction-circle:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .mg-color-options {
            display: flex;
            gap: 14px;
            flex-wrap: wrap;
            justify-content: center;
        }
        .mg-color-options .color-swatch {
            width: 56px;
            height: 56px;
            border-radius: 50%;
            border: 3px solid #ddd;
            cursor: pointer;
            transition: 0.12s;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
        }
        .mg-color-options .color-swatch:hover:not(:disabled) {
            transform: scale(1.08);
            border-color: #2b5a4c;
        }
        .mg-color-options .color-swatch:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }

        .mg-sequence-display {
            display: flex;
            gap: 16px;
            font-size: 2.2rem;
            font-weight: 700;
            color: #1d3e33;
            letter-spacing: 6px;
            padding: 8px 20px;
            background: #f0ebe4;
            border-radius: 20px;
        }
        .mg-sequence-input {
            display: flex;
            gap: 12px;
            flex-wrap: wrap;
            justify-content: center;
        }
        .mg-sequence-input .seq-btn {
            width: 52px;
            height: 52px;
            border-radius: 50%;
            border: 2px solid #cdbdb0;
            background: #fff;
            font-size: 1.2rem;
            font-weight: 700;
            cursor: pointer;
            transition: 0.1s;
            color: #1d3e33;
        }
        .mg-sequence-input .seq-btn:hover:not(:disabled) {
            background: #2b5a4c;
            color: #fff;
            border-color: #2b5a4c;
        }
        .mg-sequence-input .seq-btn:disabled {
            opacity: 0.3;
            cursor: not-allowed;
        }

        .mg-options-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            width: 100%;
            max-width: 420px;
        }
        .mg-options-grid .opt-btn {
            padding: 12px 8px;
            border-radius: 20px;
            border: 2px solid #cdbdb0;
            background: #fff;
            font-weight: 500;
            font-size: 0.95rem;
            cursor: pointer;
            transition: 0.12s;
            color: #1d3e33;
            text-align: center;
        }
        .mg-options-grid .opt-btn:hover:not(:disabled) {
            background: #f0ebe4;
            border-color: #2b5a4c;
        }
        .mg-options-grid .opt-btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }
        .mg-options-grid .opt-btn.correct {
            background: #27ae60;
            border-color: #27ae60;
            color: #fff;
        }
        .mg-options-grid .opt-btn.wrong {
            background: #c0392b;
            border-color: #c0392b;
            color: #fff;
        }

        .mg-tf-group {
            display: flex;
            gap: 20px;
        }
        .mg-tf-group .tf-btn {
            padding: 12px 36px;
            border-radius: 40px;
            border: 2px solid #cdbdb0;
            background: #fff;
            font-weight: 700;
            font-size: 1.1rem;
            cursor: pointer;
            transition: 0.12s;
            color: #1d3e33;
            min-width: 100px;
        }
        .mg-tf-group .tf-btn:hover:not(:disabled) {
            background: #2b5a4c;
            color: #fff;
            border-color: #2b5a4c;
        }
        .mg-tf-group .tf-btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }
        .mg-tf-group .tf-btn.correct {
            background: #27ae60;
            border-color: #27ae60;
            color: #fff;
        }
        .mg-tf-group .tf-btn.wrong {
            background: #c0392b;
            border-color: #c0392b;
            color: #fff;
        }

        /* ===== WINNER SCREEN ===== */
        .winner-screen {
            display: none;
            text-align: center;
            padding: 30px 20px 20px;
        }
        .winner-screen.active {
            display: block;
        }
        .winner-screen .trophy {
            font-size: 5rem;
            margin-bottom: 6px;
        }
        .winner-screen .winner-name {
            font-size: 2.8rem;
            font-weight: 800;
            color: #1d3e33;
            background: linear-gradient(135deg, #f1c40f, #f39c12);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }
        .winner-screen .winner-sub {
            font-size: 1.2rem;
            color: #5a4f44;
            margin: 6px 0 24px;
        }

        /* ===== UTILITY ===== */
        .hidden {
            display: none !important;
        }
        .text-center {
            text-align: center;
        }
        .mt-8 {
            margin-top: 8px;
        }
        .mt-16 {
            margin-top: 16px;
        }
        .gap-8 {
            gap: 8px;
        }
        .flex-center {
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .flex-wrap {
            flex-wrap: wrap;
        }
        .w-full {
            width: 100%;
        }

        /* ===== RESPONSIVE ===== */
        @media (max-width: 700px) {
            .game-container {
                padding: 20px 16px 30px;
                border-radius: 32px;
            }
            h1 {
                font-size: 1.8rem;
            }
            .player-name-grid {
                grid-template-columns: 1fr 1fr;
            }
            .wheel-wrapper {
                width: 200px;
                height: 200px;
            }
            .wheel-wrapper .spin-btn-overlay {
                width: 56px;
                height: 56px;
                font-size: 0.7rem;
            }
            .card {
                padding: 20px 18px 24px;
                min-height: 200px;
            }
            .card .card-question {
                font-size: 1.05rem;
            }
            .mg-options-grid {
                grid-template-columns: 1fr 1fr;
            }
            .mg-tf-group .tf-btn {
                padding: 10px 24px;
                min-width: 70px;
            }
            .player-status-list {
                gap: 4px 10px;
            }
            .player-status-item {
                font-size: 0.75rem;
                padding: 2px 10px 2px 8px;
            }
            .game-top-bar {
                flex-direction: column;
                align-items: stretch;
            }
        }
        @media (max-width: 460px) {
            .player-name-grid {
                grid-template-columns: 1fr;
            }
            .player-count-control {
                flex-wrap: wrap;
                padding: 8px 14px;
            }
            .setup-actions {
                justify-content: center;
            }
            .mg-options-grid {
                grid-template-columns: 1fr;
            }
            .mg-tf-group {
                flex-direction: column;
                align-items: center;
            }
        }

        /* confetti simple */
        .confetti-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 9999;
            overflow: hidden;
        }
        .confetti-piece {
            position: absolute;
            width: 10px;
            height: 10px;
            opacity: 0.9;
            animation: confetti-fall linear forwards;
        }
        @keyframes confetti-fall {
            0% {
                transform: translateY(-20px) rotate(0deg);
                opacity: 1;
            }
            100% {
                transform: translateY(110vh) rotate(720deg);
                opacity: 0;
            }
        }
    </style>
</head>
<body>

    <div class="game-container" id="app">

        <!-- ==================== SETUP ==================== -->
        <div class="setup-screen" id="setupScreen">
            <div class="setup-header">
                <div>
                    <h1>๐ŸŒ Social Change Champions</h1>
                    <p class="subtitle">Spin โ€ข Answer โ€ข Survive</p>
                </div>
                <div class="player-count-control">
                    <label for="playerCount">Players</label>
                    <input type="number" id="playerCount" min="2" max="10" value="4" />
                </div>
            </div>

            <div class="player-name-grid" id="nameGrid"></div>

            <div class="setup-actions">
                <button class="btn btn-secondary" id="randomNamesBtn">๐ŸŽฒ Random Names</button>
                <button class="btn btn-primary" id="startGameBtn">๐Ÿš€ Start Game</button>
            </div>
        </div>

        <!-- ==================== GAME ==================== -->
        <div class="game-screen" id="gameScreen">
            <!-- Top Bar -->
            <div class="game-top-bar">
                <div class="player-status-list" id="playerStatusList"></div>
                <div>
                    <span class="turn-indicator" id="turnIndicator">๐ŸŽฏ <span id="currentPlayerName">โ€”</span>'s Turn</span>
                </div>
            </div>

            <!-- Main -->
            <div class="game-main" id="gameMain">
                <!-- Wheel -->
                <div class="wheel-section">
                    <div class="wheel-wrapper">
                        <canvas id="wheelCanvas" width="400" height="400"></canvas>
                        <button class="spin-btn-overlay" id="spinBtn">SPIN</button>
                    </div>
                </div>

                <!-- Card -->
                <div class="card-section">
                    <div class="card" id="cardContainer">
                        <div class="card-badge" id="cardBadge">โœฆ CARD</div>
                        <div class="card-question" id="cardQuestion">Spin the wheel to reveal your challenge!</div>
                        <div class="card-minigame-area" id="miniGameArea">
                            <div class="mg-desc" id="mgDesc">The mini-game will appear here.</div>
                            <div id="mgControls" class="mg-controls"></div>
                            <div class="mg-feedback" id="mgFeedback"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- ==================== WINNER ==================== -->
        <div class="winner-screen" id="winnerScreen">
            <div class="trophy">๐Ÿ†</div>
            <div class="winner-name" id="winnerName">Champion</div>
            <div class="winner-sub">has made it to the top! ๐ŸŒŸ</div>
            <button class="btn btn-gold" id="playAgainBtn">๐Ÿ”„ Play Again</button>
        </div>

    </div>

    <!-- Confetti container -->
    <div class="confetti-container" id="confettiContainer"></div>

    <script>
        // ============================================================
        //  DATA โ€” 8 Cards with Questions + Miniโ€‘games
        // ============================================================
        const CARDS = [{
            id: 0,
            question: '๐ŸŒฑ What is one simple action you can take daily to reduce your carbon footprint?',
            miniGame: 'tap',
            mgLabel: 'Quick Tap',
            mgDesc: 'Tap the green button 5 times as fast as you can! (3s limit)',
            mgData: { target: 5, timeLimit: 3000 }
        }, {
            id: 1,
            question: '๐Ÿ“Š Roughly what percentage of global greenhouse gas emissions come from agriculture?',
            miniGame: 'math',
            mgLabel: 'Math Quiz',
            mgDesc: 'Choose the correct answer.',
            mgData: { question: '15 + 7 = ?', options: ['20', '22', '24', '18'], correct: 1 }
        }, {
            id: 2,
            question: 'โš–๏ธ "Climate change is the single greatest threat to human health in the 21st century."',
            miniGame: 'tf',
            mgLabel: 'True / False',
            mgDesc: 'Is this statement true or false?',
            mgData: { answer: true }
        }, {
            id: 3,
            question: 'โ˜€๏ธ Which of the following is a renewable energy source?',
            miniGame: 'trivia',
            mgLabel: 'Trivia',
            mgDesc: 'Pick the correct option.',
            mgData: { options: ['Coal', 'Natural Gas', 'Solar Power', 'Nuclear'], correct: 2 }
        }, {
            id: 4,
            question: '๐ŸŒˆ What does the term "intersectionality" mean in social justice?',
            miniGame: 'reaction',
            mgLabel: 'Reaction Time',
            mgDesc: 'Click the circle the moment it turns GREEN!',
            mgData: { timeLimit: 2000 }
        }, {
            id: 5,
            question: 'โ™ป๏ธ What are the three pillars of sustainability?',
            miniGame: 'memory',
            mgLabel: 'Memory Sequence',
            mgDesc: 'Remember and repeat the color sequence:',
            mgData: { sequence: ['๐Ÿ”ด', '๐ŸŸข', '๐Ÿ”ต'] }
        }, {
            id: 6,
            question: '๐ŸŒ What is the main objective of the UN Sustainable Development Goals?',
            miniGame: 'pattern',
            mgLabel: 'Complete the Pattern',
            mgDesc: 'What number comes next?',
            mgData: { sequence: [2, 4, 6, 8], answer: 10 }
        }, {
            id: 7,
            question: '๐Ÿ™๏ธ What does "environmental racism" refer to?',
            miniGame: 'unscramble',
            mgLabel: 'Word Unscramble',
            mgDesc: 'Unscramble the word:  R E T A W',
            mgData: { scrambled: 'RETAW', answer: 'WATER' }
        }];

        // ============================================================
        //  STATE
        // ============================================================
        let players = []; // { name, active }
        let currentPlayerIndex = 0;
        let gameActive = false;
        let spinning = false;
        let cardLocked = false; // prevent double interaction
        let currentCard = null;
        let eliminatedCount = 0;
        let gameOver = false;

        // mini-game state
        let mgState = {};

        // DOM refs
        const $ = id => document.getElementById(id);
        const setupScreen = $('setupScreen');
        const gameScreen = $('gameScreen');
        const winnerScreen = $('winnerScreen');
        const nameGrid = $('nameGrid');
        const playerCountInput = $('playerCount');
        const startBtn = $('startGameBtn');
        const randomNamesBtn = $('randomNamesBtn');
        const spinBtn = $('spinBtn');
        const wheelCanvas = $('wheelCanvas');
        const playerStatusList = $('playerStatusList');
        const currentPlayerName = $('currentPlayerName');
        const turnIndicator = $('turnIndicator');
        const cardQuestion = $('cardQuestion');
        const cardBadge = $('cardBadge');
        const mgDesc = $('mgDesc');
        const mgControls = $('mgControls');
        const mgFeedback = $('mgFeedback');
        const miniGameArea = $('miniGameArea');
        const winnerName = $('winnerName');
        const playAgainBtn = $('playAgainBtn');
        const confettiContainer = $('confettiContainer');

        // ============================================================
        //  SETUP
        // ============================================================
        function generateName(i) {
            const names = ['Aisha', 'Ben', 'Clara', 'Darius', 'Elena', 'Felix', 'Gita', 'Hugo', 'Imani', 'Jules'];
            return names[i % names.length] + (i >= names.length ? (i + 1) : '');
        }

        function renderNameInputs(count) {
            nameGrid.innerHTML = '';
            for (let i = 0; i < count; i++) {
                const div = document.createElement('div');
                div.className = 'name-input-wrap';
                const badge = document.createElement('span');
                badge.className = 'num-badge';
                badge.textContent = `${i+1}.`;
                const input = document.createElement('input');
                input.type = 'text';
                input.placeholder = `Player ${i+1}`;
                input.value = generateName(i);
                input.dataset.idx = i;
                div.appendChild(badge);
                div.appendChild(input);
                nameGrid.appendChild(div);
            }
        }

        function getPlayerNames() {
            const inputs = nameGrid.querySelectorAll('input');
            return Array.from(inputs).map(inp => inp.value.trim() || `Player ${inp.dataset.idx*1+1}`);
        }

        playerCountInput.addEventListener('change', () => {
            let val = parseInt(playerCountInput.value) || 4;
            val = Math.min(10, Math.max(2, val));
            playerCountInput.value = val;
            renderNameInputs(val);
        });

        randomNamesBtn.addEventListener('click', () => {
            const inputs = nameGrid.querySelectorAll('input');
            inputs.forEach((inp, i) => {
                inp.value = generateName(i);
            });
        });

        // ============================================================
        //  START GAME
        // ============================================================
        startBtn.addEventListener('click', () => {
            const names = getPlayerNames();
            if (names.length < 2) { alert('Need at least 2 players!'); return; }
            // check duplicates
            const unique = new Set(names);
            if (unique.size !== names.length) { alert('Player names must be unique!'); return; }

            players = names.map(name => ({ name, active: true }));
            currentPlayerIndex = 0;
            eliminatedCount = 0;
            gameOver = false;
            gameActive = true;

            setupScreen.classList.add('hidden');
            gameScreen.classList.add('active');
            winnerScreen.classList.remove('active');

            updateStatusBar();
            updateTurnIndicator();
            resetCard();
            drawWheel();
            spinBtn.disabled = false;
            cardLocked = false;
            mgState = {};
            document.querySelectorAll('.mg-feedback').forEach(el => el.textContent = '');
            document.querySelectorAll('.mg-feedback').forEach(el => el.className = 'mg-feedback');
        });

        playAgainBtn.addEventListener('click', () => {
            winnerScreen.classList.remove('active');
            gameScreen.classList.remove('active');
            setupScreen.classList.remove('hidden');
            confettiContainer.innerHTML = '';
            gameActive = false;
            gameOver = false;
            players = [];
            currentPlayerIndex = 0;
            eliminatedCount = 0;
            spinBtn.disabled = true;
            resetCard();
        });

        // ============================================================
        //  STATUS BAR
        // ============================================================
        function updateStatusBar() {
            playerStatusList.innerHTML = '';
            players.forEach((p, idx) => {
                const el = document.createElement('span');
                el.className = 'player-status-item';
                if (!p.active) el.classList.add('eliminated');
                if (idx === currentPlayerIndex && p.active) el.classList.add('current');
                const dot = document.createElement('span');
                dot.className = `dot ${p.active ? 'active' : 'eliminated'}`;
                el.appendChild(dot);
                el.appendChild(document.createTextNode(p.name));
                playerStatusList.appendChild(el);
            });
        }

        function updateTurnIndicator() {
            const p = players[currentPlayerIndex];
            currentPlayerName.textContent = p && p.active ? p.name : 'โ€”';
            if (!p || !p.active) {
                // skip to next active
                advanceToNextActive();
            }
        }

        function advanceToNextActive() {
            const start = currentPlayerIndex;
            let next = (start + 1) % players.length;
            let attempts = 0;
            while (!players[next].active && attempts < players.length) {
                next = (next + 1) % players.length;
                attempts++;
            }
            if (players[next].active) {
                currentPlayerIndex = next;
            } else {
                // no active players left? should be winner already
                declareWinner();
            }
            updateStatusBar();
            const p = players[currentPlayerIndex];
            currentPlayerName.textContent = p && p.active ? p.name : 'โ€”';
        }

        // ============================================================
        //  WHEEL
        // ============================================================
        const COLORS = ['#e74c3c', '#f39c12', '#2ecc71', '#3498db', '#9b59b6', '#1abc9c', '#e67e22', '#e84393'];
        const LABELS = ['๐ŸŒฑ', '๐Ÿ“Š', 'โš–๏ธ', 'โ˜€๏ธ', '๐ŸŒˆ', 'โ™ป๏ธ', '๐ŸŒ', '๐Ÿ™๏ธ'];

        function drawWheel(rotation = 0) {
            const ctx = wheelCanvas.getContext('2d');
            const w = 400,
                h = 400,
                cx = 200,
                cy = 200,
                r = 180;
            ctx.clearRect(0, 0, w, h);
            const n = CARDS.length;
            const arc = (2 * Math.PI) / n;
            for (let i = 0; i < n; i++) {
                const start = i * arc + rotation;
                const end = start + arc;
                ctx.beginPath();
                ctx.moveTo(cx, cy);
                ctx.arc(cx, cy, r, start, end);
                ctx.closePath();
                ctx.fillStyle = COLORS[i % COLORS.length];
                ctx.fill();
                ctx.strokeStyle = '#fff';
                ctx.lineWidth = 2;
                ctx.stroke();
                // label
                const mid = start + arc / 2;
                const tx = cx + r * 0.65 * Math.cos(mid);
                const ty = cy + r * 0.65 * Math.sin(mid);
                ctx.font = 'bold 26px sans-serif';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillStyle = '#fff';
                ctx.shadowColor = 'rgba(0,0,0,0.3)';
                ctx.shadowBlur = 8;
                ctx.fillText(LABELS[i % LABELS.length], tx, ty);
                ctx.shadowBlur = 0;
            }
            // center dot
            ctx.beginPath();
            ctx.arc(cx, cy, 16, 0, 2 * Math.PI);
            ctx.fillStyle = '#fff';
            ctx.fill();
            ctx.strokeStyle = '#2b5a4c';
            ctx.lineWidth = 3;
            ctx.stroke();
            // pointer (top)
            ctx.beginPath();
            ctx.moveTo(cx, 14);
            ctx.lineTo(cx - 14, 32);
            ctx.lineTo(cx + 14, 32);
            ctx.closePath();
            ctx.fillStyle = '#c0392b';
            ctx.fill();
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 2;
            ctx.stroke();
        }

        let wheelRotation = 0;
        let spinAnimation = null;

        function spinWheel() {
            if (spinning || !gameActive || gameOver) return;
            const activePlayers = players.filter(p => p.active);
            if (activePlayers.length <= 1) {
                declareWinner();
                return;
            }
            const current = players[currentPlayerIndex];
            if (!current || !current.active) {
                advanceToNextActive();
                return;
            }

            spinning = true;
            spinBtn.disabled = true;
            cardLocked = true;
            resetCard();

            const spins = 4 + Math.random() * 4;
            const totalRotation = spins * 2 * Math.PI + Math.random() * 2 * Math.PI;
            const targetRotation = wheelRotation + totalRotation;
            const duration = 1800 + Math.random() * 600;
            const startTime = performance.now();
            const startRot = wheelRotation;

            function animateSpin(time) {
                const elapsed = time - startTime;
                const progress = Math.min(elapsed / duration, 1);
                // easeOutCubic
                const eased = 1 - Math.pow(1 - progress, 3);
                const currentRot = startRot + totalRotation * eased;
                wheelRotation = currentRot;
                drawWheel(currentRot);
                if (progress < 1) {
                    requestAnimationFrame(animateSpin);
                } else {
                    wheelRotation = currentRot;
                    drawWheel(currentRot);
                    // determine which card
                    const idx = getCardIndexFromRotation(currentRot);
                    currentCard = CARDS[idx % CARDS.length];
                    spinning = false;
                    spinBtn.disabled = false;
                    cardLocked = false;
                    revealCard(currentCard);
                }
            }
            requestAnimationFrame(animateSpin);
        }

        function getCardIndexFromRotation(rot) {
            const n = CARDS.length;
            const arc = (2 * Math.PI) / n;
            // normalize rotation
            let r = rot % (2 * Math.PI);
            if (r < 0) r += 2 * Math.PI;
            // pointer is at top ( - PI/2 )
            const pointerAngle = -Math.PI / 2;
            let diff = (pointerAngle - r + 2 * Math.PI) % (2 * Math.PI);
            let idx = Math.floor(diff / arc);
            if (idx >= n) idx = n - 1;
            return idx;
        }

        spinBtn.addEventListener('click', spinWheel);

        // ============================================================
        //  CARD + MINIโ€‘GAME
        // ============================================================
        function resetCard() {
            cardBadge.textContent = 'โœฆ CARD';
            cardQuestion.textContent = 'Spin the wheel to reveal your challenge!';
            mgDesc.textContent = 'The mini-game will appear here.';
            mgControls.innerHTML = '';
            mgFeedback.textContent = '';
            mgFeedback.className = 'mg-feedback';
            currentCard = null;
            mgState = {};
        }

        function revealCard(card) {
            cardBadge.textContent = `โœฆ CARD ${card.id+1} ยท ${card.mgLabel}`;
            cardQuestion.textContent = card.question;
            mgDesc.textContent = card.mgDesc;
            mgControls.innerHTML = '';
            mgFeedback.textContent = '';
            mgFeedback.className = 'mg-feedback';
            mgState = { card, passed: false, done: false };

            // build mini-game UI
            switch (card.miniGame) {
                case 'tap':
                    buildTapGame(card);
                    break;
                case 'math':
                    buildMathGame(card);
                    break;
                case 'tf':
                    buildTFGame(card);
                    break;
                case 'trivia':
                    buildTriviaGame(card);
                    break;
                case 'reaction':
                    buildReactionGame(card);
                    break;
                case 'memory':
                    buildMemoryGame(card);
                    break;
                case 'pattern':
                    buildPatternGame(card);
                    break;
                case 'unscramble

Game Source: ๐ŸŒ Social Change Champions

Creator: GoldenCobra39

Libraries: none

Complexity: complex (1289 lines, 41.6 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: social-change-champions-goldencobra39" to link back to the original. Then publish at arcadelab.ai/publish.