Skip to content

wcgan7/multi-timers

Repository files navigation

Milk Tea Timers

A PWA for managing multiple tea brewing timers with accurate countdowns, visual progress rings, and audio/visual alerts on completion.

Tea Presets

Six built-in tea timers, each with a unique accent color:

Tea Duration Color
Black Tea 3:00 Dark navy
Green Tea 2:00 Forest green
Oolong Tea 4:00 Saddle brown
Jasmine Tea 2:30 Sandy orange
Earl Grey 3:30 Muted purple
Matcha 1:30 Olive green

Timers can be customised at runtime via the UPDATE_SETTINGS action, which accepts partial updates to label, duration, and color.

Features

  • Multiple concurrent timers -- all six timers run independently; start, pause, and reset each one individually.
  • Web Worker timing -- a singleton Web Worker (setInterval at 250 ms) drives all active timers. The main thread computes wall-clock remaining time from an absolute endTimestamp, so countdowns survive tab switches and device sleep. Falls back to a main-thread setInterval when the Worker cannot be initialised.
  • SVG progress ring -- TimerRing renders a pure SVG ring with stroke-dashoffset-based progress arc. useTimerAnimation provides 60 fps requestAnimationFrame interpolation between Worker ticks for smooth visual drain.
  • Screen Wake Lock -- keeps the display on while any timer is running (browsers supporting the Wake Lock API). Re-acquires the lock on visibilitychange since browsers release it when the page is hidden.
  • Completion alerts
    • Audio: A three-tone ascending beep (660 Hz, 880 Hz, 1100 Hz) generated via the Web Audio API -- no external audio files. Plays automatically when a timer reaches zero; autoplay is permitted because the user has already interacted with the page (tapping Start).
    • Visual: The finished timer's ring pulses (scale) and glows (drop-shadow) in a repeating CSS animation to attract attention.
    • Background resume: If a timer expired while the app was suspended/backgrounded, the alert sound plays immediately when the app returns to the foreground and the finished state is detected. This also covers full page reloads -- timers reconciled to finished on load trigger the alert on first render.
  • localStorage persistence -- timer state is saved on every change and reconciled on reload so timers survive page refreshes. Running timers are reconciled against wall-clock time: if endTimestamp is in the past the timer is marked finished; if still in the future, remainingSeconds is recomputed.
  • Long-press gesture -- detected via pointer events with configurable threshold (default 500 ms) and movement tolerance (default 10 px). Cancels gracefully if the pointer moves too far, avoiding accidental triggers during scroll.
  • PWA support -- installable as a standalone app with offline caching via vite-plugin-pwa and Workbox generateSW. Precaches all static assets (JS, CSS, HTML, icons). The service worker auto-updates in the background. Includes a web app manifest with PNG icons (192 px, 512 px), an SVG favicon, and an Apple touch icon (180 px). The viewport disables user scaling for tablet kiosk use.

Interactions

Gesture Action
Click / tap Toggle start / pause
Double-click / double-tap Reset timer to full duration
Long-press (500 ms) Reserved for settings access

Layout

The app displays six tea timers in a responsive CSS Grid:

Breakpoint Columns Layout
Default (> 768 px) 3 3 x 2 grid
Tablet (<= 768 px) 2 2 x 3 grid
Mobile (<= 480 px) 1 1 x 6 stacked

Each timer cell maintains a square aspect ratio. The grid is horizontally centred with a maximum width of 960 px. The app uses a warm dark theme (background #2c1e12, text #f0e6d3, title accent #d4a574).

Architecture

Layer File(s) Purpose
Entry src/main.tsx Mounts React root, registers service worker via registerSW
App Shell src/App.tsx, src/App.module.css Root layout, global styles (warm dark theme), GlobalEffects component for wake lock and alert hooks
PWA vite.config.ts, index.html, public/ Manifest, Workbox precaching, icons (SVG + PNG), Apple meta tags
Presets src/data/presets.ts DEFAULT_PRESETS array defining the six tea timer configurations
Types src/types/timer.ts, src/types/worker.ts TimerConfig, TimerState, TimerAction, WorkerCommand, WorkerTick
State src/context/TimerContext.tsx React Context + useReducer, localStorage persistence, wall-clock reconciliation
Timing src/hooks/useTimer.ts, src/workers/timer.worker.ts Singleton Web Worker (250 ms tick), main-thread fallback, visibility recovery, reference-counted lifecycle
Animation src/hooks/useTimerAnimation.ts 60 fps rAF interpolation for smooth ring drain between Worker ticks
Ring src/components/TimerRing/ Pure presentational SVG ring with progress arc and finished-state pulse/glow animation
Grid src/components/TimerGrid/ Responsive CSS Grid container rendering a TimerCell per timer
Cell src/components/TimerCell/ Interactive timer cell: click toggles start/pause, double-click resets
Alerts src/hooks/useTimerAlert.ts, src/utils/alertSound.ts Edge-detected audio alert (Web Audio API) on timer finish, with background-resume and page-reload support
Wake Lock src/hooks/useWakeLock.ts Screen Wake Lock acquire/release tied to running timer count
Gestures src/hooks/useLongPress.ts Long-press detection via pointer events with movement tolerance
Formatting src/utils/formatTime.ts mm:ss display formatter

Tech Stack

Technology Version Role
React 19 UI framework
TypeScript 5.7 Type safety (strict mode, no any)
Vite 6 Dev server and build tooling
vite-plugin-pwa 0.21 PWA manifest and Workbox service worker
Vitest 2 Test runner (jsdom environment)
React Testing Library 16 Component test utilities
@testing-library/jest-dom 6 Custom DOM matchers

Development

npm install
npm run dev          # Start Vite dev server
npm run build        # Type-check (tsc) + production build
npm run preview      # Preview production build locally
npm run lint         # ESLint
npm test             # Run tests once (Vitest)
npm run test:watch   # Watch mode

Testing

Tests use Vitest with jsdom environment and React Testing Library. Configuration is in vite.config.ts under the test key. A global setup file (src/test-setup.ts) imports @testing-library/jest-dom/vitest to provide custom DOM matchers (e.g. toBeInTheDocument).

Test coverage

Area Test file What's tested
timerReducer src/context/TimerContext.test.tsx All action types (START, PAUSE, RESET, TICK, FINISH, UPDATE_SETTINGS), non-matching id pass-through, reconciliation, persistence
formatTime src/utils/formatTime.test.ts Edge cases: 0 s, 59 s, 60 s, large values, two-digit padding, negative input, typical tea durations
TimerCell src/components/TimerCell/TimerCell.test.tsx Rendering (label, color, testid, formatted time), all status variants, click to start, click to pause, double-click to reset
TimerGrid src/components/TimerGrid/TimerGrid.test.tsx Grid layout and timer rendering
TimerRing src/components/TimerRing/TimerRing.test.tsx SVG ring rendering and progress visualisation
useTimer src/hooks/useTimer.test.tsx Fake timers, start/pause/reset/toggle, timing accuracy, tick suppression, visibility recovery, Worker lifecycle
useTimerAlert src/hooks/useTimerAlert.test.tsx Audio/visual alert triggers
useTimerAnimation src/hooks/useTimerAnimation.test.ts rAF-based animation interpolation
useWakeLock src/hooks/useWakeLock.test.tsx Wake Lock API management
useLongPress src/hooks/useLongPress.test.ts Long-press gesture detection
alertSound src/utils/alertSound.test.ts Web Audio API sound generation
timer.worker src/workers/timer.worker.test.ts Worker message handling

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages