A real-time, multi-user counter app where users throw Joshua under a bus, deploy crossing guards, and call ambulances.
The main throw-under-the-bus mechanic and counter
The main counter shows how many times Joshua has been thrown under the bus.
The counter element is visible when the page first loads.
The counter displays a numeric value fetched from the server.
The displayed value matches the count returned by the API.
Clicking the Joshua doll throws him under the bus, incrementing the counter.
Clicking the doll sends a POST request to /api/increment.
The counter display updates to reflect the new count after the throw.
Sequential throws each increment the counter by one.
The counter value is persisted to disk so it survives server restarts.
Each increment writes the new value to counter.json.
If counter.json doesn't exist, it's created with count 0.
Starting the server does not reset an existing counter.
Write handles zero, negative, and large numbers correctly.
A bus SVG animates across the screen when Joshua is thrown.
The bus SVG drives across the screen when the user clicks to throw.
The bus animation finishes and the bus returns to its hidden position.
Crossing guard and ambulance mechanics
Crossing guards stop the bus from hitting Joshua, absorbing throws.
The crossing guard button is visible on the page.
Clicking the guard button sends a POST to /api/guard.
Active crossing guards are shown as icons.
The guard button animates when clicked.
Throwing with active guards consumes a guard instead of incrementing.
Guards decay by 1 every 10 seconds on the server.
The maximum number of active guards is 10.
The ambulance picks up Joshua, decrementing the counter.
The ambulance button is visible on the page.
Clicking the ambulance sends a POST to /api/ambulance.
The ambulance reduces the counter by 1, minimum 0.
The ambulance button animates when clicked.
The ambulance has a 3-second cooldown between uses.
A cooldown timer is shown near the ambulance button.
Multi-user synchronization and live updates
Multiple users see the same counter value in real time.
The client polls GET /api/counter periodically.
Display updates when another user changes the counter.
WebSocket provides instant updates without polling.
Shows how many users are currently active.
The current user count is shown on the page.
The user count updates with each API poll.
Server tracks active users by IP with a 15-second window.
GET /api/users returns users grouped by location.
User interface polish and interactions
Prevents rapid clicking from sending multiple requests.
The isUpdating lock prevents concurrent API calls.
Rapid clicking only results in one increment.
Large numbers are formatted with locale-specific separators.
The counter uses toLocaleString for thousands separators.
The clickable Joshua doll character.
The counter bumps/pulses when the value changes.
The .bump class is applied to the counter after increment.
Non-blocking toast messages for errors and feedback.
The toast element becomes visible when an error occurs.
Toast shows a helpful error message.
The toast automatically hides after 4 seconds.
Error toasts have the toast-error CSS class.
Graceful handling when the server is unreachable.
The counter displays "Error" when the API is unreachable.
An error message div is appended to the container.
The layout adapts to different screen sizes.
Layout adapts at the 600px breakpoint.
The Joshua doll is smaller on mobile viewports.
The app is accessible to screen readers and keyboard users.
The throw button has an aria-label attribute.
SVGs have role="img" and aria-label.
The toast has aria-live="polite".
Buttons have a visible focus ring on keyboard navigation.
The throw button is activatable via Enter or Space.
Backend API endpoints and server functionality
The server serves the main page at the root URL.
Returns the current counter state.
Returns 200 with application/json content type.
Response body includes count and guards fields.
The count field is a non-negative integer.
The count reflects previous increments.
Increments the counter (throws Joshua under the bus).
Returns 200 with application/json content type.
Response body includes count and guards fields.
Each call increments the count by exactly 1.
The increment is persisted to counter.json.
No bodyPart field is needed — single target.
Graceful handling of corrupt or missing counter files.
Returns count 0 when counter.json is missing.
Recreates the counter file when missing.
Returns count 0 for invalid JSON content.
Cross-Origin Resource Sharing headers.
Access-Control-Allow-Origin: * header on GET requests.
CORS headers present on POST requests.
Static assets served from the public directory.
API rate limiting to prevent abuse.
Rate limiter middleware is applied to all /api/* routes.
Returns 429 when the rate limit is exceeded.
Server-enforced cooldown for the ambulance action.
Server tracks ambulance cooldown per IP address.
Returns 429 when the ambulance is on cooldown.
First ambulance call succeeds, immediate second is rejected.
Counter state is written atomically to prevent corruption.
Security headers via helmet middleware.
Security headers middleware is applied to all responses.
Content Security Policy allows only same-origin and inline resources.
PWA, versioning, admin, and automation
The app is installable as a PWA.
Web app manifest is served at /manifest.json.
Manifest includes name, icons, start_url, display.
A service worker is registered on page load.
HTML includes theme-color and apple-touch-icon meta tags.
Version display and navigation between pages.
The version number is displayed on the page.
The version number links to the /releases/ page.
version.json provides the current version.
Users can submit and view feature requests.
Admin panel for counter management and moderation.
Admin authentication with login and logout.
Admin can reset and set the counter value.
Admin can disable/enable throw, guard, and ambulance.
Admin can approve or deny feature requests.
Automated testing and deployment pipeline.
CI/CD runs test, deploy, and verify jobs automatically.
Approved feature requests are automatically implemented by Claude Code.
A polling script detects approved feature requests.
Claude Code is launched headlessly to implement features.
A GitHub Action notifies the production server when done.