flask_template_codex/app/static/js/main.js
2026-08-08 04:04:24 +02:00

137 lines
5.2 KiB
JavaScript

function playCompletionChime() {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) return;
const context = new AudioContext();
const oscillator = context.createOscillator();
const gain = context.createGain();
oscillator.type = "sine";
oscillator.frequency.setValueAtTime(523.25, context.currentTime);
oscillator.frequency.setValueAtTime(659.25, context.currentTime + 0.12);
gain.gain.setValueAtTime(0.0001, context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.12, context.currentTime + 0.02);
gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.35);
oscillator.connect(gain);
gain.connect(context.destination);
oscillator.start();
oscillator.stop(context.currentTime + 0.36);
oscillator.addEventListener("ended", () => context.close());
} catch (_error) {
// Audio feedback is optional; browser policy must never block completion.
}
}
function initializeTimer(timer) {
const duration = Number(timer.dataset.duration);
const taskId = timer.dataset.taskId;
const storageKey = `steady.timer.${taskId}`;
const display = timer.querySelector(".timer__display");
const progress = timer.querySelector(".timer__progress span");
const announcement = timer.querySelector(".timer__announcement");
let intervalId;
function readState() {
try {
const stored = JSON.parse(localStorage.getItem(storageKey));
if (stored && Number.isFinite(stored.remaining)) return stored;
} catch (_error) {
// A corrupt or unavailable local store simply starts a fresh timer.
}
return { remaining: duration, running: false, updatedAt: Date.now() };
}
let state = readState();
function currentRemaining() {
if (!state.running) return state.remaining;
const elapsed = Math.floor((Date.now() - state.updatedAt) / 1000);
return Math.max(0, state.remaining - elapsed);
}
function saveState() {
try {
localStorage.setItem(storageKey, JSON.stringify(state));
} catch (_error) {
// The countdown still works for this page when storage is unavailable.
}
}
function render() {
const remaining = currentRemaining();
const minutes = Math.floor(remaining / 60);
const seconds = remaining % 60;
display.textContent = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
progress.style.width = `${Math.max(0, Math.min(100, (remaining / duration) * 100))}%`;
if (remaining === 0 && state.running) {
state = { remaining: 0, running: false, updatedAt: Date.now() };
saveState();
clearInterval(intervalId);
timer.classList.add("timer--complete");
announcement.textContent = "Focus block complete. Take a breath.";
if (timer.dataset.chime === "true") playCompletionChime();
}
}
timer.querySelector("[data-timer-start]").addEventListener("click", () => {
const remaining = currentRemaining() || duration;
state = { remaining, running: true, updatedAt: Date.now() };
timer.classList.remove("timer--complete");
saveState();
clearInterval(intervalId);
intervalId = window.setInterval(render, 250);
render();
});
timer.querySelector("[data-timer-pause]").addEventListener("click", () => {
state = {
remaining: currentRemaining(),
running: false,
updatedAt: Date.now(),
};
saveState();
clearInterval(intervalId);
render();
});
timer.querySelector("[data-timer-reset]").addEventListener("click", () => {
state = { remaining: duration, running: false, updatedAt: Date.now() };
saveState();
clearInterval(intervalId);
timer.classList.remove("timer--complete");
announcement.textContent = "Timer reset.";
render();
});
if (state.running) intervalId = window.setInterval(render, 250);
render();
}
document.querySelectorAll("[data-focus-timer]").forEach(initializeTimer);
const celebration = document.querySelector('[data-celebrate="true"]');
if (celebration && celebration.dataset.chime === "true") {
playCompletionChime();
}
document.querySelectorAll("[data-reminder-capability]").forEach((panel) => {
const button = panel.querySelector("[data-check-reminders]");
const status = panel.querySelector("[data-reminder-status]");
if (!button || !status) return;
button.addEventListener("click", () => {
const supported =
"Notification" in window &&
"serviceWorker" in navigator &&
"PushManager" in window;
if (supported) {
status.textContent =
"This browser supports the foundations for reminders. " +
"Steady reminders remain disabled and no permission was requested.";
} else {
status.textContent =
"This browser does not currently expose all reminder capabilities. " +
"Your saved preference is unaffected.";
}
});
});