1
TOOLS#eee9fe17
FIFA World Cup 26 -- Live Hub
@GigaChad·deposited 4d ago·updated 1h ago·46 views
TOOLS#eee9fe17
FIFA World Cup 26 -- Live Hub
GI
@GigaChad
46Views
0Comments
1Forks
0Saves
SHARE · REMIX
FIFA World Cup 26 -- Live Hub — a HTML Tools widget by @GigaChad.
CONTROLS
No comments yet. Be the first!
✦ Remix with AI
SDK in this widget
vibes.onReadyvibes.savevibes.load
Generated prompt
You are helping me modify a vibe-coded widget from itjustvibes.com.
[VIBE CODE: "FIFA World Cup 26 -- Live Hub" by @GigaChad]
Source: https://itjustvibes.com/GigaChad/fifa-world-cup-26-live-hub
Type: HTML
--- SOURCE CODE ---
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>FIFA World Cup 26 -- Live Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Manrope:wght@400;500;600;700;800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--f-display:"Anton",Impact,"Haettenschweiler","Arial Narrow Bold","Franklin Gothic Heavy",sans-serif;
--f-body:"Manrope",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
--f-label:"Space Grotesk",ui-monospace,"SF Mono",system-ui,"Segoe UI",Roboto,Arial,sans-serif;
--ink:#070B12; --ink-2:#0B1220; --ink-3:#101a2c;
--panel:rgba(255,255,255,.038); --panel-2:rgba(255,255,255,.06);
--line:rgba(255,255,255,.09); --line-strong:rgba(255,255,255,.16);
--pitch:#19E08A; --pitch-deep:#0c8f57;
--gold:#F7C948; --gold-deep:#C9971b;
--coral:#FF4D6D; --coral-soft:#ff8aa0;
--sky:#46C2FF; --violet:#A78BFA;
--text:#EAF0F8; --muted:#93A1B8; --muted-2:#6c7a93;
--r-lg:20px; --r-md:14px; --r-sm:10px;
--maxw:1180px; --shadow:0 24px 60px -20px rgba(0,0,0,.7);
--grad-pitch:linear-gradient(120deg,var(--pitch),#39e6c2);
--grad-gold:linear-gradient(120deg,var(--gold),#ffe39a);
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{font-family:var(--f-body),system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:var(--text);background:var(--ink);line-height:1.5;overflow-x:hidden;-webkit-font-smoothing:antialiased;position:relative;min-height:100vh}
body::before{content:"";position:fixed;inset:0;z-index:-2;background:radial-gradient(1100px 620px at 50% 118%, rgba(25,224,138,.20), transparent 60%),radial-gradient(900px 520px at 84% -8%, rgba(70,194,255,.13), transparent 55%),radial-gradient(760px 480px at 8% 4%, rgba(255,77,109,.10), transparent 55%),linear-gradient(180deg,#05080e 0%, #070B12 38%, #060a11 100%)}
body::after{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;background-image:radial-gradient(circle at 50% 100%, transparent 0 360px, rgba(255,255,255,.025) 361px 363px, transparent 364px),repeating-linear-gradient(90deg, rgba(255,255,255,.012) 0 1px, transparent 1px 86px);mask-image:linear-gradient(180deg, transparent, #000 30%, #000 88%, transparent);opacity:.7}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 18px}
.topbar{position:sticky;top:0;z-index:60;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);background:linear-gradient(180deg, rgba(7,11,18,.86), rgba(7,11,18,.55));border-bottom:1px solid var(--line)}
.topbar-inner{display:flex;align-items:center;gap:14px;padding:11px 18px;max-width:var(--maxw);margin:0 auto}
.brand{display:flex;align-items:center;gap:10px;font-family:var(--f-display),sans-serif;letter-spacing:.06em;font-size:18px;line-height:1;white-space:nowrap}
.brand .ball{width:24px;height:24px;border-radius:50%;flex:none;background:radial-gradient(circle at 34% 30%, #fff 0 22%, transparent 23%),conic-gradient(from 20deg,#0b0f17,#1a2740,#0b0f17);box-shadow:0 0 0 1.5px var(--line-strong), inset 0 0 8px rgba(0,0,0,.6)}
.brand b{background:var(--grad-pitch);-webkit-background-clip:text;background-clip:text;color:transparent}
.brand .yr{color:var(--gold)}
.tz-wrap{margin-left:auto;display:flex;align-items:center;gap:8px}
.tz-wrap label{font:600 10.5px/1 var(--f-label);letter-spacing:.14em;color:var(--muted-2);text-transform:uppercase;display:none}
.tz-select{appearance:none;-webkit-appearance:none;background:var(--panel-2);color:var(--text);border:1px solid var(--line-strong);border-radius:999px;padding:8px 30px 8px 14px;font:600 12.5px/1 var(--f-body);cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2393A1B8' stroke-width='3'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center;max-width:60vw;text-overflow:ellipsis}
.tz-select:focus-visible{outline:2px solid var(--sky);outline-offset:2px}
.hero{position:relative;overflow:hidden;border-bottom:1px solid var(--line)}
.hero-inner{max-width:var(--maxw);margin:0 auto;padding:40px 18px 26px;position:relative;z-index:2}
.eyebrow{font:600 11.5px/1 var(--f-label);letter-spacing:.26em;text-transform:uppercase;color:var(--pitch);display:inline-flex;align-items:center;gap:10px;margin-bottom:14px}
.eyebrow .dot{width:6px;height:6px;border-radius:50%;background:var(--pitch);box-shadow:0 0 10px var(--pitch)}
.hero h1{font-family:var(--f-display),sans-serif;font-weight:400;font-size:clamp(44px,12.5vw,128px);line-height:.82;letter-spacing:.005em;text-transform:uppercase;margin:2px 0 6px}
.hero h1 .l1{display:block}
.hero h1 .l2{display:flex;align-items:baseline;gap:.18em;flex-wrap:wrap}
.hero h1 .word{background:linear-gradient(180deg,#fff,#cdd8e8);-webkit-background-clip:text;background-clip:text;color:transparent}
.hero h1 .twentysix{background:var(--grad-gold);-webkit-background-clip:text;background-clip:text;color:transparent;filter:drop-shadow(0 6px 22px rgba(247,201,72,.28))}
.hosts{display:flex;align-items:center;gap:9px;font-size:clamp(22px,6vw,34px);margin:8px 0 2px}
.hero-sub{color:var(--muted);font-size:clamp(14px,3.5vw,18px);font-weight:500;max-width:560px}
.hero-sub b{color:var(--text);font-weight:800}
.hero-strip{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}
.hchip{display:flex;flex-direction:column;gap:2px;padding:11px 15px;border-radius:var(--r-md);background:var(--panel);border:1px solid var(--line);min-width:96px;backdrop-filter:blur(8px)}
.hchip-n{font-family:var(--f-display),sans-serif;font-size:24px;line-height:1;color:var(--text)}
.hchip-n.g{background:var(--grad-pitch);-webkit-background-clip:text;background-clip:text;color:transparent}
.hchip-n.gold{background:var(--grad-gold);-webkit-background-clip:text;background-clip:text;color:transparent}
.hchip-l{font:600 9.5px/1.2 var(--f-label);letter-spacing:.13em;text-transform:uppercase;color:var(--muted-2)}
.nextbox{margin-top:22px;border-radius:var(--r-lg);padding:16px 18px;position:relative;overflow:hidden;background:linear-gradient(120deg, rgba(255,77,109,.10), rgba(70,194,255,.06));border:1px solid var(--line-strong);box-shadow:var(--shadow)}
.nextbox.islive{background:linear-gradient(120deg, rgba(255,77,109,.20), rgba(255,77,109,.04))}
.nextbox-top{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}
.nb-tag{font:700 10.5px/1 var(--f-label);letter-spacing:.2em;text-transform:uppercase;color:var(--coral-soft);display:inline-flex;align-items:center;gap:8px}
.nb-tag .pulse{width:8px;height:8px;border-radius:50%;background:var(--coral);box-shadow:0 0 0 0 rgba(255,77,109,.6);animation:pulse 1.8s infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(255,77,109,.55)}70%{box-shadow:0 0 0 12px rgba(255,77,109,0)}100%{box-shadow:0 0 0 0 rgba(255,77,109,0)}}
.nb-match{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.nb-side{display:flex;align-items:center;gap:9px;font-weight:800;font-size:clamp(16px,4.6vw,22px)}
.nb-side .fl{font-size:1.35em;line-height:1}
.nb-vs{font:700 12px/1 var(--f-label);letter-spacing:.1em;color:var(--muted-2);padding:4px 8px;border:1px solid var(--line);border-radius:8px}
.nb-meta{color:var(--muted);font-size:12.5px;margin-top:9px;font-weight:600;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.countdown{display:flex;gap:8px}
.cd-cell{background:rgba(0,0,0,.28);border:1px solid var(--line);border-radius:10px;padding:7px 10px;text-align:center;min-width:50px}
.cd-num{font-family:var(--f-display),sans-serif;font-size:24px;line-height:1;color:#fff;font-variant-numeric:tabular-nums}
.cd-lab{font:600 8.5px/1 var(--f-label);letter-spacing:.16em;color:var(--muted-2);text-transform:uppercase;margin-top:4px}
.ticker{border-bottom:1px solid var(--line);background:rgba(0,0,0,.32);overflow:hidden;position:relative;white-space:nowrap}
.ticker::before,.ticker::after{content:"";position:absolute;top:0;bottom:0;width:56px;z-index:3;pointer-events:none}
.ticker::before{left:0;background:linear-gradient(90deg,var(--ink),transparent)}
.ticker::after{right:0;background:linear-gradient(270deg,var(--ink),transparent)}
.ticker-track{display:inline-flex;align-items:center;will-change:transform;animation:scroll 70s linear infinite}
.ticker:hover .ticker-track{animation-play-state:paused}
@keyframes scroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.tk-item{display:inline-flex;align-items:center;gap:8px;padding:11px 18px;font-size:13px;font-weight:700;border-right:1px solid var(--line)}
.tk-item .fl{font-size:16px;line-height:1}
.tk-sc{font-variant-numeric:tabular-nums;color:#fff;background:rgba(255,255,255,.07);border-radius:6px;padding:2px 8px;font-weight:800}
.tk-vs{color:var(--muted-2);font:700 11px/1 var(--f-label)}
.tk-time{color:var(--pitch);font-weight:800;font-variant-numeric:tabular-nums}
.tk-ft{color:var(--muted-2);font:700 9.5px/1 var(--f-label);letter-spacing:.12em}
.tk-live{color:var(--coral);font:700 9.5px/1 var(--f-label);letter-spacing:.12em}
.tabs-outer{position:sticky;top:53px;z-index:55;background:linear-gradient(180deg,rgba(7,11,18,.92),rgba(7,11,18,.7));backdrop-filter:blur(12px);border-bottom:1px solid var(--line)}
.tabs{display:flex;gap:4px;max-width:var(--maxw);margin:0 auto;padding:8px 18px;overflow-x:auto;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tab{flex:none;border:1px solid transparent;background:transparent;color:var(--muted);font:700 13px/1 var(--f-body);padding:9px 15px;border-radius:999px;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;gap:7px;transition:.18s}
.tab:hover{color:var(--text);background:var(--panel)}
.tab[aria-selected="true"]{color:var(--ink);background:var(--grad-pitch);font-weight:800;box-shadow:0 6px 20px -8px rgba(25,224,138,.7)}
.tab .ic{width:15px;height:15px;display:inline-block}
.tab[aria-selected="true"] .ic{stroke:var(--ink)}
main{padding:26px 0 90px}
.view{animation:fade .4s ease both}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin:6px 0 16px;flex-wrap:wrap}
.section-head h2{font-family:var(--f-display),sans-serif;font-weight:400;text-transform:uppercase;letter-spacing:.02em;font-size:clamp(24px,6vw,40px);line-height:.95}
.section-head .lede{color:var(--muted);font-size:13.5px;font-weight:500;max-width:520px}
.klabel{font:600 11px/1 var(--f-label);letter-spacing:.2em;text-transform:uppercase;color:var(--pitch);margin-bottom:8px;display:block}
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:30px}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);padding:16px 16px 14px;position:relative;overflow:hidden}
.stat::after{content:"";position:absolute;right:-20px;top:-20px;width:80px;height:80px;border-radius:50%;background:var(--accent,var(--pitch));opacity:.10;filter:blur(8px)}
.stat .n{font-family:var(--f-display),sans-serif;font-size:34px;line-height:1;color:var(--text)}
.stat .l{font:600 10px/1.3 var(--f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:6px}
.stat .s{font-size:11.5px;color:var(--muted-2);margin-top:5px;font-weight:600}
.progress-wrap{margin:0 0 30px}
.progress-meta{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}
.progress-meta .pm-l{font:700 12px/1 var(--f-label);letter-spacing:.06em;color:var(--muted);text-transform:uppercase}
.progress-meta .pm-r{font-weight:800;font-size:13px;color:var(--muted)}
.progress-meta .pm-r b{color:var(--pitch);font-family:var(--f-display);font-size:18px}
.bar{height:9px;border-radius:99px;background:rgba(255,255,255,.07);overflow:hidden;border:1px solid var(--line)}
.bar > i{display:block;height:100%;background:var(--grad-pitch);border-radius:99px;transition:width .8s cubic-bezier(.2,.7,.2,1)}
.feature{border-radius:var(--r-lg);overflow:hidden;margin-bottom:30px;border:1px solid var(--line-strong);background:linear-gradient(120deg,rgba(70,194,255,.08),rgba(25,224,138,.05));position:relative}
.feature-tag{position:absolute;top:14px;left:16px;font:700 10px/1 var(--f-label);letter-spacing:.18em;text-transform:uppercase;color:var(--sky);z-index:2}
.feature-body{padding:42px 20px 24px;display:flex;align-items:center;justify-content:center;gap:clamp(14px,5vw,46px);flex-wrap:wrap}
.feat-team{display:flex;flex-direction:column;align-items:center;gap:7px;text-align:center}
.feat-team .fl{font-size:clamp(40px,12vw,68px);line-height:1;filter:drop-shadow(0 8px 16px rgba(0,0,0,.5))}
.feat-team .nm{font-family:var(--f-display);font-size:clamp(18px,5vw,28px);text-transform:uppercase;letter-spacing:.02em}
.feat-mid{display:flex;flex-direction:column;align-items:center;gap:6px}
.feat-mid .vs{font-family:var(--f-display);font-size:clamp(22px,6vw,34px);color:var(--coral)}
.feat-mid .ti{font-weight:800;font-size:14px;font-variant-numeric:tabular-nums}
.feat-mid .dt{font-size:11.5px;color:var(--muted);font-weight:600}
.day-head{display:flex;align-items:center;gap:12px;margin:26px 0 12px;position:sticky;top:106px;z-index:30}
.day-head .d{font-family:var(--f-display);font-size:clamp(17px,4.4vw,22px);text-transform:uppercase;letter-spacing:.02em;white-space:nowrap;background:var(--ink);padding-right:4px}
.day-head .ln{flex:1;height:1px;background:var(--line)}
.day-head .ct{font:700 10.5px/1 var(--f-label);letter-spacing:.1em;color:var(--muted-2);text-transform:uppercase;white-space:nowrap;background:var(--ink);padding-left:4px}
.matches{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:12px}
.match-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);padding:13px 14px 11px;transition:transform .18s ease, border-color .18s ease, background .18s ease;position:relative;overflow:hidden}
.match-card:hover{transform:translateY(-3px);border-color:var(--line-strong);background:var(--panel-2)}
.match-card.live{border-color:rgba(255,77,109,.5);background:linear-gradient(120deg,rgba(255,77,109,.07),var(--panel))}
.match-card.final{border-color:rgba(247,201,72,.4);background:linear-gradient(120deg,rgba(247,201,72,.08),var(--panel))}
.match-card.today{box-shadow:inset 0 0 0 1px rgba(25,224,138,.3)}
.mc-head{display:flex;align-items:center;gap:7px;margin-bottom:10px}
.chip{font:700 9.5px/1 var(--f-label);letter-spacing:.08em;text-transform:uppercase;padding:4px 8px;border-radius:7px;white-space:nowrap}
.chip.stage{color:#06121b}
.chip.grp{background:rgba(255,255,255,.07);color:var(--muted);border:1px solid var(--line)}
.chip.mno{margin-left:auto;background:transparent;color:var(--muted-2);font-weight:700;padding-right:0}
.status{font:700 9.5px/1 var(--f-label);letter-spacing:.1em;text-transform:uppercase;padding:4px 8px;border-radius:7px;display:inline-flex;align-items:center;gap:6px}
.status.up{color:var(--sky);background:rgba(70,194,255,.12)}
.status.ft{color:var(--muted);background:rgba(255,255,255,.06)}
.status.lv{color:#fff;background:var(--coral)}
.status.lv .d{width:7px;height:7px;border-radius:50%;background:#fff;animation:blink 1s steps(2) infinite}
.status.td{color:var(--pitch);background:rgba(25,224,138,.13)}
@keyframes blink{50%{opacity:.25}}
.mc-body{display:flex;align-items:center;gap:12px}
.mc-teams{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
.team-row{display:flex;align-items:center;gap:9px;min-width:0}
.team-row .fl{font-size:21px;line-height:1;flex:none;width:26px;text-align:center}
.team-row .nm{font-weight:700;font-size:14.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.team-row.ph .nm{font-style:italic;font-weight:600;color:var(--muted);font-size:13px}
.team-row .ph-badge{font:700 9px/1 var(--f-label);color:var(--muted-2);border:1px dashed var(--line-strong);border-radius:5px;padding:3px 5px;flex:none;width:26px;text-align:center}
.team-row.win .nm{color:#fff}
.team-row.win .sc{color:var(--pitch)}
.sc{margin-left:auto;font-family:var(--f-display);font-size:21px;line-height:1;color:var(--muted);font-variant-numeric:tabular-nums;flex:none;padding-left:8px}
.mc-result{flex:none;text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:2px;min-width:74px}
.mc-result .big-time{font-family:var(--f-display);font-size:23px;line-height:1;color:#fff;font-variant-numeric:tabular-nums}
.mc-result .tz{font:600 9px/1 var(--f-label);letter-spacing:.1em;color:var(--muted-2);text-transform:uppercase}
.mc-result .ftbig{font-family:var(--f-display);font-size:13px;color:var(--muted);letter-spacing:.1em}
.mc-foot{margin-top:11px;padding-top:10px;border-top:1px solid var(--line);display:flex;align-items:center;gap:8px;color:var(--muted);font-size:11.5px;font-weight:600;flex-wrap:wrap}
.mc-foot .ic{width:13px;height:13px;flex:none;opacity:.7;stroke:#93A1B8}
.mc-foot .dot{width:3px;height:3px;border-radius:50%;background:var(--muted-2);flex:none}
.mc-foot .vn{display:inline-flex;align-items:center;gap:6px;min-width:0}
.mc-foot .vn span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.filters{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-bottom:8px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);padding:11px 12px;position:sticky;top:106px;z-index:35;backdrop-filter:blur(10px)}
.search{flex:1;min-width:160px;display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.25);border:1px solid var(--line);border-radius:999px;padding:8px 14px}
.search input{flex:1;background:none;border:none;outline:none;color:var(--text);font:600 13px/1 var(--f-body)}
.search input::placeholder{color:var(--muted-2)}
.search .ic{width:15px;height:15px;opacity:.6;flex:none;stroke:#93A1B8}
.fselect{appearance:none;-webkit-appearance:none;background:rgba(0,0,0,.25);border:1px solid var(--line);border-radius:999px;color:var(--text);font:600 12.5px/1 var(--f-body);padding:9px 30px 9px 13px;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2393A1B8' stroke-width='3'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center}
.ftoggle{border:1px solid var(--line);background:rgba(0,0,0,.25);color:var(--muted);font:700 12px/1 var(--f-body);padding:9px 14px;border-radius:999px;cursor:pointer;display:inline-flex;align-items:center;gap:7px;transition:.16s}
.ftoggle.on{background:var(--coral);color:#fff;border-color:transparent}
.ftoggle .d{width:7px;height:7px;border-radius:50%;background:currentColor}
.result-count{font:600 12px/1 var(--f-label);color:var(--muted-2);letter-spacing:.05em;margin:14px 2px 4px}
.empty{padding:50px 20px;text-align:center;color:var(--muted);border:1px dashed var(--line);border-radius:var(--r-md)}
.empty b{display:block;font-family:var(--f-display);font-size:22px;color:var(--text);margin-bottom:6px;letter-spacing:.02em}
.groups-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
.group-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);overflow:hidden}
.gc-head{display:flex;align-items:center;gap:10px;padding:12px 14px;border-bottom:1px solid var(--line);background:linear-gradient(120deg,rgba(255,255,255,.04),transparent)}
.gc-head .lt{font-family:var(--f-display);font-size:26px;line-height:1;width:42px;height:42px;display:flex;align-items:center;justify-content:center;border-radius:11px;color:var(--ink);background:var(--grad-pitch)}
.gc-head .ti{font:700 11px/1.3 var(--f-label);letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
.gc-head .ti b{display:block;font-family:var(--f-display);font-size:16px;letter-spacing:.02em;color:var(--text)}
.gtable{width:100%;border-collapse:collapse;font-size:13px}
.gtable th{font:700 9px/1 var(--f-label);letter-spacing:.08em;text-transform:uppercase;color:var(--muted-2);text-align:center;padding:9px 4px;border-bottom:1px solid var(--line)}
.gtable th.tl,.gtable td.tl{text-align:left;padding-left:13px}
.gtable td{padding:9px 4px;text-align:center;font-variant-numeric:tabular-nums;font-weight:700;border-bottom:1px solid rgba(255,255,255,.05)}
.gtable tr:last-child td{border-bottom:none}
.gtable .pos{width:20px;color:var(--muted-2);font-family:var(--f-label);font-weight:700}
.gtable .tm{display:flex;align-items:center;gap:9px}
.gtable .tm .fl{font-size:18px;width:22px;text-align:center;flex:none}
.gtable .tm .nm{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gtable .pts{color:var(--text);font-family:var(--f-display);font-size:16px}
.gtable tr.q1 td{background:linear-gradient(90deg,rgba(25,224,138,.13),transparent 70%)}
.gtable tr.q2 td{background:linear-gradient(90deg,rgba(25,224,138,.07),transparent 70%)}
.gtable tr.q3 td{background:linear-gradient(90deg,rgba(70,194,255,.06),transparent 70%)}
.badge-mini{font:700 8px/1 var(--f-label);letter-spacing:.06em;padding:2px 5px;border-radius:5px;text-transform:uppercase;margin-left:6px;vertical-align:middle}
.b-host{background:rgba(70,194,255,.16);color:var(--sky)}
.b-debut{background:rgba(167,139,250,.16);color:var(--violet)}
.b-hold{background:rgba(247,201,72,.16);color:var(--gold)}
.qual-note{display:flex;gap:14px;flex-wrap:wrap;margin:0 0 18px;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--panel);font-size:12.5px;color:var(--muted);font-weight:600;align-items:center}
.qual-note .k{display:inline-flex;align-items:center;gap:7px}
.qual-note .k i{width:11px;height:11px;border-radius:3px}
.bracket-scroll{overflow-x:auto;padding-bottom:14px;scrollbar-width:thin;-webkit-overflow-scrolling:touch}
.bracket-scroll::-webkit-scrollbar{height:8px}
.bracket-scroll::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:99px}
.bracket{display:flex;gap:18px;min-width:max-content;padding:4px 2px 10px}
.brd-col{display:flex;flex-direction:column;min-width:214px;gap:10px}
.brd-colhead{font:700 10.5px/1 var(--f-label);letter-spacing:.14em;text-transform:uppercase;color:var(--col,var(--pitch));padding:6px 2px 4px;display:flex;align-items:center;gap:8px}
.brd-colhead .ct{color:var(--muted-2);font-weight:700;margin-left:auto}
.brd-col .matchstack{display:flex;flex-direction:column;gap:10px;justify-content:space-around;flex:1}
.brd-node{background:var(--panel);border:1px solid var(--line);border-radius:11px;padding:9px 10px;position:relative;transition:.16s}
.brd-node:hover{border-color:var(--line-strong);background:var(--panel-2)}
.brd-node.gold{border-color:rgba(247,201,72,.45);background:linear-gradient(120deg,rgba(247,201,72,.1),var(--panel))}
.bn-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:7px}
.bn-no{font:700 8.5px/1 var(--f-label);letter-spacing:.08em;color:var(--muted-2)}
.bn-time{font:700 10px/1 var(--f-label);color:var(--pitch);font-variant-numeric:tabular-nums}
.bn-row{display:flex;align-items:center;gap:7px;padding:3px 0;font-size:12.5px;font-weight:700;font-style:italic;color:var(--muted)}
.bn-row .fl{font-size:14px;width:18px;text-align:center;flex:none;font-style:normal}
.bn-row .nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bn-vs{height:1px;background:var(--line);margin:2px 0}
.bn-venue{font:600 9px/1.3 var(--f-label);color:var(--muted-2);margin-top:6px;letter-spacing:.04em;display:flex;align-items:center;gap:5px}
.brd-final-wrap{display:flex;flex-direction:column;justify-content:center;gap:16px;min-width:236px}
.trophy-card{border-radius:var(--r-lg);padding:20px 18px;text-align:center;background:linear-gradient(160deg,rgba(247,201,72,.16),rgba(247,201,72,.03));border:1px solid rgba(247,201,72,.4);box-shadow:0 20px 50px -20px rgba(247,201,72,.4)}
.trophy-card .tr{font-size:42px;line-height:1;filter:drop-shadow(0 6px 14px rgba(247,201,72,.4))}
.trophy-card .tt{font-family:var(--f-display);font-size:26px;text-transform:uppercase;letter-spacing:.04em;background:var(--grad-gold);-webkit-background-clip:text;background-clip:text;color:transparent;margin:8px 0 3px}
.trophy-card .ts{font:600 10.5px/1.5 var(--f-label);letter-spacing:.08em;color:var(--gold);text-transform:uppercase}
.trophy-card .tv{font-size:12px;color:var(--muted);font-weight:600;margin-top:9px}
.stad-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
.stad-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);padding:16px 16px 14px;position:relative;overflow:hidden;transition:.18s}
.stad-card:hover{transform:translateY(-3px);border-color:var(--line-strong)}
.stad-card .flag-bg{position:absolute;right:-8px;top:-8px;font-size:64px;opacity:.08;line-height:1}
.stad-card .city{display:flex;align-items:center;gap:8px;font:700 10.5px/1 var(--f-label);letter-spacing:.12em;text-transform:uppercase;color:var(--pitch);margin-bottom:7px}
.stad-card h3{font-family:var(--f-display);font-size:21px;line-height:1.04;text-transform:uppercase;letter-spacing:.01em;margin-bottom:4px}
.stad-card .fifa{font-size:11px;color:var(--muted-2);font-style:italic;font-weight:600;margin-bottom:12px}
.stad-card .row{display:flex;justify-content:space-between;gap:10px;padding:7px 0;border-top:1px solid var(--line);font-size:12px}
.stad-card .row .l{color:var(--muted);font-weight:600}
.stad-card .row .v{font-weight:800;font-variant-numeric:tabular-nums}
.stad-card .tagline{margin-top:11px;font:700 9px/1 var(--f-label);letter-spacing:.1em;text-transform:uppercase;color:var(--gold);display:inline-flex;align-items:center;gap:6px}
.host-legend{display:flex;gap:18px;flex-wrap:wrap;margin-bottom:18px;color:var(--muted);font-size:12.5px;font-weight:600}
.host-legend b{font-family:var(--f-display);font-size:17px;color:var(--text);margin-right:5px}
.notes{margin-top:36px;padding:18px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--panel);color:var(--muted-2);font-size:11.5px;line-height:1.7;font-weight:500}
.notes b{color:var(--muted);font-weight:700}
@media (min-width:560px){ .tz-wrap label{display:inline} }
@media (max-width:520px){
.gtable .h-w,.gtable .h-d,.gtable .h-l,.gtable .c-w,.gtable .c-d,.gtable .c-l,.gtable .h-gf,.gtable .h-ga,.gtable .c-gf,.gtable .c-ga{display:none}
.matches{grid-template-columns:1fr}
.day-head,.filters{top:104px}
.mc-result{min-width:64px}
.nextbox-top{flex-direction:column;align-items:flex-start}
}
@media (max-width:400px){ .hero-inner{padding-top:30px} .stat-grid{grid-template-columns:1fr 1fr} }
@media (prefers-reduced-motion:reduce){ *{animation:none!important;transition:none!important} .ticker{overflow-x:auto} }
</style>
</head>
<body>
<header class="topbar">
<div class="topbar-inner">
<div class="brand"><span class="ball"></span><span><b>WORLD CUP</b> <span class="yr">26</span></span></div>
<div class="tz-wrap">
<label for="tz">Kickoffs in</label>
<select id="tz" class="tz-select" aria-label="Choose your timezone"></select>
</div>
</div>
</header>
<section class="hero">
<div class="hero-inner">
<span class="eyebrow"><span class="dot"></span>June 11 - July 19, 2026 · 23rd FIFA World Cup</span>
<h1><span class="l1 word">FIFA World Cup</span><span class="l2"><span class="twentysix">2026</span></span></h1>
<div class="hosts" aria-label="Hosted by Canada, Mexico and the USA">🇨🇦 🇲🇽 🇺🇸</div>
<p class="hero-sub"><b>48 nations. 104 matches. 16 stadiums.</b> The largest World Cup ever, played across Canada, Mexico and the United States -- every kickoff, group and bracket in one place.</p>
<div class="hero-strip" id="heroStrip"></div>
<div class="nextbox" id="nextBox"></div>
</div>
</section>
<div class="ticker" aria-label="Match ticker"><div class="ticker-track" id="ticker"></div></div>
<div class="tabs-outer">
<nav class="tabs" id="tabs" role="tablist" aria-label="Sections">
<button class="tab" role="tab" data-view="overview" aria-selected="true"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="#93A1B8" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>Overview</button>
<button class="tab" role="tab" data-view="schedule" aria-selected="false"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="#93A1B8" stroke-width="2" stroke-linecap="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>Schedule</button>
<button class="tab" role="tab" data-view="groups" aria-selected="false"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="#93A1B8" stroke-width="2" stroke-linecap="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>Groups</button>
<button class="tab" role="tab" data-view="bracket" aria-selected="false"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="#93A1B8" stroke-width="2" stroke-linecap="round"><path d="M3 3v6a3 3 0 0 0 3 3h4"/><path d="M3 21v-6a3 3 0 0 1 3-3h4"/><path d="M14 12h4a3 3 0 0 0 3-3V3"/><circle cx="20" cy="12" r="1.6"/></svg>Bracket</button>
<button class="tab" role="tab" data-view="stadiums" aria-selected="false"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke="#93A1B8" stroke-width="2" stroke-linecap="round"><path d="M2 12a10 5 0 0 0 20 0"/><path d="M2 12a10 5 0 0 1 20 0"/><path d="M7 11.5v4"/><path d="M17 11.5v4"/><path d="M12 12v5"/></svg>Stadiums</button>
</nav>
</div>
<main>
<div class="wrap">
<div id="view-overview" class="view"></div>
<div id="view-schedule" class="view" hidden></div>
<div id="view-groups" class="view" hidden></div>
<div id="view-bracket" class="view" hidden></div>
<div id="view-stadiums" class="view" hidden></div>
</div>
</main>
<script>
/* ===================== DATA ===================== */
const FLAGS={"Mexico":"🇲🇽","South Africa":"🇿🇦","South Korea":"🇰🇷","Czechia":"🇨🇿","Canada":"🇨🇦","Bosnia & Herzegovina":"🇧🇦","Qatar":"🇶🇦","Switzerland":"🇨🇭","Brazil":"🇧🇷","Morocco":"🇲🇦","Haiti":"🇭🇹","Scotland":"🏴","USA":"🇺🇸","Paraguay":"🇵🇾","Australia":"🇦🇺","Türkiye":"🇹🇷","Germany":"🇩🇪","Curaçao":"🇨🇼","Ivory Coast":"🇨🇮","Ecuador":"🇪🇨","Netherlands":"🇳🇱","Japan":"🇯🇵","Sweden":"🇸🇪","Tunisia":"🇹🇳","Belgium":"🇧🇪","Egypt":"🇪🇬","Iran":"🇮🇷","New Zealand":"🇳🇿","Spain":"🇪🇸","Cape Verde":"🇨🇻","Saudi Arabia":"🇸🇦","Uruguay":"🇺🇾","France":"🇫🇷","Senegal":"🇸🇳","Iraq":"🇮🇶","Norway":"🇳🇴","Argentina":"🇦🇷","Algeria":"🇩🇿","Austria":"🇦🇹","Jordan":"🇯🇴","Portugal":"🇵🇹","DR Congo":"🇨🇩","Uzbekistan":"🇺🇿","Colombia":"🇨🇴","England":"🏴","Croatia":"🇭🇷","Ghana":"🇬🇭","Panama":"🇵🇦"};
const ABBR={"Mexico":"MEX","South Africa":"RSA","South Korea":"KOR","Czechia":"CZE","Canada":"CAN","Bosnia & Herzegovina":"BIH","Qatar":"QAT","Switzerland":"SUI","Brazil":"BRA","Morocco":"MAR","Haiti":"HAI","Scotland":"SCO","USA":"USA","Paraguay":"PAR","Australia":"AUS","Türkiye":"TUR","Germany":"GER","Curaçao":"CUW","Ivory Coast":"CIV","Ecuador":"ECU","Netherlands":"NED","Japan":"JPN","Sweden":"SWE","Tunisia":"TUN","Belgium":"BEL","Egypt":"EGY","Iran":"IRN","New Zealand":"NZL","Spain":"ESP","Cape Verde":"CPV","Saudi Arabia":"KSA","Uruguay":"URU","France":"FRA","Senegal":"SEN","Iraq":"IRQ","Norway":"NOR","Argentina":"ARG","Algeria":"ALG","Austria":"AUT","Jordan":"JOR","Portugal":"POR","DR Congo":"COD","Uzbekistan":"UZB","Colombia":"COL","England":"ENG","Croatia":"CRO","Ghana":"GHA","Panama":"PAN"};
const TEAM_BADGE={"USA":"host","Canada":"host","Mexico":"host","Cape Verde":"debut","Curaçao":"debut","Jordan":"debut","Uzbekistan":"debut","Argentina":"hold"};
const CITY_COUNTRY={"Mexico City":"MX","Zapopan":"MX","Monterrey":"MX","Toronto":"CA","Vancouver":"CA","Inglewood":"US","Santa Clara":"US","East Rutherford":"US","Foxborough":"US","Houston":"US","Arlington":"US","Philadelphia":"US","Atlanta":"US","Seattle":"US","Miami Gardens":"US","Kansas City":"US"};
const COUNTRY_FLAG={US:"🇺🇸",CA:"🇨🇦",MX:"🇲🇽"};
const STAGES={group:{label:"Group Stage",short:"GRP",color:"#19E08A"},r32:{label:"Round of 32",short:"R32",color:"#46C2FF"},r16:{label:"Round of 16",short:"R16",color:"#7FA9FF"},qf:{label:"Quarter-final",short:"QF",color:"#A78BFA"},sf:{label:"Semi-final",short:"SF",color:"#F7C948"},"3rd":{label:"Third-place",short:"3RD",color:"#C8895B"},final:{label:"Final",short:"FINAL",color:"#FFD166"}};
const M=(n,s,g,t,h,a,v,c,hs,as)=>({n,s,g,t,h,a,v,c,hs,as,ft:(hs!=null)});
const MATCHES=[
M(1,"group","A","2026-06-11T15:00:00-04:00","Mexico","South Africa","Estadio Azteca","Mexico City",2,0),
M(2,"group","A","2026-06-11T21:00:00-04:00","South Korea","Czechia","Estadio Akron","Zapopan",2,1),
M(3,"group","B","2026-06-12T15:00:00-04:00","Canada","Bosnia & Herzegovina","BMO Field","Toronto",1,1),
M(4,"group","D","2026-06-12T21:00:00-04:00","USA","Paraguay","SoFi Stadium","Inglewood",4,1),
M(5,"group","B","2026-06-13T15:00:00-04:00","Qatar","Switzerland","Levi's Stadium","Santa Clara"),
M(6,"group","C","2026-06-13T18:00:00-04:00","Brazil","Morocco","MetLife Stadium","East Rutherford"),
M(7,"group","C","2026-06-13T21:00:00-04:00","Haiti","Scotland","Gillette Stadium","Foxborough"),
M(8,"group","D","2026-06-14T00:00:00-04:00","Australia","Türkiye","BC Place","Vancouver"),
M(9,"group","E","2026-06-14T13:00:00-04:00","Germany","Curaçao","NRG Stadium","Houston"),
M(10,"group","F","2026-06-14T16:00:00-04:00","Netherlands","Japan","AT&T Stadium","Arlington"),
M(11,"group","E","2026-06-14T19:00:00-04:00","Ivory Coast","Ecuador","Lincoln Financial Field","Philadelphia"),
M(12,"group","F","2026-06-14T22:00:00-04:00","Sweden","Tunisia","Estadio BBVA","Monterrey"),
M(13,"group","H","2026-06-15T12:00:00-04:00","Spain","Cape Verde","Mercedes-Benz Stadium","Atlanta"),
M(14,"group","G","2026-06-15T15:00:00-04:00","Belgium","Egypt","Lumen Field","Seattle"),
M(15,"group","H","2026-06-15T18:00:00-04:00","Saudi Arabia","Uruguay","Hard Rock Stadium","Miami Gardens"),
M(16,"group","G","2026-06-15T21:00:00-04:00","Iran","New Zealand","SoFi Stadium","Inglewood"),
M(17,"group","I","2026-06-16T15:00:00-04:00","France","Senegal","MetLife Stadium","East Rutherford"),
M(18,"group","I","2026-06-16T18:00:00-04:00","Iraq","Norway","Gillette Stadium","Foxborough"),
M(19,"group","J","2026-06-16T21:00:00-04:00","Argentina","Algeria","Arrowhead Stadium","Kansas City"),
M(20,"group","J","2026-06-17T00:00:00-04:00","Austria","Jordan","Levi's Stadium","Santa Clara"),
M(21,"group","K","2026-06-17T13:00:00-04:00","Portugal","DR Congo","NRG Stadium","Houston"),
M(22,"group","L","2026-06-17T16:00:00-04:00","England","Croatia","AT&T Stadium","Arlington"),
M(23,"group","L","2026-06-17T19:00:00-04:00","Ghana","Panama","BMO Field","Toronto"),
M(24,"group","K","2026-06-17T22:00:00-04:00","Uzbekistan","Colombia","Estadio Azteca","Mexico City"),
M(25,"group","A","2026-06-18T12:00:00-04:00","Czechia","South Africa","Mercedes-Benz Stadium","Atlanta"),
M(26,"group","B","2026-06-18T15:00:00-04:00","Switzerland","Bosnia & Herzegovina","SoFi Stadium","Inglewood"),
M(27,"group","B","2026-06-18T18:00:00-04:00","Canada","Qatar","BC Place","Vancouver"),
M(28,"group","A","2026-06-18T21:00:00-04:00","Mexico","South Korea","Estadio Akron","Zapopan"),
M(29,"group","D","2026-06-19T15:00:00-04:00","USA","Australia","Lumen Field","Seattle"),
M(30,"group","C","2026-06-19T18:00:00-04:00","Scotland","Morocco","Gillette Stadium","Foxborough"),
M(31,"group","C","2026-06-19T20:30:00-04:00","Brazil","Haiti","Lincoln Financial Field","Philadelphia"),
M(32,"group","D","2026-06-19T23:00:00-04:00","Türkiye","Paraguay","Levi's Stadium","Santa Clara"),
M(33,"group","F","2026-06-20T13:00:00-04:00","Netherlands","Sweden","NRG Stadium","Houston"),
M(34,"group","E","2026-06-20T16:00:00-04:00","Germany","Ivory Coast","BMO Field","Toronto"),
M(35,"group","E","2026-06-20T20:00:00-04:00","Ecuador","Curaçao","Arrowhead Stadium","Kansas City"),
M(36,"group","F","2026-06-21T00:00:00-04:00","Tunisia","Japan","Estadio BBVA","Monterrey"),
M(37,"group","H","2026-06-21T12:00:00-04:00","Spain","Saudi Arabia","Mercedes-Benz Stadium","Atlanta"),
M(38,"group","G","2026-06-21T15:00:00-04:00","Belgium","Iran","SoFi Stadium","Inglewood"),
M(39,"group","H","2026-06-21T18:00:00-04:00","Uruguay","Cape Verde","Hard Rock Stadium","Miami Gardens"),
M(40,"group","G","2026-06-21T21:00:00-04:00","New Zealand","Egypt","BC Place","Vancouver"),
M(41,"group","J","2026-06-22T13:00:00-04:00","Argentina","Austria","AT&T Stadium","Arlington"),
M(42,"group","I","2026-06-22T17:00:00-04:00","France","Iraq","Lincoln Financial Field","Philadelphia"),
M(43,"group","I","2026-06-22T20:00:00-04:00","Norway","Senegal","MetLife Stadium","East Rutherford"),
M(44,"group","J","2026-06-22T23:00:00-04:00","Jordan","Algeria","Levi's Stadium","Santa Clara"),
M(45,"group","K","2026-06-23T13:00:00-04:00","Portugal","Uzbekistan","NRG Stadium","Houston"),
M(46,"group","L","2026-06-23T16:00:00-04:00","England","Ghana","Gillette Stadium","Foxborough"),
M(47,"group","L","2026-06-23T19:00:00-04:00","Panama","Croatia","BMO Field","Toronto"),
M(48,"group","K","2026-06-23T22:00:00-04:00","Colombia","DR Congo","Estadio Akron","Zapopan"),
M(49,"group","B","2026-06-24T15:00:00-04:00","Switzerland","Canada","BC Place","Vancouver"),
M(50,"group","B","2026-06-24T15:00:00-04:00","Bosnia & Herzegovina","Qatar","Lumen Field","Seattle"),
M(51,"group","C","2026-06-24T18:00:00-04:00","Scotland","Brazil","Hard Rock Stadium","Miami Gardens"),
M(52,"group","C","2026-06-24T18:00:00-04:00","Morocco","Haiti","Mercedes-Benz Stadium","Atlanta"),
M(53,"group","A","2026-06-24T21:00:00-04:00","Czechia","Mexico","Estadio Azteca","Mexico City"),
M(54,"group","A","2026-06-24T21:00:00-04:00","South Africa","South Korea","Estadio BBVA","Monterrey"),
M(55,"group","E","2026-06-25T16:00:00-04:00","Curaçao","Ivory Coast","Lincoln Financial Field","Philadelphia"),
M(56,"group","E","2026-06-25T16:00:00-04:00","Ecuador","Germany","MetLife Stadium","East Rutherford"),
M(57,"group","F","2026-06-25T19:00:00-04:00","Japan","Sweden","AT&T Stadium","Arlington"),
M(58,"group","F","2026-06-25T19:00:00-04:00","Tunisia","Netherlands","Arrowhead Stadium","Kansas City"),
M(59,"group","D","2026-06-25T22:00:00-04:00","Türkiye","USA","SoFi Stadium","Inglewood"),
M(60,"group","D","2026-06-25T22:00:00-04:00","Paraguay","Australia","Levi's Stadium","Santa Clara"),
M(61,"group","I","2026-06-26T15:00:00-04:00","Norway","France","Gillette Stadium","Foxborough"),
M(62,"group","I","2026-06-26T15:00:00-04:00","Senegal","Iraq","BMO Field","Toronto"),
M(63,"group","H","2026-06-26T20:00:00-04:00","Cape Verde","Saudi Arabia","NRG Stadium","Houston"),
M(64,"group","H","2026-06-26T20:00:00-04:00","Uruguay","Spain","Estadio Akron","Zapopan"),
M(65,"group","G","2026-06-26T23:00:00-04:00","Egypt","Iran","Lumen Field","Seattle"),
M(66,"group","G","2026-06-26T23:00:00-04:00","New Zealand","Belgium","BC Place","Vancouver"),
M(67,"group","L","2026-06-27T17:00:00-04:00","Panama","England","MetLife Stadium","East Rutherford"),
M(68,"group","L","2026-06-27T17:00:00-04:00","Croatia","Ghana","Lincoln Financial Field","Philadelphia"),
M(69,"group","K","2026-06-27T19:30:00-04:00","Colombia","Portugal","Hard Rock Stadium","Miami Gardens"),
M(70,"group","K","2026-06-27T19:30:00-04:00","DR Congo","Uzbekistan","Mercedes-Benz Stadium","Atlanta"),
M(71,"group","J","2026-06-27T22:00:00-04:00","Algeria","Austria","Arrowhead Stadium","Kansas City"),
M(72,"group","J","2026-06-27T22:00:00-04:00","Jordan","Argentina","AT&T Stadium","Arlington"),
M(73,"r32",null,"2026-06-28T15:00:00-04:00","Runners-up A","Runners-up B","SoFi Stadium","Inglewood"),
M(76,"r32",null,"2026-06-29T13:00:00-04:00","Winners C","Runners-up F","NRG Stadium","Houston"),
M(74,"r32",null,"2026-06-29T16:30:00-04:00","Winners E","3rd A/B/C/D/F","Gillette Stadium","Foxborough"),
M(75,"r32",null,"2026-06-29T21:00:00-04:00","Winners F","Runners-up C","Estadio BBVA","Monterrey"),
M(78,"r32",null,"2026-06-30T13:00:00-04:00","Runners-up E","Runners-up I","AT&T Stadium","Arlington"),
M(77,"r32",null,"2026-06-30T17:00:00-04:00","Winners I","3rd C/D/F/G/H","MetLife Stadium","East Rutherford"),
M(79,"r32",null,"2026-06-30T21:00:00-04:00","Winners A","3rd C/E/F/H/I","Estadio Azteca","Mexico City"),
M(80,"r32",null,"2026-07-01T12:00:00-04:00","Winners L","3rd E/H/I/J/K","Mercedes-Benz Stadium","Atlanta"),
M(82,"r32",null,"2026-07-01T16:00:00-04:00","Winners G","3rd A/E/H/I/J","Lumen Field","Seattle"),
M(81,"r32",null,"2026-07-01T20:00:00-04:00","Winners D","3rd B/E/F/I/J","Levi's Stadium","Santa Clara"),
M(84,"r32",null,"2026-07-02T15:00:00-04:00","Winners H","Runners-up J","SoFi Stadium","Inglewood"),
M(83,"r32",null,"2026-07-02T19:00:00-04:00","Runners-up K","Runners-up L","BMO Field","Toronto"),
M(85,"r32",null,"2026-07-02T23:00:00-04:00","Winners B","3rd E/F/G/I/J","BC Place","Vancouver"),
M(88,"r32",null,"2026-07-03T14:00:00-04:00","Runners-up D","Runners-up G","AT&T Stadium","Arlington"),
M(86,"r32",null,"2026-07-03T18:00:00-04:00","Winners J","Runners-up H","Hard Rock Stadium","Miami Gardens"),
M(87,"r32",null,"2026-07-03T21:30:00-04:00","Winners K","3rd D/E/I/J/L","Arrowhead Stadium","Kansas City"),
M(90,"r16",null,"2026-07-04T13:00:00-04:00","Winners M73","Winners M75","NRG Stadium","Houston"),
M(89,"r16",null,"2026-07-04T17:00:00-04:00","Winners M74","Winners M77","Lincoln Financial Field","Philadelphia"),
M(91,"r16",null,"2026-07-05T16:00:00-04:00","Winners M76","Winners M78","MetLife Stadium","East Rutherford"),
M(92,"r16",null,"2026-07-05T20:00:00-04:00","Winners M79","Winners M80","Estadio Azteca","Mexico City"),
M(93,"r16",null,"2026-07-06T15:00:00-04:00","Winners M83","Winners M84","AT&T Stadium","Arlington"),
M(94,"r16",null,"2026-07-06T20:00:00-04:00","Winners M81","Winners M82","Lumen Field","Seattle"),
M(95,"r16",null,"2026-07-07T12:00:00-04:00","Winners M86","Winners M88","Mercedes-Benz Stadium","Atlanta"),
M(96,"r16",null,"2026-07-07T16:00:00-04:00","Winners M85","Winners M87","BC Place","Vancouver"),
M(97,"qf",null,"2026-07-09T16:00:00-04:00","Winners M89","Winners M90","Gillette Stadium","Foxborough"),
M(98,"qf",null,"2026-07-10T15:00:00-04:00","Winners M93","Winners M94","SoFi Stadium","Inglewood"),
M(99,"qf",null,"2026-07-11T17:00:00-04:00","Winners M91","Winners M92","Hard Rock Stadium","Miami Gardens"),
M(100,"qf",null,"2026-07-11T21:00:00-04:00","Winners M95","Winners M96","Arrowhead Stadium","Kansas City"),
M(101,"sf",null,"2026-07-14T15:00:00-04:00","Winners M97","Winners M98","AT&T Stadium","Arlington"),
M(102,"sf",null,"2026-07-15T15:00:00-04:00","Winners M99","Winners M100","Mercedes-Benz Stadium","Atlanta"),
M(103,"3rd",null,"2026-07-18T17:00:00-04:00","Losers M101","Losers M102","Hard Rock Stadium","Miami Gardens"),
M(104,"final",null,"2026-07-19T15:00:00-04:00","Winners M101","Winners M102","MetLife Stadium","East Rutherford")
];
const VENUES=[
{name:"MetLife Stadium",fifa:"New York New Jersey Stadium",city:"East Rutherford, NJ",cc:"US",cap:82500,games:8,note:"Hosts the Final"},
{name:"AT&T Stadium",fifa:"Dallas Stadium",city:"Arlington, TX",cc:"US",cap:80000,games:9,note:"Most matches · a semi-final"},
{name:"SoFi Stadium",fifa:"Los Angeles Stadium",city:"Inglewood, CA",cc:"US",cap:70240,games:8,note:"USA group games"},
{name:"Mercedes-Benz Stadium",fifa:"Atlanta Stadium",city:"Atlanta, GA",cc:"US",cap:71000,games:8,note:"Hosts a semi-final"},
{name:"Hard Rock Stadium",fifa:"Miami Stadium",city:"Miami Gardens, FL",cc:"US",cap:65326,games:7,note:"Third-place match"},
{name:"Lincoln Financial Field",fifa:"Philadelphia Stadium",city:"Philadelphia, PA",cc:"US",cap:69000,games:6,note:""},
{name:"Gillette Stadium",fifa:"Boston Stadium",city:"Foxborough, MA",cc:"US",cap:65000,games:7,note:"A quarter-final"},
{name:"NRG Stadium",fifa:"Houston Stadium",city:"Houston, TX",cc:"US",cap:72220,games:7,note:""},
{name:"Arrowhead Stadium",fifa:"Kansas City Stadium",city:"Kansas City, MO",cc:"US",cap:76416,games:6,note:"A quarter-final"},
{name:"Levi's Stadium",fifa:"San Francisco Bay Area Stadium",city:"Santa Clara, CA",cc:"US",cap:68500,games:6,note:""},
{name:"Lumen Field",fifa:"Seattle Stadium",city:"Seattle, WA",cc:"US",cap:69000,games:6,note:""},
{name:"Estadio Azteca",fifa:"Mexico City Stadium",city:"Mexico City",cc:"MX",cap:83264,games:5,note:"Opening match · 3-time WC host"},
{name:"Estadio Akron",fifa:"Guadalajara Stadium",city:"Zapopan",cc:"MX",cap:48071,games:4,note:""},
{name:"Estadio BBVA",fifa:"Monterrey Stadium",city:"Monterrey",cc:"MX",cap:53500,games:4,note:""},
{name:"BMO Field",fifa:"Toronto Stadium",city:"Toronto",cc:"CA",cap:45000,games:6,note:""},
{name:"BC Place",fifa:"Vancouver Stadium",city:"Vancouver",cc:"CA",cap:54500,games:7,note:""}
];
/* ===================== STATE + HELPERS ===================== */
const localZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/New_York";
const TZ_OPTIONS=[
{v:"local",l:"My local time"},{v:"America/New_York",l:"New York (ET)"},{v:"America/Chicago",l:"Chicago (CT)"},
{v:"America/Denver",l:"Denver (MT)"},{v:"America/Los_Angeles",l:"Los Angeles (PT)"},{v:"America/Mexico_City",l:"Mexico City"},
{v:"Europe/London",l:"London (UK)"},{v:"Europe/Paris",l:"Central Europe"},{v:"Asia/Tokyo",l:"Tokyo"},{v:"Australia/Sydney",l:"Sydney"}
];
let state={tz:"local",view:"overview",q:"",stage:"all",group:"all",team:"all",liveOnly:false};
const zoneFor=tz=>tz==="local"?undefined:tz;
function fmtTime(iso,tz){return new Intl.DateTimeFormat("en-US",{timeZone:zoneFor(tz),hour:"numeric",minute:"2-digit"}).format(new Date(iso));}
function fmtTzAbbr(iso,tz){try{const p=new Intl.DateTimeFormat("en-US",{timeZone:zoneFor(tz),hour:"numeric",timeZoneName:"short"}).formatToParts(new Date(iso)).find(x=>x.type==="timeZoneName");return p?p.value:"";}catch(e){return "";}}
function fmtDayDate(iso,tz){return new Intl.DateTimeFormat("en-US",{timeZone:zoneFor(tz),weekday:"short",month:"short",day:"numeric"}).format(new Date(iso));}
function etDateKey(iso){return iso.slice(0,10);}
function etDateLabel(key){const d=new Date(key+"T12:00:00-04:00");return new Intl.DateTimeFormat("en-US",{timeZone:"America/New_York",weekday:"long",month:"long",day:"numeric"}).format(d);}
function todayETKey(now){return new Intl.DateTimeFormat("en-CA",{timeZone:"America/New_York",year:"numeric",month:"2-digit",day:"2-digit"}).format(new Date(now));}
function isPlaceholder(name){return !FLAGS[name];}
function flag(name){return FLAGS[name]||"";}
function abbr(name){return ABBR[name]||name.slice(0,3).toUpperCase();}
function countryOf(city){return CITY_COUNTRY[city]||"US";}
const KO_MIN=120;
function statusOf(m,now){if(m.ft)return "ft";const k=new Date(m.t).getTime();const diff=now-k;if(diff<0)return "up";if(diff<KO_MIN*60000)return "lv";return "end";}
/* Build entities from char codes (38='&'); avoids HTML-entity literals that the
It Just Vibes pipeline would decode and turn into a JS syntax error. */
function escapeHtml(s){return String(s).replace(/[&<>"']/g,function(c){return String.fromCharCode(38)+"#"+c.charCodeAt(0)+";";});}
/* ===================== STANDINGS ===================== */
function buildGroups(){
const g={};
MATCHES.filter(m=>m.s==="group").forEach(m=>{g[m.g]=g[m.g]||{};[m.h,m.a].forEach(t=>{if(!g[m.g][t])g[m.g][t]={team:t,p:0,w:0,d:0,l:0,gf:0,ga:0,pts:0};});});
MATCHES.filter(m=>m.s==="group"&&m.ft).forEach(m=>{const H=g[m.g][m.h],A=g[m.g][m.a];H.p++;A.p++;H.gf+=m.hs;H.ga+=m.as;A.gf+=m.as;A.ga+=m.hs;if(m.hs>m.as){H.w++;A.l++;H.pts+=3;}else if(m.hs<m.as){A.w++;H.l++;A.pts+=3;}else{H.d++;A.d++;H.pts++;A.pts++;}});
const out={};Object.keys(g).sort().forEach(k=>{out[k]=Object.values(g[k]).sort((a,b)=>b.pts-a.pts||(b.gf-b.ga)-(a.gf-a.ga)||b.gf-a.gf||a.team.localeCompare(b.team));});
return out;
}
const GROUPS=buildGroups();
/* ===================== HERO STRIP / NEXT / TICKER ===================== */
function renderHeroStrip(now){
const played=MATCHES.filter(m=>{const s=statusOf(m,now);return s==="ft"||s==="end";}).length;
document.getElementById("heroStrip").innerHTML=[
{n:"48",l:"Teams",c:"g"},{n:"104",l:"Matches",c:""},{n:"12",l:"Groups",c:"g"},
{n:"16",l:"Stadiums",c:""},{n:"3",l:"Host nations",c:""},{n:String(played),l:"Played",c:"gold"}
].map(s=>`<div class="hchip"><span class="hchip-n ${s.c}">${s.n}</span><span class="hchip-l">${s.l}</span></div>`).join("");
}
function renderNext(now){
const box=document.getElementById("nextBox");
const live=MATCHES.filter(m=>statusOf(m,now)==="lv").sort((a,b)=>new Date(a.t)-new Date(b.t));
const up=MATCHES.filter(m=>statusOf(m,now)==="up").sort((a,b)=>new Date(a.t)-new Date(b.t));
if(live.length){
const m=live[0];box.className="nextbox islive";
box.innerHTML=`<div class="nextbox-top"><span class="nb-tag"><span class="pulse"></span>Live now</span><span class="nb-meta" style="color:var(--coral-soft)">${STAGES[m.s].label}${m.g?" · Group "+m.g:""}</span></div>
<div class="nb-match"><span class="nb-side"><span class="fl">${flag(m.h)||"⚽"}</span>${escapeHtml(m.h)}</span><span class="nb-vs">VS</span><span class="nb-side">${escapeHtml(m.a)}<span class="fl">${flag(m.a)||"⚽"}</span></span></div>
<div class="nb-meta">🏟 ${escapeHtml(m.v)}, ${escapeHtml(m.c)} ${COUNTRY_FLAG[countryOf(m.c)]} · Kicked off ${fmtTime(m.t,state.tz)} ${fmtTzAbbr(m.t,state.tz)}</div>`;
return;
}
if(!up.length){
box.className="nextbox";
box.innerHTML=`<div class="nextbox-top"><span class="nb-tag" style="color:var(--gold)">🏆 Tournament complete</span></div><div class="nb-meta">All 104 matches have kicked off -- champions crowned at MetLife Stadium.</div>`;
return;
}
const m=up[0];const k=new Date(m.t).getTime();let diff=Math.max(0,k-now);
const d=Math.floor(diff/86400000);diff-=d*86400000;const h=Math.floor(diff/3600000);diff-=h*3600000;const mn=Math.floor(diff/60000);diff-=mn*60000;const s=Math.floor(diff/1000);
const cells=(d>0?[[d,"Days"],[h,"Hrs"],[mn,"Min"]]:[[h,"Hrs"],[mn,"Min"],[s,"Sec"]]).map(c=>`<div class="cd-cell"><div class="cd-num">${String(c[0]).padStart(2,"0")}</div><div class="cd-lab">${c[1]}</div></div>`).join("");
box.className="nextbox";
box.innerHTML=`<div class="nextbox-top"><span class="nb-tag"><span class="pulse"></span>Next kickoff</span><div class="countdown">${cells}</div></div>
<div class="nb-match"><span class="nb-side"><span class="fl">${flag(m.h)||"⚽"}</span>${escapeHtml(m.h)}</span><span class="nb-vs">VS</span><span class="nb-side">${escapeHtml(m.a)}<span class="fl">${flag(m.a)||"⚽"}</span></span></div>
<div class="nb-meta"><span style="color:${STAGES[m.s].color};font-weight:800">${STAGES[m.s].label}${m.g?" · Group "+m.g:""}</span><span class="dot" style="width:3px;height:3px;border-radius:50%;background:var(--muted-2)"></span>${fmtTime(m.t,state.tz)} ${fmtTzAbbr(m.t,state.tz)} · ${fmtDayDate(m.t,state.tz)}<span class="dot" style="width:3px;height:3px;border-radius:50%;background:var(--muted-2)"></span>🏟 ${escapeHtml(m.v)}, ${escapeHtml(m.c)} ${COUNTRY_FLAG[countryOf(m.c)]}</div>`;
}
function renderTicker(now){
const sorted=[...MATCHES].sort((a,b)=>new Date(a.t)-new Date(b.t));
const ft=sorted.filter(m=>statusOf(m,now)==="ft");
const up=sorted.filter(m=>statusOf(m,now)==="up").slice(0,18);
const lv=sorted.filter(m=>statusOf(m,now)==="lv");
const set=[...ft.slice(-5),...lv,...up];
const item=m=>{const st=statusOf(m,now);let mid;
if(st==="ft")mid=`<span class="tk-sc">${m.hs}-${m.as}</span><span class="tk-ft">FT</span>`;
else if(st==="lv")mid=`<span class="tk-vs">v</span><span class="tk-live">● LIVE</span>`;
else mid=`<span class="tk-vs">v</span><span class="tk-time">${fmtTime(m.t,state.tz)}</span>`;
return `<span class="tk-item"><span class="fl">${flag(m.h)||"⚽"}</span>${abbr(m.h)} ${mid} ${abbr(m.a)}<span class="fl">${flag(m.a)||"⚽"}</span></span>`;};
const html=set.map(item).join("");
document.getElementById("ticker").innerHTML=html+html;
}
/* ===================== MATCH CARD ===================== */
function teamRow(name,score,st,isWinner){
const ph=isPlaceholder(name);
const cls="team-row"+(ph?" ph":"")+(isWinner?" win":"");
const lead=ph?`<span class="ph-badge">TBD</span>`:`<span class="fl">${flag(name)}</span>`;
const scoreHtml=(st==="ft")?`<span class="sc">${score}</span>`:"";
return `<div class="${cls}">${lead}<span class="nm">${escapeHtml(name)}</span>${scoreHtml}</div>`;
}
function matchCard(m,now){
const st=statusOf(m,now);
const today=(etDateKey(m.t)===todayETKey(now))&&(st==="up"||st==="lv");
let statusEl;
if(st==="lv")statusEl=`<span class="status lv"><span class="d"></span>Live</span>`;
else if(st==="ft"||st==="end")statusEl=`<span class="status ft">Full-time</span>`;
else if(today)statusEl=`<span class="status td">Today</span>`;
else statusEl=`<span class="status up">Upcoming</span>`;
const homeWin=m.ft&&m.hs>m.as,awayWin=m.ft&&m.as>m.hs;
let resultEl;
if(st==="ft"||st==="end")resultEl=`<div class="mc-result"><span class="ftbig">FT</span></div>`;
else if(st==="lv")resultEl=`<div class="mc-result"><span class="big-time" style="color:var(--coral)">LIVE</span></div>`;
else resultEl=`<div class="mc-result"><span class="big-time">${fmtTime(m.t,state.tz)}</span><span class="tz">${fmtTzAbbr(m.t,state.tz)}</span></div>`;
const cc=countryOf(m.c);
const cls="match-card"+(st==="lv"?" live":"")+(m.s==="final"?" final":"")+(today?" today":"");
const grpChip=m.g?`<span class="chip grp">Grp ${m.g}</span>`:"";
return `<article class="${cls}">
<div class="mc-head"><span class="chip stage" style="background:${STAGES[m.s].color}">${STAGES[m.s].short}</span>${grpChip}<span class="chip mno">#${m.n}</span>${statusEl}</div>
<div class="mc-body"><div class="mc-teams">${teamRow(m.h,m.hs,st,homeWin)}${teamRow(m.a,m.as,st,awayWin)}</div>${resultEl}</div>
<div class="mc-foot">
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg><span>${fmtTime(m.t,state.tz)} ${fmtTzAbbr(m.t,state.tz)}</span>
<span class="dot"></span><span>${fmtDayDate(m.t,state.tz)}</span><span class="dot"></span>
<span class="vn"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M21 10c0 7-9 12-9 12s-9-5-9-12a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="2.5"/></svg><span>${escapeHtml(m.v)}, ${escapeHtml(m.c)} ${COUNTRY_FLAG[cc]}</span></span>
</div></article>`;
}
/* ===================== VIEW: OVERVIEW ===================== */
function renderOverview(now){
const el=document.getElementById("view-overview");
const total=MATCHES.length;
const played=MATCHES.filter(m=>{const s=statusOf(m,now);return s==="ft"||s==="end";}).length;
const pct=Math.round(played/total*100);
const tdKey=todayETKey(now);
const todays=MATCHES.filter(m=>etDateKey(m.t)===tdKey).sort((a,b)=>new Date(a.t)-new Date(b.t));
const marquee=[6,22,17,19,10,28,64,41,42,31];
let feat=marquee.map(n=>MATCHES.find(m=>m.n===n)).filter(m=>m&&statusOf(m,now)==="up").sort((a,b)=>new Date(a.t)-new Date(b.t))[0];
if(!feat)feat=MATCHES.filter(m=>statusOf(m,now)==="up").sort((a,b)=>new Date(a.t)-new Date(b.t))[0];
if(!feat)feat=MATCHES.find(m=>m.s==="final");
const stats=[
{n:"48",l:"Nations",s:"4 making their debut",a:"var(--pitch)"},
{n:"104",l:"Total matches",s:"40 more than 2022",a:"var(--sky)"},
{n:pct+"%",l:"Tournament played",s:played+" of "+total+" matches",a:"var(--coral)"},
{n:"39",l:"Days of football",s:"Jun 11 - Jul 19",a:"var(--violet)"},
{n:"78",l:"Matches in the USA",s:"Quarters onward all USA",a:"var(--gold)"},
{n:"82.5K",l:"Final capacity",s:"MetLife Stadium, NJ",a:"var(--pitch)"}
];
let html=`<div class="progress-wrap"><div class="progress-meta"><span class="pm-l">Group Stage → Final</span><span class="pm-r"><b>${played}</b> / ${total} matches kicked off</span></div><div class="bar"><i style="width:${Math.max(pct,2)}%"></i></div></div>`;
html+=`<div class="stat-grid">`+stats.map(s=>`<div class="stat" style="--accent:${s.a}"><div class="n">${s.n}</div><div class="l">${s.l}</div><div class="s">${s.s}</div></div>`).join("")+`</div>`;
if(feat){
const fst=statusOf(feat,now);
const tline=fst==="ft"?`${feat.hs}-${feat.as} · Full-time`:`${fmtTime(feat.t,state.tz)} ${fmtTzAbbr(feat.t,state.tz)}`;
html+=`<div class="feature"><span class="feature-tag">◆ Featured matchup</span><div class="feature-body">
<div class="feat-team"><span class="fl">${flag(feat.h)||"⚽"}</span><span class="nm">${escapeHtml(feat.h)}</span></div>
<div class="feat-mid"><span class="vs">VS</span><span class="ti">${tline}</span><span class="dt">${fmtDayDate(feat.t,state.tz)} · ${escapeHtml(feat.c)} ${COUNTRY_FLAG[countryOf(feat.c)]}</span></div>
<div class="feat-team"><span class="fl">${flag(feat.a)||"⚽"}</span><span class="nm">${escapeHtml(feat.a)}</span></div>
</div></div>`;
}
const hasToday=todays.length>0;
const list=hasToday?todays:MATCHES.filter(m=>statusOf(m,now)==="up").sort((a,b)=>new Date(a.t)-new Date(b.t)).slice(0,6);
html+=`<div class="section-head"><div><span class="klabel">${hasToday?"On today":"Coming up next"}</span><h2>${hasToday?"Today's Matches":"Next Up"}</h2></div></div>`;
html+=`<div class="matches">`+(list.length?list.map(m=>matchCard(m,now)).join(""):`<div class="empty"><b>No matches</b>Check the full schedule for what's ahead.</div>`)+`</div>`;
html+=`<div class="section-head" style="margin-top:34px"><div><span class="klabel">How 2026 works</span><h2>The Road to MetLife</h2><p class="lede">A brand-new format for the first 48-team World Cup: a wider group stage feeds an extra knockout round before the path narrows to one trophy in New Jersey.</p></div></div>`;
const road=[
{k:"48",l:"Group stage",d:"12 groups of four. Top two from each group plus the eight best third-placed teams advance.",c:"var(--pitch)"},
{k:"32",l:"Round of 32",d:"A knockout round never used at any previous World Cup. Win or go home from here.",c:"var(--sky)"},
{k:"16",l:"Round of 16",d:"Single elimination. Ties go to extra time, then penalties.",c:"#7FA9FF"},
{k:"8",l:"Quarter-finals",d:"Every match from here is played on US soil.",c:"var(--violet)"},
{k:"4",l:"Semi-finals",d:"Dallas and Atlanta host the final four.",c:"var(--gold)"},
{k:"1",l:"The Final",d:"July 19 at MetLife Stadium -- with the first-ever World Cup final halftime show.",c:"#FFD166"}
];
html+=`<div class="stat-grid">`+road.map(r=>`<div class="stat" style="--accent:${r.c}"><div class="n" style="background:none;-webkit-text-fill-color:initial;color:${r.c}">${r.k}</div><div class="l" style="color:${r.c}">${r.l}</div><div class="s" style="color:var(--muted)">${r.d}</div></div>`).join("")+`</div>`;
el.innerHTML=html;
}
/* ===================== VIEW: SCHEDULE ===================== */
function renderScheduleShell(){
const el=document.getElementById("view-schedule");
const stageOpts=`<option value="all">All stages</option>`+Object.keys(STAGES).map(k=>`<option value="${k}">${STAGES[k].label}</option>`).join("");
const grpOpts=`<option value="all">All groups</option>`+Object.keys(GROUPS).map(g=>`<option value="${g}">Group ${g}</option>`).join("");
const teams=Object.keys(FLAGS).sort();
const teamOpts=`<option value="all">All teams</option>`+teams.map(t=>`<option value="${t}">${t}</option>`).join("");
el.innerHTML=`
<div class="section-head"><div><span class="klabel">Every fixture · all times in your zone</span><h2>Full Schedule</h2></div></div>
<div class="filters">
<div class="search"><svg class="ic" viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg><input id="schSearch" type="text" placeholder="Search team, city or stadium..." autocomplete="off"></div>
<select id="schStage" class="fselect">${stageOpts}</select>
<select id="schGroup" class="fselect">${grpOpts}</select>
<select id="schTeam" class="fselect">${teamOpts}</select>
<button id="schToggle" class="ftoggle" type="button"><span class="d"></span>Today & live</button>
</div>
<div id="schResults"></div>`;
el.querySelector("#schSearch").addEventListener("input",e=>{state.q=e.target.value.trim().toLowerCase();renderScheduleResults(Date.now());});
el.querySelector("#schStage").addEventListener("change",e=>{state.stage=e.target.value;renderScheduleResults(Date.now());});
el.querySelector("#schGroup").addEventListener("change",e=>{state.group=e.target.value;renderScheduleResults(Date.now());});
el.querySelector("#schTeam").addEventListener("change",e=>{state.team=e.target.value;renderScheduleResults(Date.now());});
el.querySelector("#schToggle").addEventListener("click",e=>{state.liveOnly=!state.liveOnly;e.currentTarget.classList.toggle("on",state.liveOnly);renderScheduleResults(Date.now());});
}
function matchMatchesFilter(m,now){
if(state.stage!=="all"&&m.s!==state.stage)return false;
if(state.group!=="all"&&m.g!==state.group)return false;
if(state.team!=="all"&&m.h!==state.team&&m.a!==state.team)return false;
if(state.liveOnly){const s=statusOf(m,now);const today=etDateKey(m.t)===todayETKey(now);if(s!=="lv"&&!(today&&s==="up"))return false;}
if(state.q){const hay=(m.h+" "+m.a+" "+m.v+" "+m.c+" "+(m.g||"")).toLowerCase();if(!hay.includes(state.q))return false;}
return true;
}
function renderScheduleResults(now){
const wrap=document.getElementById("schResults");if(!wrap)return;
const list=MATCHES.filter(m=>matchMatchesFilter(m,now)).sort((a,b)=>new Date(a.t)-new Date(b.t));
if(!list.length){wrap.innerHTML=`<div class="empty"><b>No matches found</b>Try clearing a filter or searching a different team.</div>`;return;}
const days={};list.forEach(m=>{const k=etDateKey(m.t);(days[k]=days[k]||[]).push(m);});
let html=`<div class="result-count">${list.length} ${list.length===1?"match":"matches"}</div>`;
Object.keys(days).sort().forEach(k=>{
const dm=days[k];
html+=`<div class="day-head"><span class="d">${etDateLabel(k)}</span><span class="ln"></span><span class="ct">${dm.length} ${dm.length===1?"match":"matches"}</span></div>`;
html+=`<div class="matches">`+dm.map(m=>matchCard(m,now)).join("")+`</div>`;
});
wrap.innerHTML=html;
}
/* ===================== VIEW: GROUPS ===================== */
function badgeFor(team){
const b=TEAM_BADGE[team];if(!b)return "";
if(b==="host")return `<span class="badge-mini b-host">Host</span>`;
if(b==="debut")return `<span class="badge-mini b-debut">Debut</span>`;
if(b==="hold")return `<span class="badge-mini b-hold">Holders</span>`;
return "";
}
function groupCard(g){
const rows=GROUPS[g];
const body=rows.map((r,i)=>{
const gd=r.gf-r.ga;const q=i===0?"q1":i===1?"q2":i===2?"q3":"";
return `<tr class="${q}"><td class="pos">${i+1}</td><td class="tl"><div class="tm"><span class="fl">${flag(r.team)}</span><span class="nm">${escapeHtml(r.team)}${badgeFor(r.team)}</span></div></td><td>${r.p}</td><td class="c-w">${r.w}</td><td class="c-d">${r.d}</td><td class="c-l">${r.l}</td><td class="c-gf">${r.gf}</td><td class="c-ga">${r.ga}</td><td>${gd>0?"+":""}${gd}</td><td class="pts">${r.pts}</td></tr>`;
}).join("");
return `<div class="group-card"><div class="gc-head"><span class="lt">${g}</span><span class="ti">Group<b>Group ${g}</b></span></div>
<table class="gtable"><thead><tr><th style="width:18px"></th><th class="tl">Team</th><th>P</th><th class="h-w">W</th><th class="h-d">D</th><th class="h-l">L</th><th class="h-gf">GF</th><th class="h-ga">GA</th><th>GD</th><th>Pts</th></tr></thead><tbody>${body}</tbody></table></div>`;
}
function renderGroups(){
const el=document.getElementById("view-groups");
let html=`<div class="section-head"><div><span class="klabel">12 groups · live standings</span><h2>Groups & Tables</h2><p class="lede">Top two of each group qualify automatically, joined by the eight best third-placed teams -- 32 of 48 advance to the Round of 32.</p></div></div>`;
html+=`<div class="qual-note"><span class="k"><i style="background:var(--pitch)"></i>Winners -- qualify</span><span class="k"><i style="background:rgba(25,224,138,.5)"></i>Runners-up -- qualify</span><span class="k"><i style="background:var(--sky)"></i>3rd -- best 8 advance</span><span class="k" style="margin-left:auto;color:var(--muted-2)">Standings update as results come in</span></div>`;
html+=`<div class="groups-grid">`+Object.keys(GROUPS).map(groupCard).join("")+`</div>`;
el.innerHTML=html;
}
/* ===================== VIEW: BRACKET ===================== */
function brdNode(m){
const isGold=m.s==="final";
const row=name=>{const ph=isPlaceholder(name);return `<div class="bn-row">${ph?`<span class="fl" style="opacity:.4">•</span>`:`<span class="fl">${flag(name)}</span>`}<span class="nm">${escapeHtml(name)}</span></div>`;};
return `<div class="brd-node ${isGold?"gold":""}"><div class="bn-top"><span class="bn-no">Match ${m.n}</span><span class="bn-time">${fmtTime(m.t,state.tz)} ${fmtTzAbbr(m.t,state.tz)}</span></div>${row(m.h)}<div class="bn-vs"></div>${row(m.a)}<div class="bn-venue">📍 ${escapeHtml(m.c)} ${COUNTRY_FLAG[countryOf(m.c)]} · ${fmtDayDate(m.t,state.tz)}</div></div>`;
}
function brdColumn(stage,label){
const ms=MATCHES.filter(m=>m.s===stage).sort((a,b)=>a.n-b.n);
const col=STAGES[stage].color;
return `<div class="brd-col c-${stage}"><div class="brd-colhead" style="--col:${col}">${label}<span class="ct">${ms.length}</span></div><div class="matchstack">${ms.map(brdNode).join("")}</div></div>`;
}
function renderBracket(){
const el=document.getElementById("view-bracket");
const fin=MATCHES.find(m=>m.s==="final");const third=MATCHES.find(m=>m.s==="3rd");
let html=`<div class="section-head"><div><span class="klabel">June 28 → July 19 · single elimination</span><h2>The Knockout Bracket</h2><p class="lede">32 teams, six rounds, one trophy. Matchups fill in as the group stage settles -- scroll across to follow the path to the Final. </p></div></div>`;
html+=`<div class="bracket-scroll"><div class="bracket">
${brdColumn("r32","Round of 32")}
${brdColumn("r16","Round of 16")}
${brdColumn("qf","Quarter-finals")}
${brdColumn("sf","Semi-finals")}
<div class="brd-final-wrap">
<div class="trophy-card"><div class="tr">🏆</div><div class="tt">The Final</div><div class="ts">July 19 · 3:00 PM ET</div><div class="tv">MetLife Stadium · East Rutherford, NJ 🇺🇸</div></div>
${brdNode(fin)}
<div class="brd-colhead" style="--col:${STAGES['3rd'].color};margin-top:4px">Third-place</div>
${brdNode(third)}
</div>
</div></div>`;
el.innerHTML=html;
}
/* ===================== VIEW: STADIUMS ===================== */
function renderStadiums(){
const el=document.getElementById("view-stadiums");
let html=`<div class="section-head"><div><span class="klabel">16 venues · 3 nations</span><h2>The Stadiums</h2><p class="lede">Eleven venues in the United States, three in Mexico and two in Canada -- from the opening match at Estadio Azteca to the Final at MetLife.</p></div></div>`;
html+=`<div class="host-legend"><span><b>🇺🇸 11</b>venues · 78 matches</span><span><b>🇲🇽 3</b>venues · 13 matches</span><span><b>🇨🇦 2</b>venues · 13 matches</span></div>`;
const cards=[...VENUES].sort((a,b)=>b.games-a.games).map(v=>`
<div class="stad-card"><span class="flag-bg">${COUNTRY_FLAG[v.cc]}</span>
<div class="city">${COUNTRY_FLAG[v.cc]} ${escapeHtml(v.city)}</div>
<h3>${escapeHtml(v.name)}</h3>
<div class="fifa">FIFA name: ${escapeHtml(v.fifa)}</div>
<div class="row"><span class="l">Capacity</span><span class="v">~${v.cap.toLocaleString()}</span></div>
<div class="row"><span class="l">Matches hosted</span><span class="v">${v.games}</span></div>
${v.note?`<div class="tagline">★ ${escapeHtml(v.note)}</div>`:""}
</div>`).join("");
html+=`<div class="stad-grid">${cards}</div>`;
html+=`<div class="notes"><b>About this hub.</b> All kickoff times are sourced from the official FIFA match schedule and shown in the timezone you pick at the top -- switch it anytime. Results are current through the matches played on June 12; group standings are calculated automatically from those results. Knockout-stage matchups (Round of 32 onward) show their qualification slots and will resolve to real teams as groups are decided. Stadium capacities are approximate tournament configurations. This is an independent fan hub and is not affiliated with FIFA.</div>`;
el.innerHTML=html;
}
/* ===================== WIRING ===================== */
let builtSchedule=false;
function renderActive(now){
if(state.view==="overview")renderOverview(now);
else if(state.view==="schedule"){if(!builtSchedule){renderScheduleShell();builtSchedule=true;}renderScheduleResults(now);}
else if(state.view==="groups")renderGroups();
else if(state.view==="bracket")renderBracket();
else if(state.view==="stadiums")renderStadiums();
}
function showView(view){
state.view=view;
document.querySelectorAll(".tab").forEach(t=>t.setAttribute("aria-selected",String(t.dataset.view===view)));
["overview","schedule","groups","bracket","stadiums"].forEach(v=>{document.getElementById("view-"+v).hidden=(v!==view);});
renderActive(Date.now());
window.scrollTo({top:document.querySelector(".tabs-outer").offsetTop-60,behavior:"smooth"});
}
function buildTzSelect(){
const sel=document.getElementById("tz");
sel.innerHTML=TZ_OPTIONS.map(o=>`<option value="${o.v}">${o.l}${o.v==="local"?" -- "+localZone.split("/").pop().replace(/_/g," "):""}</option>`).join("");
sel.value=state.tz;
sel.addEventListener("change",e=>{
state.tz=e.target.value;saveSetting("tz",state.tz);const now=Date.now();
renderNext(now);renderTicker(now);
if(state.view==="overview")renderOverview(now);
else if(state.view==="schedule")renderScheduleResults(now);
else if(state.view==="bracket")renderBracket();
});
}
function init(){
const now=Date.now();
buildTzSelect();
renderHeroStrip(now);
renderNext(now);
renderTicker(now);
renderOverview(now);
document.getElementById("tabs").addEventListener("click",e=>{const b=e.target.closest(".tab");if(b)showView(b.dataset.view);});
setInterval(()=>{renderNext(Date.now());},1000);
}
/* ===================== VIBES PLATFORM INTEGRATION ===================== */
/* All SDK calls are guarded so the widget runs identically with or without It Just Vibes. */
async function loadSetting(k){try{if(window.vibes&&typeof vibes.load==="function")return await vibes.load(k);}catch(e){}return null;}
async function saveSetting(k,v){try{if(window.vibes&&typeof vibes.save==="function")await vibes.save(k,v);}catch(e){}}
async function start(){
try{const tz=await loadSetting("tz");if(tz&&TZ_OPTIONS.some(o=>o.v===tz))state.tz=tz;}catch(e){}
if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",init,{once:true});
else init();
}
function boot(){
if(window.vibes&&typeof vibes.onReady==="function"){try{vibes.onReady(start);return;}catch(e){}}
start();
}
boot();
</script>
</body>
</html>
```
[REQUESTED CHANGES]
(no specific request — apply your best judgment)
--- HOW TO RESPOND (READ FIRST) ---
Before writing any code, follow this exact process:
1. **ANALYZE** the widget source code provided above and identify:
a. Which Vibes SDK features it already uses (vibes.save, vibes.load, vibes.shared.join, etc.)
b. Which SDK features would genuinely benefit THIS specific widget — tailored to what it does, not a dump of everything available.
2. **PRESENT A NUMBERED LIST** covering:
- SDK features currently active in this widget
- New SDK features that would concretely improve this widget (be specific: why this widget, what it enables)
3. **WAIT** — do not write any code yet. Reply with your analysis and numbered list, then stop and ask the user which numbered items they want.
4. **IMPLEMENT ONLY** the items the user confirms, plus any explicit change they requested. Do not add unrequested features.
**IMPORTANT — Shared state room names:**
If you add vibes.shared.join(), do NOT use a hardcoded string literal as the room name (e.g. vibes.shared.join("lobby")) unless the user explicitly wants ALL viewers to share one single global state. A hardcoded room name means every person who visits this widget reads and writes the same shared state — it is a global room. For per-user or per-session isolation, derive the room name from a variable (e.g. a user ID, session token, or random value). When in doubt, use vibes.save/vibes.load for per-user persistence instead.
--- VIBES SDK CONTEXT ---
## Vibes SDK Reference
You are building an HTML widget for It Just Vibes (itjustvibes.com). The Vibes SDK is auto-injected — do NOT add a script tag. Just use `window.vibes` (or just `vibes`).
### Setup
Wrap your startup code in `vibes.onReady`:
```js
vibes.onReady(async () => {
const saved = await vibes.load("myKey");
// your widget logic here
});
```
### State (Per-User Persistence)
Every user gets their own isolated state per widget. All methods return Promises.
| Method | Description |
|--------|-------------|
| `await vibes.save(key, value)` | Save JSON-serializable data |
| `await vibes.load(key)` | Load saved data (returns `null` if not found) |
| `await vibes.delete(key)` | Delete a saved key |
| `await vibes.listKeys()` | Get array of all saved key names |
**Key rules:**
- Keys are strings, max 64 characters, alphanumeric + dashes/underscores
- Values must be JSON-serializable (objects, arrays, strings, numbers, booleans)
- Max 100KB per value, 500KB per widget per user, 5MB per user total
- Max 100 keys per widget per user
### Fetch Proxy
Use direct `fetch()` for APIs with permissive CORS headers (`Access-Control-Allow-Origin: *`). Use `vibes.fetch` for APIs without CORS headers (the proxy handles cross-origin requests):
```js
const resp = await vibes.fetch("https://api.example.com/data", {
method: "GET", // GET, POST, PUT, DELETE
headers: {}, // optional headers
body: null, // optional body (string)
timeout: null // optional timeout in ms
});
const data = await resp.json(); // or resp.text()
console.log(resp.status, resp.ok);
```
### Multiplayer (Shared State)
Real-time shared state across all users viewing the same widget.
```js
// Join a room (call once at startup)
await vibes.shared.join("lobby", { persistent: true });
// Set shared state (broadcasts to all users)
await vibes.shared.set("score", { player1: 10, player2: 7 });
// Read shared state (synchronous, returns last known value)
const score = vibes.shared.get("score");
// Listen for changes to a specific key
vibes.shared.onChange("score", (newValue) => {
console.log("Score updated:", newValue);
});
// Listen for any shared state change
vibes.shared.onAny((key, value) => {
console.log(key, "changed to", value);
});
// Get number of connected users
const count = await vibes.shared.getUserCount();
// Leave room
vibes.shared.leave();
// Clear all shared state for this room
await vibes.shared.clear();
```
**Shared state options:**
- `{ persistent: true }` — state survives page reloads (stored server-side)
- Default room name is "__default__" if omitted
### Rules
1. **Do NOT use localStorage, sessionStorage, or window.storage** — they are blocked or undefined in the sandbox. Use `vibes.save`/`vibes.load` instead.
2. **Do NOT add a script tag to import the SDK** — it is auto-injected.
3. **Wrap startup code in `vibes.onReady()`** — the SDK may not be ready immediately.
4. **Await all SDK calls** — every method (except `vibes.shared.get`) returns a Promise.
5. **Do NOT import external JS libraries via script tags** — they will be blocked. Include library code inline or use a CDN link in a `<link>` tag for CSS only.
6. **Keep total code under 80KB** — that is the default widget size limit (your account limit may be higher).
### Rate Limits
| Operation | Limit |
|-----------|-------|
| Writes (save + delete combined) | 30/min |
| Reads (load) | 60/min |
| List keys | 30/min |
| Fetch proxy | Rate limited per widget |
### Error Handling
All SDK methods can reject. Wrap in try/catch:
```js
try {
await vibes.save("key", value);
} catch (err) {
console.error("Save failed:", err.message);
}
```
Fetch proxy errors include `err.code`: `"RATE_LIMITED"`, `"BLOCKED"`, `"FETCH_ERROR"`.
### Data Export
Export rows of data as CSV or Excel directly from your widget.
```js
// Register dataset for the platform's "Export data" button
// AND optionally trigger an immediate download
vibes.exportData(rows, { filename: 'results.csv' })
// rows: Array of arrays (each inner array is one row; first row = headers)
// options.filename: sets the download filename and format (csv or xlsx)
// options.directDownload: false to only register without downloading (default: true)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `filename` | string | `'data.csv'` | Download filename; extension determines format (`.csv` or `.xlsx`) |
| `directDownload` | boolean | `true` | Trigger immediate browser download in addition to registering the dataset |
**Notes:**
- First call registers the dataset so the widget chrome shows an "Export data" button
- Use `.csv` extension for CSV, `.xlsx` for Excel
- CSV injection safety is automatic (formula-starting cells prefixed with `'`)
- Data stays in the browser — no server round-trip
--- FORK & RESUBMIT INSTRUCTION ---
Return the complete, self-contained HTML file with all changes applied.
It should be ready to paste into itjustvibes.com/submit to create a new fork.
Include this comment at the top of the output:
/* Forked from: https://itjustvibes.com/GigaChad/fifa-world-cup-26-live-hub */