Application: Gemini Canvas
Durée: 20 minutes
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Le défi de la calculatrice brisée</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Chicle&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
touch-action: manipulation; /* Prevents double-tap zoom on mobile */
}
.calculator-key {
transition: all 0.1s ease;
}
.calculator-key:active {
transform: scale(0.95);
box-shadow: inset 2px 2px 5px rgba(0,0,0,0.2);
}
.broken-key {
background-color: #ef4444 !important; /* red-500 */
color: #fee2e2 !important; /* red-100 */
text-decoration: line-through;
pointer-events: none;
opacity: 0.7;
position: relative;
}
.broken-key::after {
content: 'X';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(2.5);
color: rgba(255, 255, 255, 0.5);
font-weight: bold;
}
.font-chicle {
font-family: 'Chicle', cursive;
}
#progress-bar {
transition: width 0.3s ease-in-out;
}
</style>
</head>
<body class="bg-sky-100 flex items-center justify-center min-h-screen p-4">
<div class="w-full max-w-md mx-auto bg-white rounded-2xl shadow-2xl p-6 border-4 border-gray-200">
<div class="text-center mb-4">
<h1 class="text-3xl md:text-4xl font-bold text-sky-700 font-chicle tracking-wide">Le Défi de la Calculatrice Brisée</h1>
</div>
<!-- Progress Bar -->
<div id="progress-container" class="w-full bg-gray-200 rounded-full h-4 mb-4 overflow-hidden">
<div id="progress-bar" class="bg-green-500 h-4 rounded-full" style="width: 0%"></div>
</div>
<!-- Challenge Display -->
<div id="challenge-container" class="bg-sky-50 border-2 border-sky-200 rounded-lg p-4 mb-4 text-center">
<p class="text-lg text-gray-700">
<span class="font-semibold">Défi:</span>
<span id="challenge-text"></span>
</p>
<p class="text-md text-red-600 font-semibold mt-1">
<span id="broken-key-label">Touche brisée</span>: <span id="broken-key-text" class="px-2 py-1 bg-red-100 text-red-700 rounded-md"></span>
</p>
</div>
<div id="end-game-container" class="hidden text-center p-4">
<h2 class="text-2xl font-bold text-sky-700">Tu as relevé tous les défis !</h2>
<p class="mt-2 text-gray-600">Tu es un vrai génie des mathématiques !</p>
</div>
<!-- Calculator -->
<div id="calculator" class="bg-gray-800 rounded-xl p-4">
<!-- Screen -->
<div id="calculator-screen" class="bg-gray-200 rounded-lg h-20 mb-4 p-4 text-4xl font-mono text-right text-gray-900 overflow-x-auto overflow-y-hidden flex items-center justify-end">0</div>
<!-- Keys -->
<div class="grid grid-cols-4 gap-3">
<button data-key="clear" class="calculator-key col-span-2 bg-amber-500 hover:bg-amber-600 text-white font-bold text-2xl py-4 rounded-lg">C</button>
<button data-key="del" class="calculator-key bg-amber-500 hover:bg-amber-600 text-white font-bold text-2xl py-4 rounded-lg">⌫</button>
<button data-key="/" class="calculator-key bg-indigo-500 hover:bg-indigo-600 text-white font-bold text-2xl py-4 rounded-lg">÷</button>
<button data-key="7" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">7</button>
<button data-key="8" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">8</button>
<button data-key="9" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">9</button>
<button data-key="*" class="calculator-key bg-indigo-500 hover:bg-indigo-600 text-white font-bold text-2xl py-4 rounded-lg">×</button>
<button data-key="4" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">4</button>
<button data-key="5" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">5</button>
<button data-key="6" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">6</button>
<button data-key="-" class="calculator-key bg-indigo-500 hover:bg-indigo-600 text-white font-bold text-2xl py-4 rounded-lg">-</button>
<button data-key="1" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">1</button>
<button data-key="2" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">2</button>
<button data-key="3" class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">3</button>
<button data-key="+" class="calculator-key bg-indigo-500 hover:bg-indigo-600 text-white font-bold text-2xl py-4 rounded-lg">+</button>
<button data-key="0" class="calculator-key col-span-2 bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">0</button>
<button data-key="." class="calculator-key bg-gray-600 hover:bg-gray-700 text-white font-bold text-2xl py-4 rounded-lg">.</button>
<button data-key="=" class="calculator-key bg-green-500 hover:bg-green-600 text-white font-bold text-2xl py-4 rounded-lg">=</button>
</div>
</div>
<!-- Feedback and Next/Restart Button -->
<div id="feedback-container" class="h-20 mt-4 flex flex-col items-center justify-center">
<p id="feedback-text" class="text-xl font-semibold text-center"></p>
<button id="next-challenge-btn" class="hidden mt-2 bg-sky-600 text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-sky-700 transition-transform transform hover:scale-105">Défi suivant!</button>
<button id="restart-btn" class="hidden mt-2 bg-amber-500 text-white font-bold py-2 px-6 rounded-lg shadow-md hover:bg-amber-600 transition-transform transform hover:scale-105">Recommencer</button>
</div>
</div>
<script>
let challenges = [];
let currentChallengeIndex = 0;
const TOTAL_CHALLENGES = 10;
// --- DOM Elements ---
const screen = document.getElementById('calculator-screen');
const challengeContainer = document.getElementById('challenge-container');
const endGameContainer = document.getElementById('end-game-container');
const calculator = document.getElementById('calculator');
const challengeText = document.getElementById('challenge-text');
const brokenKeyLabel = document.getElementById('broken-key-label');
const brokenKeyText = document.getElementById('broken-key-text');
const feedbackText = document.getElementById('feedback-text');
const nextChallengeBtn = document.getElementById('next-challenge-btn');
const restartBtn = document.getElementById('restart-btn');
const keys = document.querySelectorAll('.calculator-key');
const progressBar = document.getElementById('progress-bar');
let currentInput = '0';
// --- Challenge Generation ---
function generateChallenge() {
const type = Math.floor(Math.random() * 4); // 0: add, 1: subtract, 2: multiply, 3: display number
let num1, num2, question, targetResult, brokenKeys;
let digitsInProblem;
switch (type) {
case 0: // Addition
num1 = Math.floor(Math.random() * 40) + 10;
num2 = Math.floor(Math.random() * 40) + 10;
question = `Calcule <strong>${num1} + ${num2}</strong>.`;
targetResult = num1 + num2;
digitsInProblem = `${num1}${num2}`.split('');
brokenKeys = [digitsInProblem[Math.floor(Math.random() * digitsInProblem.length)]];
break;
case 1: // Subtraction
num1 = Math.floor(Math.random() * 40) + 50;
num2 = Math.floor(Math.random() * 40) + 10;
question = `Calcule <strong>${num1} - ${num2}</strong>.`;
targetResult = num1 - num2;
digitsInProblem = `${num1}${num2}`.split('');
brokenKeys = [digitsInProblem[Math.floor(Math.random() * digitsInProblem.length)]];
break;
case 2: // Multiplication
num1 = Math.floor(Math.random() * 8) + 2;
num2 = Math.floor(Math.random() * 8) + 2;
question = `Calcule <strong>${num1} × ${num2}</strong>.`;
targetResult = num1 * num2;
if (Math.random() > 0.5) {
brokenKeys = ['*'];
} else {
digitsInProblem = `${num1}${num2}`.split('');
brokenKeys = [digitsInProblem[Math.floor(Math.random() * digitsInProblem.length)]];
}
break;
case 3: // Display Number
default:
targetResult = Math.floor(Math.random() * 90) + 10;
question = `Affiche <strong>${targetResult}</strong>.`;
digitsInProblem = `${targetResult}`.split('');
// Break 1 or 2 digits from the number
const firstBroken = digitsInProblem[Math.floor(Math.random() * digitsInProblem.length)];
brokenKeys = [firstBroken];
if (digitsInProblem.length > 1 && Math.random() > 0.5) {
let secondBroken;
do {
secondBroken = digitsInProblem[Math.floor(Math.random() * digitsInProblem.length)];
} while(secondBroken === firstBroken);
brokenKeys.push(secondBroken);
}
break;
}
return { question, brokenKeys, targetResult };
}
function generateAllChallenges() {
challenges = [];
for(let i=0; i<TOTAL_CHALLENGES; i++) {
challenges.push(generateChallenge());
}
}
// --- Game Flow ---
function startGame() {
currentChallengeIndex = 0;
generateAllChallenges();
endGameContainer.classList.add('hidden');
challengeContainer.classList.remove('hidden');
calculator.classList.remove('hidden');
restartBtn.classList.add('hidden');
loadChallenge(currentChallengeIndex);
}
function loadChallenge(index) {
// Reset state
feedbackText.textContent = '';
feedbackText.className = 'text-xl font-semibold text-center';
nextChallengeBtn.classList.add('hidden');
currentInput = '0';
updateScreen();
// Update progress bar
const progress = ((index) / challenges.length) * 100;
progressBar.style.width = `${progress}%`;
// Clear previous broken keys
document.querySelectorAll('.broken-key').forEach(key => key.classList.remove('broken-key'));
// Load new challenge
const challenge = challenges[index];
challengeText.innerHTML = challenge.question;
brokenKeyLabel.textContent = challenge.brokenKeys.length > 1 ? 'Touches brisées' : 'Touche brisée';
brokenKeyText.textContent = challenge.brokenKeys.join(', ');
// Set new broken keys
challenge.brokenKeys.forEach(keyVal => {
const keyElement = document.querySelector(`button[data-key="${keyVal}"]`);
if (keyElement) {
keyElement.classList.add('broken-key');
}
});
}
function finishGame() {
progressBar.style.width = '100%';
challengeContainer.classList.add('hidden');
calculator.classList.add('hidden');
endGameContainer.classList.remove('hidden');
nextChallengeBtn.classList.add('hidden');
restartBtn.classList.remove('hidden');
}
// --- UI & Interaction ---
function updateScreen() {
screen.textContent = currentInput.replace(/\*/g, '×').replace(/\//g, '÷');
}
function handleKeyPress(key) {
const challenge = challenges[currentChallengeIndex];
if (challenge.brokenKeys.includes(key)) {
feedbackText.textContent = `Oups! La touche '${key}' est brisée !`;
feedbackText.className = 'text-xl font-semibold text-center text-red-500 animate-pulse';
setTimeout(() => {
if (feedbackText.textContent === `Oups! La touche '${key}' est brisée !`) {
feedbackText.textContent = '';
feedbackText.classList.remove('animate-pulse');
}
}, 2000);
return;
}
if (key === 'clear') {
currentInput = '0';
} else if (key === 'del') {
if (currentInput.length > 1) {
currentInput = currentInput.slice(0, -1);
} else {
currentInput = '0';
}
} else if (key === '=') {
evaluate();
} else {
if (currentInput === '0' && key !== '.') {
currentInput = key;
} else {
currentInput += key;
}
}
updateScreen();
}
function evaluate() {
const challenge = challenges[currentChallengeIndex];
let result;
try {
const sanitizedInput = currentInput.replace(/[^-()\d/*+.]/g, '');
if (sanitizedInput !== currentInput) {
throw new Error("Invalid characters in expression");
}
result = new Function('return ' + sanitizedInput)();
if (result === challenge.targetResult) {
feedbackText.textContent = 'Bravo, c\'est la bonne réponse !';
feedbackText.className = 'text-xl font-semibold text-center text-green-600';
nextChallengeBtn.classList.remove('hidden');
progressBar.style.width = `${(currentChallengeIndex + 1) / challenges.length * 100}%`;
} else {
feedbackText.textContent = `Pas tout à fait. Le résultat est ${result}, essaie encore !`;
feedbackText.className = 'text-xl font-semibold text-center text-amber-600';
}
} catch (error) {
feedbackText.textContent = 'Calcul invalide. Vérifie ton équation.';
feedbackText.className = 'text-xl font-semibold text-center text-red-600';
}
}
// --- Event Listeners ---
keys.forEach(key => {
key.addEventListener('click', () => {
handleKeyPress(key.dataset.key);
});
});
nextChallengeBtn.addEventListener('click', () => {
currentChallengeIndex++;
if (currentChallengeIndex >= challenges.length) {
finishGame();
} else {
loadChallenge(currentChallengeIndex);
}
});
restartBtn.addEventListener('click', startGame);
// --- Initial load ---
startGame();
</script>
</body>
</html>