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.
Pripravljeni popraviti, zgraditi
ali skalirati?
30 minut, z mano osebno. Preberem vaš sistem kot dnevniško datoteko in povem, kaj bi naredil najprej. Brez prezentacij, brez prodajnega lijaka.
— Davor Majc, ustanovitelj, Numen