Created
May 5, 2026 00:42
-
-
Save camilotorresmestra/9e320f7f6b1a1391019e3a0ae929c2ed to your computer and use it in GitHub Desktop.
betplay_fecha19.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Liga BetPlay 2026-I · Fecha 19 · Jornada de Infarto</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow+Condensed:ital,wght@0,400;0,600;0,700;1,600&family=Barlow:wght@300;400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg: #06090D; | |
| --s1: #0C1521; | |
| --s2: #111D2B; | |
| --border: #1A2B3D; | |
| --gold: #F6C200; | |
| --gold2: #FFD84F; | |
| --green: #00A651; | |
| --red: #D42535; | |
| --blue: #1A6FD4; | |
| --text: #DAE4EE; | |
| --muted: #4E6478; | |
| --muted2: #7A96A8; | |
| } | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| html{scroll-behavior:smooth} | |
| body{ | |
| background:var(--bg); | |
| color:var(--text); | |
| font-family:'Barlow',system-ui,sans-serif; | |
| min-height:100vh; | |
| overflow-x:hidden; | |
| } | |
| body::before{ | |
| content:''; | |
| position:fixed;inset:0; | |
| background: | |
| radial-gradient(ellipse 60% 50% at 15% 60%,rgba(0,166,81,.035) 0%,transparent 70%), | |
| radial-gradient(ellipse 50% 40% at 85% 20%,rgba(246,194,0,.03) 0%,transparent 70%); | |
| pointer-events:none;z-index:0; | |
| } | |
| /* ─── HEADER ─── */ | |
| .header{ | |
| position:relative;z-index:10; | |
| background:var(--s1); | |
| border-bottom:1px solid var(--border); | |
| padding:12px 20px; | |
| display:flex;align-items:center;justify-content:space-between; | |
| } | |
| .hdr-left{display:flex;align-items:center;gap:12px} | |
| .hdr-logo{ | |
| width:42px;height:42px;background:var(--gold);border-radius:50%; | |
| display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0; | |
| box-shadow:0 0 20px rgba(246,194,0,.25); | |
| } | |
| .hdr-league{ | |
| font-family:'Bebas Neue',sans-serif;font-size:19px; | |
| letter-spacing:2px;color:var(--gold);line-height:1; | |
| } | |
| .hdr-sub{font-size:10px;color:var(--muted);letter-spacing:1.5px;text-transform:uppercase} | |
| .hdr-right{text-align:right} | |
| .hdr-fecha{ | |
| font-family:'Barlow Condensed',sans-serif;font-size:22px; | |
| font-weight:700;line-height:1;letter-spacing:.5px; | |
| } | |
| .hdr-date{font-size:11px;color:var(--muted);margin-top:1px} | |
| /* ─── LAYOUT ─── */ | |
| .layout{ | |
| position:relative;z-index:1; | |
| display:grid; | |
| grid-template-columns:1fr 272px; | |
| gap:0;max-width:1280px;margin:0 auto;padding:20px 20px 40px; | |
| } | |
| main{padding-right:20px} | |
| @media(max-width:820px){ | |
| .layout{grid-template-columns:1fr} | |
| main{padding-right:0} | |
| .sidebar{margin-top:16px} | |
| } | |
| /* ─── HERO ─── */ | |
| .hero{margin-bottom:20px} | |
| .hero-title{ | |
| font-family:'Bebas Neue',sans-serif; | |
| font-size:clamp(40px,6vw,68px); | |
| letter-spacing:3px;line-height:.92; | |
| } | |
| .hero-title em{color:var(--gold);font-style:normal} | |
| .hero-sub{ | |
| font-size:11px;color:var(--muted2); | |
| letter-spacing:2px;text-transform:uppercase; | |
| margin-top:6px; | |
| } | |
| /* ─── SECTION SHELL ─── */ | |
| .section{ | |
| background:var(--s1);border:1px solid var(--border); | |
| border-radius:12px;padding:18px 20px;margin-bottom:14px; | |
| } | |
| /* ─── TIMELINE ─── */ | |
| .tl-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px} | |
| .tl-label{ | |
| font-family:'Barlow Condensed',sans-serif;font-size:11px; | |
| font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--muted); | |
| } | |
| .min-badge{ | |
| font-family:'Bebas Neue',sans-serif;font-size:17px; | |
| background:var(--gold);color:#000;padding:2px 10px;border-radius:4px; | |
| letter-spacing:1px;min-width:52px;text-align:center; | |
| } | |
| .slider-marks{ | |
| display:flex;justify-content:space-between;margin-bottom:5px;padding:0 2px; | |
| } | |
| .slider-marks span{font-size:9px;color:var(--muted);font-family:'Barlow Condensed',sans-serif} | |
| .slider-wrap{position:relative;padding-bottom:18px} | |
| input[type=range]{ | |
| width:100%;-webkit-appearance:none;appearance:none; | |
| height:5px; | |
| background:linear-gradient(to right,var(--gold) var(--pct,0%),var(--border) var(--pct,0%)); | |
| border-radius:3px;cursor:pointer;outline:none; | |
| } | |
| input[type=range]::-webkit-slider-thumb{ | |
| -webkit-appearance:none;width:20px;height:20px; | |
| background:var(--gold);border-radius:50%; | |
| border:3px solid var(--bg); | |
| box-shadow:0 0 14px rgba(246,194,0,.55); | |
| } | |
| input[type=range]::-moz-range-thumb{ | |
| width:20px;height:20px;background:var(--gold);border-radius:50%; | |
| border:3px solid var(--bg); | |
| } | |
| .ht-mark{ | |
| position:absolute;bottom:0;left:calc(45/95*100%); | |
| transform:translateX(-50%);font-size:9px;color:var(--muted); | |
| letter-spacing:1px;text-transform:uppercase;text-align:center; | |
| } | |
| .ht-mark::before{ | |
| content:'';display:block;width:1px;height:8px; | |
| background:var(--border);margin:0 auto 2px; | |
| } | |
| /* ─── CONTROLS ─── */ | |
| .controls{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap} | |
| .btn{ | |
| padding:7px 15px;border-radius:7px;border:1px solid var(--border); | |
| background:var(--s2);color:var(--text); | |
| font-family:'Barlow Condensed',sans-serif;font-size:12px; | |
| font-weight:700;letter-spacing:1.5px;text-transform:uppercase; | |
| cursor:pointer;transition:all .2s; | |
| } | |
| .btn:hover{border-color:var(--gold);color:var(--gold)} | |
| .btn.primary{background:var(--gold);color:#000;border-color:var(--gold)} | |
| .btn.primary:hover{background:var(--gold2);border-color:var(--gold2)} | |
| .speed-wrap{display:flex;align-items:center;gap:6px;margin-left:auto} | |
| .speed-label{font-size:11px;color:var(--muted)} | |
| .speed-select{ | |
| background:var(--s2);border:1px solid var(--border);color:var(--text); | |
| font-family:'Barlow Condensed',sans-serif;font-size:12px; | |
| padding:4px 8px;border-radius:5px;cursor:pointer; | |
| } | |
| /* ─── KEY MOMENTS ─── */ | |
| .moments-label{ | |
| font-family:'Barlow Condensed',sans-serif;font-size:10px; | |
| font-weight:700;letter-spacing:2px;text-transform:uppercase; | |
| color:var(--muted);margin-bottom:8px;margin-top:14px; | |
| } | |
| .moments-row{ | |
| display:flex;gap:5px;overflow-x:auto;padding-bottom:3px; | |
| scrollbar-width:thin;scrollbar-color:var(--border) transparent; | |
| } | |
| .moment-chip{ | |
| flex-shrink:0;padding:4px 9px;border-radius:16px; | |
| border:1px solid var(--border);background:var(--s2); | |
| cursor:pointer;transition:all .18s;display:flex;align-items:center;gap:4px; | |
| } | |
| .moment-chip:hover,.moment-chip.active{ | |
| border-color:var(--gold);background:rgba(246,194,0,.08); | |
| } | |
| .chip-min{font-family:'Bebas Neue',sans-serif;font-size:13px;color:var(--gold);line-height:1} | |
| .chip-lbl{font-size:10px;color:var(--text);white-space:nowrap} | |
| /* ─── MATCH CARDS ─── */ | |
| .matches{display:flex;flex-direction:column;gap:9px;margin-bottom:14px} | |
| .match-card{ | |
| background:var(--s1);border:1px solid var(--border); | |
| border-radius:10px;padding:13px 16px 10px; | |
| transition:border-color .3s,box-shadow .3s; | |
| } | |
| .match-card.hot{ | |
| border-color:rgba(246,194,0,.25); | |
| box-shadow:0 0 20px rgba(246,194,0,.04); | |
| } | |
| .match-card.just-scored{animation:goalFlash .7s ease-out} | |
| @keyframes goalFlash{ | |
| 0%{background:rgba(246,194,0,.15);border-color:var(--gold)} | |
| 100%{background:var(--s1)} | |
| } | |
| .mc-top{display:flex;align-items:center;gap:10px;margin-bottom:8px} | |
| .team-side{display:flex;align-items:center;gap:8px;flex:1} | |
| .team-side.away{flex-direction:row-reverse;text-align:right} | |
| .t-emoji{font-size:24px;line-height:1;flex-shrink:0} | |
| .t-short{ | |
| font-family:'Barlow Condensed',sans-serif;font-size:17px; | |
| font-weight:700;letter-spacing:.5px;line-height:1; | |
| } | |
| .t-tag{font-size:9px;letter-spacing:1px;text-transform:uppercase;margin-top:2px} | |
| .tag-in{color:var(--green)}.tag-out{color:var(--red)} | |
| .tag-bubble{color:var(--gold)}.tag-muted{color:var(--muted)} | |
| .score-mid{text-align:center;min-width:88px} | |
| .score-num{ | |
| font-family:'Bebas Neue',sans-serif;font-size:38px; | |
| line-height:1;letter-spacing:5px;transition:all .2s; | |
| } | |
| .score-venue{font-size:9px;color:var(--muted);letter-spacing:.5px;margin-top:1px} | |
| /* ─── MINI TIMELINE ─── */ | |
| .mini-tl{position:relative;height:18px;margin-top:2px} | |
| .mini-track{ | |
| position:absolute;inset:0;top:50%;height:2px; | |
| transform:translateY(-50%);background:var(--border);border-radius:1px; | |
| } | |
| .mini-progress{ | |
| position:absolute;left:0;top:0;bottom:0; | |
| background:rgba(246,194,0,.15);border-radius:1px; | |
| transition:width .12s; | |
| } | |
| .mini-cursor{ | |
| position:absolute;top:0;bottom:0;width:2px; | |
| background:rgba(246,194,0,.5);transform:translateX(-50%); | |
| transition:left .12s; | |
| } | |
| .epip{ | |
| position:absolute;width:9px;height:9px;border-radius:50%; | |
| top:50%;transform:translate(-50%,-50%); | |
| transition:opacity .25s,transform .2s;cursor:pointer; | |
| } | |
| .epip.past{opacity:1} | |
| .epip.future{opacity:.18} | |
| .epip.at-now{transform:translate(-50%,-50%) scale(1.9);box-shadow:0 0 6px var(--gold)} | |
| /* ─── EVENT FEED ─── */ | |
| .feed-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px} | |
| .feed-label{ | |
| font-family:'Barlow Condensed',sans-serif;font-size:11px; | |
| font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--muted); | |
| } | |
| .feed-count{font-family:'Bebas Neue',sans-serif;font-size:15px;color:var(--gold)} | |
| .feed-list{display:flex;flex-direction:column;gap:5px;max-height:210px;overflow-y:auto} | |
| .feed-item{ | |
| display:flex;align-items:flex-start;gap:9px;padding:7px 10px; | |
| background:var(--s2);border-radius:7px; | |
| border-left:3px solid var(--border); | |
| animation:fadeUp .3s ease-out; | |
| } | |
| @keyframes fadeUp{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}} | |
| .fi-min{ | |
| font-family:'Bebas Neue',sans-serif;font-size:15px; | |
| color:var(--gold);min-width:28px;line-height:1.2; | |
| } | |
| .fi-main{font-size:12px;font-weight:500;line-height:1.4} | |
| .fi-sub{font-size:9px;color:var(--muted2);margin-top:1px} | |
| .feed-empty{text-align:center;padding:18px;color:var(--muted);font-size:12px;font-style:italic} | |
| /* ─── SIDEBAR ─── */ | |
| .standings-card{ | |
| background:var(--s1);border:1px solid var(--border);border-radius:12px; | |
| padding:16px;position:sticky;top:16px; | |
| } | |
| .sc-title{ | |
| font-family:'Bebas Neue',sans-serif;font-size:22px; | |
| letter-spacing:2px;color:var(--gold);line-height:1; | |
| } | |
| .sc-sub{ | |
| font-size:10px;color:var(--muted);letter-spacing:1px; | |
| text-transform:uppercase;margin-bottom:14px;margin-top:1px; | |
| } | |
| .sc-note{ | |
| font-size:9px;color:var(--muted);font-style:italic; | |
| margin-bottom:10px;line-height:1.5; | |
| } | |
| .st-row{ | |
| display:flex;align-items:center;gap:7px;padding:6px 8px; | |
| border-radius:6px;margin-bottom:2px; | |
| border:1px solid transparent;transition:background .4s,border-color .4s,opacity .4s; | |
| } | |
| .st-row.in{background:rgba(0,166,81,.1);border-color:rgba(0,166,81,.2)} | |
| .st-row.out{background:rgba(212,37,53,.05);border-color:rgba(212,37,53,.12);opacity:.6} | |
| .st-row.bubble{background:rgba(246,194,0,.07);border-color:rgba(246,194,0,.18)} | |
| .st-row.safe{background:transparent} | |
| .st-pos{font-family:'Bebas Neue',sans-serif;font-size:14px;min-width:18px;color:var(--muted)} | |
| .st-row.in .st-pos{color:var(--green)} | |
| .st-emoji{font-size:14px} | |
| .st-name{flex:1;font-family:'Barlow Condensed',sans-serif;font-size:13px;font-weight:600} | |
| .st-gain{font-size:9px;min-width:22px;text-align:right;color:var(--muted)} | |
| .st-gain.pos{color:var(--green)}.st-gain.neg{color:var(--red)} | |
| .st-pts{font-family:'Bebas Neue',sans-serif;font-size:15px;min-width:20px;text-align:right} | |
| .st-row.in .st-pts{color:var(--green)} | |
| .st-row.out .st-pts{color:var(--red)} | |
| .cutoff-div{display:flex;align-items:center;gap:7px;margin:5px 0} | |
| .cutoff-line{flex:1;border:none;border-top:1.5px dashed rgba(246,194,0,.35)} | |
| .cutoff-txt{font-size:9px;color:var(--gold);letter-spacing:1px;text-transform:uppercase;white-space:nowrap} | |
| @keyframes stFlash{ | |
| 0%{background:rgba(246,194,0,.3)}100%{} | |
| } | |
| .st-row.flash{animation:stFlash .7s ease-out} | |
| /* ─── SCROLLBAR ─── */ | |
| ::-webkit-scrollbar{width:3px;height:3px} | |
| ::-webkit-scrollbar-track{background:transparent} | |
| ::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px} | |
| </style> | |
| </head> | |
| <body> | |
| <!-- HEADER --> | |
| <div class="header"> | |
| <div class="hdr-left"> | |
| <div class="hdr-logo">⚽</div> | |
| <div> | |
| <div class="hdr-league">Liga BetPlay Dimayor</div> | |
| <div class="hdr-sub">2026-I · Torneo Apertura · Fútbol Colombiano</div> | |
| </div> | |
| </div> | |
| <div class="hdr-right"> | |
| <div class="hdr-fecha">Fecha 19</div> | |
| <div class="hdr-date">3 de mayo de 2026</div> | |
| </div> | |
| </div> | |
| <div class="layout"> | |
| <main> | |
| <!-- HERO --> | |
| <div class="hero"> | |
| <div class="hero-title">Jornada de<br><em>Infarto</em></div> | |
| <div class="hero-sub">5 partidos simultáneos · 2 cupos en juego · drama hasta el final</div> | |
| </div> | |
| <!-- TIMELINE CONTROL --> | |
| <div class="section"> | |
| <div class="tl-top"> | |
| <div class="tl-label">🕐 Línea de Tiempo</div> | |
| <div class="min-badge" id="minBadge">0'</div> | |
| </div> | |
| <div class="slider-wrap"> | |
| <div class="slider-marks"> | |
| <span>0'</span><span>15'</span><span>30'</span><span>45'</span> | |
| <span>60'</span><span>75'</span><span>90'</span><span>95'+</span> | |
| </div> | |
| <input type="range" id="slider" min="0" max="95" value="0" step="1"> | |
| <div class="ht-mark">ET</div> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn primary" id="playBtn" onclick="togglePlay()">▶ Reproducir</button> | |
| <button class="btn" onclick="resetTL()">↩ Inicio</button> | |
| <div class="speed-wrap"> | |
| <span class="speed-label">Velocidad</span> | |
| <select class="speed-select" id="speedSel"> | |
| <option value="600">Lento</option> | |
| <option value="250" selected>Normal</option> | |
| <option value="80">Rápido</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="moments-label">⚡ Momentos Clave</div> | |
| <div class="moments-row" id="momentsRow"></div> | |
| </div> | |
| <!-- MATCH CARDS --> | |
| <div class="matches" id="matchCards"></div> | |
| <!-- EVENT FEED --> | |
| <div class="section"> | |
| <div class="feed-hdr"> | |
| <div class="feed-label">📡 Relato del partido</div> | |
| <div class="feed-count" id="feedCount">0 goles</div> | |
| </div> | |
| <div class="feed-list" id="feedList"> | |
| <div class="feed-empty">Mueve el deslizador para ver qué pasó…</div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- SIDEBAR --> | |
| <aside class="sidebar"> | |
| <div class="standings-card"> | |
| <div class="sc-title">Tabla en Vivo</div> | |
| <div class="sc-sub">Posiciones proyectadas · Fecha 19</div> | |
| <div class="sc-note">* Proyección si los marcadores actuales fueran definitivos. Desempate real por diferencia de goles no aplicado.</div> | |
| <div id="standingsList"></div> | |
| </div> | |
| </aside> | |
| </div> | |
| <script> | |
| // ═══════════════ DATA ═══════════════ | |
| const MATCHES = [ | |
| { | |
| id:'sfe_int', | |
| hot:true, | |
| home:{key:'sfe',name:'Santa Fe', short:'SFE',emoji:'❤️', color:'#D42535',pts:26}, | |
| away:{key:'int',name:'Inter Bogotá', short:'INT',emoji:'🌐',color:'#1A6FD4',pts:28}, | |
| venue:'El Campín · Bogotá', | |
| events:[ | |
| {min:49, team:'away', player:'F. Sanguinetti', note:'Inter se adelanta — Santa Fe en peligro', emoji:'😱'}, | |
| {min:70, team:'home', player:'H. Rodallega', note:'¡RODALLEGA! El Campín explota — iguala el legendario', emoji:'🔥'}, | |
| {min:74, team:'home', player:'E. Olivera', note:'Olivera la vuelta — ¡Santa Fe delante!', emoji:'🚀'}, | |
| {min:83, team:'home', player:'N. Bustos', note:'¡Bustos y Santa Fe clasifica a playoffs!', emoji:'🎉'}, | |
| ] | |
| }, | |
| { | |
| id:'tol_cal', | |
| hot:false, | |
| home:{key:'tol',name:'Deportes Tolima', short:'TOL',emoji:'🍷',color:'#8B0000',pts:30}, | |
| away:{key:'cal',name:'Deportivo Cali', short:'CAL',emoji:'🍃',color:'#228B22',pts:26}, | |
| venue:'M. Murillo Toro · Ibagué', | |
| events:[ | |
| {min:35, team:'home', player:'E. Valencia', note:'Tolima abre el marcador de penalti', emoji:'⚽'}, | |
| {min:63, team:'away', player:'E. Valencia', note:'Cali empata de penalti — pero sigue eliminado', emoji:'😤'}, | |
| ] | |
| }, | |
| { | |
| id:'med_agu', | |
| hot:false, | |
| home:{key:'med',name:'Ind. Medellín', short:'MED',emoji:'🔴',color:'#CC0000',pts:26}, | |
| away:{key:'agu',name:'Águilas Doradas', short:'AGU',emoji:'🦅',color:'#DAA520',pts:21}, | |
| venue:'Atanasio Girardot · Medellín', | |
| events:[ | |
| {min:28, team:'away', player:'A. Ricaurte', note:'¡Águilas sorprende en el Atanasio!', emoji:'😲'}, | |
| {min:55, team:'away', player:'J. Rivaldo', note:'Doble golpe — Medellín queda eliminado', emoji:'💀'}, | |
| {min:88, team:'home', player:'D. Moreno', note:'Solo el honor para el Poderoso', emoji:'😢'}, | |
| ] | |
| }, | |
| { | |
| id:'ali_mil', | |
| hot:false, | |
| home:{key:'ali',name:'Alianza FC', short:'ALI',emoji:'🟠',color:'#E06000',pts:20}, | |
| away:{key:'mil',name:'Millonarios', short:'MIL',emoji:'💙',color:'#003087',pts:25}, | |
| venue:'Municipal · Valledupar', | |
| events:[ | |
| {min:44, team:'home', player:'Y. Londoño', note:'Alianza se adelanta antes del descanso', emoji:'⚽'}, | |
| {min:52, team:'home', player:'P. Franco', note:'2-0 — Millonarios en crisis total', emoji:'😰'}, | |
| {min:58, team:'away', player:'R. Contreras',note:'Contreras recorta — el Embajador despierta', emoji:'💪'}, | |
| {min:91, team:'away', player:'R. Contreras',note:'Contreras empata al 91\'… demasiado tarde para clasificar', emoji:'😭'}, | |
| ] | |
| }, | |
| { | |
| id:'for_buc', | |
| hot:false, | |
| home:{key:'for',name:'Fortaleza FC', short:'FOR',emoji:'🏰',color:'#006400',pts:18}, | |
| away:{key:'buc',name:'Bucaramanga', short:'BUC',emoji:'🟡',color:'#D4A800',pts:22}, | |
| venue:'La Independencia · Tunja', | |
| events:[ | |
| {min:39, team:'home', player:'J.S. Herrera', note:'Fortaleza se adelanta en Tunja', emoji:'⚽'}, | |
| {min:78, team:'home', player:'A. Arroyo', note:'Arroyo liquida a Bucaramanga', emoji:'✅'}, | |
| {min:85, team:'away', player:'F. Sambueza', note:'El veterano Sambueza descuenta', emoji:'⚽'}, | |
| ] | |
| }, | |
| ]; | |
| const KEY_MOMENTS = [ | |
| {min:0, label:'Inicio', emoji:'🟢'}, | |
| {min:28, label:'Águilas 1-0 MED', emoji:'🦅'}, | |
| {min:35, label:'TOL 1-0 CAL', emoji:'⚽'}, | |
| {min:39, label:'FOR 1-0 BUC', emoji:'🏰'}, | |
| {min:44, label:'ALI 1-0 MIL', emoji:'⚽'}, | |
| {min:45, label:'Medio Tiempo', emoji:'⏸️'}, | |
| {min:49, label:'INT 1-0 SFE !', emoji:'😱'}, | |
| {min:55, label:'Águilas 2-0 MED', emoji:'💀'}, | |
| {min:63, label:'TOL 1-1 CAL', emoji:'⚽'}, | |
| {min:70, label:'Rodallega gol', emoji:'🔥'}, | |
| {min:74, label:'SFE 2-1 INT', emoji:'🚀'}, | |
| {min:83, label:'SFE clasifica', emoji:'🎉'}, | |
| {min:91, label:'Contreras 91\'', emoji:'😭'}, | |
| {min:95, label:'Final', emoji:'🏁'}, | |
| ]; | |
| // Teams NOT playing today (safe top-8 already secured before this gameday) | |
| const SAFE_TEAMS = [ | |
| {key:'nac',name:'Nacional', emoji:'💚',pts:40}, | |
| {key:'jun',name:'Junior', emoji:'🔵',pts:35}, | |
| {key:'pas',name:'D. Pasto', emoji:'⚫',pts:34}, | |
| {key:'onc',name:'Once Caldas', emoji:'⚪',pts:33}, | |
| {key:'ame',name:'América', emoji:'🔴',pts:30}, | |
| ]; | |
| // ═══════════════ STATE ═══════════════ | |
| let cur = 0; | |
| let playing = false; | |
| let playTimer = null; | |
| const prevStatus = {}; // track flashing | |
| // ═══════════════ HELPERS ═══════════════ | |
| function getScore(match, min) { | |
| let h=0,a=0; | |
| for(const e of match.events) { | |
| if(e.min<=min){e.team==='home'?h++:a++} | |
| } | |
| return [h,a]; | |
| } | |
| function gainPts(h,a){ | |
| if(h>a)return[3,0]; | |
| if(a>h)return[0,3]; | |
| return[1,1]; | |
| } | |
| // ═══════════════ STANDINGS ═══════════════ | |
| function computeStandings(min) { | |
| const live = {}; | |
| for(const t of SAFE_TEAMS) live[t.key]={...t,livePoints:t.pts,safe:true}; | |
| for(const m of MATCHES) { | |
| live[m.home.key]={key:m.home.key,name:m.home.name,emoji:m.home.emoji,pts:m.home.pts,livePoints:m.home.pts,safe:false}; | |
| live[m.away.key]={key:m.away.key,name:m.away.name,emoji:m.away.emoji,pts:m.away.pts,livePoints:m.away.pts,safe:false}; | |
| } | |
| for(const m of MATCHES) { | |
| const [h,a]=getScore(m,min); | |
| const [hp,ap]=gainPts(h,a); | |
| live[m.home.key].livePoints+=hp; | |
| live[m.away.key].livePoints+=ap; | |
| } | |
| return Object.values(live).sort((a,b)=>b.livePoints-a.livePoints||a.name.localeCompare(b.name)); | |
| } | |
| function renderStandings(min) { | |
| const sorted = computeStandings(min); | |
| const el = document.getElementById('standingsList'); | |
| let html=''; | |
| sorted.forEach((t,i)=>{ | |
| const pos=i+1; | |
| const isIn=pos<=8; | |
| const cls=t.safe?'safe':(isIn?'in':(pos<=10?'bubble':'out')); | |
| const gain=t.livePoints-t.pts; | |
| const gainStr=gain>0?'+'+gain:(gain<0?gain:''); | |
| const gainCls=gain>0?'pos':gain<0?'neg':''; | |
| const statusKey=t.key+':'+isIn; | |
| const shouldFlash = prevStatus[t.key] && prevStatus[t.key]!==statusKey; | |
| prevStatus[t.key]=statusKey; | |
| if(pos===9){ | |
| html+=`<div class="cutoff-div"><div class="cutoff-line"></div><div class="cutoff-txt">Clasificados</div><div class="cutoff-line"></div></div>`; | |
| } | |
| html+=`<div class="st-row ${cls}${shouldFlash?' flash':''}" id="st-${t.key}"> | |
| <div class="st-pos">${pos}</div> | |
| <div class="st-emoji">${t.emoji}</div> | |
| <div class="st-name">${t.name}</div> | |
| <div class="st-gain ${gainCls}">${gainStr}</div> | |
| <div class="st-pts">${t.livePoints}</div> | |
| </div>`; | |
| }); | |
| el.innerHTML=html; | |
| } | |
| // ═══════════════ MATCH CARDS ═══════════════ | |
| function buildMatchCards() { | |
| const el=document.getElementById('matchCards'); | |
| el.innerHTML=MATCHES.map(m=>{ | |
| const pips=m.events.map(e=> | |
| `<div class="epip future" id="pip-${m.id}-${e.min}" | |
| style="left:calc(${e.min}/95*100%);background:${e.team==='home'?m.home.color:m.away.color}" | |
| title="${e.min}' ${e.player}"></div>` | |
| ).join(''); | |
| return `<div class="match-card${m.hot?' hot':''}" id="card-${m.id}"> | |
| <div class="mc-top"> | |
| <div class="team-side"> | |
| <div class="t-emoji">${m.home.emoji}</div> | |
| <div> | |
| <div class="t-short">${m.home.short}</div> | |
| <div class="t-tag tag-bubble" id="htag-${m.id}"></div> | |
| </div> | |
| </div> | |
| <div class="score-mid"> | |
| <div class="score-num" id="score-${m.id}">0 – 0</div> | |
| <div class="score-venue">${m.venue}</div> | |
| </div> | |
| <div class="team-side away"> | |
| <div class="t-emoji">${m.away.emoji}</div> | |
| <div> | |
| <div class="t-short">${m.away.short}</div> | |
| <div class="t-tag tag-bubble" id="atag-${m.id}"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mini-tl"> | |
| <div class="mini-track"> | |
| <div class="mini-progress" id="prog-${m.id}"></div> | |
| </div> | |
| <div class="mini-cursor" id="cursor-${m.id}"></div> | |
| ${pips} | |
| </div> | |
| </div>`; | |
| }).join(''); | |
| } | |
| // ═══════════════ FEED ═══════════════ | |
| function renderFeed(min) { | |
| const all=[]; | |
| for(const m of MATCHES) { | |
| for(const e of m.events) { | |
| if(e.min<=min) all.push({...e, matchHome:m.home.short, matchAway:m.away.short, matchId:m.id, | |
| color:e.team==='home'?m.home.color:m.away.color}); | |
| } | |
| } | |
| all.sort((a,b)=>b.min-a.min); | |
| const el=document.getElementById('feedList'); | |
| document.getElementById('feedCount').textContent=all.length+' gol'+(all.length!==1?'es':''); | |
| if(!all.length){el.innerHTML='<div class="feed-empty">Mueve el deslizador para ver qué pasó…</div>';return;} | |
| el.innerHTML=all.map(e=>` | |
| <div class="feed-item" style="border-left-color:${e.color}"> | |
| <div class="fi-min">${e.min}'</div> | |
| <div style="font-size:16px">${e.emoji}</div> | |
| <div> | |
| <div class="fi-main">${e.player} — ${e.note}</div> | |
| <div class="fi-sub">${e.matchHome} vs ${e.matchAway}</div> | |
| </div> | |
| </div>`).join(''); | |
| } | |
| // ═══════════════ KEY MOMENTS ═══════════════ | |
| function buildMoments() { | |
| const el=document.getElementById('momentsRow'); | |
| el.innerHTML=KEY_MOMENTS.map(m=>` | |
| <div class="moment-chip" data-min="${m.min}" onclick="jumpTo(${m.min})"> | |
| <span class="chip-min">${m.min}'</span> | |
| <span class="chip-lbl">${m.label}</span> | |
| </div>`).join(''); | |
| } | |
| // ═══════════════ CORE UPDATE ═══════════════ | |
| function update(min) { | |
| cur=min; | |
| const pct=(min/95*100).toFixed(1)+'%'; | |
| // Slider fill | |
| document.getElementById('slider').style.setProperty('--pct',pct); | |
| document.getElementById('minBadge').textContent=min===95?"90+5'":min+"'"; | |
| for(const m of MATCHES) { | |
| const [h,a]=getScore(m,min); | |
| document.getElementById(`score-${m.id}`).textContent=`${h} – ${a}`; | |
| document.getElementById(`prog-${m.id}`).style.width=pct; | |
| document.getElementById(`cursor-${m.id}`).style.left=pct; | |
| // pips | |
| for(const e of m.events) { | |
| const pip=document.getElementById(`pip-${m.id}-${e.min}`); | |
| if(!pip)continue; | |
| const past=e.min<=min; | |
| const atNow=Math.abs(e.min-min)<=1; | |
| pip.className=`epip ${past?'past':'future'}${atNow?' at-now':''}`; | |
| } | |
| } | |
| renderFeed(min); | |
| renderStandings(min); | |
| // Highlight active moment chip | |
| document.querySelectorAll('.moment-chip').forEach(c=>{ | |
| c.classList.toggle('active',parseInt(c.dataset.min)===min); | |
| }); | |
| } | |
| function jumpTo(min) { | |
| stopPlay(); | |
| document.getElementById('slider').value=min; | |
| update(min); | |
| } | |
| function resetTL(){jumpTo(0)} | |
| // ═══════════════ PLAY ═══════════════ | |
| function togglePlay(){ | |
| if(playing){stopPlay();return;} | |
| playing=true; | |
| document.getElementById('playBtn').textContent='⏸ Pausar'; | |
| if(cur>=95)cur=0; | |
| const speed=()=>parseInt(document.getElementById('speedSel').value); | |
| function step(){ | |
| if(cur>=95){stopPlay();return;} | |
| cur++; | |
| document.getElementById('slider').value=cur; | |
| update(cur); | |
| playTimer=setTimeout(step,speed()); | |
| } | |
| playTimer=setTimeout(step,speed()); | |
| } | |
| function stopPlay(){ | |
| playing=false; | |
| clearTimeout(playTimer); | |
| document.getElementById('playBtn').textContent='▶ Reproducir'; | |
| } | |
| // ═══════════════ EVENTS ═══════════════ | |
| document.getElementById('slider').addEventListener('input',e=>{ | |
| stopPlay(); | |
| update(parseInt(e.target.value)); | |
| }); | |
| // ═══════════════ INIT ═══════════════ | |
| buildMatchCards(); | |
| buildMoments(); | |
| update(0); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment