Added timer
This commit is contained in:
Binary file not shown.
6
main.py
6
main.py
@@ -59,13 +59,17 @@ 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:
|
||||||
log.warning(f"issue with problem: {problem}")
|
log.warning(f"issue with problem: {problem}")
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
stepIndex++;
|
// reset styles
|
||||||
|
mainEl.classList.remove("fade-out");
|
||||||
|
nextEl.style.opacity = 0;
|
||||||
|
|
||||||
setTimeout(showStep, 2000);
|
stepIndex++;
|
||||||
}, 500);
|
|
||||||
|
setTimeout(showStep, 1500);
|
||||||
|
}, 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
65
www/scripts/timer.js
Normal 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})`;
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user