Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Volume Quiz</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #f0f9ff; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; /* Vertically centers the content */ | |
| min-height: 100vh; | |
| margin: 0; | |
| padding: 2rem 1rem; /* Added padding for spacing */ | |
| box-sizing: border-box; | |
| } | |
| #main-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-end; | |
| gap: 2rem; | |
| margin-top: 2rem; | |
| width: 100%; | |
| max-width: 600px; | |
| flex-wrap: wrap; | |
| } | |
| .beaker-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| text-align: center; | |
| cursor: pointer; | |
| padding: 1rem; | |
| border-radius: 20px; | |
| border: 4px solid transparent; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .beaker-container.selected { | |
| border-color: #4f46e5; | |
| background-color: #eef2ff; | |
| } | |
| .beaker-container.correct { | |
| border-color: #22c55e; | |
| background-color: #f0fdf4; | |
| } | |
| .beaker-container.incorrect { | |
| border-color: #ef4444; | |
| background-color: #fef2f2; | |
| } | |
| .order-number { | |
| position: absolute; | |
| top: -10px; | |
| right: -10px; | |
| background-color: #4f46e5; | |
| color: white; | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1rem; | |
| border: 2px solid white; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
| } | |
| .beaker { | |
| width: 120px; | |
| height: 240px; | |
| border: 8px solid #9ca3af; | |
| border-top: none; | |
| border-radius: 0 0 20px 20px; | |
| background-color: rgba(255, 255, 255, 0.5); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .water { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| background-color: #3b82f6; | |
| transition: height 1s ease-in-out; | |
| } | |
| .beaker-label { | |
| margin-top: 1rem; | |
| font-size: 1.8rem; | |
| color: #1e3a8a; | |
| } | |
| #feedback { | |
| height: 3rem; | |
| font-size: 1.5rem; | |
| margin-top: 1rem; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| #order-answer-panel { | |
| margin-top: 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .answer-slots { | |
| display: flex; | |
| gap: 1rem; | |
| } | |
| .slot { | |
| width: 50px; | |
| height: 50px; | |
| border: 2px dashed #9ca3af; | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| color: #374151; | |
| background-color: #f9fafb; | |
| } | |
| </style> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> | |
| </head> | |
| <body class="bg-blue-50"> | |
| <div id="ui-panel" class="text-center p-4 bg-white rounded-xl shadow-lg w-full max-w-3xl"> | |
| <h1 id="question-text" class="text-3xl text-blue-700"></h1> | |
| <p id="instruction-text" class="text-gray-600 mt-2 max-w-prose mx-auto"></p> | |
| <p id="feedback" class=""></p> | |
| <div id="order-answer-panel" class="hidden"> | |
| <p class="text-gray-600">Your Answer:</p> | |
| <div id="answer-slots" class="answer-slots"></div> | |
| </div> | |
| </div> | |
| <div id="main-container"></div> | |
| <div id="button-container" class="mt-6 flex flex-wrap justify-center gap-4"> | |
| <button id="submit-button" class="bg-indigo-600 text-white py-3 px-8 rounded-lg shadow-md hover:bg-indigo-700 transition-transform transform hover:scale-105"> | |
| Submit | |
| </button> | |
| <button id="reset-selection-button" class="bg-gray-500 text-white py-3 px-8 rounded-lg shadow-md hover:bg-gray-600 transition-transform transform hover:scale-105 hidden"> | |
| Reset Selection | |
| </button> | |
| <button id="new-question-button" class="bg-green-600 text-white py-3 px-8 rounded-lg shadow-md hover:bg-green-700 transition-transform transform hover:scale-105 hidden"> | |
| New Question | |
| </button> | |
| </div> | |
| <script> | |
| // --- State --- | |
| let currentQuestion = {}; | |
| let selectedAnswer = null; | |
| let selectedOrder = []; | |
| let answered = false; | |
| let questionMode = 'compare'; // 'compare' or 'order' | |
| // --- Elements --- | |
| const questionTextEl = document.getElementById('question-text'); | |
| const instructionTextEl = document.getElementById('instruction-text'); | |
| const mainContainer = document.getElementById('main-container'); | |
| const feedbackEl = document.getElementById('feedback'); | |
| const submitButton = document.getElementById('submit-button'); | |
| const newQuestionButton = document.getElementById('new-question-button'); | |
| const resetSelectionButton = document.getElementById('reset-selection-button'); | |
| const orderAnswerPanel = document.getElementById('order-answer-panel'); | |
| const answerSlotsContainer = document.getElementById('answer-slots'); | |
| // --- Logic --- | |
| function generateUniqueVolumes(count, min, max, step) { | |
| const volumes = new Set(); | |
| while (volumes.size < count) { | |
| const volume = Math.floor(Math.random() * ((max - min) / step + 1) + min / step) * step; | |
| volumes.add(volume); | |
| } | |
| return Array.from(volumes); | |
| } | |
| function generateAndLoadQuestion() { | |
| // 1. Reset State | |
| answered = false; | |
| selectedAnswer = null; | |
| selectedOrder = []; | |
| mainContainer.innerHTML = ''; | |
| feedbackEl.textContent = ''; | |
| feedbackEl.className = ''; | |
| orderAnswerPanel.classList.add('hidden'); | |
| // 2. Configure Buttons | |
| submitButton.classList.remove('hidden'); | |
| newQuestionButton.classList.add('hidden'); | |
| resetSelectionButton.classList.add('hidden'); | |
| // 3. Decide question type | |
| questionMode = Math.random() < 0.5 ? 'compare' : 'order'; | |
| if (questionMode === 'compare') { | |
| generateCompareQuestion(); | |
| } else { | |
| generateOrderQuestion(); | |
| } | |
| } | |
| function generateCompareQuestion() { | |
| instructionTextEl.textContent = "Click to select a container, then press Submit."; | |
| const [volume1, volume2] = generateUniqueVolumes(2, 50, 450, 5); | |
| const questionType = Math.random() < 0.5 ? 'smaller' : 'greatest'; | |
| const correctAnswer = (questionType === 'smaller' ? (volume1 < volume2 ? 'A' : 'B') : (volume1 > volume2 ? 'A' : 'B')); | |
| currentQuestion = { | |
| question: `Container <span class="text-gray-400">___</span> has the ${questionType} volume of water.`, | |
| containers: [{ name: 'A', volume: volume1 }, { name: 'B', volume: volume2 }], | |
| correctAnswer: correctAnswer, | |
| maxVolume: 500 | |
| }; | |
| renderBeakers(); | |
| } | |
| function generateOrderQuestion() { | |
| instructionTextEl.textContent = "Click the containers in the correct order."; | |
| resetSelectionButton.classList.remove('hidden'); | |
| const volumes = generateUniqueVolumes(3, 50, 450, 5); | |
| const containers = [ | |
| { name: 'A', volume: volumes[0] }, | |
| { name: 'B', volume: volumes[1] }, | |
| { name: 'C', volume: volumes[2] }, | |
| ]; | |
| const orderType = Math.random() < 0.5 ? 'ascending' : 'descending'; // smallest to greatest OR greatest to smallest | |
| const sortedContainers = [...containers].sort((a, b) => orderType === 'ascending' ? a.volume - b.volume : b.volume - a.volume); | |
| currentQuestion = { | |
| question: `Arrange the containers in ${orderType} order of volume.`, | |
| containers: containers, | |
| correctAnswer: sortedContainers.map(c => c.name), | |
| maxVolume: 500 | |
| }; | |
| renderBeakers(); | |
| renderAnswerSlots(); | |
| } | |
| function renderBeakers() { | |
| questionTextEl.innerHTML = currentQuestion.question; | |
| currentQuestion.containers.forEach(containerData => { | |
| const containerEl = document.createElement('div'); | |
| containerEl.className = 'beaker-container'; | |
| containerEl.dataset.name = containerData.name; | |
| containerEl.innerHTML = ` | |
| <div class="beaker"><div class="water" style="height: 0%;"></div></div> | |
| <p class="beaker-label">${containerData.name}</p> | |
| `; | |
| mainContainer.appendChild(containerEl); | |
| setTimeout(() => { | |
| const waterEl = containerEl.querySelector('.water'); | |
| const percentage = (containerData.volume / currentQuestion.maxVolume) * 100; | |
| waterEl.style.height = `${percentage}%`; | |
| }, 100); | |
| containerEl.addEventListener('click', () => handleSelection(containerEl)); | |
| }); | |
| } | |
| function renderAnswerSlots() { | |
| orderAnswerPanel.classList.remove('hidden'); | |
| answerSlotsContainer.innerHTML = ''; | |
| for (let i = 0; i < currentQuestion.containers.length; i++) { | |
| const slot = document.createElement('div'); | |
| slot.className = 'slot'; | |
| slot.textContent = selectedOrder[i] || ''; | |
| answerSlotsContainer.appendChild(slot); | |
| } | |
| } | |
| function handleSelection(selectedContainerEl) { | |
| if (answered) return; | |
| const name = selectedContainerEl.dataset.name; | |
| if (questionMode === 'compare') { | |
| document.querySelectorAll('.beaker-container').forEach(el => el.classList.remove('selected')); | |
| selectedContainerEl.classList.add('selected'); | |
| selectedAnswer = name; | |
| } else { // Order mode | |
| if (selectedOrder.includes(name)) return; // Already selected | |
| if (selectedOrder.length < currentQuestion.containers.length) { | |
| selectedOrder.push(name); | |
| selectedContainerEl.classList.add('selected'); | |
| const orderNumberDiv = document.createElement('div'); | |
| orderNumberDiv.className = 'order-number'; | |
| orderNumberDiv.textContent = selectedOrder.length; | |
| selectedContainerEl.appendChild(orderNumberDiv); | |
| renderAnswerSlots(); | |
| } | |
| } | |
| } | |
| function handleSubmit() { | |
| if (answered) return; | |
| if ((questionMode === 'compare' && !selectedAnswer) || (questionMode === 'order' && selectedOrder.length < currentQuestion.containers.length)) { | |
| feedbackEl.textContent = "Please make a complete selection."; | |
| feedbackEl.className = "text-yellow-600"; | |
| return; | |
| } | |
| answered = true; | |
| let isCorrect = false; | |
| if (questionMode === 'compare') { | |
| isCorrect = selectedAnswer === currentQuestion.correctAnswer; | |
| const selectedEl = document.querySelector(`.beaker-container[data-name="${selectedAnswer}"]`); | |
| if (isCorrect) { | |
| const filledText = `<span class="text-blue-800">${currentQuestion.correctAnswer}</span>`; | |
| questionTextEl.innerHTML = currentQuestion.question.replace(/<span.*>___<\/span>/, filledText); | |
| } else { | |
| selectedEl.classList.add('incorrect'); | |
| const correctEl = document.querySelector(`.beaker-container[data-name="${currentQuestion.correctAnswer}"]`); | |
| correctEl.classList.add('correct'); | |
| } | |
| selectedEl.classList.add(isCorrect ? 'correct' : 'incorrect'); | |
| selectedEl.classList.remove('selected'); | |
| } else { // Order mode | |
| isCorrect = JSON.stringify(selectedOrder) === JSON.stringify(currentQuestion.correctAnswer); | |
| if (isCorrect) { | |
| mainContainer.querySelectorAll('.beaker-container').forEach(el => el.classList.add('correct')); | |
| } else { | |
| feedbackEl.textContent = "Not quite. The correct order is shown on the beakers."; | |
| feedbackEl.className = "text-red-600"; | |
| // Show correct order numbers | |
| currentQuestion.containers.forEach(container => { | |
| const el = document.querySelector(`.beaker-container[data-name="${container.name}"]`); | |
| el.classList.add('incorrect'); | |
| // Remove the user's selection number before showing the correct one | |
| const existingOrderNumber = el.querySelector('.order-number'); | |
| if (existingOrderNumber) { | |
| existingOrderNumber.remove(); | |
| } | |
| const correctIndex = currentQuestion.correctAnswer.indexOf(container.name); | |
| const orderNumberDiv = document.createElement('div'); | |
| orderNumberDiv.className = 'order-number'; | |
| orderNumberDiv.style.backgroundColor = '#22c55e'; // Green | |
| orderNumberDiv.textContent = correctIndex + 1; | |
| el.appendChild(orderNumberDiv); | |
| }); | |
| } | |
| } | |
| if(isCorrect){ | |
| feedbackEl.textContent = "Correct! Great job!"; | |
| feedbackEl.className = "text-green-600"; | |
| } | |
| submitButton.classList.add('hidden'); | |
| resetSelectionButton.classList.add('hidden'); | |
| newQuestionButton.classList.remove('hidden'); | |
| } | |
| function handleResetSelection() { | |
| selectedOrder = []; | |
| document.querySelectorAll('.beaker-container').forEach(el => { | |
| el.classList.remove('selected'); | |
| const orderNum = el.querySelector('.order-number'); | |
| if(orderNum) orderNum.remove(); | |
| }); | |
| renderAnswerSlots(); | |
| } | |
| // --- Event Listeners --- | |
| submitButton.addEventListener('click', handleSubmit); | |
| newQuestionButton.addEventListener('click', generateAndLoadQuestion); | |
| resetSelectionButton.addEventListener('click', handleResetSelection) | |
| window.onload = generateAndLoadQuestion; | |
| </script> | |
| </body> | |
| </html> |