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. |
- Backend: FastAPI + Jinja2 (server-rendered pages plus a small JSON API), SQLite. Single-user, no login.
- Frontend: Tailwind (standalone, vendored) + anime.js v4 (vendored). No bundler, no npm.
- Design: "rainy paper" — warm kraft surfaces, espresso ink, a muted rain slate-blue accent.
Controls are embossed from the sheet (soft-UI / neumorphism) rather than boxed on top of it:
no hard borders, a highlight up-left and a warm shade down-right, and buttons that press in
on
: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 --reloadOpen 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-checkstests/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).
- Timer — Focus/Break/Deep presets plus custom minutes via a ± stepper. The countdown runs on a circular rain-on-glass porthole: condensation fogs the pane at the start and clears as the session elapses, rain streaks run down the glass, a progress arc fills the rim, and a ripple blooms on completion. Sessions are logged to the DB; a browser notification fires on finish.
- Ambient rain — a header toggle switch. Start turns it on with the session; Pause
and Reset turn it back off. Switching mode or finishing a session leaves it playing.
State persists in
localStorage. - Reminders — custom (message + h/m) plus the original app's quick presets, once/daily/weekly recurrence, snooze; notifications fire while a tab is open.
- Tasks — a focus list; pick a task on the timer to log time against it.
- Stats — today's focus, day streak, totals, and an animated 7-day bar chart.
Only completed focus sessions count toward streaks and totals — breaks and abandoned
sessions are stored but excluded from every stat (focus._focus).
- Notifications fire only while a Focus tab is open (in-tab poll +
NotificationAPI). Closed-tab firing needs a service worker + Web Push + a server scheduler. - The countdown runs on an interval, so it keeps going in a background tab — but browsers throttle hidden-tab timers to roughly once a minute, so a completion notification can arrive up to a minute late if you never return to the tab. The clock itself is wall-clock derived and always correct on return.
- The
<select>option list is drawn by the OS and can't be styled; it followscolor-scheme, which is why that is declared on:rootand.dark. - A stray
*/inside a CSS comment silently deletes the rule that follows it — that once cost the whole light theme.tests/test_css.pynow fails if one reappears. - Tailwind standalone (no purge/build); fonts via Google Fonts CDN — self-host if offline matters.
- Single-user by design: no auth, one SQLite file, no multi-device sync.
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.




