- Scoreboard overlay driven by video.currentTime (scores/ticker/timeline as pure
functions of playback time; scrub-back removes points; slow-mo doesn't affect).
- Score bar + timeline + VS intro rendered verbatim from the design handoff
(drafts/score-bar.html, drafts/vs-screen.html) — only {{ }} data holes filled.
- Fixed 1150 px design canvas scaled to any player size via ResizeObserver so
the design's pixel-perfect measurements survive at every player width.
- Gear-menu injection adds a "Scoreboard" section with 7 toggle rows (Score bar,
Live scoring feed, Match info, Point timeline, Club logos, Country flags,
Penalties) using a custom 26x14 track/knob toggle matching the design.
Persists per user in localStorage['msb_prefs']; penalties defaults off.
- VS card fades in on first play of a session (skippable by click/tap/key,
2 s hold then 0.5 s fade); skipped entirely when no athlete metadata.
- SportsMatch::headerData() extended to expose sport, headshots, club logos,
division, format, match_date and per-video scoreboard defaults from existing
JSON columns (no schema migration).
- Every missing field hides its element (no "NOT SET" placeholders); athlete
name falls back to RED/BLUE per brief.
- Pushed .replay-badge down from top:2.5cqi to top:8cqi so it clears the
new KARATE KUMITE identity block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Handoff: Live Match Scoreboard Overlay (Karate / Taekwondo)
Paste this file to Claude Code as the brief. The bundled Match Points Overlay.dc.html is a design reference prototype written in HTML — not production code. Recreate it inside the existing video platform (React/Vue/whatever the player is built in), using the codebase's own component and styling patterns. Fidelity: high — colors, type, sizes below are final.
Goal
An overlay that sits on top of the video player and shows live match state for a karate kumite or taekwondo kyorugi bout. Every part must be independently switchable on and off (per-viewer preference, and per-broadcast defaults set by the uploader).
Structure
The overlay is a layer inside the player container:
<div class="player"> position: relative
<video> base layer
<div class="overlay"> position:absolute; inset:0; pointer-events:none
...parts... each part pointer-events:none except the toggle chip
</div>
</div>
Nothing about the video element changes. The overlay never intercepts clicks except on its own toggle control.
Switchable parts
Each is a boolean, defaulting to on, stored per user (localStorage or profile) and overridable per video:
| key | part | position in frame |
|---|---|---|
identity |
discipline label + match subtitle, with a 4px red-gradient rule at its left | top-left, 22px / 26px inset |
ticker |
"LIVE SCORING" header + last 4 judged calls | top-right, 20px / 24px inset, 262px wide |
scorebar |
the main bottom bar (both corner panels + center clock) | bottom, full width minus 26px, height 84px, 18px from bottom |
timeline |
thin segmented point-history strip | bottom, 5px tall, 8px from bottom |
clubs |
club logo slot + club name inside each corner panel | inside scorebar |
flags |
country flag chip next to each athlete name | inside scorebar |
penalties |
SENSHU chip (red) and C1 / GAM-JEOM chip (blue) | inside scorebar |
Also a master overlay toggle: a chip in the video box (bottom-right, SCOREBOARD ON/OFF, dark translucent, 1px white 18% border, red dot when on, grey when off) that hides everything at once. This chip is the only pointer-events:auto element. When the overlay is off, the chip drops from 112px above the bottom to 18px.
Where the platform has a player settings menu (like captions), expose the same booleans there.
Layout spec (design width 1150px, 16:9-ish frame)
Scrim over the video so text reads: bottom gradient rgba(6,6,8,.9) 0% → .45 at 15% → 0 at 30%, top gradient rgba(6,6,8,.85) 0% → .4 at 14% → 0 at 30%. A diagonal texture layer over the frame: repeating-linear-gradient(115deg, rgba(255,255,255,.5) 0 1px, transparent 1px 4px), opacity .16, mix-blend-mode: overlay.
Bottom bar (84px tall, flex row, no gaps between panels)
- Red panel —
flex:1 1 0; min-width:0; overflow:hidden; transform:skewX(-9deg), backgroundlinear-gradient(90deg, rgba(122,26,22,.94), rgba(180,52,44,.9)),border-bottom:3px solid #ff6a5e. Inner row un-skews once:transform:skewX(9deg); height:100%; padding:0 16px; display:flex; align-items:center; gap:12px. Applying the counter-skew to the row (not to each child) is what keeps the score numeral from overlapping the name. Children in order: club logo box (46×46,rgba(0,0,0,.32)fill, 1px white 24% border, monospace 8px "CLUB LOGO" placeholder) → text column (flex:1 1 auto; min-width:0) → score box (width:56px; flex:none; text-align:right). Text column row 1: flag chip 26×17 → athlete name (25px/800, uppercase,flex:1 1 auto; min-width:0, nowrap + ellipsis) → corner label "AKA" (11px, .24em tracking, 66% white). Text column row 2: club name (12px, .03em tracking, 85% white, uppercase, ellipsis) → SENSHU chip (10px/700, .18em, 1px 6px padding,#ffdf6bon#3a2a00). - Center clock —
width:118px; flex:none,rgba(9,9,11,.9)+backdrop-filter: blur(8px),border-bottom:3px solid #2c2a28. Stacked: round label (11px, .3em,#8f8a83), clockmm:ss(34px/800,#efe9e0, tabular-nums), three 20×3px round pips (#e8534afor completed,#3a3734for pending). - Blue panel — mirror of red: gradient
rgba(30,72,140,.9) → rgba(18,44,92,.94),border-bottom:3px solid #6aa6ff, order reversed (score, text column right-aligned, club logo), penalty chip#c8492fon white text. - Score numerals:
'Zen Old Mincho', serif, 46px, weight 700, white,text-shadow: 0 6px 20px rgba(0,0,0,.5).
Ticker (top-right)
Header row: 7px red dot pulsing (opacity 1 → .25 → 1, 1.6s infinite) + "LIVE SCORING" (12px, .3em, #d5cfc7, text-shadow for legibility). Each entry: rgba(10,10,12,.62) + blur(6px), 7px/10px padding, border-right: 3px solid <corner color>, animating in with translateY(10px) → 0, opacity 0 → 1, .45s ease. Entry content: timestamp (12px, #79736c) · call name (17px/700, .12em, uppercase, #efe9e0) · points (Zen Old Mincho 19px, corner color). Newest at top, keep 4.
Karate calls: Ippon +3, Waza-ari +2, Yuko +1, Chukoku/Keikoku/Hansoku penalties. Taekwondo calls: head kick +3, turning body +4, body kick +2, punch +1, gam-jeom (awards +1 to the opponent).
Timeline strip
14 equal segments, 5px tall, 2px gaps; red / blue for scored points in chronological order, #2a2724 for empty. Grows as the bout progresses.
Sport switch
One sport setting flips labels without changing layout:
| Karate | Taekwondo | |
|---|---|---|
| discipline label | KARATE KUMITE | TAEKWONDO KYORUGI |
| corners | AKA / AO | HONG / CHUNG |
| round label | KUMITE | ROUND n / 3 |
| penalty chip | C1 | GAM-JEOM n |
| point names | Ippon / Waza-ari / Yuko | head, turning, body, punch |
Data contract
Drive the overlay from the existing Points panel. Suggested shape:
type Corner = 'red' | 'blue';
interface Athlete { name: string; countryCode: string; flagUrl?: string; clubName: string; clubLogoUrl?: string; }
interface ScoreEvent { id: string; at: string; corner: Corner; label: string; points: number; }
interface MatchState {
sport: 'karate' | 'taekwondo';
title: string; subtitle: string; // "Finals", "Senior −75 kg · Tatami 1"
red: Athlete; blue: Athlete;
redScore: number; blueScore: number;
senshu: Corner | null;
penalties: { red: number; blue: number };
round: number; totalRounds: number;
clockSeconds: number; running: boolean;
events: ScoreEvent[];
}
Scores, clock and events come from the scorekeeper; the overlay is presentational. The clock counts down once per second while running. New events prepend to the ticker and push a segment onto the timeline.
Design tokens
Colors: ink #0a0a0c / panel rgba(9,9,11,.9) / bone #efe9e0 / muted #8f8a83 / brand red #e8534a / red edge #ff6a5e / red panel #7a1a16 → #b4342c / blue panel #1e488c → #122c5c / blue edge #6aa6ff / senshu #ffdf6b on #3a2a00 / penalty #c8492f.
Type: Barlow Condensed 400/600/700/800 for all UI; Zen Old Mincho 700 for score numerals and the discipline label.
Geometry: panel skew -9deg (content counter-skewed +9deg), bar height 84px, frame inset 26px, bottom inset 18px, gaps 12px, no border radius on overlay parts.
Accessibility / safety
- Keep every text span
min-width:0inside its flex column so names truncate instead of overflowing. - Minimum on-screen text size 12px at 1150px design width; scale the whole overlay proportionally with the player (
transform: scale()on a fixed 1150px layer, or clamp-based sizing) so it stays legible at small player sizes and in fullscreen. - Below ~700px player width, hide
identityandtickerautomatically and keep only the score bar. - Overlay is decorative for screen readers; expose the same data as text in the existing Points panel.
Files
Match Points Overlay.dc.html— the reference prototype (open in a browser; the SCOREBOARD chip demonstrates the master toggle).assets/mat.png— still frame used as the video stand-in. Not part of the deliverable.- Flags and club logos are mockup placeholders in the prototype; wire real images in.
Corrections after first implementation pass
The first build broke the score bar. Specific fixes:
- Keep the skew, but counter-skew ONCE. The panel gets
transform: skewX(-9deg); a single inner row getstransform: skewX(9deg); height:100%; display:flex; align-items:center. Do not apply the counter-skew to individual children, and do not drop the skew altogether — the parallelogram edges are the design. - The bar is a fixed-height 84px strip, vertically centered content. In the broken build the names sat on the very bottom edge and the panels were full-bleed to the frame. The bar is inset 26px left/right, sits above the player control bar, and its content is vertically centered — nothing touches the bar's top or bottom edge.
- Both scores must render. Red score right-aligned in a 56px
flex:nonebox at the panel's inner end; blue score left-aligned in the mirrored position. In the broken build the red score was missing and the blue "1" was floating at the top edge. - Never let panel content overflow. Text spans need
min-width:0andoverflow:hidden; text-overflow:ellipsis; fixed boxes (logo 46px, score 56px) areflex:none. - Empty data: when an athlete/club is unknown, render the corner as "—" or hide the row — do not print "NOT SET".
- Club logo and flag slots must be filled or hidden. If no image, hide the slot (toggle default off) rather than showing an empty box.
Controls belong in the player's gear menu
Remove any floating on-screen toggle chip. The scoreboard switches live in the existing player settings (gear) menu, as a "Scoreboard" section — same pattern as quality/captions:
- Gear button in the control bar opens a popup anchored bottom-right, above the control bar.
- Popup: dark translucent panel (
rgba(10,10,12,.94)+blur(10px), 1px white 16% border), 236px wide, a "SCOREBOARD" header (11px, .3em tracking,#8f8a83), then one switch row per part: Score bar, Live scoring feed, Match info, Point timeline, Club logos, Country flags, Penalties. - Rows: 14px label, uppercase, .14em tracking; switch is a 26×14 track (
#e8534aon,#3a3734off) with a 10px knob; hoverrgba(255,255,255,.07). - Persist each switch per user; the uploader's per-video defaults seed them.
- The popup closes on outside click / Esc, and never covers the score bar.
The updated prototype in this folder demonstrates the gear menu and all seven switches.
Target: video.takeone.bh video page (e.g. /videos/11vu7R)
Studied the live page. The overlay must be driven by what the platform already stores, and by playback time — not by a live clock.
What the platform already has
- Points panel: rounds (number, name, optional start time) and point events shaped like
@00:11 · Point (1 pt) · Blue|Red|Bothwith a running score printed asScore <blue> – <red>. - Coach review: time-ranged notes (
01:40 — 01:45, text, author) and reaction icons. - Player: Highlights badge, mini player, playback speed menu, ¼× ½× ¾× slow-motion buttons, scrub speeds 1× 5× 20× in the points editor.
- No fighter metadata at all — no athlete names, clubs, flags, weight class, or event name. This is exactly why the first implementation printed "NOT SET".
Required changes on the platform side
- Add match metadata to the video's sports settings (one form, next to Add Round): event name, division/category, weight class, bout number, tatami/ring, date, venue — and per corner: athlete name, country (ISO code + flag image), club name, club logo, plus optional headshot for the VS card.
- Any field left empty hides its element. Never render a placeholder string like "NOT SET". If no flag → hide the flag chip; no club logo → hide the logo slot; no athlete name → fall back to "RED" / "BLUE".
- Corners are Red and Blue in the existing data. Karate labels (AKA/AO) or taekwondo (HONG/CHUNG) are a display option, not new data.
- A "Both" point event awards to both corners — increment both scores and emit two ticker rows.
Binding to playback
The overlay is a pure function of video.currentTime:
- Scores = sum of point values for events with
at <= currentTime, per corner. - Round label = the round whose start time is the latest
<= currentTime. - Clock =
currentTimeformatted mm:ss (offset from the round start if round start times exist). It does not count down. - Ticker = the last 4 events with
at <= currentTime, newest on top; a new event animates in as playback crosses it. Scrubbing backwards removes them again. - Timeline strip = one segment per point event across the video duration, red/blue, positioned by timestamp — clicking a segment can seek to that point (nice-to-have; keep
pointer-eventsoff otherwise). - Slow-motion (¼× ½× ¾×) and playback speed must not affect the overlay: it reads
currentTime, never a timer.
Pre-match VS card
Rendered as a poster/intro layer at 16:9 over the player before playback starts (and available as an exportable share image at 1200×630 for the og-image). Content: event name, division line, both fighters' 3:4 photos with 104px circular club crests overlapping the inner corner, name, flag + country, club, the weight class under the VS mark, and a bout/tatami/date line. Background: two slow drifting red/blue radial glows plus a periodic light sweep — CSS animations only, no video.
Controls
Everything toggles from the gear menu in the existing control bar, under a "Scoreboard" section: Score bar, Live scoring feed, Match info, Point timeline, Club logos, Country flags, Penalties. Penalties defaults off because the current data model has no penalty events; enable it when penalties are added to the Points panel. The overlay layer is pointer-events:none so the scrubber and buttons stay clickable, and the score bar sits above the 48px control band (bottom ≈ 56px) so nothing crosses the progress bar.
The prototype in this folder uses the real point data from /videos/11vu7R (blue 1 @0:11, red 1 @0:11, red 2 @0:30, red 1 @0:51, blue 1 @1:52, blue 1 @2:39 → 4–3 red).
VS intro before playback
The VS card is part of the play experience, not a separate screen:
- On load / on pressing play, the player renders the VS card inside the video box at 16:9, covering the frame, while the video is buffering/paused at frame 0.
- It holds for 2 seconds, then fades out over ~0.5s and playback starts (
video.play()fires as the fade begins, so the first frames are already moving as the card clears). - The scoreboard overlay fades in after the card clears.
- The card is skippable: any click, key press, or tap on the frame ends it immediately and starts playback.
- Only on the first play of a session — a replay/scrub does not re-trigger it (the prototype's play button replays it for demo purposes).
- If match metadata is missing, skip the intro entirely rather than showing an empty card.