Basketball Scoreboard
A real-time basketball scoreboard plugin for WordPress — operator panel, live broadcast overlay, and OCR-assisted score reading from a camera feed. Built to run in a gym with no guarantees on the network.
Overview
I built this for basketball. KK Jesenice needed a scoreboard overlay that an operator could run from a WordPress admin panel and project live onto the gym wall or pipe into a streaming setup. The constraint was real: a basketball gym is not a server room. You get a laptop, a projector, and a Wi-Fi router that may or may not survive the match.
The plugin has two surfaces. The operator panel lives in the WordPress admin — it shows quarter, game clock, shot clock, scores, fouls, team logos, and league name. Every field updates every 500 milliseconds over AJAX, but only when something has actually changed; the diff-guard keeps the link quiet on a congested gym network. The overlay page polls the same endpoint and repaints the DOM. Both run on the same codebase; the script detects wp-admin body class to decide which direction to run.
The shot clock is not just a display — it counts down in-browser at 1 Hz, with 14 s and 24 s reset presets wired to single buttons for FIBA and NBA rules. The game clock works the same way. Fouls are tracked per team as five coloured dots: orange through four, red at five. At the resolution and distance of a gym display, colour reads faster than a number.
There is also an OCR mode. If the club already has a physical scoreboard, an operator can point a camera at it (or upload a photo), draw bounding rectangles around the score, clock, and quarter sections, and let Tesseract.js extract the values. Each region has independent preprocessing — threshold, contrast, grayscale toggle, noise removal — because a red LED segment and a white LCD digit need completely different treatment. Konva.js handles the canvas region interaction.
Architecture
Reading the diagram: Two input paths (manual panel and OCR camera) write into a shared state layer — WordPress transients served over AJAX with a 500 ms poll and a diff-guard so idle matches don’t chatter. The overlay page at /overlay/[slug] consumes that endpoint and repaints only on change; it works as an OBS browser source for streaming. In-browser clocks (game clock and per-team shot clocks) run client-side at 1 Hz so a network hiccup doesn’t freeze the display.
Scoreboard display
Overlay layout: quarter + game clock on the left, team names with foul dots, and large score numerals. Foul dots are colour-coded — orange through four, red at five — readable at projector distance without squinting at digits.
Six things shipped,
three hard ones solved.
Key contributions
- Built the entire WordPress plugin from scratch — custom post type, admin operator UI, AJAX state layer, and public overlay renderer.
- Implemented dual input modes: a manual scoreboard panel with ±1 / ±2 / ±3 score buttons and a live OCR path via camera or uploaded image.
- OCR pipeline uses Tesseract.js on a Canvas 2× up-scaled region with per-region threshold, contrast, and grayscale preprocessing controls.
- State is persisted via WordPress transients and polled every 500 ms — update is only written when data actually changes, keeping the network quiet.
- Game clock counts down in-browser; shot clock supports 14 s and 24 s reset presets matching FIBA/NBA rules.
- Foul dots render in orange up to 4, turn red at the 5th — visible at broadcast distance without reading numbers.
- Overlay exposed at
/overlay/[slug]via custom rewrite rules; embeddable as a shortcode for OBS browser source.
Challenges solved
- Gym Wi-Fi is unreliable — AJAX polling with a change-hash guard keeps the overlay responsive without hammering the server on a slow link.
- Scoreboard displays at camera distance; foul count has to read as colour, not digits — designed the five-dot system accordingly.
- OCR from a physical scoreboard camera needs preprocessing per-region (different brightness zones, LED vs. LCD digits) — tunable sliders per marked region rather than one global threshold.
The constraint wasn't the code — it was the environment. A gym has a projector, a laptop, and Wi-Fi that might work. The system had to be solid enough that none of that mattered.
What's under the hood.
Ready to fix, build,
or scale?
30 minutes, with me personally. I'll read your system like a log file and tell you what I'd do first. No pitch deck, no sales funnel.
— Davor Majc, founder, Numen