Created
April 3, 2026 01:46
-
-
Save JKHeadley/c23fc90107a85b715f6095c8be9fb7de to your computer and use it in GitHub Desktop.
B2Lead Film Score Quiz - Name That Tune
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Name That Tune! - B2Lead Film Score Day</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Bangers&family=Inter:wght@400;600;700;900&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js"></script> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { font-family: 'Inter', sans-serif; background: linear-gradient(135deg, #0f0c29, #1a1a4e, #24243e); color: #fff; min-height: 100vh; overflow-x: hidden; } | |
| .screen { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } | |
| .team-a-bg { background: linear-gradient(135deg, #F59E0B, #D97706) !important; color: #1a1a2e !important; } | |
| .team-b-bg { background: linear-gradient(135deg, #06B6D4, #0891B2) !important; color: #1a1a2e !important; } | |
| .team-a-accent { border-left: 4px solid #F59E0B; } | |
| .team-b-accent { border-left: 4px solid #06B6D4; } | |
| .team-a-glow { color: #F59E0B; text-shadow: 0 0 30px rgba(245,158,11,0.5); } | |
| .team-b-glow { color: #06B6D4; text-shadow: 0 0 30px rgba(6,182,212,0.5); } | |
| .join-container { text-align: center; max-width: 480px; width: 100%; } | |
| .music-icon { font-size: 64px; display: block; margin-bottom: 10px; animation: float 3s ease-in-out infinite; } | |
| @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } | |
| .game-title { font-family: 'Bangers', cursive; font-size: 56px; letter-spacing: 3px; background: linear-gradient(90deg, #F59E0B, #EF4444, #06B6D4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 5px; } | |
| .game-subtitle { font-size: 18px; color: rgba(255,255,255,0.6); margin-bottom: 40px; } | |
| .name-input { width: 100%; padding: 16px 20px; font-size: 20px; border: 2px solid rgba(255,255,255,0.2); border-radius: 12px; background: rgba(255,255,255,0.1); color: #fff; text-align: center; outline: none; transition: border-color 0.3s; margin-bottom: 20px; } | |
| .name-input:focus { border-color: #06B6D4; } | |
| .name-input::placeholder { color: rgba(255,255,255,0.4); } | |
| .team-label { font-size: 16px; color: rgba(255,255,255,0.7); margin-bottom: 15px; } | |
| .team-buttons { display: flex; gap: 15px; justify-content: center; } | |
| .team-btn { flex: 1; padding: 20px 15px; border: none; border-radius: 16px; cursor: pointer; font-size: 18px; font-weight: 700; transition: transform 0.2s, box-shadow 0.2s; display: flex; flex-direction: column; align-items: center; gap: 8px; } | |
| .team-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 25px rgba(0,0,0,0.3); } | |
| .team-btn:active { transform: translateY(0); } | |
| .team-a-btn { background: linear-gradient(135deg, #F59E0B, #D97706); color: #1a1a2e; } | |
| .team-b-btn { background: linear-gradient(135deg, #06B6D4, #0891B2); color: #1a1a2e; } | |
| .team-emoji { font-size: 28px; } | |
| .team-name { font-size: 16px; } | |
| .error-text { color: #EF4444; margin-top: 15px; min-height: 20px; } | |
| .player-container { text-align: center; max-width: 480px; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 15px; } | |
| .player-header { display: flex; align-items: center; gap: 12px; justify-content: center; flex-wrap: wrap; } | |
| .round-badge { background: rgba(255,255,255,0.15); padding: 8px 18px; border-radius: 20px; font-weight: 700; font-size: 16px; } | |
| .round-badge.large { font-size: 22px; padding: 10px 24px; } | |
| .team-badge { padding: 6px 14px; border-radius: 20px; font-weight: 700; font-size: 14px; } | |
| .hint-text { font-style: italic; color: rgba(255,255,255,0.7); font-size: 18px; min-height: 24px; } | |
| .status-text { font-size: 20px; font-weight: 700; min-height: 30px; transition: color 0.3s; } | |
| .status-text.waiting { color: rgba(255,255,255,0.5); } | |
| .status-text.active { color: #10B981; animation: pulse-text 1s infinite; } | |
| .status-text.buzzed { color: #F59E0B; } | |
| @keyframes pulse-text { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } | |
| .buzz-btn { width: 220px; height: 220px; border-radius: 50%; border: none; background: linear-gradient(145deg, #666, #444); color: rgba(255,255,255,0.4); font-family: 'Bangers', cursive; font-size: 48px; cursor: not-allowed; transition: all 0.3s; position: relative; margin: 10px 0; -webkit-tap-highlight-color: transparent; } | |
| .buzz-btn.active { background: linear-gradient(145deg, #EF4444, #DC2626); color: #fff; cursor: pointer; box-shadow: 0 0 40px rgba(239,68,68,0.4), 0 0 80px rgba(239,68,68,0.2); animation: buzz-pulse 1.5s ease-in-out infinite; } | |
| @keyframes buzz-pulse { 0%, 100% { box-shadow: 0 0 40px rgba(239,68,68,0.4), 0 0 80px rgba(239,68,68,0.2); transform: scale(1); } 50% { box-shadow: 0 0 60px rgba(239,68,68,0.6), 0 0 120px rgba(239,68,68,0.3); transform: scale(1.05); } } | |
| .buzz-btn.active:active { transform: scale(0.95); box-shadow: 0 0 20px rgba(239,68,68,0.3); } | |
| .buzz-text { text-shadow: 2px 2px 4px rgba(0,0,0,0.3); letter-spacing: 4px; } | |
| .buzz-result { flex-direction: column; align-items: center; gap: 5px; } | |
| .position-number { font-family: 'Bangers', cursive; font-size: 72px; color: #10B981; text-shadow: 0 0 20px rgba(16,185,129,0.5); } | |
| .position-text { font-size: 16px; color: rgba(255,255,255,0.7); } | |
| .timer-display { font-family: 'Bangers', cursive; font-size: 36px; padding: 5px 20px; border-radius: 10px; background: rgba(255,255,255,0.1); } | |
| .timer-green { color: #10B981; } | |
| .timer-yellow { color: #F59E0B; } | |
| .timer-red { color: #EF4444; animation: timer-blink 0.5s infinite; } | |
| @keyframes timer-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } | |
| .scoreboard-mini { display: flex; gap: 12px; width: 100%; max-width: 340px; margin-top: 10px; } | |
| .score-mini { flex: 1; padding: 12px; border-radius: 12px; text-align: center; display: flex; flex-direction: column; gap: 4px; } | |
| .score-team-name { font-size: 12px; font-weight: 700; } | |
| .score-value { font-family: 'Bangers', cursive; font-size: 32px; } | |
| .host-container { width: 100%; max-width: 1100px; padding: 15px; } | |
| .host-header { text-align: center; margin-bottom: 20px; } | |
| .host-title { font-family: 'Bangers', cursive; font-size: 36px; letter-spacing: 2px; } | |
| .host-badge { background: #EF4444; padding: 4px 14px; border-radius: 8px; font-size: 16px; font-family: 'Inter', sans-serif; vertical-align: middle; margin-left: 10px; } | |
| .host-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } | |
| .host-left, .host-right { display: flex; flex-direction: column; gap: 15px; } | |
| .round-card, .timer-card, .buzz-list-card, .players-card, .scoreboard-card { background: rgba(255,255,255,0.08); border-radius: 16px; padding: 20px; } | |
| .card-title { font-size: 16px; color: rgba(255,255,255,0.6); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1px; } | |
| .question-card { margin-top: 15px; } | |
| .question-answer { font-family: 'Bangers', cursive; font-size: 32px; color: #F59E0B; margin-bottom: 5px; } | |
| .question-hint { font-style: italic; color: rgba(255,255,255,0.5); font-size: 16px; margin-bottom: 10px; } | |
| .youtube-link { display: inline-block; background: #EF4444; color: #fff; padding: 10px 20px; border-radius: 8px; text-decoration: none; font-weight: 700; font-size: 16px; transition: background 0.2s; } | |
| .youtube-link:hover { background: #DC2626; } | |
| .timer-card { text-align: center; } | |
| .timer-big { font-family: 'Bangers', cursive; font-size: 64px; line-height: 1; } | |
| .timer-label { font-size: 14px; color: rgba(255,255,255,0.5); text-transform: uppercase; } | |
| .host-controls { display: flex; gap: 10px; flex-wrap: wrap; } | |
| .ctrl-btn { padding: 12px 20px; border: none; border-radius: 10px; font-size: 16px; font-weight: 700; cursor: pointer; transition: transform 0.2s, opacity 0.2s; flex: 1; min-width: 100px; } | |
| .ctrl-btn:hover { transform: translateY(-2px); } | |
| .ctrl-btn:active { transform: translateY(0); } | |
| .start-btn { background: #10B981; color: #fff; } | |
| .stop-btn { background: #EF4444; color: #fff; } | |
| .next-btn { background: #6366F1; color: #fff; } | |
| .reset-btn { background: rgba(255,255,255,0.15); color: #fff; } | |
| .danger-btn { background: rgba(239,68,68,0.2); color: #EF4444; border: 1px solid rgba(239,68,68,0.3); } | |
| .award-section { text-align: center; } | |
| .award-label { font-size: 14px; color: rgba(255,255,255,0.6); margin-bottom: 10px; text-transform: uppercase; } | |
| .award-buttons { display: flex; gap: 10px; } | |
| .award-btn { flex: 1; padding: 14px; border: none; border-radius: 10px; font-size: 16px; font-weight: 700; cursor: pointer; transition: transform 0.2s; } | |
| .award-btn:hover { transform: translateY(-2px); } | |
| .buzz-list { display: flex; flex-direction: column; gap: 8px; max-height: 250px; overflow-y: auto; } | |
| .buzz-entry { display: flex; align-items: center; gap: 12px; padding: 10px 14px; background: rgba(255,255,255,0.05); border-radius: 8px; } | |
| .buzz-position { font-family: 'Bangers', cursive; font-size: 24px; color: #10B981; min-width: 40px; } | |
| .buzz-player { flex: 1; font-weight: 600; font-size: 16px; } | |
| .buzz-team-tag { font-size: 12px; padding: 3px 8px; border-radius: 6px; background: rgba(255,255,255,0.1); font-weight: 600; } | |
| .player-list { display: flex; flex-wrap: wrap; gap: 6px; } | |
| .player-tag { padding: 5px 12px; border-radius: 6px; font-size: 13px; font-weight: 600; background: rgba(255,255,255,0.05); } | |
| .empty-text { color: rgba(255,255,255,0.3); font-style: italic; } | |
| .scoreboard-big { display: flex; align-items: center; gap: 15px; } | |
| .score-big { flex: 1; padding: 15px; border-radius: 12px; text-align: center; } | |
| .score-big-team { display: block; font-size: 13px; font-weight: 700; } | |
| .score-big-value { font-family: 'Bangers', cursive; font-size: 48px; display: block; } | |
| .score-big-vs { font-family: 'Bangers', cursive; font-size: 24px; color: rgba(255,255,255,0.3); } | |
| .host-footer { text-align: center; margin-top: 20px; } | |
| .gameover-container { text-align: center; position: relative; } | |
| .gameover-title { font-family: 'Bangers', cursive; font-size: 72px; letter-spacing: 4px; margin-bottom: 20px; background: linear-gradient(90deg, #F59E0B, #EF4444, #06B6D4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } | |
| .winner-text { font-family: 'Bangers', cursive; font-size: 48px; margin-bottom: 30px; } | |
| .scoreboard-final { display: flex; align-items: center; gap: 20px; justify-content: center; } | |
| .score-final { padding: 25px 40px; border-radius: 16px; text-align: center; } | |
| .score-final-team { display: block; font-size: 16px; font-weight: 700; } | |
| .score-final-value { font-family: 'Bangers', cursive; font-size: 72px; display: block; } | |
| .score-final-vs { font-family: 'Bangers', cursive; font-size: 32px; color: rgba(255,255,255,0.3); } | |
| .confetti-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 999; } | |
| .confetti-piece { position: absolute; top: -10px; width: 10px; height: 20px; border-radius: 2px; animation: confetti-fall linear forwards; } | |
| @keyframes confetti-fall { 0% { transform: translateY(0) rotate(0deg); opacity: 1; } 100% { transform: translateY(100vh) rotate(720deg); opacity: 0; } } | |
| @media (max-width: 768px) { .host-grid { grid-template-columns: 1fr; } .game-title { font-size: 42px; } .buzz-btn { width: 180px; height: 180px; font-size: 40px; } .gameover-title { font-size: 48px; } .winner-text { font-size: 32px; } .score-final { padding: 15px 25px; } .score-final-value { font-size: 48px; } } | |
| @media (max-width: 400px) { .game-title { font-size: 36px; } .buzz-btn { width: 160px; height: 160px; font-size: 36px; } .team-buttons { flex-direction: column; } } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="joinScreen" class="screen"> | |
| <div class="join-container"> | |
| <div class="logo-area"> | |
| <span class="music-icon">🎬</span> | |
| <h1 class="game-title">Name That Tune!</h1> | |
| <p class="game-subtitle">B2Lead Film Score Day</p> | |
| </div> | |
| <div class="join-form"> | |
| <input type="text" id="playerName" placeholder="Enter your name" class="name-input" maxlength="30" autocomplete="off"> | |
| <p class="team-label">Pick your team:</p> | |
| <div class="team-buttons"> | |
| <button class="team-btn team-a-btn" onclick="joinGame('Team A')"><span class="team-emoji">🎯</span><span class="team-name">Team A</span></button> | |
| <button class="team-btn team-b-btn" onclick="joinGame('Team B')"><span class="team-emoji">🌟</span><span class="team-name">Team B</span></button> | |
| </div> | |
| <p id="joinError" class="error-text"></p> | |
| <p style="margin-top:30px"><a href="#" onclick="enterHostMode(); return false;" style="color:rgba(255,255,255,0.15); font-size:12px; text-decoration:none;">Host Mode</a></p> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="playerScreen" class="screen" style="display:none"> | |
| <div class="player-container"> | |
| <div class="player-header"> | |
| <div class="round-badge">Round <span id="pRoundNum">1</span> of <span id="pTotalRounds">10</span></div> | |
| <div id="pTeamBadge" class="team-badge"></div> | |
| </div> | |
| <div id="pHint" class="hint-text" style="display:none"></div> | |
| <div id="pStatus" class="status-text waiting">Waiting for host to start the round...</div> | |
| <div id="pTimerDisplay" class="timer-display" style="display:none"><span id="pTimer">30</span>s</div> | |
| <button id="buzzButton" class="buzz-btn" onclick="buzz()" disabled><span class="buzz-text">BUZZ!</span></button> | |
| <div id="buzzResult" class="buzz-result" style="display:none"> | |
| <span id="buzzPosition" class="position-number"></span> | |
| <span id="buzzMessage" class="position-text"></span> | |
| </div> | |
| <div class="scoreboard-mini"> | |
| <div class="score-mini team-a-bg"><span class="score-team-name">Team A</span><span id="pScoreA" class="score-value">0</span></div> | |
| <div class="score-mini team-b-bg"><span class="score-team-name">Team B</span><span id="pScoreB" class="score-value">0</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="hostScreen" class="screen" style="display:none"> | |
| <div class="host-container"> | |
| <div class="host-header"><h1 class="host-title">🎬 Name That Tune! <span class="host-badge">HOST</span></h1></div> | |
| <div class="host-grid"> | |
| <div class="host-left"> | |
| <div class="round-card"> | |
| <div class="round-badge large">Round <span id="hRoundNum">1</span> of <span id="hTotalRounds">10</span></div> | |
| <div class="question-card"> | |
| <div class="question-answer" id="hAnswer">---</div> | |
| <div class="question-hint" id="hHint"></div> | |
| <a id="hYoutubeLink" class="youtube-link" href="#" target="_blank" style="display:none">▶ Play Film Score</a> | |
| </div> | |
| </div> | |
| <div class="timer-card"><div class="timer-big" id="hTimer">30</div><div class="timer-label">seconds</div></div> | |
| <div class="host-controls"> | |
| <button id="btnStart" class="ctrl-btn start-btn" onclick="hostStartRound()">Start Round</button> | |
| <button id="btnStop" class="ctrl-btn stop-btn" onclick="hostStopRound()" style="display:none">Stop Round</button> | |
| <button id="btnNext" class="ctrl-btn next-btn" onclick="hostNextRound()">Next Round</button> | |
| <button id="btnReset" class="ctrl-btn reset-btn" onclick="hostResetRound()">Reset Round</button> | |
| </div> | |
| <div class="award-section"> | |
| <p class="award-label">Award point to:</p> | |
| <div class="award-buttons"> | |
| <button class="award-btn team-a-bg" onclick="hostAwardPoint('Team A')">Team A</button> | |
| <button class="award-btn team-b-bg" onclick="hostAwardPoint('Team B')">Team B</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="host-right"> | |
| <div class="buzz-list-card"><h3 class="card-title">Buzz Order</h3><div id="hBuzzList" class="buzz-list"><p class="empty-text">No buzzes yet...</p></div></div> | |
| <div class="players-card"><h3 class="card-title">Players (<span id="hPlayerCount">0</span>)</h3><div id="hPlayerList" class="player-list"></div></div> | |
| <div class="scoreboard-card"> | |
| <h3 class="card-title">Scoreboard</h3> | |
| <div class="scoreboard-big"> | |
| <div class="score-big team-a-bg"><span class="score-big-team">Team A</span><span id="hScoreA" class="score-big-value">0</span></div> | |
| <div class="score-big-vs">VS</div> | |
| <div class="score-big team-b-bg"><span class="score-big-team">Team B</span><span id="hScoreB" class="score-big-value">0</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="host-footer"><button class="ctrl-btn danger-btn" onclick="hostResetGame()">Reset Entire Game</button></div> | |
| </div> | |
| </div> | |
| <div id="gameOverScreen" class="screen" style="display:none"> | |
| <div class="gameover-container"> | |
| <h1 class="gameover-title">Game Over!</h1> | |
| <div id="winnerAnnouncement" class="winner-text"></div> | |
| <div class="scoreboard-final"> | |
| <div class="score-final team-a-bg"><span class="score-final-team">Team A</span><span id="fScoreA" class="score-final-value">0</span></div> | |
| <div class="score-final-vs">VS</div> | |
| <div class="score-final team-b-bg"><span class="score-final-team">Team B</span><span id="fScoreB" class="score-final-value">0</span></div> | |
| </div> | |
| <div id="confetti" class="confetti-container"></div> | |
| </div> | |
| </div> | |
| <script> | |
| var QUESTIONS = [ | |
| {round:1, title:"Jaws", hint:"You're gonna need a bigger boat", youtubeUrl:"https://youtube.com/clip/Ugkxbl7GSdxKsaPJXu3rAjssKOoUotAhz_eN?si=sDtemmz3MOng1Aci"}, | |
| {round:2, title:"The Good, the Bad and the Ugly", hint:"A three-way standoff in the desert", youtubeUrl:"https://youtube.com/clip/Ugkx-_6w7tyxCIy6mhJo89DbZygXU5h6ia5m?si=6xJ3-HmE9pTp2Ixe"}, | |
| {round:3, title:"Psycho", hint:"A shower you'll never forget", youtubeUrl:"https://youtube.com/clip/UgkxcL_f2GFuAPxyMxXpRRyR-D26cEuwBQJs?si=vUnt1l2VHuP_Za1j"}, | |
| {round:4, title:"Rocky", hint:"Gonna fly now", youtubeUrl:"https://youtube.com/clip/Ugkxm7dMcO8n6Huh2eEFIgEtfb9vVJNom8QC?si=NubSXjrUaBhE9zjx"}, | |
| {round:5, title:"E.T. the Extra-Terrestrial", hint:"Phone home", youtubeUrl:"https://youtube.com/clip/UgkxlKXtryvpus5aqVzQk4gOxHtRh0qJmqGn?si=jZkcZcNA9_Nc6_rp"}, | |
| {round:6, title:"The Pink Panther", hint:"A bumbling detective", youtubeUrl:"https://youtube.com/clip/Ugkx7Z7BXKkLpUO-b2lw4YpyGjbmytEERt2b?si=onUUQTnRah8GJJUQ"}, | |
| {round:7, title:"2001: A Space Odyssey", hint:"Open the pod bay doors", youtubeUrl:"https://youtube.com/clip/Ugkx99a8HgWx9KD_x9l9phQHz4qtQVeZe2K0?si=BQKSRwQEuIFUiHNC"}, | |
| {round:8, title:"Chariots of Fire", hint:"Running in slow motion on a beach", youtubeUrl:"https://youtube.com/clip/UgkxU_7sATU6sajPylUKoSHAvoxcm840pDcl?si=rlWRzWP80ra4Jnny"}, | |
| {round:9, title:"The Exorcist", hint:"Tubular Bells", youtubeUrl:"https://youtube.com/clip/UgkxCsCfIlo1ZKIrslwMGzvV48gIHwgEcM6O?si=FKlWd4NZK8o1aRzg"}, | |
| {round:10, title:"Cinema Paradiso", hint:"A boy who loved movies", youtubeUrl:"https://youtube.com/clip/Ugkxnagcgw1E_myc_Dm4m8aNBxEe57I-ZOhq?si=4IInD5TwRGOEPLOP"} | |
| ]; | |
| var SUPABASE_URL = 'https://kkzaugvaoytyoviwofsn.supabase.co'; | |
| var SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtremF1Z3Zhb3l0eW92aXdvZnNuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM4ODgxNjEsImV4cCI6MjA4OTQ2NDE2MX0.bBoSWc_Ag0syFmPNyLujvNNM4AKUt-wG8ezK03jHMTI'; | |
| var sb = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); | |
| var channel = null; | |
| var gameState = { currentRound:1, totalRounds:QUESTIONS.length, roundActive:false, timerEndTime:0, timerDuration:30, scores:{teamA:0,teamB:0}, buzzes:[], players:[] }; | |
| var playerName='', playerTeam='', hasBuzzedThisRound=false, isHostMode=false, timerInterval=null, lastKnownRound=0; | |
| function setupChannel() { | |
| channel = sb.channel('film-quiz-game', { config: { broadcast: { self: true } } }); | |
| channel.on('broadcast', {event:'game-state'}, function(p) { | |
| var d = p.payload; | |
| gameState.currentRound=d.currentRound; gameState.roundActive=d.roundActive; | |
| gameState.timerEndTime=d.timerEndTime; gameState.timerDuration=d.timerDuration; | |
| gameState.scores=d.scores; gameState.buzzes=d.buzzes; gameState.players=d.players; | |
| if (isHostMode) updateHostUI(); else updatePlayerUI(); | |
| }); | |
| channel.on('broadcast', {event:'buzz'}, function(p) { | |
| if (!isHostMode) return; | |
| var d = p.payload; | |
| for (var i=0; i<gameState.buzzes.length; i++) { if (gameState.buzzes[i].player===d.player) return; } | |
| gameState.buzzes.push({player:d.player, team:d.team, position:gameState.buzzes.length+1, time:Date.now()}); | |
| broadcastState(); | |
| }); | |
| channel.on('broadcast', {event:'player-join'}, function(p) { | |
| if (!isHostMode) return; | |
| var d = p.payload; | |
| for (var i=0; i<gameState.players.length; i++) { | |
| if (gameState.players[i].name===d.name) { gameState.players[i].team=d.team; broadcastState(); return; } | |
| } | |
| gameState.players.push({name:d.name, team:d.team}); | |
| broadcastState(); | |
| }); | |
| channel.on('broadcast', {event:'buzz-confirm'}, function(p) { | |
| if (isHostMode) return; | |
| var d = p.payload; | |
| if (d.player !== playerName) return; | |
| document.getElementById('buzzButton').style.display='none'; | |
| document.getElementById('buzzResult').style.display='flex'; | |
| document.getElementById('buzzPosition').textContent='#'+d.position; | |
| document.getElementById('buzzMessage').textContent='Position #'+d.position; | |
| }); | |
| channel.subscribe(function(status) { console.log('Channel:', status); }); | |
| } | |
| function broadcastState() { | |
| if (!channel) return; | |
| channel.send({type:'broadcast', event:'game-state', payload:{ | |
| currentRound:gameState.currentRound, roundActive:gameState.roundActive, | |
| timerEndTime:gameState.timerEndTime, timerDuration:gameState.timerDuration, | |
| scores:gameState.scores, buzzes:gameState.buzzes, players:gameState.players | |
| }}); | |
| if (isHostMode) updateHostUI(); | |
| } | |
| // Timer | |
| function startTimer() { stopTimer(); timerInterval=setInterval(updateTimerDisplay,100); updateTimerDisplay(); } | |
| function stopTimer() { if (timerInterval){clearInterval(timerInterval);timerInterval=null;} } | |
| function getTimeRemaining() { if(!gameState.timerEndTime) return gameState.timerDuration; return Math.max(0,Math.ceil((gameState.timerEndTime-Date.now())/1000)); } | |
| function getTimerColor(r) { var p=r/gameState.timerDuration; if(p>0.5)return'timer-green'; if(p>0.2)return'timer-yellow'; return'timer-red'; } | |
| function updateTimerDisplay() { | |
| var r = getTimeRemaining(); | |
| if (isHostMode) { | |
| var el=document.getElementById('hTimer'); el.textContent=r; | |
| el.className='timer-big '+(gameState.roundActive?getTimerColor(r):''); | |
| if (r<=0 && gameState.roundActive) hostStopRound(); | |
| } else { | |
| var d=document.getElementById('pTimerDisplay'), el=document.getElementById('pTimer'); | |
| if (gameState.roundActive) { d.style.display=''; el.textContent=r; d.className='timer-display '+getTimerColor(r); } | |
| else { d.style.display='none'; } | |
| } | |
| } | |
| // Init | |
| function enterHostMode() { | |
| isHostMode=true; | |
| document.getElementById('joinScreen').style.display='none'; | |
| document.getElementById('hostScreen').style.display='flex'; | |
| setupChannel(); updateHostUI(); startTimer(); | |
| } | |
| document.addEventListener('DOMContentLoaded', function() { | |
| if (window.location.hash==='#host') { enterHostMode(); } | |
| else { document.getElementById('joinScreen').style.display='flex'; document.getElementById('playerName').focus(); } | |
| }); | |
| // Player join | |
| function joinGame(team) { | |
| var name=document.getElementById('playerName').value.trim(); | |
| if (!name) { document.getElementById('joinError').textContent='Please enter your name first!'; return; } | |
| playerName=name; playerTeam=team; | |
| document.getElementById('joinScreen').style.display='none'; | |
| document.getElementById('playerScreen').style.display='flex'; | |
| var badge=document.getElementById('pTeamBadge'); | |
| badge.textContent=team; badge.className='team-badge '+(team==='Team A'?'team-a-bg':'team-b-bg'); | |
| setupChannel(); startTimer(); | |
| setTimeout(function(){ channel.send({type:'broadcast',event:'player-join',payload:{name:playerName,team:playerTeam}}); },500); | |
| } | |
| // Player UI | |
| function updatePlayerUI() { | |
| if (gameState.currentRound>gameState.totalRounds){showGameOver();return;} | |
| if (gameState.currentRound!==lastKnownRound) { | |
| lastKnownRound=gameState.currentRound; hasBuzzedThisRound=false; | |
| document.getElementById('buzzResult').style.display='none'; | |
| document.getElementById('buzzButton').style.display=''; | |
| } | |
| document.getElementById('pRoundNum').textContent=gameState.currentRound; | |
| document.getElementById('pTotalRounds').textContent=gameState.totalRounds; | |
| var hintEl=document.getElementById('pHint'), q=QUESTIONS[gameState.currentRound-1]; | |
| if (gameState.roundActive&&q&&q.hint){hintEl.textContent='"'+q.hint+'"';hintEl.style.display='';} else {hintEl.style.display='none';} | |
| var buzzBtn=document.getElementById('buzzButton'), statusEl=document.getElementById('pStatus'); | |
| if (gameState.roundActive&&!hasBuzzedThisRound){buzzBtn.disabled=false;buzzBtn.classList.add('active');statusEl.textContent='Name that tune!';statusEl.className='status-text active';} | |
| else if (hasBuzzedThisRound){buzzBtn.disabled=true;buzzBtn.classList.remove('active');statusEl.textContent='You buzzed in!';statusEl.className='status-text buzzed';} | |
| else {buzzBtn.disabled=true;buzzBtn.classList.remove('active');statusEl.textContent='Waiting for host to start the round...';statusEl.className='status-text waiting';} | |
| document.getElementById('pScoreA').textContent=gameState.scores.teamA; | |
| document.getElementById('pScoreB').textContent=gameState.scores.teamB; | |
| } | |
| // Buzz | |
| function buzz() { | |
| if (hasBuzzedThisRound||!gameState.roundActive) return; | |
| hasBuzzedThisRound=true; | |
| var buzzBtn=document.getElementById('buzzButton'); buzzBtn.disabled=true; buzzBtn.classList.remove('active'); | |
| channel.send({type:'broadcast',event:'buzz',payload:{player:playerName,team:playerTeam}}); | |
| document.getElementById('pStatus').textContent='Buzz sent!'; | |
| document.getElementById('pStatus').className='status-text buzzed'; | |
| } | |
| // Host UI | |
| function updateHostUI() { | |
| if (gameState.currentRound>gameState.totalRounds){showGameOver();return;} | |
| document.getElementById('hRoundNum').textContent=gameState.currentRound; | |
| document.getElementById('hTotalRounds').textContent=gameState.totalRounds; | |
| var q=QUESTIONS[gameState.currentRound-1]; | |
| if (q){ | |
| document.getElementById('hAnswer').textContent=q.title||'---'; | |
| document.getElementById('hHint').textContent=q.hint?'"'+q.hint+'"':''; | |
| var yt=document.getElementById('hYoutubeLink'); | |
| if(q.youtubeUrl){yt.href=q.youtubeUrl;yt.style.display='';} else {yt.style.display='none';} | |
| } | |
| if(gameState.roundActive){document.getElementById('btnStart').style.display='none';document.getElementById('btnStop').style.display='';} | |
| else{document.getElementById('btnStart').style.display='';document.getElementById('btnStop').style.display='none';} | |
| var bl=document.getElementById('hBuzzList'); | |
| if(gameState.buzzes.length>0){ | |
| bl.innerHTML=gameState.buzzes.map(function(b){ | |
| var tc=b.team==='Team A'?'team-a-accent':'team-b-accent'; | |
| return '<div class="buzz-entry '+tc+'"><span class="buzz-position">#'+b.position+'</span><span class="buzz-player">'+esc(b.player)+'</span><span class="buzz-team-tag">'+b.team+'</span></div>'; | |
| }).join(''); | |
| gameState.buzzes.forEach(function(b){channel.send({type:'broadcast',event:'buzz-confirm',payload:{player:b.player,position:b.position}});}); | |
| } else { bl.innerHTML='<p class="empty-text">No buzzes yet...</p>'; } | |
| document.getElementById('hPlayerCount').textContent=gameState.players.length; | |
| var pl=document.getElementById('hPlayerList'); | |
| if(gameState.players.length>0){pl.innerHTML=gameState.players.map(function(p){var tc=p.team==='Team A'?'team-a-accent':'team-b-accent';return '<span class="player-tag '+tc+'">'+esc(p.name)+'</span>';}).join('');} | |
| else{pl.innerHTML='<p class="empty-text">No players yet...</p>';} | |
| document.getElementById('hScoreA').textContent=gameState.scores.teamA; | |
| document.getElementById('hScoreB').textContent=gameState.scores.teamB; | |
| } | |
| function esc(t){var d=document.createElement('div');d.textContent=t;return d.innerHTML;} | |
| // Host controls | |
| function hostStartRound(){gameState.roundActive=true;gameState.timerEndTime=Date.now()+(gameState.timerDuration*1000);gameState.buzzes=[];broadcastState();} | |
| function hostStopRound(){gameState.roundActive=false;gameState.timerEndTime=0;broadcastState();} | |
| function hostNextRound(){if(gameState.currentRound>=gameState.totalRounds){gameState.currentRound=gameState.totalRounds+1;broadcastState();return;}gameState.currentRound++;gameState.roundActive=false;gameState.timerEndTime=0;gameState.buzzes=[];broadcastState();} | |
| function hostResetRound(){if(!confirm('Reset this round?'))return;gameState.roundActive=false;gameState.timerEndTime=0;gameState.buzzes=[];broadcastState();} | |
| function hostResetGame(){if(!confirm('Reset the ENTIRE game?'))return;gameState.currentRound=1;gameState.roundActive=false;gameState.timerEndTime=0;gameState.scores={teamA:0,teamB:0};gameState.buzzes=[];gameState.players=[];broadcastState();} | |
| function hostAwardPoint(t){if(t==='Team A')gameState.scores.teamA++;else gameState.scores.teamB++;broadcastState();} | |
| // Game over | |
| function showGameOver(){ | |
| stopTimer(); | |
| document.getElementById('joinScreen').style.display='none';document.getElementById('playerScreen').style.display='none'; | |
| document.getElementById('hostScreen').style.display='none';document.getElementById('gameOverScreen').style.display='flex'; | |
| document.getElementById('fScoreA').textContent=gameState.scores.teamA;document.getElementById('fScoreB').textContent=gameState.scores.teamB; | |
| var w=document.getElementById('winnerAnnouncement'); | |
| if(gameState.scores.teamA>gameState.scores.teamB){w.textContent='Team A Wins!';w.className='winner-text team-a-glow';} | |
| else if(gameState.scores.teamB>gameState.scores.teamA){w.textContent='Team B Wins!';w.className='winner-text team-b-glow';} | |
| else{w.textContent="It's a Tie!";w.className='winner-text';} | |
| var c=document.getElementById('confetti'),cols=['#F59E0B','#06B6D4','#EF4444','#10B981','#8B5CF6','#F97316']; | |
| for(var i=0;i<60;i++){var p=document.createElement('div');p.className='confetti-piece';p.style.left=Math.random()*100+'%';p.style.backgroundColor=cols[Math.floor(Math.random()*cols.length)];p.style.animationDelay=Math.random()*3+'s';p.style.animationDuration=(2+Math.random()*3)+'s';c.appendChild(p);} | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment