Refine timer, failures, and visibility; debounce snitch
This commit is contained in:
parent
5ac93f114c
commit
42e735569f
127
app.js
127
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;
|
||||
|
||||
@ -64,7 +64,6 @@
|
||||
<div id="snitch-layer"></div>
|
||||
|
||||
<div class="card-container skin-default" id="main-card">
|
||||
<div id="timer-ring"></div>
|
||||
<div class="header-info">
|
||||
<button class="shop-btn" onclick="openShop()" title="Student Trunk">🎒</button>
|
||||
<div class="currency-badge">
|
||||
@ -106,7 +105,10 @@
|
||||
<button class="key-btn key-clear" onclick="pressKey('C')">C</button>
|
||||
<button class="key-btn" onclick="pressKey(0)">0</button>
|
||||
<button class="key-btn" onclick="pressKey('BS')">⌫</button>
|
||||
<button class="key-btn key-cast" onclick="checkAnswer()">CAST SPELL</button>
|
||||
<button class="key-btn key-cast" onclick="checkAnswer()">
|
||||
<span class="cast-label">CAST SPELL</span>
|
||||
<div class="cast-timer"><div class="cast-timer-fill" id="cast-timer-fill"></div></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="feedback" id="feedback-msg"></div>
|
||||
|
||||
55
style.css
55
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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user