Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Volume Champions</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; | |
| } | |
| .difficulty-btn.active { | |
| background-color: #3b82f6; /* blue-500 */ | |
| color: white; | |
| box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); | |
| } | |
| .jug-container { | |
| transition: transform 0.2s ease-in-out; | |
| } | |
| .jug-container:hover { | |
| transform: scale(1.05); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 flex items-center justify-center min-h-screen"> | |
| <div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-5xl bg-white rounded-2xl shadow-lg"> | |
| <!-- Header --> | |
| <header class="text-center mb-6"> | |
| <h1 class="text-4xl sm:text-5xl font-bold text-blue-600">Volume Champions</h1> | |
| <p class="text-gray-500 mt-2">Select a level and solve the puzzle!</p> | |
| </header> | |
| <!-- Difficulty Controls --> | |
| <div id="difficulty-controls" class="flex justify-center space-x-2 sm:space-x-4 mb-8"> | |
| <button data-level="easy" class="difficulty-btn w-24 sm:w-32 py-2 px-4 bg-gray-200 text-gray-700 font-bold rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300">Easy</button> | |
| <button data-level="medium" class="difficulty-btn w-24 sm:w-32 py-2 px-4 bg-gray-200 text-gray-700 font-bold rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300">Medium</button> | |
| <button data-level="hard" class="difficulty-btn w-24 sm:w-32 py-2 px-4 bg-gray-200 text-gray-700 font-bold rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300">Hard</button> | |
| </div> | |
| <!-- Main Content Area --> | |
| <main class="space-y-8"> | |
| <!-- Problem Visualization --> | |
| <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"> | |
| <!-- Jugs and glasses will be rendered here --> | |
| </div> | |
| <!-- Questions and Actions --> | |
| <div id="questions-container" class="bg-green-50 p-6 rounded-xl border border-green-200"> | |
| <div id="questions-area" class="space-y-4 mb-6"> | |
| <!-- Questions will be rendered here --> | |
| </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> | |
| <!-- Feedback Area --> | |
| <div id="feedback-area" class="h-16 flex items-center justify-center text-2xl font-bold transition-all duration-300"></div> | |
| </main> | |
| </div> | |
| <script> | |
| // --- DOM Elements --- | |
| const difficultyControls = document.getElementById('difficulty-controls'); | |
| 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'); | |
| // --- Game State --- | |
| let gameState = { | |
| level: 'easy', | |
| problemData: [], | |
| questions: [] | |
| }; | |
| // --- Image URLs & SVG Templates --- | |
| // Using local file references as requested. | |
| const jugImageURLs = [ | |
| 'jug.webp', | |
| 'carafe.avif', | |
| 'roundpot.jpg' | |
| ]; | |
| const glassSVG = `<svg viewBox="0 0 20 30" class="w-6 h-9" fill="#FBBF24"><path d="M1,0 H19 L17,30 H3 L1,0 Z"></path></svg>`; | |
| // --- Difficulty Settings --- | |
| const difficultySettings = { | |
| easy: { numContainers: 2, maxUnits: 5 }, | |
| medium: { numContainers: 3, maxUnits: 10 }, | |
| hard: { numContainers: 4, maxUnits: 12 } | |
| }; | |
| // --- Utility Functions --- | |
| function getRandomInt(min, max) { | |
| return Math.floor(Math.random() * (max - min + 1)) + min; | |
| } | |
| // --- Core Game Logic --- | |
| function generateProblem(level) { | |
| gameState.level = level; | |
| const settings = difficultySettings[level]; | |
| const containerNames = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); | |
| const usedValues = new Set(); | |
| gameState.problemData = []; | |
| for (let i = 0; i < settings.numContainers; i++) { | |
| let value; | |
| do { | |
| value = getRandomInt(1, settings.maxUnits); | |
| } while (usedValues.has(value)); | |
| usedValues.add(value); | |
| gameState.problemData.push({ name: containerNames[i], value: value }); | |
| } | |
| renderProblem(); | |
| generateQuestions(); | |
| updateActiveButton(); | |
| feedbackArea.innerHTML = ''; | |
| } | |
| function renderProblem() { | |
| problemDisplay.innerHTML = ''; | |
| // Shuffle images for variety | |
| const shuffledImages = [...jugImageURLs].sort(() => 0.5 - Math.random()); | |
| gameState.problemData.forEach((container, index) => { | |
| let glassesHTML = ''; | |
| for(let i = 0; i < container.value; i++) { | |
| glassesHTML += glassSVG; | |
| } | |
| // Pick a new jug image for each container, looping if needed | |
| const currentImageURL = shuffledImages[index % shuffledImages.length]; | |
| const containerHTML = ` | |
| <div class="jug-container text-center p-4 bg-white rounded-lg shadow"> | |
| <img src="${currentImageURL}" alt="Jug" class="w-24 h-24 mx-auto object-contain"> | |
| <p class="text-3xl font-bold text-purple-700 mt-2">${container.name}</p> | |
| <div class="flex flex-wrap justify-center gap-1 mt-4"> | |
| ${glassesHTML} | |
| </div> | |
| <p class="text-sm text-gray-500 mt-2">(${container.value} glasses)</p> | |
| </div> | |
| `; | |
| problemDisplay.innerHTML += containerHTML; | |
| }); | |
| } | |
| function generateQuestions() { | |
| questionsArea.innerHTML = ''; | |
| gameState.questions = []; | |
| const data = [...gameState.problemData]; | |
| const sortedData = [...data].sort((a, b) => a.value - b.value); | |
| const smallest = sortedData[0]; | |
| const greatest = sortedData[sortedData.length - 1]; | |
| const questionTemplates = [ | |
| { | |
| text: `Container <span class="font-bold text-green-700">__</span> has the smallest volume.`, | |
| getAnswer: () => smallest.name | |
| }, | |
| { | |
| text: `Container <span class="font-bold text-green-700">__</span> has the greatest volume.`, | |
| getAnswer: () => greatest.name | |
| }, | |
| { | |
| text: `Container ${greatest.name} has more volume than Container <span class="font-bold text-green-700">__</span>.`, | |
| getAnswer: () => data.filter(c => c.value < greatest.value).map(c => c.name) | |
| }, | |
| { | |
| text: `Container ${smallest.name} has less volume than Container <span class="font-bold text-green-700">__</span>.`, | |
| getAnswer: () => data.filter(c => c.value > smallest.value).map(c => c.name) | |
| } | |
| ]; | |
| // Select questions based on level | |
| let selectedQuestions = []; | |
| if (gameState.level === 'easy') { | |
| selectedQuestions = [questionTemplates[0], questionTemplates[1]]; | |
| } else { | |
| // Shuffle and pick for medium/hard | |
| questionTemplates.sort(() => 0.5 - Math.random()); | |
| selectedQuestions = questionTemplates.slice(0, 3); | |
| } | |
| selectedQuestions.forEach((q, index) => { | |
| const answer = q.getAnswer(); | |
| gameState.questions.push({ | |
| id: `q${index}`, | |
| correctAnswer: answer | |
| }); | |
| const optionsHTML = gameState.problemData.map(c => `<option value="${c.name}">${c.name}</option>`).join(''); | |
| const questionHTML = ` | |
| <div class="flex items-center justify-center text-lg sm:text-xl"> | |
| <label for="q${index}" class="text-gray-700">${q.text.replace('__', ` | |
| <select id="q${index}" class="mx-2 p-1 border-2 border-green-400 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"> | |
| <option value="">?</option> | |
| ${optionsHTML} | |
| </select> | |
| `)}</label> | |
| </div> | |
| `; | |
| questionsArea.innerHTML += questionHTML; | |
| }); | |
| } | |
| function checkAnswers() { | |
| let allCorrect = true; | |
| gameState.questions.forEach(q => { | |
| const selectElement = document.getElementById(q.id); | |
| const userAnswer = selectElement.value; | |
| let isCorrect = false; | |
| if (Array.isArray(q.correctAnswer)) { | |
| isCorrect = q.correctAnswer.includes(userAnswer); | |
| } else { | |
| isCorrect = userAnswer === q.correctAnswer; | |
| } | |
| if (isCorrect) { | |
| selectElement.classList.remove('border-red-400'); | |
| selectElement.classList.add('border-green-400'); | |
| } else { | |
| selectElement.classList.remove('border-green-400'); | |
| selectElement.classList.add('border-red-400'); | |
| allCorrect = false; | |
| } | |
| }); | |
| if(allCorrect) { | |
| feedbackArea.innerHTML = `<span class="text-green-500">๐ Well Done! All Correct! ๐</span>`; | |
| } else { | |
| feedbackArea.innerHTML = `<span class="text-red-500">Some answers are incorrect. Try again!</span>`; | |
| } | |
| } | |
| function updateActiveButton() { | |
| document.querySelectorAll('.difficulty-btn').forEach(btn => { | |
| if(btn.dataset.level === gameState.level) { | |
| btn.classList.add('active'); | |
| } else { | |
| btn.classList.remove('active'); | |
| } | |
| }); | |
| } | |
| // --- Event Listeners --- | |
| difficultyControls.addEventListener('click', (e) => { | |
| if (e.target.tagName === 'BUTTON') { | |
| generateProblem(e.target.dataset.level); | |
| } | |
| }); | |
| checkBtn.addEventListener('click', checkAnswers); | |
| newQuestionBtn.addEventListener('click', () => { | |
| generateProblem(gameState.level); | |
| }); | |
| // --- Initial Load --- | |
| generateProblem('easy'); | |
| </script> | |
| </body> | |
| </html> |