Video player: lock frame to strict 16:9 at all times

Remove max-height clamps and override portrait/square/ultrawide/theater
variants so the .ytp-wrap box shape never distorts. Portrait content
letterboxes inside the 16:9 frame via object-fit: contain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ghassan 2026-08-10 13:39:15 +03:00
parent b05ff14b3b
commit 5b6bd86d70
10 changed files with 412 additions and 758 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because one or more lines are too long

View File

@ -1,349 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;600;700;800&family=Zen+Old+Mincho:wght@400;700&display=swap" rel="stylesheet">
<style>
body { margin:0; background:#0a0a0c; }
a { color:#e8534a; } a:hover { color:#ff7d70; }
@keyframes riseIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
@keyframes driftA { 0%,100% { transform: translate3d(0,0,0) scale(1); } 50% { transform: translate3d(60px,-28px,0) scale(1.14); } }
@keyframes driftB { 0%,100% { transform: translate3d(0,0,0) scale(1.08); } 50% { transform: translate3d(-54px,26px,0) scale(1); } }
@keyframes sweep { 0% { transform: translateX(-60%) skewX(-14deg); opacity:0; } 25% { opacity:.5; } 60% { opacity:0; } 100% { transform: translateX(160%) skewX(-14deg); opacity:0; } }
@keyframes pulseDot { 0%,100% { opacity:1; } 50% { opacity:.25; } }
</style>
</helmet>
<div style="min-height:100vh;background:#0a0a0c;padding:40px;display:flex;flex-direction:column;align-items:flex-start;gap:22px;overflow-x:auto;font-family:'Barlow Condensed',sans-serif">
<div style="position:relative;width:1150px;flex:none;aspect-ratio:16/9;overflow:hidden;background:#08080a;box-shadow:0 30px 80px rgba(0,0,0,.6)">
<div style="position:absolute;inset:-12%;background:radial-gradient(closest-side, rgba(168,42,34,.62), rgba(8,8,10,0) 72%);width:70%;left:-6%;animation:driftA 19s ease-in-out infinite"></div>
<div style="position:absolute;inset:-12%;background:radial-gradient(closest-side, rgba(28,64,136,.62), rgba(8,8,10,0) 72%);width:70%;left:36%;animation:driftB 23s ease-in-out infinite"></div>
<div style="position:absolute;top:0;bottom:0;left:0;width:26%;background:linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.09), rgba(255,255,255,0));animation:sweep 11s linear infinite"></div>
<div style="position:absolute;inset:0;opacity:.12;mix-blend-mode:overlay;background:repeating-linear-gradient(115deg, rgba(255,255,255,.6) 0 1px, transparent 1px 5px)"></div>
<div style="position:absolute;top:0;bottom:0;left:0;width:54%;clip-path:polygon(0 0, 100% 0, 78% 100%, 0 100%);background:linear-gradient(120deg, rgba(122,26,22,.5), rgba(8,8,10,0) 76%)"></div>
<div style="position:absolute;top:0;bottom:0;right:0;width:54%;clip-path:polygon(22% 0, 100% 0, 100% 100%, 0 100%);background:linear-gradient(300deg, rgba(24,52,110,.5), rgba(8,8,10,0) 76%)"></div>
<div style="position:absolute;top:34px;left:0;right:0;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-family:'Zen Old Mincho',serif;font-size:24px;letter-spacing:.44em;color:#efe9e0;text-transform:uppercase;text-indent:.44em">Takeone Karate Series</div>
<div style="display:flex;align-items:center;gap:14px;font-size:12px;letter-spacing:.34em;color:#8f8a83;text-transform:uppercase">
<span>Ladies</span><span style="width:4px;height:4px;background:#e8534a"></span><span>Classification</span><span style="width:4px;height:4px;background:#6aa6ff"></span><span>Round 1</span>
</div>
</div>
<div style="position:absolute;inset:96px 0 54px;display:grid;grid-template-columns:1fr 200px 1fr;align-items:start">
<div style="display:flex;flex-direction:column;align-items:center;padding:34px 24px 0;min-width:0">
<div style="position:relative;width:228px;height:304px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:12px;font-family:ui-monospace,monospace;font-size:10px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-26px;right:-26px;width:104px;height:104px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);box-shadow:0 10px 30px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.06em;color:rgba(255,255,255,.7);text-align:center;line-height:1.35">CLUB<br>LOGO</div>
</div>
<div style="margin-top:34px;padding-top:12px;border-top:3px solid #e8534a;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-size:42px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">Fawzia Ahmed</div>
<div style="display:flex;align-items:center;gap:12px">
<span style="width:40px;height:27px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.32);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;color:rgba(255,255,255,.85)">FLAG</span>
<span style="font-size:14px;letter-spacing:.3em;color:#d3b3ad;text-transform:uppercase">Bahrain</span>
</div>
<div style="font-size:13px;letter-spacing:.18em;color:#9d9691;text-transform:uppercase">Manama Karate Club</div>
</div>
</div>
<div style="display:flex;flex-direction:column;align-items:center;gap:12px;padding-top:96px">
<span style="font-family:'Zen Old Mincho',serif;font-size:88px;line-height:.8;font-weight:700;color:#efe9e0;text-shadow:0 8px 34px rgba(0,0,0,.75)">VS</span>
<span style="padding:7px 16px;border:1px solid rgba(255,255,255,.3);font-size:14px;letter-spacing:.32em;color:#efe9e0;text-transform:uppercase">61 kg</span>
<span style="font-size:11px;letter-spacing:.34em;color:#8f8a83;text-transform:uppercase">Ladies · 3 min</span>
</div>
<div style="display:flex;flex-direction:column;align-items:center;padding:34px 24px 0;min-width:0">
<div style="position:relative;width:228px;height:304px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:12px;font-family:ui-monospace,monospace;font-size:10px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-26px;left:-26px;width:104px;height:104px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);box-shadow:0 10px 30px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.06em;color:rgba(255,255,255,.7);text-align:center;line-height:1.35">CLUB<br>LOGO</div>
</div>
<div style="margin-top:34px;padding-top:12px;border-top:3px solid #6aa6ff;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-size:42px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">Noor Salman</div>
<div style="display:flex;align-items:center;gap:12px">
<span style="font-size:14px;letter-spacing:.3em;color:#a8b6cf;text-transform:uppercase">Bahrain</span>
<span style="width:40px;height:27px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.32);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;color:rgba(255,255,255,.85)">FLAG</span>
</div>
<div style="font-size:13px;letter-spacing:.18em;color:#9d9691;text-transform:uppercase">Riffa Martial Arts</div>
</div>
</div>
</div>
<div style="position:absolute;bottom:26px;left:0;right:0;display:flex;align-items:center;justify-content:center;gap:16px;font-size:11px;letter-spacing:.32em;color:#8f8a83;text-transform:uppercase">
<span>Bout 12</span><span style="width:4px;height:4px;background:#e8534a"></span><span>Tatami 1</span><span style="width:4px;height:4px;background:#6aa6ff"></span><span>Aug 08, 2026 · Manama</span>
</div>
</div>
<div style="position:relative;width:1150px;flex:none;aspect-ratio:16/9;overflow:hidden;border-radius:10px;background:#111;box-shadow:0 30px 80px rgba(0,0,0,.6)">
<img src="assets/mat.png" alt="" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover">
<div style="position:absolute;inset:0;background:linear-gradient(to top, rgba(6,6,8,.9) 0%, rgba(6,6,8,.45) 15%, rgba(6,6,8,0) 30%), linear-gradient(to bottom, rgba(6,6,8,.85) 0%, rgba(6,6,8,.4) 14%, rgba(6,6,8,0) 30%)"></div>
<div style="display:{{ overlayDisplay }}">
<!-- top left: match identity -->
<div style="position:absolute;top:22px;left:26px;pointer-events:none;display:{{ dIdentity }};align-items:stretch;gap:12px">
<div style="width:4px;background:linear-gradient(#e8534a,#7c1d18)"></div>
<div style="display:flex;flex-direction:column;gap:2px">
<div style="font-family:'Zen Old Mincho',serif;font-size:15px;letter-spacing:.34em;color:#efe9e0;text-transform:uppercase">{{ disciplineLabel }}</div>
<div style="font-size:13px;letter-spacing:.28em;color:#cfc9c1;text-transform:uppercase;text-shadow:0 1px 6px rgba(0,0,0,.9)">Ladies Classification · 61 kg · Round 1</div>
</div>
</div>
<!-- top right: live point ticker -->
<div style="position:absolute;top:20px;right:24px;width:262px;pointer-events:none;display:{{ dTicker }};flex-direction:column;gap:7px">
<div style="display:flex;align-items:center;justify-content:flex-end;gap:8px;font-size:12px;letter-spacing:.3em;color:#d5cfc7;text-transform:uppercase;text-shadow:0 1px 6px rgba(0,0,0,.9)">
<span style="width:7px;height:7px;border-radius:50%;background:#e8534a;animation:pulseDot 1.6s infinite"></span>Live scoring
</div>
<sc-for list="{{ feed }}" as="ev" hint-placeholder-count="4">
<div style="display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:7px 10px;background:rgba(10,10,12,.62);backdrop-filter:blur(6px);border-right:3px solid transparent;animation:riseIn .45s ease both;border-color:{{ ev.color }}">
<span style="font-size:12px;letter-spacing:.16em;color:#79736c">{{ ev.time }}</span>
<span style="font-size:17px;letter-spacing:.12em;font-weight:700;color:#efe9e0;text-transform:uppercase">{{ ev.name }}</span>
<span style="font-family:'Zen Old Mincho',serif;font-size:19px;font-weight:700;color:{{ ev.color }}">{{ ev.pts }}</span>
</div>
</sc-for>
</div>
<!-- bottom scoreboard -->
<div style="position:absolute;left:0;right:0;bottom:56px;padding:0 26px;display:{{ dBar }};align-items:stretch;gap:0;height:84px;z-index:2;pointer-events:none">
<div style="flex:1 1 0;min-width:0;transform:skewX(-9deg);overflow:hidden;background:linear-gradient(90deg, rgba(122,26,22,.94), rgba(180,52,44,.9));border-bottom:3px solid #ff6a5e">
<div style="transform:skewX(9deg);height:100%;padding:0 16px;display:flex;align-items:center;gap:12px">
<div style="width:46px;height:46px;flex:none;background:rgba(0,0,0,.32);border:1px solid rgba(255,255,255,.24);display:{{ dClubs }};align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;letter-spacing:.05em;color:rgba(255,255,255,.65);text-align:center;line-height:1.25">CLUB<br>LOGO</div>
<div style="flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:3px">
<div style="display:flex;align-items:center;gap:10px;min-width:0">
<span style="width:26px;height:17px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 4px,rgba(255,255,255,.1) 4px 8px);box-shadow:0 0 0 1px rgba(255,255,255,.3);display:{{ dFlags }};align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.85)">FLAG</span>
<span style="flex:1 1 auto;min-width:0;font-size:25px;line-height:1;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Fawzia A.</span>
</div>
<div style="display:flex;align-items:center;gap:8px;min-width:0">
<span style="flex:1 1 auto;min-width:0;font-size:12px;letter-spacing:.03em;color:rgba(255,232,228,.85);text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Manama Karate Club</span>
<span style="display:{{ dPenalty }};font-size:10px;letter-spacing:.18em;padding:1px 6px;background:#ffdf6b;color:#3a2a00;font-weight:700;flex:none">SENSHU</span>
</div>
</div>
<div style="width:60px;flex:none;display:flex;flex-direction:column;align-items:flex-end;gap:5px"><span style="font-size:11px;letter-spacing:.24em;color:rgba(255,236,232,.7)">{{ redCorner }}</span><span style="font-family:'Zen Old Mincho',serif;font-size:46px;line-height:.8;font-weight:700;color:#fff;text-shadow:0 6px 20px rgba(0,0,0,.5)">{{ akaScore }}</span></div>
</div>
</div>
<div style="width:118px;flex:none;transform:skewX(-9deg);background:rgba(9,9,11,.9);backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;border-bottom:3px solid #2c2a28">
<div style="transform:skewX(9deg);font-size:11px;letter-spacing:.3em;color:#8f8a83;text-transform:uppercase">{{ roundLabel }}</div>
<div style="transform:skewX(9deg);font-size:34px;line-height:1;font-weight:800;color:#efe9e0;font-variant-numeric:tabular-nums">{{ clock }}</div>
<div style="transform:skewX(9deg);display:flex;gap:4px">
<span style="width:20px;height:3px;background:#e8534a"></span>
<span style="width:20px;height:3px;background:#e8534a"></span>
<span style="width:20px;height:3px;background:#3a3734"></span>
</div>
</div>
<div style="flex:1 1 0;min-width:0;transform:skewX(-9deg);overflow:hidden;background:linear-gradient(90deg, rgba(30,72,140,.9), rgba(18,44,92,.94));border-bottom:3px solid #6aa6ff">
<div style="transform:skewX(9deg);height:100%;padding:0 16px;display:flex;align-items:center;gap:12px">
<div style="width:60px;flex:none;display:flex;flex-direction:column;align-items:flex-start;gap:5px"><span style="font-size:11px;letter-spacing:.24em;color:rgba(226,238,255,.7)">{{ blueCorner }}</span><span style="font-family:'Zen Old Mincho',serif;font-size:46px;line-height:.8;font-weight:700;color:#fff;text-shadow:0 6px 20px rgba(0,0,0,.5)">{{ aoScore }}</span></div>
<div style="flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:3px;align-items:flex-end;text-align:right">
<div style="display:flex;align-items:center;gap:10px;min-width:0">
<span style="flex:1 1 auto;min-width:0;font-size:25px;line-height:1;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Noor S.</span>
<span style="width:26px;height:17px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 4px,rgba(255,255,255,.1) 4px 8px);box-shadow:0 0 0 1px rgba(255,255,255,.3);display:{{ dFlags }};align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.85)">FLAG</span>
</div>
<div style="display:flex;align-items:center;gap:8px;min-width:0">
<span style="display:{{ dPenalty }};font-size:10px;letter-spacing:.18em;padding:1px 6px;background:#c8492f;color:#fff;font-weight:700;flex:none">{{ penaltyLabel }}</span>
<span style="flex:1 1 auto;min-width:0;font-size:12px;letter-spacing:.1em;color:rgba(226,238,255,.85);text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Riffa Martial Arts</span>
</div>
</div>
<div style="width:46px;height:46px;flex:none;background:rgba(0,0,0,.32);border:1px solid rgba(255,255,255,.24);display:{{ dClubs }};align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;letter-spacing:.05em;color:rgba(255,255,255,.65);text-align:center;line-height:1.25">CLUB<br>LOGO</div>
</div>
</div>
</div>
<!-- point timeline strip -->
<div style="position:absolute;left:26px;right:26px;bottom:48px;height:5px;display:{{ dTimeline }};gap:2px;z-index:2;pointer-events:none">
<sc-for list="{{ timeline }}" as="seg" hint-placeholder-count="14">
<div style="flex:1;background:{{ seg.color }}"></div>
</sc-for>
</div>
</div>
<div style="position:absolute;inset:0;z-index:8;display:{{ introDisplay }};opacity:{{ introOpacity }};transition:opacity .55s ease;background:#08080a;overflow:hidden">
<div style="position:absolute;inset:-12%;width:70%;left:-6%;background:radial-gradient(closest-side, rgba(168,42,34,.62), rgba(8,8,10,0) 72%);animation:driftA 19s ease-in-out infinite"></div>
<div style="position:absolute;inset:-12%;width:70%;left:36%;background:radial-gradient(closest-side, rgba(28,64,136,.62), rgba(8,8,10,0) 72%);animation:driftB 23s ease-in-out infinite"></div>
<div style="position:absolute;top:0;bottom:0;left:0;width:26%;background:linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.09), rgba(255,255,255,0));animation:sweep 11s linear infinite"></div>
<div style="position:absolute;top:26px;left:0;right:0;display:flex;flex-direction:column;align-items:center;gap:5px">
<div style="font-family:'Zen Old Mincho',serif;font-size:19px;letter-spacing:.42em;color:#efe9e0;text-transform:uppercase;text-indent:.42em">Takeone Karate Series</div>
<div style="font-size:11px;letter-spacing:.32em;color:#8f8a83;text-transform:uppercase">Ladies · Classification · Round 1</div>
</div>
<div style="position:absolute;inset:82px 0 46px;display:grid;grid-template-columns:1fr 150px 1fr;align-items:center">
<div style="display:flex;flex-direction:column;align-items:center;padding:0 20px">
<div style="position:relative;width:168px;height:224px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:10px;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-18px;right:-18px;width:70px;height:70px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.7);text-align:center;line-height:1.3">CLUB<br>LOGO</div>
</div>
<div style="margin-top:30px;padding-top:12px;border-top:3px solid #e8534a;display:flex;flex-direction:column;align-items:center;gap:6px">
<div style="font-size:30px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">Fawzia Ahmed</div>
<div style="display:flex;align-items:center;gap:10px">
<span style="width:30px;height:20px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.3);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.85)">FLAG</span>
<span style="font-size:12px;letter-spacing:.26em;color:#d3b3ad;text-transform:uppercase">Bahrain · Manama Karate Club</span>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;align-items:center;gap:10px">
<span style="font-family:'Zen Old Mincho',serif;font-size:64px;line-height:.8;font-weight:700;color:#efe9e0;text-shadow:0 8px 30px rgba(0,0,0,.75)">VS</span>
<span style="padding:5px 13px;border:1px solid rgba(255,255,255,.3);font-size:12px;letter-spacing:.3em;color:#efe9e0;text-transform:uppercase">61 kg</span>
</div>
<div style="display:flex;flex-direction:column;align-items:center;padding:0 20px">
<div style="position:relative;width:168px;height:224px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:10px;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-18px;left:-18px;width:70px;height:70px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.7);text-align:center;line-height:1.3">CLUB<br>LOGO</div>
</div>
<div style="margin-top:30px;padding-top:12px;border-top:3px solid #6aa6ff;display:flex;flex-direction:column;align-items:center;gap:6px">
<div style="font-size:30px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">Noor Salman</div>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-size:12px;letter-spacing:.26em;color:#a8b6cf;text-transform:uppercase">Bahrain · Riffa Martial Arts</span>
<span style="width:30px;height:20px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.3);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:7px;color:rgba(255,255,255,.85)">FLAG</span>
</div>
</div>
</div>
</div>
</div>
<div style="position:absolute;left:0;right:0;bottom:0;display:flex;flex-direction:column;background:linear-gradient(to top, rgba(6,6,8,.94) 40%, rgba(6,6,8,0));z-index:9">
<div style="height:4px;background:rgba(255,255,255,.28);position:relative">
<div style="position:absolute;left:0;top:0;bottom:0;width:23%;background:#e8534a"></div>
<div style="position:absolute;left:23%;top:-3px;width:10px;height:10px;border-radius:50%;background:#e8534a"></div>
</div>
<div style="height:44px;padding:0 16px;display:flex;align-items:center;gap:18px;color:#efe9e0">
<button onClick="{{ replayIntro }}" style="display:flex;gap:4px;padding:0;background:transparent;border:0;cursor:pointer;color:inherit" style-hover="opacity:.75"><span style="width:4px;height:15px;background:currentColor"></span><span style="width:4px;height:15px;background:currentColor"></span></button>
<span style="display:flex;align-items:center;gap:2px"><span style="width:6px;height:9px;background:currentColor"></span><span style="width:0;height:0;border-right:8px solid currentColor;border-top:8px solid transparent;border-bottom:8px solid transparent"></span><span style="width:5px;height:5px;border-right:2px solid currentColor;border-top:2px solid currentColor;transform:rotate(45deg);margin-left:2px"></span></span>
<span style="font-size:13px;letter-spacing:.1em;color:#cfc9c1;font-variant-numeric:tabular-nums">0:57 / 4:11</span>
<span style="flex:1"></span>
<button onClick="{{ toggleMenu }}" style="width:24px;height:24px;flex:none;display:flex;align-items:center;justify-content:center;background:transparent;border:0;cursor:pointer;color:{{ gearColor }}" style-hover="color:#fff">
<span style="width:15px;height:15px;border:2px solid currentColor;border-radius:50%;display:block;position:relative"><span style="position:absolute;inset:3px;border:2px solid currentColor;border-radius:50%"></span></span>
</button>
<span style="width:22px;height:16px;border:2px solid #e8534a;border-radius:3px;display:block"></span>
<span style="width:22px;height:16px;border:2px solid currentColor;border-radius:2px;display:flex;align-items:flex-end;justify-content:flex-end;padding:2px"><span style="width:9px;height:6px;background:currentColor"></span></span>
<span style="width:22px;height:14px;border:2px solid currentColor;border-radius:2px;display:block"></span>
<span style="width:18px;height:16px;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;gap:4px">
<span style="border-left:2px solid currentColor;border-top:2px solid currentColor"></span><span style="border-right:2px solid currentColor;border-top:2px solid currentColor"></span>
<span style="border-left:2px solid currentColor;border-bottom:2px solid currentColor"></span><span style="border-right:2px solid currentColor;border-bottom:2px solid currentColor"></span>
</span>
</div>
</div>
<div style="position:absolute;right:118px;bottom:58px;width:236px;z-index:10;display:{{ menuDisplay }};flex-direction:column;background:rgba(10,10,12,.94);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.16);box-shadow:0 20px 50px rgba(0,0,0,.6)">
<div style="padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.1);font-size:11px;letter-spacing:.3em;color:#8f8a83;text-transform:uppercase">Scoreboard</div>
<sc-for list="{{ menuItems }}" as="it" hint-placeholder-count="7">
<button onClick="{{ it.toggle }}" style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;background:transparent;border:0;cursor:pointer;font-family:'Barlow Condensed',sans-serif;font-size:14px;letter-spacing:.14em;color:#efe9e0;text-transform:uppercase;text-align:left" style-hover="background:rgba(255,255,255,.07)">
<span>{{ it.label }}</span>
<span style="width:26px;height:14px;flex:none;background:{{ it.track }};position:relative"><span style="position:absolute;top:2px;left:{{ it.knob }};width:10px;height:10px;background:#efe9e0"></span></span>
</button>
</sc-for>
</div>
</div>
<div style="width:1150px;flex:none;display:flex;align-items:center;justify-content:space-between;color:#6f6a64;font-size:14px;letter-spacing:.22em;text-transform:uppercase">
<span>Overlay is a function of playback time · points come from the Points panel</span>
<span style="color:#efe9e0">{{ statusLine }}</span>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1230,&quot;height&quot;:1400},&quot;sport&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;Karate&quot;,&quot;Taekwondo&quot;],&quot;default&quot;:&quot;Karate&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;showTicker&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;}}">
class Component extends DCLogic {
state = {
t: 107, aka: 6, ao: 4, menu: false,
parts: { scorebar: true, ticker: true, identity: true, timeline: true, clubs: true, flags: true, penalties: false }
};
playIntro = () => {
clearTimeout(this.t1); clearTimeout(this.t2);
this.setState({ intro: true, introFade: false });
this.t1 = setTimeout(() => this.setState({ introFade: true }), 2000);
this.t2 = setTimeout(() => this.setState({ intro: false }), 2600);
};
componentDidMount() {
this.playIntro();
this.iv = setInterval(() => this.setState(s => ({ t: s.t > 0 ? s.t - 1 : 0 })), 1000);
}
componentWillUnmount() { clearInterval(this.iv); }
renderVals() {
const tkd = (this.props.sport ?? 'Karate') === 'Taekwondo';
const RED = '#ff6a5e', BLUE = '#6aa6ff';
const ev = [
{ at: 11, corner: 'blue', pts: 1 }, { at: 11, corner: 'red', pts: 1 },
{ at: 30, corner: 'red', pts: 2 }, { at: 51, corner: 'red', pts: 1 },
{ at: 112, corner: 'blue', pts: 1 }, { at: 159, corner: 'blue', pts: 1 }
];
const aka = ev.filter(e => e.corner === 'red').reduce((a, e) => a + e.pts, 0);
const ao = ev.filter(e => e.corner === 'blue').reduce((a, e) => a + e.pts, 0);
const p = this.state.parts;
const m = Math.floor(this.state.t / 60), s = String(this.state.t % 60).padStart(2, '0');
// mirrors the platform's Points data: round, @timestamp, corner, point value
const events = [
{ at: 11, corner: 'blue', pts: 1 }, { at: 11, corner: 'red', pts: 1 },
{ at: 30, corner: 'red', pts: 2 }, { at: 51, corner: 'red', pts: 1 },
{ at: 112, corner: 'blue', pts: 1 }, { at: 159, corner: 'blue', pts: 1 }
];
const fmt = n => Math.floor(n / 60) + ":" + String(n % 60).padStart(2, '0');
const karateFeed = events.slice().reverse().slice(0, 4).map(e => ({
time: "@" + fmt(e.at),
name: e.corner === 'red' ? "Point · Red" : "Point · Blue",
pts: "+" + e.pts,
color: e.corner === 'red' ? RED : BLUE
}));
const totals = events.reduce((a, e) => (a[e.corner] += e.pts, a), { red: 0, blue: 0 });
const tkdFeed = [
{ time: "1:12", name: "Head kick", pts: "+3", color: RED },
{ time: "1:44", name: "Turning body", pts: "+4", color: BLUE },
{ time: "2:03", name: "Body kick", pts: "+2", color: RED },
{ time: "2:21", name: "Gam-jeom", pts: "+1", color: BLUE }
];
return {
disciplineLabel: tkd ? "Taekwondo Kyorugi" : "Karate Kumite",
overlayDisplay: 'contents',
introDisplay: this.state.intro ? 'block' : 'none',
introOpacity: this.state.introFade ? 0 : 1,
replayIntro: this.playIntro,
menuDisplay: this.state.menu ? 'flex' : 'none',
gearColor: this.state.menu ? '#e8534a' : '#efe9e0',
toggleMenu: () => this.setState(s => ({ menu: !s.menu })),
menuItems: [
['scorebar', 'Score bar'], ['ticker', 'Live scoring feed'], ['identity', 'Match info'],
['timeline', 'Point timeline'], ['clubs', 'Club logos'], ['flags', 'Country flags'], ['penalties', 'Penalties']
].map(([k, label]) => ({
label,
toggle: () => this.setState(s => ({ parts: Object.assign({}, s.parts, { [k]: !s.parts[k] }) })),
track: p[k] ? '#e8534a' : '#3a3734',
knob: p[k] ? '14px' : '2px'
})),
dIdentity: p.identity ? 'flex' : 'none',
dTicker: p.ticker ? 'flex' : 'none',
dBar: p.scorebar ? 'flex' : 'none',
dTimeline: p.timeline ? 'flex' : 'none',
dClubs: p.clubs ? 'flex' : 'none',
dFlags: p.flags ? 'flex' : 'none',
dPenalty: p.penalties ? 'inline-block' : 'none',
redCorner: "Red",
blueCorner: "Blue",
penaltyLabel: tkd ? "GAM-JEOM 1" : "C1",
roundLabel: "Round 1",
penaltyLabel2: null,
akaScore: aka,
aoScore: ao,
clock: "2:39",
feed: (this.props.showTicker ?? true) ? (tkd ? tkdFeed : karateFeed) : [],
timeline: [RED, '#2a2724', BLUE, RED, '#2a2724', '#2a2724', BLUE, RED, '#2a2724', RED, BLUE, '#2a2724', RED, '#2a2724']
.map(c => ({ color: c })),
statusLine: aka > ao ? "Red leads +" + (aka - ao) : ao > aka ? "Blue leads +" + (ao - aka) : "Level"
};
}
}
</script>
</body>
</html>

