A production-ready, responsive competition timer web application built for CrossFit events. Runs locally in any modern browser, scales beautifully from laptop to 4K TV, and works offline after the first load.
✅ Multiple Timer Modes:
- Stopwatch (count up with optional time cap)
- Countdown (count down to 00:00)
- Standard Intervals (work/rest cycles)
- Custom Sequence (build your own)
✅ Operator & Audience Display:
- Operator view with full controls on laptop
- Audience display on extended monitor/TV
- Cross-window synchronization via BroadcastChannel + localStorage fallback
- Automatic recovery after refresh
✅ Professional Features:
- Timestamp-based timer (no drift, works after tab throttling)
- Preparation countdown ("GET READY" state)
- Audio cues (countdown beeps, work/rest signals, completion horn)
- Customizable logo with position/size/opacity controls
- Keyboard shortcuts for hands-free operation
- 80% test coverage for critical functions
✅ Responsive Design:
- Works on 1366×768 (laptop), 1920×1080 (Full HD), 3840×2160 (4K)
- CSS clamp() for fluid scaling
- Safe margins for TV overscan
- Fullscreen mode for audience display
- Reduced motion support
✅ Persistence:
- Saves settings and presets to localStorage
- Recovers active workout after page refresh
- Export/import functionality for backup
- Node.js 18+ and npm/yarn
- Modern browser (Chrome, Firefox, Safari, Edge)
- For TV display: HDMI cable + extended display capability
cd "d:\Apo\Timer Fittest"
npm installIf you prefer yarn:
yarn installnpm run devThe app will open at http://localhost:3000 and auto-reload on changes.
npm run buildOutput is in dist/ folder. Deploy to any static hosting or run locally:
npx http-server dist- Select Timer Mode from the main screen
- Configure your workout:
- Set workout name (optional)
- Configure duration/intervals/sequence
- Choose preparation countdown (0-10 seconds or custom)
- Click Start to begin
While a workout is running, you can:
| Control | Action |
|---|---|
| Start/Pause/Resume | Toggle timer state |
| Reset | Return to mode selection (asks for confirmation) |
| Skip | Jump to next phase |
| Previous | Go back one phase |
| Mute/Unmute | Toggle all sounds |
| Fullscreen | Expand to fullscreen |
| Open Audience Display | Launch display for TV/projector |
| Key | Action |
|---|---|
| Space | Start / Pause / Resume |
| R | Reset (with confirmation) |
| F | Fullscreen |
| M | Mute / Unmute |
| → | Next Phase |
| ← | Previous Phase |
| Escape | Exit Fullscreen |
- Position the operator laptop on the side
- Connect extended monitor/TV via HDMI
- In the app, click "Open Audience Display"
- Move the new window to the TV/projector
- Press F in the audience window for fullscreen
- The display automatically syncs with operator controls
Build complex workouts with the Custom Sequence builder:
- Add unlimited Work and Rest phases
- Reorder phases with up/down buttons
- Duplicate phases instantly
- Calculate total workout duration
- Save as presets for future use
- Load previously saved presets
- Prepare your logo file (PNG, SVG, or WebP with transparency recommended)
- Place it in the project root directory
- Name it
FittestLogo.png(or update the path in code)
To change the logo file name:
- Edit
src/components/LogoOverlay.tsx - Update the
logoPathprop default value - Rebuild:
npm run build
In the Operator Controls panel:
- Show Logo: Toggle visibility
- Position: Top-left, top-right, bottom-left, or bottom-right
- Size: Adjust 50-300px
- Opacity: 0-100%
Customize sound effects:
- Master Volume: 0-100%
- Countdown Beep: Beep every second in final 10 seconds
- Work/Start Horn: Ascending tone at work phase start
- Rest Signal: Lower tone at rest phase start
- Completion Horn: High tone when workout finishes
Note: The browser may block autoplay. Click any button in the app to initialize audio.
When setting up a workout, select:
- Off: Skip preparation
- 3 seconds: GET READY for 3 seconds
- 5 seconds: GET READY for 5 seconds
- 10 seconds: GET READY for 10 seconds
- Custom: Set your own duration (in seconds)
During preparation, the app displays:
- 3
- 2
- 1
- GO (with distinctive sound)
Accepted time formats:
- 45 → 00:45
- 1:30 → 01:30
- 2:15:30 → 2:15:30
Run all tests:
npm testRun tests with UI:
npm run test:uiTest coverage includes:
- Timer calculations (count up, count down, phase transitions)
- Pause/resume accuracy
- Preparation countdown behavior
- Custom sequence progression
- Preset persistence and recovery
- Window synchronization
- TimerEngine: Timestamp-based calculations (no drift)
- useTimer: State management hook
- useAudio: Audio cue system
- windowSync: Multi-window synchronization
- persistence: localStorage layer
App (main entry point)
├── Mode Selection
├── Setup Screens
│ ├── StopwatchSetup
│ ├── CountdownSetup
│ ├── StandardIntervalSetup
│ └── CustomSequenceBuilder
├── Running State
│ ├── TimerDisplay (full screen)
│ ├── OperatorControls (sidebar)
│ ├── DisplaySettings
│ └── SoundSettings
└── AudienceDisplay (separate window)
IDLE
↓
CONFIGURING (user sets up workout)
↓
READY (ready to start)
↓
PREPARING (optional countdown)
↓
RUNNING_WORK / RUNNING_REST (active timer)
├→ PAUSED (user paused)
│ └→ RUNNING_WORK/REST (resume)
└→ FINISHED (time's up)
npm run build
vercel deploy distnpm run build
netlify deploy --prod --dir=distnpm run build
# Copy dist/ folder to your web server
# Serve as static files- Set browser zoom to 100% on the TV
- Enable fullscreen (F key in audience window)
- Use Safe Area margins - text stays 1rem inside edges (prevents overscan)
- Test font sizes at your venue's viewing distance
- Disable TV overscan in TV settings (if applicable)
- Use portrait or landscape depending on your setup
| Resolution | Aspect | Use Case |
|---|---|---|
| 1366×768 | 16:9 | Standard laptop |
| 1920×1080 | 16:9 | Full HD TV |
| 2560×1440 | 16:9 | High-end monitor |
| 3840×2160 | 16:9 | 4K TV |
- Click in the app window (required for browser autoplay policy)
- Check browser console for errors
- Verify speaker/headphone volume
- Try "Test Sounds" button in settings
- Check browser console for errors
- Refresh both windows
- Ensure both windows are on the same domain
- Try closing and reopening audience window
- Verify file exists at
public/FittestLogo.png - Check file format (PNG, SVG, WebP)
- Ensure "Show Logo" is enabled in Display Settings
- Verify browser console has no 404 errors
- This shouldn't happen (uses performance.now())
- Restart the browser if issues occur
- Check system clock (not app issue)
- Close unnecessary browser tabs
- Check DevTools Performance tab
- Reduce logo opacity if many visual effects lag
Press ? or view the Help panel in the app to see all shortcuts.
Planned for future releases:
- Preset sharing via URL
- Split-screen operator/audience on same display
- Multiple countdown sequences (e.g., "3...2...1...GO" with final horn)
- Custom horn sounds upload
- Leaderboard integration
- Real-time stream integration (OBS, Twitch)
| Browser | Support | Notes |
|---|---|---|
| Chrome 90+ | ✅ Full | Recommended |
| Firefox 88+ | ✅ Full | Excellent performance |
| Safari 14+ | ✅ Full | Some fullscreen limitations |
| Edge 90+ | ✅ Full | Chromium-based |
| Chrome Mobile | Operator use only |
- First Load: ~50KB gzipped
- Time to Interactive: <1s on 4G
- Core Web Vitals: All green
- Offline Support: Full PWA compliance
- Memory Usage: <50MB typical
MIT - Use freely for your event!
Found a bug? Have a feature request?
- Check existing issues in the project
- Test in latest Chrome before reporting
- Include browser version and error message
- Describe the exact steps to reproduce
Built with React, TypeScript, Vite, and Tailwind CSS.
Ready to time your next competition! 🏋️ ⏱️ 🔥