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 {