Timer app for children. Multi-theme, multi-language (fr/en/nl/de/es), PWA. Architecture: ES modules, Flux/Redux store, themes as self-contained folders.
js/
main.js — bootstrap (createStore, bindEvents, initFromURL)
store/ — reducer, actions, initialState, store (with sideEffects)
managers/ — SoundManager, LockManager, ParticleManager
themes/ — BarRenderer.js, DefaultBar.js, loader.js, themeCache.js
themes/{id}/ — bar.js, theme.json, locales.js (per theme)
ui/ — render.js, renderTimer.js, renderTheme.js, renderPresets.js,
renderSettings.js, renderLock.js, renderSequence.js,
DurationInput.js, bindEvents.js, initFromURL.js, i18n.js
locales/ui/ — en.js, fr.js, nl.js, de.js, es.js
css/main.css
minuteur.html
specs/ — authoritative specs (do not modify)
tests/ — vitest unit tests + playwright e2e
See .claude/plan.md for the full phased implementation plan.
Phases 1–8 complete. App fully functional. Phase 9 (canvas particles) — deferred. Phase 10 (tests) — partial (reducer + loader unit tests, basic e2e suite).
Working through all 15 themes visually, in order. For each: check bar rendering, idle/running/done animations, vehicle behaviour, edge cases.
Order: espace → princesse → pompier → train → ocean → disco → dino → foot → licorne → minecraft → patisserie → pates → oeufs → vapeur
espace — in progress. Fixes applied:
- Rocket stays at correct X position when paused (
--vlCSS custom property +rocketIdlekeyframes usetranslateX(var(--vl))) b-elapsedextended tofxlto close the dark gap behind the rocket- Rocket exhaust flame element (
.rocket-exhaust) added — visible when.running, hidden when.paused - Background shooting stars overlay (
#espace-bg-overlay) injected intodocument.body; cleaned up automatically byBarRenderer.setup()on theme switch
princesse and beyond — not started
SET_SUBMODEpayload carries resolved preset seconds (resolved inbindEvents.js, not reducer)__DEV__flag:location.hostname === 'localhost'— store logs actions in consolewindow.storeexposed on localhost for Playwright E2E testsservestrips query params from.htmlURLs → all URLs use/minuteur(no.html)- DurationInput:
drumon mobile (pointer:coarse),segmentedon desktop+segmented theme,smartelsewhere - Smart input: plain number = minutes; normalized display after validation ("90" → "1h 30m")
- ParticleManager
setBatteryModewired from store subscriber inmain.js
npm run dev # serve on :3000
npm test # vitest unit tests
npm run test:e2e # playwright e2e