Added timer

This commit is contained in:
2026-05-04 15:32:19 -04:00
parent fbe46b8215
commit 93f0fa7c09
8 changed files with 203 additions and 83 deletions

Binary file not shown.

View File

@@ -59,6 +59,7 @@ def generate_problem_message():
while not problem_solved: while not problem_solved:
problem["type"] = "generated_problem" problem["type"] = "generated_problem"
problem["data"] = generate_problem() problem["data"] = generate_problem()
problem["time"] = get_time_for_problem(problem["data"])
problem["data"]["steps"] = generate_steps(problem["data"]); problem["data"]["steps"] = generate_steps(problem["data"]);
problem_solved = check_solution(problem["data"]["steps"][-1]["after"], problem["data"]["solution"]) problem_solved = check_solution(problem["data"]["steps"][-1]["after"], problem["data"]["solution"])
if not problem_solved: if not problem_solved:
@@ -66,6 +67,9 @@ def generate_problem_message():
return problem return problem
def get_time_for_problem(problem):
return 30
def main(): def main():
problem_solved = False problem_solved = False
while not problem_solved: while not problem_solved:

View File

@@ -1,3 +1,4 @@
<!-- Copyright John Salguero All rights Reserved -->
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@@ -9,10 +10,31 @@
<canvas id="waveCanvas"></canvas> <canvas id="waveCanvas"></canvas>
<canvas id="particleCanvas"></canvas> <canvas id="particleCanvas"></canvas>
<div id="timer">
<svg width="60" height="60">
<circle
id="timer-bg"
cx="30"
cy="30"
r="25"
/>
<circle
id="timer-progress"
cx="30"
cy="30"
r="25"
/>
</svg>
<div id="timer-text"></div>
</div>
<div id="problem"></div> <div id="problem"></div>
<div id="equation"></div> <div id="equation-main"></div>
<div id="equation-next"></div>
<div id="explanation"></div> <div id="explanation"></div>
<script src="scripts/timer.js"></script>
<script src="scripts/background.js"></script> <script src="scripts/background.js"></script>
<script src="scripts/problem.js"></script> <script src="scripts/problem.js"></script>
<script src="scripts/web_sockets.js"></script> <script src="scripts/web_sockets.js"></script>

View File

@@ -1,3 +1,4 @@
// Copyright John Salguero All rights Reserved
const canvas = document.getElementById("waveCanvas"); const canvas = document.getElementById("waveCanvas");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");

View File

