calculation / index.html
LAshi's picture
remove heading - Initial Deployment
1847f37 verified
raw
history blame contribute delete
24.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ultimate Speed Math Trainer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.timer-bar {
transition: width linear;
height: 4px;
}
.feedback-message {
opacity: 0;
transition: opacity 0.3s ease;
height: 24px;
}
.feedback-message.show {
opacity: 1;
}
.settings-panel {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.settings-panel.open {
transform: translateX(0);
}
@media (max-width: 640px) {
.settings-panel {
width: 100%;
}
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
<div class="container mx-auto px-4 py-8 pb-16 sm:pb-8 flex-1 flex flex-col">
<!-- Header with stats -->
<header class="flex justify-between items-center mb-8">
<button id="settings-btn" class="text-gray-400 hover:text-white p-2">
<i class="fas fa-cog text-2xl transition-opacity duration-300"></i>
</button>
<div class="flex space-x-4 text-sm">
<div class="bg-gray-800 px-3 py-1 rounded">
<span class="text-green-400"></span> <span id="correct-count">0</span>
</div>
<div class="bg-gray-800 px-3 py-1 rounded">
<span class="text-red-400"></span> <span id="incorrect-count">0</span>
</div>
<div class="bg-gray-800 px-3 py-1 rounded">
<span class="text-yellow-400">%</span> <span id="accuracy">0</span>
</div>
</div>
</header>
<!-- Main training area -->
<main class="flex-1 flex flex-col items-center justify-center">
<!-- Timer bar -->
<div class="w-full bg-gray-700 rounded-full mb-6">
<div id="timer-bar" class="timer-bar bg-blue-500 rounded-full" style="width: 100%"></div>
</div>
<!-- Question display -->
<div id="question" class="text-5xl sm:text-6xl md:text-8xl font-bold mb-8 sm:mb-12 text-center min-h-[80px] sm:min-h-[120px] flex items-center justify-center">
Ready?
</div>
<!-- Answer input -->
<div class="w-full max-w-md mb-6 sm:mb-8 px-2 sm:px-0">
<input
type="number"
id="answer-input"
class="w-full bg-gray-800 text-2xl sm:text-3xl text-center py-3 sm:py-4 px-4 sm:px-6 rounded-lg border-2 border-gray-700 focus:border-blue-500 focus:outline-none"
placeholder="Your answer"
autofocus
>
</div>
<!-- Feedback message -->
<div id="feedback" class="feedback-message text-xl font-semibold mb-4"></div>
</main>
</div>
<!-- Settings backdrop (hidden by default) -->
<div id="settings-backdrop" class="fixed inset-0 bg-black bg-opacity-50 z-0 opacity-0 pointer-events-none transition-opacity duration-300"></div>
<!-- Settings panel (hidden by default) -->
<div id="settings-panel" class="settings-panel fixed inset-y-0 left-0 w-full sm:w-96 bg-gray-800 shadow-xl p-4 sm:p-6 overflow-y-auto z-10 border-r border-gray-700">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-blue-400">Configuration</h2>
<button id="close-settings" class="text-gray-400 hover:text-white">
<i class="fas fa-times text-2xl"></i>
</button>
</div>
<!-- Mode selection -->
<div class="mb-8 pb-6 border-b border-gray-700">
<h3 class="text-lg font-semibold mb-3">Mode</h3>
<div class="space-y-2">
<label class="flex items-center space-x-3">
<input type="radio" name="mode" value="multiplication" class="form-radio text-blue-500" checked>
<span>Multiplication (A × B = ?)</span>
</label>
<label class="flex items-center space-x-3">
<input type="radio" name="mode" value="factoring" class="form-radio text-blue-500">
<span>Factoring (C ÷ A = ?)</span>
</label>
</div>
</div>
<!-- Number ranges -->
<div class="mb-8 pb-6 border-b border-gray-700">
<h3 class="text-lg font-semibold mb-3">Number Ranges</h3>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium mb-1">Multiplier Min (A)</label>
<input type="number" id="multiplier-min" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 focus:border-blue-500 focus:outline-none" value="2" min="1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Multiplier Max (A)</label>
<input type="number" id="multiplier-max" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="12" min="1">
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium mb-1">Multiplicand Min (B)</label>
<input type="number" id="multiplicand-min" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="2" min="1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Multiplicand Max (B)</label>
<input type="number" id="multiplicand-max" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="12" min="1">
</div>
</div>
<div id="product-range-container" class="grid grid-cols-2 gap-4 hidden">
<div>
<label class="block text-sm font-medium mb-1">Product Min (C)</label>
<input type="number" id="product-min" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="10" min="1">
</div>
<div>
<label class="block text-sm font-medium mb-1">Product Max (C)</label>
<input type="number" id="product-max" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="100" min="1">
</div>
</div>
</div>
<!-- Exclusions -->
<div class="mb-8 pb-6 border-b border-gray-700">
<h3 class="text-lg font-semibold mb-3">Exclusions</h3>
<div>
<label class="block text-sm font-medium mb-1">Exclude these numbers (comma separated)</label>
<input type="text" id="exclude-list" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" placeholder="e.g., 5, 10, 11">
<p class="text-xs text-gray-400 mt-1">Numbers to exclude from appearing in problems</p>
</div>
</div>
<!-- Time limit -->
<div class="mb-8">
<h3 class="text-lg font-semibold mb-3">Time Limit (seconds)</h3>
<input type="number" id="time-limit" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2" value="5" min="1">
</div>
<!-- Action buttons -->
<div class="flex space-x-3">
<button id="save-settings" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded">
Save & Restart
</button>
<button id="reset-stats" class="bg-gray-700 hover:bg-gray-600 text-white font-medium py-3 px-4 rounded">
Reset Stats
</button>
</div>
</div>
<script>
// Configuration object with default values
let config = {
mode: 'multiplication',
multiplierMin: 2,
multiplierMax: 12,
multiplicandMin: 2,
multiplicandMax: 12,
productMin: 10,
productMax: 100,
excludeList: [],
timeLimit: 5
};
// Game state
let state = {
currentQuestion: null,
correctAnswer: null,
timer: null,
timeLeft: 0,
correctCount: 0,
incorrectCount: 0,
isRunning: false
};
// DOM elements
const elements = {
question: document.getElementById('question'),
answerInput: document.getElementById('answer-input'),
feedback: document.getElementById('feedback'),
timerBar: document.getElementById('timer-bar'),
correctCount: document.getElementById('correct-count'),
incorrectCount: document.getElementById('incorrect-count'),
accuracy: document.getElementById('accuracy'),
settingsBtn: document.getElementById('settings-btn'),
settingsPanel: document.getElementById('settings-panel'),
closeSettings: document.getElementById('close-settings'),
saveSettings: document.getElementById('save-settings'),
resetStats: document.getElementById('reset-stats'),
modeRadios: document.querySelectorAll('input[name="mode"]'),
multiplierMin: document.getElementById('multiplier-min'),
multiplierMax: document.getElementById('multiplier-max'),
multiplicandMin: document.getElementById('multiplicand-min'),
multiplicandMax: document.getElementById('multiplicand-max'),
productMin: document.getElementById('product-min'),
productMax: document.getElementById('product-max'),
productRangeContainer: document.getElementById('product-range-container'),
excludeList: document.getElementById('exclude-list'),
timeLimit: document.getElementById('time-limit')
};
// Initialize the game
function init() {
loadConfig();
updateStats();
setupEventListeners();
updateModeUI();
generateQuestion();
}
// Load config from localStorage
function loadConfig() {
const savedConfig = localStorage.getItem('mathTrainerConfig');
if (savedConfig) {
Object.assign(config, JSON.parse(savedConfig));
updateSettingsForm();
}
}
// Save config to localStorage
function saveConfig() {
localStorage.setItem('mathTrainerConfig', JSON.stringify(config));
}
// Update the settings form with current config values
function updateSettingsForm() {
// Set radio button
document.querySelector(`input[name="mode"][value="${config.mode}"]`).checked = true;
// Set number inputs
elements.multiplierMin.value = config.multiplierMin;
elements.multiplierMax.value = config.multiplierMax;
elements.multiplicandMin.value = config.multiplicandMin;
elements.multiplicandMax.value = config.multiplicandMax;
elements.productMin.value = config.productMin;
elements.productMax.value = config.productMax;
elements.excludeList.value = config.excludeList.join(', ');
elements.timeLimit.value = config.timeLimit;
updateModeUI();
}
// Update UI based on selected mode
function updateModeUI() {
if (config.mode === 'factoring') {
elements.productRangeContainer.classList.remove('hidden');
} else {
elements.productRangeContainer.classList.add('hidden');
}
}
// Set up event listeners
function setupEventListeners() {
// Answer submission
elements.answerInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
checkAnswer();
}
});
// Settings button
elements.settingsBtn.addEventListener('click', () => {
elements.settingsPanel.classList.add('open');
document.getElementById('settings-backdrop').classList.add('opacity-100', 'pointer-events-auto');
elements.settingsBtn.querySelector('i').classList.add('opacity-0');
elements.answerInput.blur();
});
// Close settings
elements.closeSettings.addEventListener('click', () => {
elements.settingsPanel.classList.remove('open');
document.getElementById('settings-backdrop').classList.remove('opacity-100', 'pointer-events-auto');
elements.settingsBtn.querySelector('i').classList.remove('opacity-0');
elements.answerInput.focus();
});
// Close settings when clicking backdrop
document.getElementById('settings-backdrop').addEventListener('click', () => {
elements.settingsPanel.classList.remove('open');
document.getElementById('settings-backdrop').classList.remove('opacity-100', 'pointer-events-auto');
elements.settingsBtn.querySelector('i').classList.remove('opacity-0');
elements.answerInput.focus();
});
// Save settings
elements.saveSettings.addEventListener('click', saveSettings);
// Reset stats
elements.resetStats.addEventListener('click', resetStats);
// Mode change
elements.modeRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
if (e.target.value === 'factoring') {
elements.productRangeContainer.classList.remove('hidden');
} else {
elements.productRangeContainer.classList.add('hidden');
}
});
});
}
// Save settings from form to config
function saveSettings() {
// Get mode
config.mode = document.querySelector('input[name="mode"]:checked').value;
// Get number ranges
config.multiplierMin = parseInt(elements.multiplierMin.value) || 1;
config.multiplierMax = parseInt(elements.multiplierMax.value) || 12;
config.multiplicandMin = parseInt(elements.multiplicandMin.value) || 1;
config.multiplicandMax = parseInt(elements.multiplicandMax.value) || 12;
config.productMin = parseInt(elements.productMin.value) || 10;
config.productMax = parseInt(elements.productMax.value) || 100;
// Get exclude list
const excludeStr = elements.excludeList.value.trim();
config.excludeList = excludeStr ? excludeStr.split(',').map(num => parseInt(num.trim())).filter(num => !isNaN(num)) : [];
// Get time limit
config.timeLimit = parseInt(elements.timeLimit.value) || 5;
// Save to localStorage
saveConfig();
// Close settings panel
elements.settingsPanel.classList.remove('open');
document.getElementById('settings-backdrop').classList.remove('opacity-100', 'pointer-events-auto');
elements.settingsBtn.querySelector('i').classList.remove('opacity-0');
// Restart game with new settings
resetGame();
setTimeout(() => {
elements.answerInput.focus();
elements.answerInput.select();
}, 100);
}
// Reset game stats
function resetStats() {
state.correctCount = 0;
state.incorrectCount = 0;
updateStats();
}
// Reset the game state
function resetGame() {
clearTimeout(state.timer);
state.isRunning = false;
generateQuestion();
}
// Update stats display
function updateStats() {
elements.correctCount.textContent = state.correctCount;
elements.incorrectCount.textContent = state.incorrectCount;
const total = state.correctCount + state.incorrectCount;
const accuracy = total > 0 ? Math.round((state.correctCount / total) * 100) : 0;
elements.accuracy.textContent = accuracy;
}
// Generate a new question
function generateQuestion() {
clearTimeout(state.timer);
let num1, num2, question, answer;
if (config.mode === 'multiplication') {
// Generate multiplication question
[num1, num2] = generateValidNumbers(
config.multiplierMin,
config.multiplierMax,
config.multiplicandMin,
config.multiplicandMax
);
question = `${num1} × ${num2} =`;
answer = num1 * num2;
} else {
// Generate factoring question
let attempts = 0;
const maxAttempts = 100;
while (attempts < maxAttempts) {
num1 = getRandomInt(config.multiplierMin, config.multiplierMax);
num2 = getRandomInt(config.multiplicandMin, config.multiplicandMax);
// Check if numbers are excluded
if (config.excludeList.includes(num1) || config.excludeList.includes(num2)) {
attempts++;
continue;
}
const product = num1 * num2;
// Check if product is within range
if (product >= config.productMin && product <= config.productMax) {
question = `${product} ÷ ${num1} =`;
answer = num2;
break;
}
attempts++;
}
// If we couldn't find a valid question after max attempts
if (attempts >= maxAttempts) {
question = "Couldn't generate question with current settings";
answer = null;
}
}
state.currentQuestion = question;
state.correctAnswer = answer;
elements.question.textContent = question;
elements.answerInput.value = '';
elements.feedback.textContent = '';
elements.feedback.classList.remove('show', 'text-green-400', 'text-red-400');
if (answer !== null) {
startTimer();
}
}
// Generate valid numbers that aren't in the exclude list
function generateValidNumbers(min1, max1, min2, max2) {
let num1, num2;
let attempts = 0;
const maxAttempts = 100;
do {
num1 = getRandomInt(min1, max1);
num2 = getRandomInt(min2, max2);
attempts++;
if (attempts >= maxAttempts) {
// If we can't find valid numbers after many attempts, just use any
break;
}
} while (config.excludeList.includes(num1) || config.excludeList.includes(num2));
return [num1, num2];
}
// Get random integer between min and max (inclusive)
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Start the timer for the current question
function startTimer() {
clearTimeout(state.timer);
state.timeLeft = config.timeLimit;
state.isRunning = true;
// Update timer bar immediately
updateTimerBar();
// Start countdown
state.timer = setInterval(() => {
state.timeLeft -= 0.1;
updateTimerBar();
if (state.timeLeft <= 0) {
clearInterval(state.timer);
timeUp();
}
}, 100);
}
// Update the timer bar display
function updateTimerBar() {
const percentage = Math.max(0, (state.timeLeft / config.timeLimit) * 100);
elements.timerBar.style.width = `${percentage}%`;
// Change color based on time left
if (percentage > 50) {
elements.timerBar.className = 'timer-bar bg-blue-500 rounded-full';
} else if (percentage > 25) {
elements.timerBar.className = 'timer-bar bg-yellow-500 rounded-full';
} else {
elements.timerBar.className = 'timer-bar bg-red-500 rounded-full';
}
}
// Handle when time is up
function timeUp() {
state.isRunning = false;
state.incorrectCount++;
updateStats();
showFeedback(`Time's up! Answer: ${state.correctAnswer}`, false);
// Auto-advance after delay
setTimeout(() => {
generateQuestion();
}, 2000);
}
// Check the user's answer
function checkAnswer() {
if (!state.isRunning || state.correctAnswer === null) return;
clearTimeout(state.timer);
state.isRunning = false;
const userAnswer = parseInt(elements.answerInput.value.trim());
if (isNaN(userAnswer)) {
showFeedback('Please enter a number', false);
return;
}
if (userAnswer === state.correctAnswer) {
state.correctCount++;
showFeedback('Correct!', true);
} else {
state.incorrectCount++;
showFeedback(`Incorrect! Answer: ${state.correctAnswer}`, false);
}
updateStats();
// Auto-advance after delay
setTimeout(() => {
generateQuestion();
}, 1500);
}
// Show feedback message
function showFeedback(message, isCorrect) {
elements.feedback.textContent = message;
elements.feedback.classList.add('show');
if (isCorrect) {
elements.feedback.classList.add('text-green-400');
elements.feedback.classList.remove('text-red-400');
} else {
elements.feedback.classList.add('text-red-400');
elements.feedback.classList.remove('text-green-400');
}
}
// Initialize the game when the page loads
window.addEventListener('DOMContentLoaded', init);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LAshi/calculation" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>