A motion-first focus web app — Pomodoro timer, reminders, focus tasks and streak tracking, built around a rain-on-glass porthole that clears as you work. One Python process, one SQLite file, no build step, no npm.
Grown from Youth Reminder App, the small Tkinter reminder tool that used to live in this repo. That version is still in the history (and
remainder_app_py), and its three quick presets — Study Break, Workout, Chill Time — are still here on the Reminders page.

![]() |
![]() |
| Timer — the pane fogs at the start and clears as the session elapses; a progress arc fills the rim. | Stats — today’s focus, day streak, totals, and an animated 7-day chart. |


:active. Light kraft “day” is the default; a warm “night rain” theme lives under .dark.
Reduced motion and reduced transparency are respected throughout.python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.txt # Windows
# (macOS/Linux: source .venv/bin/activate && pip install -r requirements.txt)
.venv/Scripts/python -m uvicorn app:app --reload
Open http://127.0.0.1:8000 — /styleguide shows the design system.
Optional: drop any looping rain recording at static/audio/rain.mp3 for the ambient
track. It’s gitignored (a ~24MB binary), and the app is missing-file-safe without it — the
switch simply plays nothing.
.venv/Scripts/python -m pytest -q # 46 tests: pure logic, the HTTP layer, and the CSS guard
python focus.py && python reminders.py # inline self-checks
tests/test_focus.py and tests/test_reminders.py cover the pure modules; tests/test_api.py
drives every route through TestClient against a temp SQLite file; tests/test_css.py guards
the theme against a silent CSS parse failure (see Known ceilings).
localStorage.Only completed focus sessions count toward streaks and totals — breaks and abandoned
sessions are stored but excluded from every stat (focus._focus).
Notification API).
Closed-tab firing needs a service worker + Web Push + a server scheduler.<select> option list is drawn by the OS and can’t be styled; it follows color-scheme,
which is why that is declared on :root and .dark.*/ inside a CSS comment silently deletes the rule that follows it — that once cost
the whole light theme. tests/test_css.py now fails if one reappears.app.py (routes + API) · db.py (SQLite) · focus.py / reminders.py (pure, tested logic) ·
templates/ · static/{css,js,vendor,audio} · tests/ · docs/ (README images).
Design tokens live in static/css/tokens.css (HSL custom properties plus the soft-UI and
rain/glass primitives); the shared motion vocabulary is static/js/motion.js.