A free, full-screen basketball shot clock that runs in any web browser β no download, no sign-up. Built as a single static HTML page and deployed via GitHub Pages.
π Live: proshotclock.com
- Two configurable reset presets β default 24 / 14 (NBA/FIBA), with one-tap presets for NCAA (30 / 20), high school (35) and FIBA 3x3 (12) in Settings
- Custom times from 0.1 to 60.0 seconds for drills
- External full-screen display (
display.html) for a second monitor or projector, kept in sync live via theBroadcastChannelAPI - End-of-clock buzzer (
siren.mp3) when the timer hits zero - Color states as time runs down β green while running, amber under 10s, red under 5s, switching to tenths of a second for the final stretch
- Recall to undo an accidental reset
- Blank display toggle for use between possessions
- Customizable keyboard shortcuts, saved in the browser
- Settings (presets, shortcuts, display options) persist via
localStorage - Collapsible donation sidebar (Buy Me a Coffee, PayPal, Satispay, Ethereum)
Defaults (all remappable in Settings):
| Key | Action |
|---|---|
Space |
Start / stop the clock |
β |
Reset to preset 1 (default 24) |
β |
Reset to preset 2 (default 14) |
β |
Blank the display |
Enter |
Focus the custom-time input |
The control page is the operator console; display.html is a separate
full-screen LED view for the audience.
- Click π₯ Open display on the main page β it opens in a new window.
- Drag it to your second monitor / projector and double-click it for full screen.
- Click once on the display to enable its buzzer (browsers block autoplay until a user gesture).
The console broadcasts its state on every tick over the BroadcastChannel API
(same browser, same origin), with localStorage providing the initial state when
a display window opens. You can open multiple display windows; each one syncs
independently. If you run the display on the same machine, enable Mute this
page's buzzer in Settings so the horn only sounds once.
Open β Settings on the main page to configure:
- Reset presets (Reset 1 / Reset 2), with quick presets for NBA/FIBA, NCAA, high school and FIBA 3x3 (12s)
- Display options β show tenths under 5s, FIBA blank-at-full, mute the console buzzer
- Keyboard shortcuts β click a field and press the key to remap it
All settings are saved to localStorage and persist across sessions (per
browser).
| Path | Description |
|---|---|
/ (index.html) |
Main shot clock (operator console) + below-the-fold rules guide and FAQ |
/?decimals=off |
Main clock with the final-seconds tenths turned off (whole seconds, rounded up) |
/display.html |
External full-screen LED display for a second screen (noindex) |
/pieni (pieni/index.html) |
Redirect to /?decimals=off β kept so old links don't 404 (noindex) |
index.html accepts a decimals query parameter (off/0/false to hide the
sub-5-second tenths). It applies for that visit only and isn't saved unless the
user saves it in Settings. /pieni β once a hand-maintained duplicate page β is
now just a redirect to that view.
The two pages are intentionally different in how the countdown renders and are kept that way; don't unify their display logic.
.
βββ index.html # Main app / operator console + SEO/guide content
βββ display.html # External full-screen LED display (synced via BroadcastChannel)
βββ pieni/
β βββ index.html # Empty/Full display variant
βββ siren.mp3 # End-of-clock buzzer
βββ robots.txt # Crawler rules (incl. AI/answer-engine bots)
βββ sitemap.xml # XML sitemap
βββ llms.txt # Summary for LLM/AI crawlers
βββ CNAME # Custom domain for GitHub Pages (proshotclock.com)
βββ README.md
It's a static site with no build step. Either open the file directly:
open index.htmlβ¦or serve the folder so that the /pieni route and siren.mp3 resolve cleanly:
python3 -m http.server 8000
# then visit http://localhost:8000Hosted on GitHub Pages from the main branch with the custom domain in
CNAME. Any push to main publishes automatically:
git add -A
git commit -m "Describe your change"
git push origin mainindex.html includes Open Graph / Twitter cards and JSON-LD structured data
(WebApplication + FAQPage). robots.txt, sitemap.xml, and llms.txt
support both traditional search engines and AI answer engines.
Three revenue hooks, all live in the static page (no backend):
Sponsor banner. Driven by sponsor.json. While active is false, the
guide shows an "advertise on ProShotClock" call-to-action; the external display
shows nothing. To run a sponsor, edit sponsor.json and push:
{
"active": true,
"name": "Acme Sports",
"imageUrl": "https://.../logo.png",
"linkUrl": "https://acme.example",
"until": "2026-12-31"
}The banner then appears at the top of the guide and as a strip on the
external display (seen by the whole gym). Sell a slot with a hosted checkout
(Stripe Payment Link / Gumroad β no backend), collect the logo, paste it here.
For fully automatic "pay β banner appears", add a Stripe webhook that rewrites
sponsor.json via a serverless function (e.g. a free Cloudflare Worker) or a
GitHub Action.
Affiliate gear. The "Recommended shot-clock gear" block in the guide uses
Amazon Associates. Replace every AMAZON_TAG-20 in index.html with your real
Associates tag to start earning.
Display ads (optional). #ad-guide is an empty slot in the guide, kept
clean and dormant. Drop an AdSense unit there to activate. At low traffic the
affiliate and sponsor hooks will out-earn ads, so this is best left off until
volume grows.
Β© NiccolΓ² Bevacqua. All rights reserved.