@@ -1,13 +1,26 @@
// Copyright John Salguero All rights Reserved
function showProblem() { function showProblem() {
const problemEl = document.getElementById("problem"); 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); 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() { function showStep() {
@@ -16,33 +29,47 @@ function showStep() {
return; return;
} }
const eqEl = document.getElementById("equation"); const mainEl = document.getElementById("equation-main");
const nextEl = document.getElementById("equation-next");
const expEl = document.getElementById("explanation"); const expEl = document.getElementById("explanation");
const step = problemData.steps[stepIndex]; const step = problemData.steps[stepIndex];
// 1. Show explanation
expEl.innerHTML = step.step; expEl.innerHTML = step.step;
expEl.style.opacity = 1;
// STEP 1: fade out // 2. Prepare next equation
eqEl.classList.remove("fade-in"); nextEl.innerHTML = `\\(${formatMath(step.after)}\\)`;
eqEl.classList.add("fade-out"); nextEl.classList.remove("fade-in", "move-up");
nextEl.style.opacity = 0;
MathJax.typesetPromise([nextEl]);
setTimeout(() => { setTimeout(() => {
// STEP 2: update content // 3. Fade in next equation (below)
eqEl.innerHTML = `\\(${formatMath(step.after)}\\)`; nextEl.style.opacity = 1;
MathJax.typesetPromise(); nextEl.classList.add("move-up");
// STEP 3: force reflow (CRUCIAL) // 4. Fade out old + explanation
void eqEl.offsetWidth; mainEl.classList.add("fade-out");
expEl.style.opacity = 0;
// STEP 4: fade back in setTimeout(() => {
eqEl.classList.remove("fade-out"); // 5. Promote next → main
eqEl.classList.add("fade-in"); mainEl.innerHTML = nextEl.innerHTML;
MathJax.typesetPromise([mainEl]);
// reset styles
mainEl.classList.remove("fade-out");
nextEl.style.opacity = 0;
stepIndex++; stepIndex++;
setTimeout(showStep, 2000); setTimeout(showStep, 1500);
}, 500); }, 500);
}, 1000); // time to read explanation
} }
function startSteps() { function startSteps() {
@@ -50,46 +77,6 @@ function startSteps() {
showStep(); 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 = `
<hr/>
<div class="step">
<div>\\(${formatted_step_before}\\)</div>
<div>${s.step}</div>
<div>\\(${formatted_step_after}\\)</div>
</div>
`;
stepsEl.innerHTML = html;
MathJax.typesetPromise();
stepIndex++;
setTimeout(showNextStep, 2000); // time between steps
}
function formatMath(str) { function formatMath(str) {
return str return str
.replace(/\*\*/g, "^") // Exponent Fix .replace(/\*\*/g, "^") // Exponent Fix

65
www/scripts/timer.js Normal file
View File

@@ -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})`;
}

View File

@@ -1,3 +1,4 @@
// Copyright John Salguero All rights Reserved
//const socket = new WebSocket("ws://localhost:8000/ws"); //const socket = new WebSocket("ws://localhost:8000/ws");
const socket = new WebSocket("ws://10.0.0.50:8000/ws"); const socket = new WebSocket("ws://10.0.0.50:8000/ws");
let problemData = null; let problemData = null;
@@ -13,15 +14,16 @@ socket.onopen = () => {
socket.onmessage = (event) => { socket.onmessage = (event) => {
const msg = JSON.parse(event.data); const msg = JSON.parse(event.data);
console.log("Received problem:", msg);
if (msg.type === "generated_problem") { if (msg.type === "generated_problem") {
problemData = msg.data; problemData = msg.data;
stepIndex = 0; stepIndex = 0;
showProblem(); showProblem();
initial_duration = msg.time;
// start animation sequence startTimer(msg.time);
startSteps();
} }
}; };

View File

@@ -1,3 +1,4 @@
/* Copyright John Salguero All rights Reserved */
html, body { html, body {
margin: 0; margin: 0;
height: 100vh; height: 100vh;
@@ -11,9 +12,46 @@ html, body {
font-family: Arial; 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 { #problem {
position: absolute; position: absolute;
top: 10%; top: 15%;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
@@ -21,18 +59,18 @@ html, body {
text-align: center; text-align: center;
} }
#equation { #equation-main, #equation-next {
position: absolute; position: absolute;
top: 30%; top: 30%;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
font-size: 32px; font-size: 48px;
transition: all 0.5s ease;
}
font-family: "Comic Sans MS", cursive; /* surprisingly effective */ #equation-next {
text-shadow: opacity: 0;
0 0 2px rgba(255,255,255,0.8), transform: translate(-50%, 40px); /* start slightly below */
0 0 6px rgba(255,255,255,0.4);
transition: opacity 0.5s ease, transform 0.5s ease;
} }
#explanation { #explanation {
@@ -42,7 +80,7 @@ html, body {
transform: translateX(-50%); transform: translateX(-50%);
font-size: 24px; font-size: 24px;
opacity: 0.8; opacity: 0.8;
} 28px; }
#steps { #steps {
position: absolute; position: absolute;
@@ -73,6 +111,10 @@ html, body {
z-index: 0; z-index: 0;
} }
#timer.low {
animation: pulse 1s infinite;
}
.fade-out { .fade-out {
opacity: 0; opacity: 0;
transform: translateX(-50%) scale(0.95); transform: translateX(-50%) scale(0.95);
@@ -83,17 +125,14 @@ html, body {
transform: translateX(-50%) scale(1); transform: translateX(-50%) scale(1);
} }
.chalk-in { .move-up {
opacity: 0; transform: translate(-50%, 0);
transform: scale(1.05);
filter: blur(6px);
} }
.chalk-in-active { @keyframes pulse {
opacity: 1; 0% { transform: scale(1); }
transform: scale(1); 50% { transform: scale(1.1); }
filter: blur(0px); 100% { transform: scale(1); }
transition: all 0.4s ease;
} }
@keyframes gradientShift { @keyframes gradientShift {