View File

@ -1,102 +0,0 @@
# TASK: Add a match scoreboard overlay + VS intro to the video player on video.takeone.bh
Implement this end to end in this codebase, in the existing player and video page. Follow the codebase's own framework, component and styling patterns. Do not ship the reference HTML as-is — recreate it.
Reference prototype (design intent, exact colors/geometry): `Match Points Overlay.dc.html` in this folder. Open it in a browser: it shows the standalone VS card, then the player frame with the VS intro, score bar, live scoring feed, point timeline, control bar and gear menu.
---
## 1. What exists today (do not rebuild)
- **Points panel** on the video page: rounds (number, optional name, optional start time) and point events shaped `@mm:ss · Point (N pt) · Blue | Red | Both`, with a running score printed `Score <blue> <red>`.
- **Coach review** panel: time-ranged notes with author, plus reaction icons.
- **Player**: progress bar, play/pause, volume, time `0:57 / 4:11`, gear (settings) menu, loop, mini player (PiP), theater, fullscreen; playback-speed menu and ¼× ½× ¾× slow motion; a "Highlights" badge top-right.
## 2. What to add to the data model
A **Match metadata** form for sports videos, next to "Add Round":
- Event/series name, division/category, weight class, bout number, tatami/ring, date, venue.
- Per corner (red, blue): athlete name, country name + ISO code, flag image, club name, club logo, optional headshot (3:4).
Rules:
- Every field is optional. **An empty field hides its element.** Never render placeholder strings ("NOT SET", "—", empty boxes).
- No athlete name → show the corner label ("RED" / "BLUE") in the name slot.
- No flag image → hide the flag chip. No club logo → hide the logo slot. No headshot → hide the photo frame and center the text block.
- Corners in data stay **red** and **blue**. Display labels are a per-video option: Red/Blue (default), Aka/Ao (karate), Hong/Chung (taekwondo).
- A `Both` point event increments both corners and emits two feed rows.
## 3. Overlay: driven by playback time
The overlay is a pure function of `video.currentTime`. No timers, no countdown. Slow motion and playback speed must not affect it; scrubbing backwards must remove points again.
- **Scores** = sum of point values for events with `at <= currentTime`, per corner.
- **Round label** = the round whose start time is the latest `<= currentTime`.
- **Clock** = `currentTime` as `m:ss` (offset from round start when round start times exist).
- **Live scoring feed** = last 4 events with `at <= currentTime`, newest on top, each animating in (translateY 10px → 0, opacity 0 → 1, .45s ease) as playback crosses it.
- **Point timeline** = one segment per event across the full duration, red or blue, positioned by timestamp; empty segments `#2a2724`.
## 4. Layout spec (design width 1150px, frame 16:9)
Overlay layer: `position:absolute; inset:0; pointer-events:none;` inside the player container. Only the gear menu is interactive. Scrims: bottom `rgba(6,6,8,.9) → .45 @15% → 0 @30%`, top `rgba(6,6,8,.85) → .4 @14% → 0 @30%`. No texture/grain layer over the footage.
**Match info** — top-left, 22px/26px inset: 4px vertical rule `linear-gradient(#e8534a,#7c1d18)`; discipline line in `Zen Old Mincho` 15px, .34em tracking, `#efe9e0`; subtitle 13px, .28em, `#cfc9c1`, with `text-shadow: 0 1px 6px rgba(0,0,0,.9)`.
**Live scoring feed** — top-right, 20px/24px inset, 262px wide. Header: 7px red dot pulsing (opacity 1→.25→1, 1.6s) + "LIVE SCORING" 12px, .3em, `#d5cfc7`. Rows: `rgba(10,10,12,.62)` + `blur(6px)`, padding 7px/10px, `border-right:3px solid <corner color>`; timestamp 12px `#79736c` · label 17px/700 .12em uppercase `#efe9e0` · points in `Zen Old Mincho` 19px in the corner color.
**Score bar** — 84px tall, inset 26px left/right, `bottom: 56px` (clears the 48px control band), z-index below controls. Three panels, no gaps:
- Red panel: `flex:1 1 0; min-width:0; overflow:hidden; transform:skewX(-9deg)`, background `linear-gradient(90deg, rgba(122,26,22,.94), rgba(180,52,44,.9))`, `border-bottom:3px solid #ff6a5e`.
Inside, ONE inner row un-skews: `transform:skewX(9deg); height:100%; padding:0 16px; display:flex; align-items:center; gap:12px`. Never counter-skew individual children — that is what made scores collide with names in the first attempt.
Children: club logo 46×46 (`rgba(0,0,0,.32)`, 1px white 24% border) → text column (`flex:1 1 auto; min-width:0`) → score box (`width:60px; flex:none`, column, right-aligned).
Text column row 1: flag chip 26×17 + athlete name (25px/800 uppercase, `flex:1 1 auto; min-width:0`, nowrap + ellipsis). Row 2: club name (12px, .03em, uppercase, ellipsis) + penalty chip.
Score box: corner label 11px .24em above the numeral; numeral `Zen Old Mincho` 46px/700 white, `text-shadow:0 6px 20px rgba(0,0,0,.5)`.
- Center: `width:118px; flex:none`, `rgba(9,9,11,.9)` + `blur(8px)`, `border-bottom:3px solid #2c2a28`; round label 11px .3em `#8f8a83`, clock 34px/800 `#efe9e0` tabular-nums, three 20×3px pips (`#e8534a` done, `#3a3734` pending).
- Blue panel: exact mirror — score box, text column right-aligned, club logo. Gradient `rgba(30,72,140,.9) → rgba(18,44,92,.94)`, `border-bottom:3px solid #6aa6ff`.
Both panels must be true mirrors: logo → flag+name → club+penalty → score, with the corner label above its own score on both sides.
**Point timeline** — `left/right: 26px; bottom: 48px; height: 5px`, segments with 2px gaps.
## 5. Gear-menu controls
No floating on-screen toggle. Add a **Scoreboard** section to the existing gear menu (same pattern as quality/speed), anchored above the gear button:
Panel `rgba(10,10,12,.94)` + `blur(10px)`, 1px white 16% border, 236px wide, header "SCOREBOARD" 11px .3em `#8f8a83`. One switch row per part:
`Score bar · Live scoring feed · Match info · Point timeline · Club logos · Country flags · Penalties`
Rows: 14px uppercase label, .14em tracking, hover `rgba(255,255,255,.07)`; switch = 26×14 track (`#e8534a` on, `#3a3734` off) with a 10px knob. Persist per user; the uploader's per-video settings seed the defaults. **Penalties defaults off** — the Points panel has no penalty events yet; enable when it does. Gear icon turns `#e8534a` while the menu is open. Menu closes on outside click and Esc, and never covers the score bar.
## 6. VS intro before playback
Part of the play experience, inside the video box at 16:9:
- On first play of a session, render the VS card over the frame while the video is paused at frame 0.
- Hold **2s**, then fade out over 0.5s; call `video.play()` as the fade begins so motion is already running when the card clears. Scoreboard fades in after.
- Any click, tap or key press skips it immediately and starts playback.
- Replays and scrubs do not re-trigger it. If match metadata is missing, skip the intro entirely.
**VS card content**: event name in `Zen Old Mincho` 24px .44em uppercase; division line 12px .34em with red/blue 4px square separators; per fighter a **3:4 photo frame** with a **104px circular club crest** overlapping its inner-bottom corner (`bottom:-26px`, `#0d0d10`, 1px white 28% border, `0 10px 30px rgba(0,0,0,.6)`), then a 3px corner-colored rule, name 42px/800 uppercase, flag chip 40×27 + country 14px .3em, club name 13px .18em; center column: "VS" `Zen Old Mincho` 88px, weight class in a 1px-bordered chip, format line; bottom line: bout · tatami · date · venue.
**Background animation** (CSS only, no video): two radial glows — red `rgba(168,42,34,.62)` and blue `rgba(28,64,136,.62)`, 70% width, drifting `translate3d(±60px, ∓28px) scale(1→1.14)` over 19s and 23s ease-in-out infinite — plus a 26%-wide white 9% light sweep crossing every 11s. No center divider line.
Also expose the VS card as a 1200×630 share image for `og-image`.
## 7. Design tokens
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, "VS" and the discipline/event lines.
Geometry: skew `-9deg` with a single `+9deg` counter-skew row; bar 84px; frame inset 26px; gaps 12px; no border radius on overlay parts.
## 8. Acceptance criteria
1. Overlay never blocks the scrubber or any control (`pointer-events:none`, controls above it).
2. Score bar sits fully above the control band; nothing crosses the progress bar or the panels' bottom borders.
3. No text is clipped or overlapping at 1150px design width: every text span has `min-width:0` with ellipsis; fixed boxes are `flex:none`.
4. Red and blue panels are exact mirrors.
5. Scores, feed, round and timeline all match the Points panel at any `currentTime`, including after scrubbing backwards and at ¼×/½×/¾× speed.
6. Missing metadata hides elements — no placeholder text anywhere.
7. Overlay scales with the player (fixed 1150px layer scaled, or clamp-based sizing); minimum rendered text 12px; below ~700px player width only the score bar remains.
8. VS intro holds 2s, fades, playback starts; skippable; first play only.
9. Every one of the seven parts toggles independently from the gear menu and persists per user.

