diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index db1cba6..5d3abc5 100644 Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ diff --git a/main.py b/main.py index 1193273..3fe46c1 100644 --- a/main.py +++ b/main.py @@ -59,13 +59,17 @@ def generate_problem_message(): while not problem_solved: problem["type"] = "generated_problem" problem["data"] = generate_problem() + problem["time"] = get_time_for_problem(problem["data"]) problem["data"]["steps"] = generate_steps(problem["data"]); problem_solved = check_solution(problem["data"]["steps"][-1]["after"], problem["data"]["solution"]) if not problem_solved: log.warning(f"issue with problem: {problem}") return problem - + +def get_time_for_problem(problem): + return 30 + def main(): problem_solved = False while not problem_solved: diff --git a/www/index.html b/www/index.html index 4ff39fb..58f1757 100644 --- a/www/index.html +++ b/www/index.html @@ -1,3 +1,4 @@ + @@ -9,10 +10,31 @@ + +
+ + + + +
+
+
-
+
+
+ diff --git a/www/scripts/background.js b/www/scripts/background.js index 8678003..466e31e 100644 --- a/www/scripts/background.js +++ b/www/scripts/background.js @@ -1,3 +1,4 @@ +// Copyright John Salguero All rights Reserved const canvas = document.getElementById("waveCanvas"); const ctx = canvas.getContext("2d"); diff --git a/www/scripts/problem.js b/www/scripts/problem.js index bbd77b7..ea236e7 100644 --- a/www/scripts/problem.js +++ b/www/scripts/problem.js @@ -1,13 +1,26 @@ - +// Copyright John Salguero All rights Reserved function showProblem() { const problemEl = document.getElementById("problem"); - const stepsEl = document.getElementById("equation"); + const mainEl = document.getElementById("equation-main"); + const nextEl = document.getElementById("equation-next"); + const expEl = document.getElementById("explanation"); const formatted = formatMath(problemData.problem); - problemEl.innerHTML = `\\(${formatted}\\)`; - stepsEl.innerHTML = ""; - MathJax.typesetPromise(); + // Set the top problem (static) + problemEl.innerHTML = `\\(${formatted}\\)`; + + // Initialize the main equation (this is what evolves) + mainEl.innerHTML = `\\(${formatted}\\)`; + + // Clear animation layer + explanation + nextEl.innerHTML = ""; + nextEl.style.opacity = 0; + + expEl.innerHTML = ""; + expEl.style.opacity = 0; + + MathJax.typesetPromise([problemEl, mainEl]); } function showStep() { @@ -16,33 +29,47 @@ function showStep() { return; } - const eqEl = document.getElementById("equation"); + const mainEl = document.getElementById("equation-main"); + const nextEl = document.getElementById("equation-next"); const expEl = document.getElementById("explanation"); const step = problemData.steps[stepIndex]; + // 1. Show explanation expEl.innerHTML = step.step; + expEl.style.opacity = 1; - // STEP 1: fade out - eqEl.classList.remove("fade-in"); - eqEl.classList.add("fade-out"); + // 2. Prepare next equation + nextEl.innerHTML = `\\(${formatMath(step.after)}\\)`; + nextEl.classList.remove("fade-in", "move-up"); + nextEl.style.opacity = 0; + + MathJax.typesetPromise([nextEl]); setTimeout(() => { - // STEP 2: update content - eqEl.innerHTML = `\\(${formatMath(step.after)}\\)`; - MathJax.typesetPromise(); + // 3. Fade in next equation (below) + nextEl.style.opacity = 1; + nextEl.classList.add("move-up"); - // STEP 3: force reflow (CRUCIAL) - void eqEl.offsetWidth; + // 4. Fade out old + explanation + mainEl.classList.add("fade-out"); + expEl.style.opacity = 0; - // STEP 4: fade back in - eqEl.classList.remove("fade-out"); - eqEl.classList.add("fade-in"); + setTimeout(() => { + // 5. Promote next → main + mainEl.innerHTML = nextEl.innerHTML; + MathJax.typesetPromise([mainEl]); - stepIndex++; + // reset styles + mainEl.classList.remove("fade-out"); + nextEl.style.opacity = 0; - setTimeout(showStep, 2000); - }, 500); + stepIndex++; + + setTimeout(showStep, 1500); + }, 500); + + }, 1000); // time to read explanation } function startSteps() { @@ -50,46 +77,6 @@ function startSteps() { showStep(); } -function startSequence() { - setTimeout(() => { - showNextStep(); - }, 3000); // thinking time -} - -function showNextStep() { - const stepsEl = document.getElementById("steps"); - - if (stepIndex >= problemData.steps.length) { - // finished → request next problem - setTimeout(() => { - requestNextProblem(); - }, 3000); - - return; - } - - const s = problemData.steps[stepIndex]; - const formatted_step_before = formatMath(s.before); - const formatted_step_after = formatMath(s.after); - - const html = ` -
-
-
\\(${formatted_step_before}\\)
-
${s.step}
-
\\(${formatted_step_after}\\)
-
- `; - - stepsEl.innerHTML = html; - - MathJax.typesetPromise(); - - stepIndex++; - - setTimeout(showNextStep, 2000); // time between steps -} - function formatMath(str) { return str .replace(/\*\*/g, "^") // Exponent Fix diff --git a/www/scripts/timer.js b/www/scripts/timer.js new file mode 100644 index 0000000..8d4a0cd --- /dev/null +++ b/www/scripts/timer.js @@ -0,0 +1,65 @@ +// Copyright John Salguero All rights Reserved +const radius = 25; +const circumference = 2 * Math.PI * radius; +const circle = document.getElementById("timer-progress"); + +const startColor = { r: 34, g: 197, b: 94 }; // green +const middleColor = { r: 234, g: 179, b: 8 }; // yellow +const endColor = { r: 239, g: 68, b: 68 }; // red + +circle.style.strokeDasharray = circumference; +circle.style.strokeDashoffset = 0; + +let timerInterval = null; + +function startTimer(duration) { + let timeLeft = duration; + const textEl = document.getElementById("timer-text"); + + // clear previous timer + if (timerInterval) { + clearInterval(timerInterval); + } + + timerInterval = setInterval(() => { + timeLeft--; + + // update number + textEl.textContent = timeLeft; + + // 🔥 recompute progress EVERY tick + const progress = (duration - timeLeft) / duration; + + // update circle + const offset = circumference * progress; + circle.style.strokeDashoffset = -offset; + + // color interpolation + if (progress < 0.5) { + circle.style.stroke = lerpColor(startColor, middleColor, progress * 2); + } else { + circle.style.stroke = lerpColor(middleColor, endColor, (progress - 0.5) * 2); + } + + // low-time pulse + if (timeLeft < 10) { + document.getElementById("timer").classList.add("low"); + } + + if (timeLeft <= 0) { + document.getElementById("timer").classList.remove("low"); + clearInterval(timerInterval); + // start animation sequence + startSteps(); + } + + }, 1000); +} + +function lerpColor(start, end, t) { + const r = Math.round(start.r + (end.r - start.r) * t); + const g = Math.round(start.g + (end.g - start.g) * t); + const b = Math.round(start.b + (end.b - start.b) * t); + + return `rgb(${r}, ${g}, ${b})`; +} \ No newline at end of file diff --git a/www/scripts/web_sockets.js b/www/scripts/web_sockets.js index 58b4f62..33e5fcc 100644 --- a/www/scripts/web_sockets.js +++ b/www/scripts/web_sockets.js @@ -1,3 +1,4 @@ +// Copyright John Salguero All rights Reserved //const socket = new WebSocket("ws://localhost:8000/ws"); const socket = new WebSocket("ws://10.0.0.50:8000/ws"); let problemData = null; @@ -13,15 +14,16 @@ socket.onopen = () => { socket.onmessage = (event) => { const msg = JSON.parse(event.data); + console.log("Received problem:", msg); if (msg.type === "generated_problem") { + problemData = msg.data; stepIndex = 0; showProblem(); - - // start animation sequence - startSteps(); + initial_duration = msg.time; + startTimer(msg.time); } }; diff --git a/www/style/styles.css b/www/style/styles.css index a64deee..70ab896 100644 --- a/www/style/styles.css +++ b/www/style/styles.css @@ -1,3 +1,4 @@ +/* Copyright John Salguero All rights Reserved */ html, body { margin: 0; height: 100vh; @@ -11,9 +12,46 @@ html, body { font-family: Arial; } +#timer { + position: absolute; + top: 20px; + right: 20px; + width: 60px; + height: 60px; + z-index: 10; +} + +#timer svg { + transform: rotate(-90deg); /* start from top */ +} + +#timer-bg { + fill: none; + stroke: rgba(255,255,255,0.2); + stroke-width: 8; +} + +#timer-progress { + fill: none; + stroke: #22c55e; /* green */ + stroke-width: 8; + stroke-linecap: round; + transition: stroke-dashoffset 1s linear; +} + +#timer-text { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 28px; + font-weight: bold; + color: white; +} + #problem { position: absolute; - top: 10%; + top: 15%; left: 50%; transform: translateX(-50%); @@ -21,18 +59,18 @@ html, body { text-align: center; } -#equation { +#equation-main, #equation-next { position: absolute; top: 30%; left: 50%; transform: translateX(-50%); - font-size: 32px; + font-size: 48px; + transition: all 0.5s ease; +} - font-family: "Comic Sans MS", cursive; /* surprisingly effective */ - text-shadow: - 0 0 2px rgba(255,255,255,0.8), - 0 0 6px rgba(255,255,255,0.4); - transition: opacity 0.5s ease, transform 0.5s ease; +#equation-next { + opacity: 0; + transform: translate(-50%, 40px); /* start slightly below */ } #explanation { @@ -42,7 +80,7 @@ html, body { transform: translateX(-50%); font-size: 24px; opacity: 0.8; -} 28px; +} #steps { position: absolute; @@ -73,6 +111,10 @@ html, body { z-index: 0; } +#timer.low { + animation: pulse 1s infinite; +} + .fade-out { opacity: 0; transform: translateX(-50%) scale(0.95); @@ -83,17 +125,14 @@ html, body { transform: translateX(-50%) scale(1); } -.chalk-in { - opacity: 0; - transform: scale(1.05); - filter: blur(6px); +.move-up { + transform: translate(-50%, 0); } -.chalk-in-active { - opacity: 1; - transform: scale(1); - filter: blur(0px); - transition: all 0.4s ease; +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } } @keyframes gradientShift {