Initial commit - Science Bowl buzzer system
This commit is contained in:
BIN
.gitignore
vendored
Normal file
BIN
.gitignore
vendored
Normal file
Binary file not shown.
22
config.js
Normal file
22
config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Import the functions you need from the SDKs you need
|
||||||
|
import { initializeApp } from "firebase/app";
|
||||||
|
import { getAnalytics } from "firebase/analytics";
|
||||||
|
// TODO: Add SDKs for Firebase products that you want to use
|
||||||
|
// https://firebase.google.com/docs/web/setup#available-libraries
|
||||||
|
|
||||||
|
// Your web app's Firebase configuration
|
||||||
|
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: "AIzaSyDNk5EGWDPBr8MkUFNdfvhP1NvnDxWERq8",
|
||||||
|
authDomain: "science-bowl-practice-8800a.firebaseapp.com",
|
||||||
|
databaseURL: "https://science-bowl-practice-8800a-default-rtdb.firebaseio.com",
|
||||||
|
projectId: "science-bowl-practice-8800a",
|
||||||
|
storageBucket: "science-bowl-practice-8800a.firebasestorage.app",
|
||||||
|
messagingSenderId: "240054855565",
|
||||||
|
appId: "1:240054855565:web:2897ab544b9f1c1b3d3fc4",
|
||||||
|
measurementId: "G-4TD0W788X5"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize Firebase
|
||||||
|
const app = initializeApp(firebaseConfig);
|
||||||
|
const analytics = getAnalytics(app);
|
||||||
90
display.html
Normal file
90
display.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Science Bowl - Moderator Display</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body class="display-body">
|
||||||
|
<div class="display-container">
|
||||||
|
<div class="scoreboard">
|
||||||
|
<div class="player-score player-1">
|
||||||
|
<h3>🔴 Player 1</h3>
|
||||||
|
<div class="score" id="score1">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="player-score player-2">
|
||||||
|
<h3>🔵 Player 2</h3>
|
||||||
|
<div class="score" id="score2">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="player-score player-3">
|
||||||
|
<h3>🟢 Player 3</h3>
|
||||||
|
<div class="score" id="score3">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="player-score player-4">
|
||||||
|
<h3>🟡 Player 4</h3>
|
||||||
|
<div class="score" id="score4">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buzzer-indicator" id="buzzerIndicator">
|
||||||
|
<div class="buzzer-lights">
|
||||||
|
<div class="light player-1" id="light1"></div>
|
||||||
|
<div class="light player-2" id="light2"></div>
|
||||||
|
<div class="light player-3" id="light3"></div>
|
||||||
|
<div class="light player-4" id="light4"></div>
|
||||||
|
</div>
|
||||||
|
<div class="buzzed-player" id="buzzedPlayer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="question-section">
|
||||||
|
<div class="question-header">
|
||||||
|
<span class="question-category" id="questionCategory">CATEGORY</span>
|
||||||
|
<span class="question-number">Question <span id="questionNum">1</span>/25</span>
|
||||||
|
</div>
|
||||||
|
<div class="question-text" id="questionText">
|
||||||
|
Click "New Question" to start
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="answer-section" id="answerSection" style="display: none;">
|
||||||
|
<h3>Player Answer:</h3>
|
||||||
|
<div class="player-answer" id="playerAnswer"></div>
|
||||||
|
<h3>Correct Answer:</h3>
|
||||||
|
<div class="correct-answer" id="correctAnswer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button id="newQuestion" class="control-btn primary">New Question</button>
|
||||||
|
<button id="activateBuzzer" class="control-btn" disabled>Activate Buzzers</button>
|
||||||
|
<button id="markCorrect" class="control-btn success" style="display: none;">✓ Correct (+4)</button>
|
||||||
|
<button id="markIncorrect" class="control-btn danger" style="display: none;">✗ Incorrect (-4)</button>
|
||||||
|
<button id="showAnswer" class="control-btn" style="display: none;">Show Answer</button>
|
||||||
|
<button id="resetGame" class="control-btn warning">Reset Game</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="category-filter">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="category-checkbox" value="PHYSICS" checked> Physics
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="category-checkbox" value="CHEMISTRY" checked> Chemistry
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="category-checkbox" value="BIOLOGY" checked> Biology
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="category-checkbox" value="MATH" checked> Math
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="category-checkbox" value="EARTH SCIENCE" checked> Earth Science
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/9.17.1/firebase-app-compat.js"></script>
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/9.17.1/firebase-database-compat.js"></script>
|
||||||
|
<script src="config.js"></script>
|
||||||
|
<script src="display.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
199
display.js
Normal file
199
display.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
const API_URL = 'https://scibowldb.com/api/questions/random';
|
||||||
|
|
||||||
|
// DOM elements
|
||||||
|
const questionText = document.getElementById('questionText');
|
||||||
|
const questionCategory = document.getElementById('questionCategory');
|
||||||
|
const questionNum = document.getElementById('questionNum');
|
||||||
|
const correctAnswer = document.getElementById('correctAnswer');
|
||||||
|
const playerAnswer = document.getElementById('playerAnswer');
|
||||||
|
const answerSection = document.getElementById('answerSection');
|
||||||
|
const buzzedPlayer = document.getElementById('buzzedPlayer');
|
||||||
|
|
||||||
|
const newQuestionBtn = document.getElementById('newQuestion');
|
||||||
|
const activateBuzzerBtn = document.getElementById('activateBuzzer');
|
||||||
|
const markCorrectBtn = document.getElementById('markCorrect');
|
||||||
|
const markIncorrectBtn = document.getElementById('markIncorrect');
|
||||||
|
const showAnswerBtn = document.getElementById('showAnswer');
|
||||||
|
const resetGameBtn = document.getElementById('resetGame');
|
||||||
|
|
||||||
|
let currentQuestion = null;
|
||||||
|
let currentQuestionNumber = 1;
|
||||||
|
let currentBuzzedPlayer = null;
|
||||||
|
|
||||||
|
// Initialize game state
|
||||||
|
database.ref('gameState').set({
|
||||||
|
buzzerActive: false,
|
||||||
|
currentQuestion: null,
|
||||||
|
scores: {
|
||||||
|
player1: 0,
|
||||||
|
player2: 0,
|
||||||
|
player3: 0,
|
||||||
|
player4: 0
|
||||||
|
},
|
||||||
|
questionNumber: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for game state changes
|
||||||
|
database.ref('gameState').on('value', (snapshot) => {
|
||||||
|
const state = snapshot.val();
|
||||||
|
if (!state) return;
|
||||||
|
|
||||||
|
// Update scores
|
||||||
|
document.getElementById('score1').textContent = state.scores.player1 || 0;
|
||||||
|
document.getElementById('score2').textContent = state.scores.player2 || 0;
|
||||||
|
document.getElementById('score3').textContent = state.scores.player3 || 0;
|
||||||
|
document.getElementById('score4').textContent = state.scores.player4 || 0;
|
||||||
|
|
||||||
|
// Update buzzer lights
|
||||||
|
if (state.buzzer && state.buzzer.playerId) {
|
||||||
|
const playerNum = state.buzzer.playerId.replace('player', '');
|
||||||
|
currentBuzzedPlayer = state.buzzer.playerId;
|
||||||
|
|
||||||
|
// Light up the buzzer
|
||||||
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||||
|
document.getElementById(`light${playerNum}`).classList.add('active');
|
||||||
|
|
||||||
|
buzzedPlayer.textContent = `Player ${playerNum} buzzed in!`;
|
||||||
|
|
||||||
|
// Show grading buttons
|
||||||
|
markCorrectBtn.style.display = 'inline-block';
|
||||||
|
markIncorrectBtn.style.display = 'inline-block';
|
||||||
|
showAnswerBtn.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update player answer
|
||||||
|
if (state.playerAnswer) {
|
||||||
|
playerAnswer.textContent = state.playerAnswer.answer;
|
||||||
|
answerSection.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch new question
|
||||||
|
newQuestionBtn.addEventListener('click', async () => {
|
||||||
|
const categories = Array.from(document.querySelectorAll('.category-checkbox:checked'))
|
||||||
|
.map(cb => cb.value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ categories })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
currentQuestion = data;
|
||||||
|
|
||||||
|
questionText.textContent = currentQuestion.tossup_question;
|
||||||
|
questionCategory.textContent = currentQuestion.category;
|
||||||
|
correctAnswer.textContent = currentQuestion.tossup_answer;
|
||||||
|
questionNum.textContent = currentQuestionNumber;
|
||||||
|
|
||||||
|
// Update Firebase
|
||||||
|
database.ref('gameState').update({
|
||||||
|
currentQuestion: currentQuestion,
|
||||||
|
buzzerActive: false,
|
||||||
|
buzzer: null,
|
||||||
|
playerAnswer: null,
|
||||||
|
questionNumber: currentQuestionNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset UI
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
buzzedPlayer.textContent = '';
|
||||||
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||||
|
markCorrectBtn.style.display = 'none';
|
||||||
|
markIncorrectBtn.style.display = 'none';
|
||||||
|
showAnswerBtn.style.display = 'none';
|
||||||
|
|
||||||
|
activateBuzzerBtn.disabled = false;
|
||||||
|
currentQuestionNumber++;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching question:', error);
|
||||||
|
alert('Error loading question. Check console.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate buzzers
|
||||||
|
activateBuzzerBtn.addEventListener('click', () => {
|
||||||
|
database.ref('gameState').update({
|
||||||
|
buzzerActive: true,
|
||||||
|
buzzer: null,
|
||||||
|
playerAnswer: null
|
||||||
|
});
|
||||||
|
activateBuzzerBtn.disabled = true;
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark correct
|
||||||
|
markCorrectBtn.addEventListener('click', () => {
|
||||||
|
if (!currentBuzzedPlayer) return;
|
||||||
|
|
||||||
|
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||||
|
return (score || 0) + 4;
|
||||||
|
});
|
||||||
|
|
||||||
|
resetForNextQuestion();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark incorrect
|
||||||
|
markIncorrectBtn.addEventListener('click', () => {
|
||||||
|
if (!currentBuzzedPlayer) return;
|
||||||
|
|
||||||
|
database.ref(`gameState/scores/${currentBuzzedPlayer}`).transaction((score) => {
|
||||||
|
return (score || 0) - 4;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reactivate buzzers for other players
|
||||||
|
database.ref('gameState').update({
|
||||||
|
buzzerActive: true,
|
||||||
|
buzzer: null,
|
||||||
|
playerAnswer: null
|
||||||
|
});
|
||||||
|
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
markCorrectBtn.style.display = 'none';
|
||||||
|
markIncorrectBtn.style.display = 'none';
|
||||||
|
showAnswerBtn.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show answer
|
||||||
|
showAnswerBtn.addEventListener('click', () => {
|
||||||
|
answerSection.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset game
|
||||||
|
resetGameBtn.addEventListener('click', () => {
|
||||||
|
if (confirm('Reset all scores and start over?')) {
|
||||||
|
currentQuestionNumber = 1;
|
||||||
|
database.ref('gameState').set({
|
||||||
|
buzzerActive: false,
|
||||||
|
currentQuestion: null,
|
||||||
|
scores: {
|
||||||
|
player1: 0,
|
||||||
|
player2: 0,
|
||||||
|
player3: 0,
|
||||||
|
player4: 0
|
||||||
|
},
|
||||||
|
questionNumber: 1
|
||||||
|
});
|
||||||
|
questionText.textContent = 'Click "New Question" to start';
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetForNextQuestion() {
|
||||||
|
database.ref('gameState').update({
|
||||||
|
buzzerActive: false,
|
||||||
|
buzzer: null,
|
||||||
|
playerAnswer: null
|
||||||
|
});
|
||||||
|
|
||||||
|
buzzedPlayer.textContent = '';
|
||||||
|
document.querySelectorAll('.light').forEach(l => l.classList.remove('active'));
|
||||||
|
markCorrectBtn.style.display = 'none';
|
||||||
|
markIncorrectBtn.style.display = 'none';
|
||||||
|
showAnswerBtn.style.display = 'none';
|
||||||
|
answerSection.style.display = 'none';
|
||||||
|
currentBuzzedPlayer = null;
|
||||||
|
}
|
||||||
47
index.html
Normal file
47
index.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Science Bowl Buzzer System</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Science Bowl Buzzer System</h1>
|
||||||
|
|
||||||
|
<div class="role-selection">
|
||||||
|
<h2>Select Your Role:</h2>
|
||||||
|
|
||||||
|
<a href="display.html" class="role-btn moderator-btn">
|
||||||
|
<h3>📺 Moderator Display</h3>
|
||||||
|
<p>Main screen showing questions and scores</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="player-buttons">
|
||||||
|
<a href="player.html?id=1" class="role-btn player-btn player-1">
|
||||||
|
<h3>🔴 Player 1</h3>
|
||||||
|
</a>
|
||||||
|
<a href="player.html?id=2" class="role-btn player-btn player-2">
|
||||||
|
<h3>🔵 Player 2</h3>
|
||||||
|
</a>
|
||||||
|
<a href="player.html?id=3" class="role-btn player-btn player-3">
|
||||||
|
<h3>🟢 Player 3</h3>
|
||||||
|
</a>
|
||||||
|
<a href="player.html?id=4" class="role-btn player-btn player-4">
|
||||||
|
<h3>🟡 Player 4</h3>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="instructions">
|
||||||
|
<h3>Setup Instructions:</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Open the Moderator Display on your main screen</li>
|
||||||
|
<li>Each player opens their assigned player page on their laptop</li>
|
||||||
|
<li>Press spacebar or click the buzzer to buzz in!</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
player.html
Normal file
42
player.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Science Bowl - Player</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body class="player-body">
|
||||||
|
<div class="player-container">
|
||||||
|
<div class="player-header">
|
||||||
|
<h1 id="playerName">Player</h1>
|
||||||
|
<div class="score-display">
|
||||||
|
Score: <span id="playerScore">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-message" id="statusMessage">
|
||||||
|
Waiting for question...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buzzer-container">
|
||||||
|
<button id="buzzer" class="buzzer-btn">
|
||||||
|
<span class="buzzer-text">BUZZ</span>
|
||||||
|
<span class="buzzer-hint">Press SPACE</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="answer-container" id="answerContainer" style="display: none;">
|
||||||
|
<input type="text" id="answerInput" placeholder="Type your answer...">
|
||||||
|
<button id="submitAnswer" class="submit-btn">Submit Answer</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="question-display" id="questionDisplay"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/9.17.1/firebase-app-compat.js"></script>
|
||||||
|
<script src="https://www.gstatic.com/firebasejs/9.17.1/firebase-database-compat.js"></script>
|
||||||
|
<script src="config.js"></script>
|
||||||
|
<script src="player.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
110
player.js
Normal file
110
player.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// Get player ID from URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const playerId = urlParams.get('id') || '1';
|
||||||
|
const playerColors = ['#ff4444', '#4444ff', '#44ff44', '#ffff44'];
|
||||||
|
const playerNames = ['Player 1', 'Player 2', 'Player 3', 'Player 4'];
|
||||||
|
|
||||||
|
// DOM elements
|
||||||
|
const playerName = document.getElementById('playerName');
|
||||||
|
const playerScore = document.getElementById('playerScore');
|
||||||
|
const statusMessage = document.getElementById('statusMessage');
|
||||||
|
const buzzer = document.getElementById('buzzer');
|
||||||
|
const answerContainer = document.getElementById('answerContainer');
|
||||||
|
const answerInput = document.getElementById('answerInput');
|
||||||
|
const submitAnswer = document.getElementById('submitAnswer');
|
||||||
|
const questionDisplay = document.getElementById('questionDisplay');
|
||||||
|
|
||||||
|
// Set player identity
|
||||||
|
playerName.textContent = playerNames[playerId - 1];
|
||||||
|
playerName.style.color = playerColors[playerId - 1];
|
||||||
|
document.body.style.setProperty('--player-color', playerColors[playerId - 1]);
|
||||||
|
|
||||||
|
// Game state
|
||||||
|
let canBuzz = false;
|
||||||
|
let hasBuzzed = false;
|
||||||
|
|
||||||
|
// Listen to game state
|
||||||
|
database.ref('gameState').on('value', (snapshot) => {
|
||||||
|
const state = snapshot.val();
|
||||||
|
if (!state) return;
|
||||||
|
|
||||||
|
// Update score
|
||||||
|
if (state.scores && state.scores[`player${playerId}`] !== undefined) {
|
||||||
|
playerScore.textContent = state.scores[`player${playerId}`];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update question
|
||||||
|
if (state.currentQuestion) {
|
||||||
|
questionDisplay.textContent = state.currentQuestion.tossup_question;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update buzzer state
|
||||||
|
if (state.buzzerActive) {
|
||||||
|
canBuzz = true;
|
||||||
|
hasBuzzed = false;
|
||||||
|
buzzer.disabled = false;
|
||||||
|
buzzer.classList.remove('locked');
|
||||||
|
statusMessage.textContent = 'Ready to buzz!';
|
||||||
|
answerContainer.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
canBuzz = false;
|
||||||
|
buzzer.disabled = true;
|
||||||
|
buzzer.classList.add('locked');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this player buzzed in
|
||||||
|
if (state.buzzer && state.buzzer.playerId === `player${playerId}`) {
|
||||||
|
statusMessage.textContent = 'You buzzed in! Answer now:';
|
||||||
|
answerContainer.style.display = 'block';
|
||||||
|
answerInput.focus();
|
||||||
|
} else if (state.buzzer && state.buzzer.playerId) {
|
||||||
|
statusMessage.textContent = `${state.buzzer.playerId} buzzed in`;
|
||||||
|
buzzer.classList.add('locked');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Buzzer click
|
||||||
|
buzzer.addEventListener('click', buzzIn);
|
||||||
|
|
||||||
|
// Spacebar to buzz
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.code === 'Space' && canBuzz && !hasBuzzed) {
|
||||||
|
e.preventDefault();
|
||||||
|
buzzIn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function buzzIn() {
|
||||||
|
if (!canBuzz || hasBuzzed) return;
|
||||||
|
|
||||||
|
hasBuzzed = true;
|
||||||
|
database.ref('gameState/buzzer').set({
|
||||||
|
playerId: `player${playerId}`,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visual feedback
|
||||||
|
buzzer.classList.add('buzzed');
|
||||||
|
setTimeout(() => buzzer.classList.remove('buzzed'), 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit answer
|
||||||
|
submitAnswer.addEventListener('click', submitPlayerAnswer);
|
||||||
|
answerInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') submitPlayerAnswer();
|
||||||
|
});
|
||||||
|
|
||||||
|
function submitPlayerAnswer() {
|
||||||
|
const answer = answerInput.value.trim();
|
||||||
|
if (!answer) return;
|
||||||
|
|
||||||
|
database.ref('gameState/playerAnswer').set({
|
||||||
|
playerId: `player${playerId}`,
|
||||||
|
answer: answer,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
answerInput.value = '';
|
||||||
|
answerContainer.style.display = 'none';
|
||||||
|
statusMessage.textContent = 'Answer submitted! Waiting for moderator...';
|
||||||
|
}
|
||||||
375
styles.css
Normal file
375
styles.css
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-selection {
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-btn {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 15px 0;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 3px solid #dee2e6;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-btn:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moderator-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-1 { border-color: #ff4444; }
|
||||||
|
.player-2 { border-color: #4444ff; }
|
||||||
|
.player-3 { border-color: #44ff44; }
|
||||||
|
.player-4 { border-color: #ffff44; }
|
||||||
|
|
||||||
|
/* Player Screen */
|
||||||
|
.player-body {
|
||||||
|
background: var(--player-color, #667eea);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-header {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--player-color);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message {
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-container {
|
||||||
|
margin: 50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-btn {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 10px solid white;
|
||||||
|
background: linear-gradient(135deg, #ff6b6b, #ee5a6f);
|
||||||
|
color: white;
|
||||||
|
font-size: 3em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-btn:hover:not(:disabled) {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 25px 50px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-btn:active:not(:disabled) {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-btn.buzzed {
|
||||||
|
animation: buzz 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes buzz {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-btn.locked {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-hint {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.3em;
|
||||||
|
margin-top: 10px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#answerInput {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
border: 3px solid var(--player-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
padding: 15px 40px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
background: var(--player-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-display {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display Screen */
|
||||||
|
.display-body {
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoreboard {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-score {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
text-align: center;
|
||||||
|
border: 3px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-score.player-1 { border-color: #ff4444; }
|
||||||
|
.player-score.player-2 { border-color: #4444ff; }
|
||||||
|
.player-score.player-3 { border-color: #44ff44; }
|
||||||
|
.player-score.player-4 { border-color: #ffff44; }
|
||||||
|
|
||||||
|
.score {
|
||||||
|
font-size: 3em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-indicator {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzer-lights {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.player-1 { border: 5px solid #ff4444; }
|
||||||
|
.light.player-2 { border: 5px solid #4444ff; }
|
||||||
|
.light.player-3 { border: 5px solid #44ff44; }
|
||||||
|
.light.player-4 { border: 5px solid #ffff44; }
|
||||||
|
|
||||||
|
.light.active {
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
box-shadow: 0 0 30px currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.player-1.active { background: #ff4444; }
|
||||||
|
.light.player-2.active { background: #4444ff; }
|
||||||
|
.light.player-3.active { background: #44ff44; }
|
||||||
|
.light.player-4.active { background: #ffff44; }
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
50% { transform: scale(1.1); opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.buzzed-player {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffff44;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-section {
|
||||||
|
background: white;
|
||||||
|
color: #333;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-text {
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-section {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-answer, .correct-answer {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
.control-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.primary { background: #667eea; color: white; }
|
||||||
|
.success { background: #44ff44; color: #333; }
|
||||||
|
.danger { background: #ff4444; color: white; }
|
||||||
|
.warning { background: #ffaa00; color: #333; }
|
||||||
|
.category-filter {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.category-filter label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.instructions {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.instructions ol {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.instructions li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user