View File

@ -1,200 +0,0 @@
# 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)`, background `linear-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, `#ffdf6b` on `#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`), clock `mm:ss` (34px/800, `#efe9e0`, tabular-nums), three 20×3px round pips (`#e8534a` for completed, `#3a3734` for 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 `#c8492f` on 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:
```ts
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:0` inside 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 `identity` and `ticker` automatically 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:
1. **Keep the skew, but counter-skew ONCE.** The panel gets `transform: skewX(-9deg)`; a single inner row gets `transform: 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.
2. **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.
3. **Both scores must render.** Red score right-aligned in a 56px `flex:none` box 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.
4. **Never let panel content overflow.** Text spans need `min-width:0` and `overflow:hidden; text-overflow:ellipsis`; fixed boxes (logo 46px, score 56px) are `flex:none`.
5. **Empty data:** when an athlete/club is unknown, render the corner as "—" or hide the row — do not print "NOT SET".
6. **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 (`#e8534a` on, `#3a3734` off) with a 10px knob; hover `rgba(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|Both` with a running score printed as `Score <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
1. **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.
2. **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".
3. **Corners are Red and Blue** in the existing data. Karate labels (AKA/AO) or taekwondo (HONG/CHUNG) are a display option, not new data.
4. **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 = `currentTime` formatted 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-events` off 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 → 43 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1002 KiB

View File

@ -1,94 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>VS screen — exact markup</title>
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;600;700;800&family=Zen+Old+Mincho:wght@700&display=swap" rel="stylesheet">
<style>
body { margin:0; background:#0a0a0c; font-family:'Barlow Condensed',sans-serif; }
@keyframes driftA { 0%,100% { transform: translate3d(0,0,0) scale(1); } 50% { transform: translate3d(60px,-28px,0) scale(1.14); } }
@keyframes driftB { 0%,100% { transform: translate3d(0,0,0) scale(1.08); } 50% { transform: translate3d(-54px,26px,0) scale(1); } }
@keyframes sweep { 0% { transform: translateX(-60%) skewX(-14deg); opacity:0; } 25% { opacity:.5; } 60% { opacity:0; } 100% { transform: translateX(160%) skewX(-14deg); opacity:0; } }
</style>
</head>
<body>
<!--
VS SCREEN — copy verbatim. Replace ONLY the {{ }} holes.
Sits inside the player container as an absolutely positioned 16:9 layer:
position:absolute; inset:0; z-index:8; opacity 1 -> 0 over .55s after a 2s hold.
Photo frames are 3:4. The club crest overlaps the photo's INNER-bottom corner (right on the left fighter, left on the right one).
Hide a missing element with display:none — never substitute placeholder text.
The three animated background layers are decorative; keep them behind everything (they are first in source order).
-->
<div style="position:relative;width:1150px;aspect-ratio:16/9;overflow:hidden;background:#08080a">
<div style="position:absolute;inset:-12%;width:70%;left:-6%;background:radial-gradient(closest-side, rgba(168,42,34,.62), rgba(8,8,10,0) 72%);animation:driftA 19s ease-in-out infinite"></div>
<div style="position:absolute;inset:-12%;width:70%;left:36%;background:radial-gradient(closest-side, rgba(28,64,136,.62), rgba(8,8,10,0) 72%);animation:driftB 23s ease-in-out infinite"></div>
<div style="position:absolute;top:0;bottom:0;left:0;width:26%;background:linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.09), rgba(255,255,255,0));animation:sweep 11s linear infinite"></div>
<div style="position:absolute;top:0;bottom:0;left:0;width:54%;clip-path:polygon(0 0, 100% 0, 78% 100%, 0 100%);background:linear-gradient(120deg, rgba(122,26,22,.5), rgba(8,8,10,0) 76%)"></div>
<div style="position:absolute;top:0;bottom:0;right:0;width:54%;clip-path:polygon(22% 0, 100% 0, 100% 100%, 0 100%);background:linear-gradient(300deg, rgba(24,52,110,.5), rgba(8,8,10,0) 76%)"></div>
<!-- event header -->
<div style="position:absolute;top:34px;left:0;right:0;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-family:'Zen Old Mincho',serif;font-size:24px;letter-spacing:.44em;color:#efe9e0;text-transform:uppercase;text-indent:.44em">{{ eventName }}</div>
<div style="display:flex;align-items:center;gap:14px;font-size:12px;letter-spacing:.34em;color:#8f8a83;text-transform:uppercase">
<span>{{ category }}</span><span style="width:4px;height:4px;background:#e8534a"></span><span>{{ division }}</span><span style="width:4px;height:4px;background:#6aa6ff"></span><span>{{ roundName }}</span>
</div>
</div>
<div style="position:absolute;inset:96px 0 54px;display:grid;grid-template-columns:1fr 200px 1fr;align-items:start">
<!-- RED fighter -->
<div style="display:flex;flex-direction:column;align-items:center;padding:34px 24px 0;min-width:0">
<div style="position:relative;width:228px;height:304px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:12px;font-family:ui-monospace,monospace;font-size:10px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-26px;right:-26px;width:104px;height:104px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);box-shadow:0 10px 30px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.06em;color:rgba(255,255,255,.7);text-align:center;line-height:1.35">CLUB<br>LOGO</div>
</div>
<div style="margin-top:34px;padding-top:12px;border-top:3px solid #e8534a;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-size:42px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">{{ redName }}</div>
<div style="display:flex;align-items:center;gap:12px">
<span style="width:40px;height:27px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.32);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;color:rgba(255,255,255,.85)">FLAG</span>
<span style="font-size:14px;letter-spacing:.3em;color:#d3b3ad;text-transform:uppercase">{{ redCountry }}</span>
</div>
<div style="display:flex;align-items:center;gap:10px;margin-top:2px">
<span style="font-size:13px;letter-spacing:.18em;color:#9d9691;text-transform:uppercase">{{ redClub }}</span>
</div>
</div>
</div>
<!-- centre -->
<div style="display:flex;flex-direction:column;align-items:center;gap:12px;padding-top:96px">
<span style="font-family:'Zen Old Mincho',serif;font-size:88px;line-height:.8;font-weight:700;color:#efe9e0;text-shadow:0 8px 34px rgba(0,0,0,.75)">VS</span>
<span style="padding:7px 16px;border:1px solid rgba(255,255,255,.3);font-size:14px;letter-spacing:.32em;color:#efe9e0;text-transform:uppercase">{{ weightClass }}</span>
<span style="font-size:11px;letter-spacing:.34em;color:#8f8a83;text-transform:uppercase">{{ format }}</span>
</div>
<!-- BLUE fighter — mirror -->
<div style="display:flex;flex-direction:column;align-items:center;padding:34px 24px 0;min-width:0">
<div style="position:relative;width:228px;height:304px">
<div style="position:absolute;inset:0;background:repeating-linear-gradient(135deg,rgba(255,255,255,.1) 0 6px,rgba(255,255,255,.03) 6px 12px);border:1px solid rgba(255,255,255,.16);display:flex;align-items:flex-end;justify-content:center;padding-bottom:12px;font-family:ui-monospace,monospace;font-size:10px;letter-spacing:.1em;color:rgba(255,255,255,.6)">FIGHTER PHOTO</div>
<div style="position:absolute;bottom:-26px;left:-26px;width:104px;height:104px;border-radius:50%;background:#0d0d10;border:1px solid rgba(255,255,255,.28);box-shadow:0 10px 30px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:9px;letter-spacing:.06em;color:rgba(255,255,255,.7);text-align:center;line-height:1.35">CLUB<br>LOGO</div>
</div>
<div style="margin-top:34px;padding-top:12px;border-top:3px solid #6aa6ff;display:flex;flex-direction:column;align-items:center;gap:8px">
<div style="font-size:42px;line-height:.95;font-weight:800;color:#fff;text-transform:uppercase;white-space:nowrap">{{ blueName }}</div>
<div style="display:flex;align-items:center;gap:12px">
<span style="font-size:14px;letter-spacing:.3em;color:#a8b6cf;text-transform:uppercase">{{ blueCountry }}</span>
<span style="width:40px;height:27px;flex:none;background:repeating-linear-gradient(135deg,rgba(255,255,255,.28) 0 5px,rgba(255,255,255,.1) 5px 10px);box-shadow:0 0 0 1px rgba(255,255,255,.32);display:flex;align-items:center;justify-content:center;font-family:ui-monospace,monospace;font-size:8px;color:rgba(255,255,255,.85)">FLAG</span>
</div>
<div style="display:flex;align-items:center;gap:10px;margin-top:2px">
<span style="font-size:13px;letter-spacing:.18em;color:#9d9691;text-transform:uppercase">{{ blueClub }}</span>
</div>
</div>
</div>
</div>
<!-- bout line -->
<div style="position:absolute;bottom:26px;left:0;right:0;display:flex;align-items:center;justify-content:center;gap:16px;font-size:11px;letter-spacing:.32em;color:#8f8a83;text-transform:uppercase">
<span>{{ bout }}</span><span style="width:4px;height:4px;background:#e8534a"></span><span>{{ tatami }}</span><span style="width:4px;height:4px;background:#6aa6ff"></span><span>{{ dateVenue }}</span>
</div>
</div>
</body>
</html>

View File

@ -220,23 +220,31 @@
background: #000;
border-radius: 12px;
overflow: hidden;
/* default aspect ratio; overridden per orientation */
/* Always 16:9 — no max-height so the shape is never distorted. */
aspect-ratio: 16/9;
max-height: 70vh;
/* Establish a size container so descendants can size themselves relative
to the player width (used by the coach-note overlay to scale). */
container-type: inline-size;
container-name: ytpwrap;
}
.ytp-wrap.portrait { aspect-ratio: 9/16; max-height: 80vh; width: auto; max-width: 100%; margin: 0 auto; }
.ytp-wrap.square { aspect-ratio: 1/1; max-height: 75vh; max-width: 75vh; margin: 0 auto; }
.ytp-wrap.ultrawide { aspect-ratio: 21/9; max-height: 65vh; }
/* Orientation classes intentionally forced to 16:9 too, so the frame
shape never changes with the underlying media orientation. The video
element itself uses object-fit: contain and will letterbox as needed. */
.ytp-wrap.portrait,
.ytp-wrap.square,
.ytp-wrap.ultrawide {
aspect-ratio: 16/9;
width: 100%;
max-width: none;
max-height: none;
margin: 0;
}
/* Theater mode */
/* Theater mode — keep 16:9 shape, just remove rounded corners. */
.ytp-wrap.theater {
max-height: 80vh;
aspect-ratio: unset;
height: 80vh;
aspect-ratio: 16/9;
height: auto;
max-height: none;
border-radius: 0;
}
@ -604,10 +612,10 @@
/* ── Mobile ── */
@media (max-width: 768px) {
.ytp-wrap { border-radius: 0; max-height: 56vw; margin: 0; width: 100%; }
.ytp-wrap.portrait { max-height: 75vh; width: auto; max-width: 100%; }
.ytp-wrap.square { max-height: 85vw; max-width: 85vw; }
.ytp-wrap.ultrawide { max-height: 50vw; }
.ytp-wrap { border-radius: 0; margin: 0; width: 100%; aspect-ratio: 16/9; max-height: none; }
.ytp-wrap.portrait,
.ytp-wrap.square,
.ytp-wrap.ultrawide { aspect-ratio: 16/9; width: 100%; max-width: none; max-height: none; }
.video-view-page .ytp-wrap ~ * { padding-left: 16px; padding-right: 16px; }
}