diff --git a/app.js b/app.js index c03aa9e..49ad5eb 100644 --- a/app.js +++ b/app.js @@ -81,10 +81,11 @@ currentInput: "", attempts: 0, startTime: 0, - timeTurnerCharges: 0 + timeTurnerCharges: 0, + forcedAnswer: false }; - let dementor = { interval: null, progress: 0 }; + let dementor = { interval: null, progress: 0, timedOut: false }; let snitchTimer = null; let cheatClicks = 0; @@ -168,6 +169,7 @@ resetDementor(); game.currentInput = ""; game.attempts = 0; + game.forcedAnswer = false; game.startTime = Date.now(); document.getElementById('answer-display').innerText = ""; @@ -175,6 +177,10 @@ const hintGrid = document.getElementById('hint-grid'); hintGrid.innerHTML = ""; hintGrid.classList.remove('has-stars'); + const castBtn = document.querySelector('.key-cast'); + const answerDisplay = document.getElementById('answer-display'); + if (castBtn) castBtn.classList.remove('ready-cast'); + if (answerDisplay) answerDisplay.classList.remove('forced-answer'); if (game.timeTurnerCharges > 0) { game.timeTurnerCharges--; @@ -284,24 +290,7 @@ } saveData(); updateUI(); setTimeout(generateQuestion, 1000); } else { - state.streak = 0; - game.attempts++; - addToTroubleList(game.currentFact.n1, game.currentFact.n2); - card.classList.add('apply-shake'); - setTimeout(() => card.classList.remove('apply-shake'), 500); - dementor.progress += 0.2; updateDementorVisuals(); - saveData(); updateUI(); - game.currentInput = ""; - document.getElementById('answer-display').innerText = ""; - - if (game.attempts >= 3) { - feedback.style.color = '#ffb0b0'; - feedback.innerText = `Answer is ${game.currentFact.ans}.`; - } else { - feedback.style.color = '#ffb0b0'; - feedback.innerText = "Count the stars..."; - drawHintGrid(game.currentFact.n1, game.currentFact.n2); - } + handleWrongAttempt(); } } @@ -697,6 +686,7 @@ function startDementor(resume = false) { clearInterval(dementor.interval); if (!resume) dementor.progress = 0; + dementor.timedOut = false; let speed = 250; const step = 0.02; if (game.timeTurnerCharges > 0) speed = 600; @@ -706,6 +696,9 @@ if (!game.active) return; if (dementor.progress < 1) { dementor.progress += step; updateDementorVisuals(); + } else if (!dementor.timedOut) { + dementor.timedOut = true; + handleDementorTimeout(); } }, speed); } @@ -725,9 +718,11 @@ overlay.style.setProperty('--circle-blur', `${blur}px`); overlay.style.setProperty('--vignette-strength', vignette.toFixed(2)); overlay.style.background = buildDementorBackground(p, size, darkness, vignette); - const ring = document.getElementById('timer-ring'); - if (ring) ring.style.setProperty('--timer-progress', p.toFixed(3)); + const castTimer = document.getElementById('cast-timer-fill'); + if (castTimer) castTimer.style.width = `${Math.max(0, 100 - p * 100)}%`; document.documentElement.style.setProperty('--focus-glow', glow.toFixed(2)); + const uiGlow = Math.min(1.2, 0.15 + (p * 1.05)); + document.documentElement.style.setProperty('--ui-glow', uiGlow.toFixed(2)); } function resetDementor() { @@ -739,10 +734,12 @@ overlay.style.setProperty('--circle-blur', '0px'); overlay.style.setProperty('--vignette-strength', '0.18'); overlay.style.background = 'transparent'; - const ring = document.getElementById('timer-ring'); - if (ring) ring.style.setProperty('--timer-progress', '0'); + const castTimer = document.getElementById('cast-timer-fill'); + if (castTimer) castTimer.style.width = '100%'; document.documentElement.style.setProperty('--focus-glow', '0'); + document.documentElement.style.setProperty('--ui-glow', '0'); saveData(); + dementor.timedOut = false; } function buildDementorBackground(progress, size, strength, vignette) { @@ -798,6 +795,82 @@ grid.classList.add('has-stars'); } + function handleWrongAttempt(options = {}) { + const { timedOut = false } = options; + const feedback = document.getElementById('feedback-msg'); + const card = document.querySelector('.card-container'); + state.streak = 0; + game.attempts = timedOut ? 3 : game.attempts + 1; + if (card) { + card.classList.add('apply-shake'); + setTimeout(() => card.classList.remove('apply-shake'), 500); + } + if (!timedOut) { + dementor.progress += 0.2; updateDementorVisuals(); + game.currentInput = ""; + document.getElementById('answer-display').innerText = ""; + } + if (game.attempts >= 3) { + revealAnswerAndAdvance(timedOut ? 2000 : 0, timedOut); + return; + } + if (game.attempts === 2) { + feedback.style.color = '#ffb0b0'; + feedback.innerText = "Count the stars..."; + drawHintGrid(game.currentFact.n1, game.currentFact.n2); + } else { + feedback.style.color = '#ffb0b0'; + feedback.innerText = "Try again!"; + } + saveData(); updateUI(); + } + + function revealAnswerAndAdvance(delayMs = 0, timedOut = false) { + const feedback = document.getElementById('feedback-msg'); + const card = document.querySelector('.card-container'); + const factId = `${Math.min(game.currentFact.n1, game.currentFact.n2)}x${Math.max(game.currentFact.n1, game.currentFact.n2)}`; + game.active = false; + setTimeout(() => { + // guard in case question changed + const currentId = `${Math.min(game.currentFact.n1, game.currentFact.n2)}x${Math.max(game.currentFact.n1, game.currentFact.n2)}`; + if (currentId !== factId) return; + if (card) { + card.classList.add('apply-shake'); + setTimeout(() => card.classList.remove('apply-shake'), 500); + } + feedback.style.color = '#ffb0b0'; + feedback.innerText = `Answer is ${game.currentFact.ans}. Press CAST to continue.`; + addToTroubleList(game.currentFact.n1, game.currentFact.n2); + game.currentInput = game.currentFact.ans.toString(); + const answerDisplay = document.getElementById('answer-display'); + if (answerDisplay) { + answerDisplay.innerText = game.currentInput; + answerDisplay.classList.add('forced-answer'); + } + const castBtn = document.querySelector('.key-cast'); + if (castBtn) castBtn.classList.add('ready-cast'); + game.forcedAnswer = true; + resetDementor(); + saveData(); updateUI(); + game.active = true; + }, delayMs); + } + + function handleDementorTimeout() { + const factId = `${Math.min(game.currentFact.n1, game.currentFact.n2)}x${Math.max(game.currentFact.n1, game.currentFact.n2)}`; + game.active = false; + setTimeout(() => { + const currentId = `${Math.min(game.currentFact.n1, game.currentFact.n2)}x${Math.max(game.currentFact.n1, game.currentFact.n2)}`; + if (currentId !== factId) return; + const feedback = document.getElementById('feedback-msg'); + if (feedback) { + feedback.style.color = '#ffb0b0'; + feedback.innerText = "Saved by a Professor..."; + } + handleWrongAttempt({ timedOut: true }); + }, 2000); + } + function applyXPToMastery() { const levelData = CURRICULUM[state.levelIndex]; const requiredFacts = getFactsForLevel(state.levelIndex); @@ -864,7 +937,7 @@ // --- GOLDEN SNITCH EVENT --- function scheduleSnitch() { if (snitchTimer) clearTimeout(snitchTimer); - const delay = 20000 + Math.random() * 20000; // 20–40s + const delay = 45000 + Math.random() * 30000; // 45–75s snitchTimer = setTimeout(spawnSnitch, delay); } @@ -874,6 +947,10 @@ scheduleSnitch(); return; } + if (dementor.progress > 0.6) { // scared off by looming dementor + scheduleSnitch(); + return; + } const snitch = document.createElement('div'); snitch.className = 'flying-snitch'; const fromLeft = Math.random() > 0.5; diff --git a/index.html b/index.html index e71076b..6a5e5b7 100644 --- a/index.html +++ b/index.html @@ -64,7 +64,6 @@
-
@@ -106,7 +105,10 @@ - +
diff --git a/style.css b/style.css index 13a9ac0..d84671b 100644 --- a/style.css +++ b/style.css @@ -5,6 +5,7 @@ --burgundy: #740001; --bg-dark: #121212; --focus-glow: 0; + --ui-glow: 0; } * { box-sizing: border-box; user-select: none; -webkit-tap-highlight-color: transparent; } @@ -238,21 +239,6 @@ display: flex; flex-direction: column; transition: background 0.3s, border-color 0.3s; } - #timer-ring { - position: absolute; - inset: -6px; - pointer-events: none; - border-radius: 14px; - z-index: 90; - --timer-progress: 0; - --timer-fill: #d4af37; - background: conic-gradient(var(--timer-fill) calc(var(--timer-progress) * 1turn), rgba(255,255,255,0.08) 0); - mask: radial-gradient(circle at 50% 50%, transparent calc(100% - 8px), #000 calc(100% - 6px)); - -webkit-mask: radial-gradient(circle at 50% 50%, transparent calc(100% - 8px), #000 calc(100% - 6px)); - opacity: 0.9; - box-shadow: 0 0 12px rgba(212, 175, 55, 0.45); - } - .header-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-family: 'Cinzel', serif; color: var(--burgundy); font-size: 0.9rem; font-weight: bold; } #xp-text { display: inline-block; @@ -264,6 +250,11 @@ text-shadow: 0 1px 4px rgba(0,0,0,0.6); box-shadow: 0 0 6px rgba(0,0,0,0.25), inset 0 0 8px rgba(255, 225, 140, 0.15); } + .header-info, #xp-text, #mastery-display, .currency-badge, .shop-btn, .help-btn { + text-shadow: + 0 0 calc(4px + 10px * var(--ui-glow)) rgba(255,255,240,calc(0.25 + 0.35 * var(--ui-glow))), + 0 0 calc(6px + 12px * var(--ui-glow)) rgba(0,0,0,0.35); + } .currency-badge { background: var(--burgundy); color: var(--gold); padding: 5px 10px; @@ -298,6 +289,14 @@ position: relative; z-index: 200; } + #answer-display.forced-answer { + animation: pulseAnswer 1s ease-in-out infinite alternate; + border-bottom-color: #ffd966; + } + @keyframes pulseAnswer { + from { box-shadow: 0 0 12px rgba(255,217,102,0.6); } + to { box-shadow: 0 0 18px rgba(255,217,102,0.95); } + } #hint-grid { display: grid; justify-content: center; gap: 6px; margin: 5px auto; max-width: 220px; @@ -320,7 +319,23 @@ -webkit-text-stroke: 1px rgba(0,0,0,0.55); } .key-btn:active { background: var(--gold); } - .key-cast { grid-column: span 3; background: var(--burgundy); color: var(--gold); font-weight: bold; } + .key-cast { grid-column: span 3; background: var(--burgundy); color: var(--gold); font-weight: bold; position: relative; overflow: hidden; } + .cast-label { position: relative; z-index: 2; } + .cast-timer { + position: absolute; left: 8px; right: 8px; bottom: 6px; + height: 6px; background: rgba(0,0,0,0.3); border-radius: 4px; + overflow: hidden; z-index: 1; + box-shadow: inset 0 0 4px rgba(0,0,0,0.4); + } + .cast-timer-fill { + height: 100%; width: 100%; border-radius: 4px; + background: linear-gradient(90deg, #ffd966, #d4af37); + box-shadow: 0 0 8px rgba(255, 217, 102, 0.7); + transition: width 0.2s ease-out; + } + .key-cast.ready-cast { + box-shadow: 0 0 14px rgba(255,217,102,0.9), inset 0 0 8px rgba(0,0,0,0.4); + } .card-container .key-btn.key-clear { background: #ffd966; color: #2c2c2c; @@ -331,11 +346,11 @@ .feedback { height: 20px; margin: 5px 0; font-weight: bold; font-size: 1rem; - color: #fffdf2; + color: #fff8f8; text-shadow: - 0 0 calc(4px + 10px * var(--focus-glow)) rgba(255, 245, 230, calc(0.4 + 0.45 * var(--focus-glow))), - 0 0 calc(8px + 16px * var(--focus-glow)) rgba(0, 0, 0, 0.55); - -webkit-text-stroke: 0.5px rgba(0,0,0,0.35); + 0 0 calc(5px + 12px * var(--focus-glow)) rgba(255, 240, 240, calc(0.5 + 0.45 * var(--focus-glow))), + 0 0 calc(9px + 18px * var(--focus-glow)) rgba(0, 0, 0, 0.75); + -webkit-text-stroke: 0.6px rgba(0,0,0,0.55); } .quick-consumables { display: grid;