Every task now has a duration (10 min default) and a child-initiated start: a play icon on the kiosk, alongside the checkbox, begins a server-anchored countdown for that specific task. Deliberately not a beep-every-minute alarm -- audio is milestone-only (start/halfway/almost-done/time's-up), since frequent interrupting alerts can backfire for ADHD kids rather than help. Countdown is drift-free (recomputed each tick from the fixed started_at anchor, never decremented), audio is synthesized client-side via Web Audio (zero external assets, matching this app's existing hand-rolled-PNG-icon philosophy), and the child's own start tap is what unlocks audio playback for the rest of the session on iOS. Two real bugs caught and fixed during design, before they shipped: - Marking a task done now clears started_at (both the kiosk and parent routes) -- without this, unchecking a finished task later would resurrect a stale timer and instantly show "time's up" on a task the child hasn't touched today. - The parent editor's poll-pause guard only covered contenteditable fields; a plain number input for duration would have been silently unprotected from being clobbered by an incoming 4s poll mid-edit. Extended the guard to cover it, verified live that the DOM and focus both survive a poll while the field is focused. Schema: calendar_tasks gains duration_minutes (NOT NULL DEFAULT 10) and started_at (nullable). Verified against a simulated copy of the real production schema/data that the static DEFAULT backfills every existing row automatically (no business-logic backfill needed, unlike is_admin), and that repeated boots stay idempotent. Duplicate-calendar carries durations forward but always resets started_at, verified end-to-end. Kiosk countdown/milestone-firing verified live in-browser end-to-end: real timer start, halfway/almost-done/done firing at the correct proportional thresholds (not fixed minutes, so it scales from ~1min to 20+min tasks), and a mid-countdown page reload resuming at the correct remaining time with already-passed milestones backfilled silently rather than replayed.
99 lines
3.7 KiB
HTML
99 lines
3.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>My Week</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;700;800&family=Nunito:wght@500;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="/css/shared.css">
|
|
<meta name="theme-color" content="#F1F5FB">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<!--KIOSK_HEAD_TAGS-->
|
|
<style>
|
|
/* Kiosk is touch-first and has nothing to edit — bigger targets, no toolbar chrome. */
|
|
header{ justify-content: center; text-align: center; }
|
|
.toolbar{ display:none; }
|
|
.board{ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
|
|
.task-text{ font-size: 1.02rem; }
|
|
|
|
/* weekdayBoard always holds exactly 5 day-cards (Mon-Fri) — on a tablet
|
|
in landscape there's room for all 5 side by side, but the 240px
|
|
auto-fit minimum (sized for narrower/portrait views) was only letting
|
|
4 fit, pushing Friday to a second row. Force exactly 5 columns once
|
|
there's clearly tablet-landscape width to do it justice; narrower
|
|
views keep the auto-fit wrapping behavior above. 960px excludes large
|
|
phones in landscape (~926px max) while covering real tablets.
|
|
*/
|
|
@media (orientation: landscape) and (min-width: 960px) {
|
|
.board{ grid-template-columns: repeat(5, 1fr); }
|
|
}
|
|
|
|
/* Full-bleed standalone launch: respect notches/home-indicator, and stop
|
|
iOS's rubber-band overscroll from flashing white space at the edges. */
|
|
body{
|
|
padding-top: max(24px, env(safe-area-inset-top));
|
|
padding-right: max(16px, env(safe-area-inset-right));
|
|
padding-bottom: max(60px, env(safe-area-inset-bottom));
|
|
padding-left: max(16px, env(safe-area-inset-left));
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
/* 24px checkbox alone is well under Apple's 44pt touch-target minimum —
|
|
make the whole row tappable and give the checkbox itself more room. */
|
|
.task.kiosk{ cursor: pointer; padding: 8px 4px; flex-wrap: wrap; }
|
|
.task.kiosk input[type=checkbox]{ width: 32px; height: 32px; }
|
|
|
|
.timer-start{
|
|
border: none; background: none; cursor: pointer; flex: none;
|
|
font-size: 1.1rem; line-height: 1; color: var(--ink-soft); padding: 4px 8px;
|
|
}
|
|
.timer-start:hover{ color: var(--ink); }
|
|
|
|
/* flex-basis:100% on a flex-wrap row forces the timer bar onto its own
|
|
line below the checkbox/text/button, instead of squeezing in beside them. */
|
|
.task-timer{
|
|
flex-basis: 100%;
|
|
display: flex; align-items: center; gap: 8px;
|
|
margin: 6px 0 2px 40px;
|
|
}
|
|
.timer-track{
|
|
flex: 1; height: 6px; border-radius: 3px;
|
|
background: rgba(46,52,70,0.1); overflow: hidden;
|
|
}
|
|
.timer-fill{
|
|
height: 100%; width: 0%; background: var(--ink-soft); border-radius: 3px;
|
|
}
|
|
.timer-fill.milestone-almostDone{ background: var(--morning); }
|
|
.timer-fill.milestone-done{ background: var(--danger); }
|
|
.timer-label{
|
|
font-size: 0.78rem; font-weight: 800; color: var(--ink-soft);
|
|
white-space: nowrap; min-width: 42px; text-align: right;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div>
|
|
<h1 id="childName">…</h1>
|
|
<div class="subtitle" id="calTitle"></div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="board" id="weekdayBoard"></div>
|
|
<div class="weekend-toggle-wrap" id="weekendWrap"></div>
|
|
|
|
<div id="emptyState" class="empty-state" style="display:none; max-width:600px; margin:60px auto;">
|
|
No calendar has been set up yet. Ask a parent to create one.
|
|
</div>
|
|
|
|
<script src="/js/calendarRender.js"></script>
|
|
<script src="/js/chime.js"></script>
|
|
<script src="/js/kiosk.js"></script>
|
|
|
|
</body>
|
|
</html>
|