A minimalist Pomodoro study timer with built-in session tracking and a statistics dashboard. Built with vanilla JavaScript, HTML and CSS — zero dependencies, no build step. Open it in a browser and start studying.
The Pomodoro Technique breaks work into focused intervals (traditionally 25 minutes) separated by short breaks, with a longer break every few rounds. FocusFlow automates the cycle and quietly records every session so you can see how much you've actually studied.
- Full Pomodoro cycle — focus → short break → long break, with the long break triggered automatically after a configurable number of rounds.
- Accurate countdown — driven by absolute timestamps, so it stays correct even when the browser throttles a backgrounded tab.
- Circular progress ring — a smooth SVG ring that re-themes its colour for focus / short break / long break.
- Session tracking — every completed focus block is saved to
localStoragewith its task label, duration and timestamp. - Statistics dashboard — focus time today, sessions today, current day streak, all-time totals, and a hand-rolled 7-day SVG bar chart.
- Task labels — note what you're studying; it shows up in your history.
- Customisable — durations, long-break interval, auto-start, sound and desktop-notification toggles, all persisted.
- Completion feedback — a synthesised Web Audio chime (no audio file needed) plus optional desktop notifications.
- Keyboard shortcuts — Space start/pause, R reset, S skip.
- Accessible & responsive — semantic markup, ARIA roles, a skip link, reduced-motion support, and a layout that works down to phone widths.
Because the app uses ES modules, browsers won't load it directly from a
file:// path — you need to serve it over HTTP. Any static server works:
# Python 3 (already on most machines)
python3 -m http.server 8000
# …then open http://localhost:8000# or, if you have Node installed
npx serveThat's it — no install, no compilation.
.
├── index.html # Markup & view structure
├── css/
│ ├── variables.css # Design tokens (colour, spacing, motion)
│ └── style.css # Layout & components
├── js/
│ ├── app.js # Controller — wires DOM ↔ logic (the only DOM-heavy module)
│ ├── timer.js # Timestamp-based countdown engine
│ ├── storage.js # Safe localStorage wrapper (memory fallback)
│ ├── settings.js # Defaults + validation/normalisation
│ ├── stats.js # Pure analytics functions (streaks, daily totals…)
│ ├── chart.js # Dependency-free SVG bar chart
│ └── notifications.js # Web Audio chime + desktop notifications
└── assets/
└── favicon.svg
- Separation of concerns.
stats.jsis a set of pure functions, so the analytics can be reasoned about (and tested) without a DOM.app.jsis the only orchestration layer. - Resilient persistence.
storage.jsfeature-detectslocalStorageand falls back to an in-memory store in private-browsing mode, so the app never crashes on write. - Defensive settings. Stored settings are clamped to sane ranges on load, so a corrupted value can't break the timer.
- No drift. The timer derives remaining time from a target end-timestamp rather than counting down a variable, which is the standard fix for background-tab throttling.
- Building a small but complete app with a clear module architecture and no framework.
- DOM APIs, ES modules, the Web Audio API, the Notifications API,
and
localStorage. - Drawing data visualisations by hand with SVG.
- Writing accessible, responsive UI and thinking about edge cases (tab throttling, storage failure, bad input).
Ideas I might add later: exportable CSV of sessions, weekly/monthly views,
per-task time breakdowns, a light theme, and a small unit-test suite for
stats.js.
MIT © 2026 Anton