| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Volume Conservation Challenge</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <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=Nunito:wght@400;700&display=swap" rel="stylesheet"> |
| | <style> |
| | body { |
| | font-family: 'Nunito', sans-serif; |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-100 flex items-center justify-center min-h-screen p-4"> |
| | <div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-6xl bg-white rounded-2xl shadow-lg"> |
| |
|
| | <header class="text-center mb-6"> |
| | <h1 class="text-4xl sm:text-5xl font-bold text-blue-600">Volume Voyagers</h1> |
| | <p class="text-gray-500 mt-2">The water amount might trick your eyes! Count the glasses.</p> |
| | </header> |
| |
|
| | |
| |
|
| | <main class="space-y-8 mt-12"> |
| | |
| | <div id="problem-display" class="grid grid-cols-1 md:grid-cols-3 gap-6 bg-blue-50 p-6 rounded-xl border border-blue-200 min-h-[420px] items-end"> |
| | |
| | </div> |
| |
|
| | <div id="questions-container" class="bg-green-50 p-6 rounded-xl border border-green-200"> |
| | <div id="questions-area" class="space-y-6 mb-6"> |
| | |
| | </div> |
| | <div class="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4"> |
| | <button id="check-btn" class="w-full sm:w-auto bg-green-500 text-white font-bold py-3 px-8 rounded-lg shadow-md hover:bg-green-600 transition-transform transform hover:scale-105">Check Answers</button> |
| | <button id="new-question-btn" class="w-full sm:w-auto bg-purple-500 text-white font-bold py-3 px-8 rounded-lg shadow-md hover:bg-purple-600 transition-transform transform hover:scale-105">New Question</button> |
| | </div> |
| | </div> |
| | <div id="feedback-area" class="h-16 flex items-center justify-center text-2xl font-bold transition-all duration-300"></div> |
| | </main> |
| | </div> |
| |
|
| | <script> |
| | const problemDisplay = document.getElementById('problem-display'); |
| | const questionsArea = document.getElementById('questions-area'); |
| | const checkBtn = document.getElementById('check-btn'); |
| | const newQuestionBtn = document.getElementById('new-question-btn'); |
| | const feedbackArea = document.getElementById('feedback-area'); |
| | |
| | let gameState = { |
| | problemData: [], |
| | questions: [], |
| | userAnswers: {} |
| | }; |
| | |
| | const glassSVG = `<svg viewBox="0 0 20 30" class="w-6 h-9" fill="#a5f3fc"><path d="M1,0 H19 L17,30 H3 L1,0 Z"></path></svg>`; |
| | |
| | const containerSVGs = { |
| | beaker: { |
| | // Beaker: Made thinner for contrast |
| | svg: (fillHeight) => ` |
| | <svg viewBox="0 0 120 150"> |
| | |
| | <rect x="33" y="${140 - fillHeight}" width="54" height="${fillHeight}" fill="#38bdf8" /> |
| | |
| | <path d="M30 10 H90 V140 H30 Z" stroke="#a0aec0" stroke-width="3" fill="rgba(237, 242, 247, 0.4)" /> |
| | </svg>`, |
| | factor: 22, // Increased factor because it's thinner |
| | maxFill: 130 |
| | }, |
| | flask: { |
| | // Flask: Made thinner for contrast |
| | svg: (fillHeight) => ` |
| | <svg viewBox="0 0 120 150"> |
| | <defs> |
| | <clipPath id="flaskClip"> |
| | <path d="M45 10 H75 V50 L100 140 H20 L45 50 Z" /> |
| | </clipPath> |
| | </defs> |
| | |
| | <rect x="20" y="${140 - fillHeight}" width="80" height="${fillHeight}" fill="#38bdf8" clip-path="url(#flaskClip)" /> |
| | |
| | <path d="M45 10 H75 V50 L100 140 H20 L45 50 Z" stroke="#a0aec0" stroke-width="3" fill="rgba(237, 242, 247, 0.4)" /> |
| | </svg>`, |
| | factor: 6, // Increased factor |
| | maxFill: 130 |
| | }, |
| | cylinder: { |
| | // Cylinder: Made thinner for contrast |
| | svg: (fillHeight) => ` |
| | <svg viewBox="0 0 120 150"> |
| | |
| | <rect x="23" y="${140 - fillHeight}" width="74" height="${fillHeight}" rx="3" fill="#38bdf8" /> |
| | |
| | <rect x="20" y="40" width="80" height="100" rx="5" stroke="#a0aec0" stroke-width="3" fill="rgba(237, 242, 247, 0.4)" /> |
| | </svg>`, |
| | factor: 10, // Increased factor |
| | maxFill: 100 |
| | }, |
| | wideBowl: { |
| | // wideBowl: Very wide and shallow to make water level appear low |
| | svg: (fillHeight) => ` |
| | <svg viewBox="0 0 250 150"> |
| | |
| | <rect x="8" y="${140 - fillHeight}" width="234" height="${fillHeight}" rx="5" fill="#38bdf8" /> |
| | |
| | <rect x="5" y="70" width="240" height="70" rx="8" stroke="#a0aec0" stroke-width="3" fill="rgba(237, 242, 247, 0.4)" /> |
| | </svg>`, |
| | factor: 1.8, // Very low factor means height increases slowly |
| | maxFill: 65 // Max fill height is low |
| | } |
| | }; |
| | |
| | function getRandomInt(min, max) { |
| | return Math.floor(Math.random() * (max - min + 1)) + min; |
| | } |
| | |
| | function generateProblem() { |
| | const numContainers = 3; |
| | const maxUnits = 8; |
| | const containerNames = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); |
| | const usedValues = new Set(); |
| | const containerTypes = Object.keys(containerSVGs).sort(() => 0.5 - Math.random()); |
| | |
| | gameState.problemData = []; |
| | for (let i = 0; i < numContainers; i++) { |
| | let value; |
| | do { |
| | value = getRandomInt(1, maxUnits); |
| | } while (usedValues.has(value)); |
| | usedValues.add(value); |
| | gameState.problemData.push({ |
| | name: containerNames[i], |
| | value: value, |
| | type: containerTypes[i % containerTypes.length] |
| | }); |
| | } |
| | renderProblem(); |
| | generateQuestions(); |
| | feedbackArea.innerHTML = ''; |
| | } |
| | |
| | function renderProblem() { |
| | problemDisplay.innerHTML = ''; |
| | gameState.problemData.forEach(container => { |
| | let glassesHTML = Array(container.value).fill(glassSVG).join(''); |
| | |
| | const containerInfo = containerSVGs[container.type]; |
| | const fillHeight = Math.min(container.value * containerInfo.factor, containerInfo.maxFill); |
| | const containerHTML = containerInfo.svg(fillHeight); |
| | |
| | const widthClass = container.type === 'wideBowl' ? 'w-64' : 'w-32'; |
| | |
| | const itemHTML = ` |
| | <div class="flex flex-col items-center text-center p-2"> |
| | <p class="text-xl font-bold text-gray-600 mb-2">${container.value} Glasses</p> |
| | <div class="flex flex-wrap justify-center gap-1 min-h-[70px] mb-2">${glassesHTML}</div> |
| | <div class="text-3xl text-blue-500 font-bold mb-2">↓</div> |
| | <div class="${widthClass} h-32 mb-6 flex items-center justify-center">${containerHTML}</div> |
| | <p class="text-2xl font-bold text-purple-700">Container ${container.name}</p> |
| | </div> |
| | `; |
| | problemDisplay.innerHTML += itemHTML; |
| | }); |
| | } |
| | |
| | function generateQuestions() { |
| | questionsArea.innerHTML = ''; |
| | gameState.questions = []; |
| | gameState.userAnswers = {}; |
| | const sortedData = [...gameState.problemData].sort((a, b) => a.value - b.value); |
| | const smallest = sortedData[0]; |
| | const greatest = sortedData[sortedData.length - 1]; |
| | |
| | const questionTemplates = [ |
| | { text: `Which container has the <span class="font-bold text-red-500">smallest</span> volume?`, answer: smallest.name }, |
| | { text: `Which container has the <span class="font-bold text-green-600">greatest</span> volume?`, answer: greatest.name }, |
| | ]; |
| | |
| | questionTemplates.forEach((q, index) => { |
| | gameState.questions.push({ id: `q${index}`, correctAnswer: q.answer }); |
| | const optionsHTML = gameState.problemData.map(c => ` |
| | <button data-question-id="q${index}" data-value="${c.name}" |
| | class="answer-btn bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-6 rounded-lg transition-colors"> |
| | ${c.name} |
| | </button> |
| | `).join(''); |
| | |
| | const questionHTML = ` |
| | <div class="flex flex-col sm:flex-row items-center justify-center text-lg sm:text-xl space-y-2 sm:space-y-0"> |
| | <p class="text-gray-700 mr-4 text-center">${q.text}</p> |
| | <div class="flex space-x-2"> |
| | ${optionsHTML} |
| | </div> |
| | </div> |
| | `; |
| | questionsArea.innerHTML += questionHTML; |
| | }); |
| | } |
| | |
| | function checkAnswers() { |
| | let allCorrect = true; |
| | if (Object.keys(gameState.userAnswers).length !== gameState.questions.length) { |
| | allCorrect = false; |
| | } |
| | |
| | gameState.questions.forEach(q => { |
| | const userAnswer = gameState.userAnswers[q.id]; |
| | const buttonsForQuestion = questionsArea.querySelectorAll(`[data-question-id="${q.id}"]`); |
| | |
| | buttonsForQuestion.forEach(btn => btn.classList.remove('border-4', 'border-green-500', 'border-red-500')); |
| | |
| | if (!userAnswer) { |
| | allCorrect = false; |
| | return; |
| | } |
| | |
| | const selectedButton = questionsArea.querySelector(`[data-question-id="${q.id}"][data-value="${userAnswer}"]`); |
| | |
| | if (userAnswer === q.correctAnswer) { |
| | selectedButton.classList.add('border-4', 'border-green-500'); |
| | } else { |
| | selectedButton.classList.add('border-4', 'border-red-500'); |
| | allCorrect = false; |
| | } |
| | }); |
| | |
| | feedbackArea.innerHTML = allCorrect |
| | ? `<span class="text-green-500">๐ Excellent! You didn't get tricked! ๐</span>` |
| | : `<span class="text-red-500">Not quite. Count the glasses carefully!</span>`; |
| | } |
| | |
| | questionsArea.addEventListener('click', (e) => { |
| | if (e.target.classList.contains('answer-btn')) { |
| | const questionId = e.target.dataset.questionId; |
| | const selectedValue = e.target.dataset.value; |
| | |
| | gameState.userAnswers[questionId] = selectedValue; |
| | |
| | const buttonsForQuestion = questionsArea.querySelectorAll(`[data-question-id="${questionId}"]`); |
| | buttonsForQuestion.forEach(btn => { |
| | btn.classList.remove('bg-blue-500', 'text-white'); |
| | btn.classList.add('bg-gray-200', 'text-gray-700'); |
| | }); |
| | |
| | e.target.classList.add('bg-blue-500', 'text-white'); |
| | e.target.classList.remove('bg-gray-200', 'text-gray-700'); |
| | } |
| | }); |
| | |
| | checkBtn.addEventListener('click', checkAnswers); |
| | newQuestionBtn.addEventListener('click', generateProblem); |
| | |
| | // Generate the initial problem |
| | generateProblem(); |
| | </script> |
| | </body> |
| | </html> |
| |
|
| |
|