NP@naman-parashar-s-team / World Cup scores + oddsPublic
World Cup scores + odds
A JS snippet on QuikRun, callable at quik.run/r/world-cup.
JS· node:20· 1,158 runs· 0 forks· updated 1d ago
World Cup scores + odds.js
// FIFA World Cup - live scores + a win-probability model, server-rendered on every request.// Data: TheSportsDB (public test key "3", league 4429), a SINGLE fetch. That feed has no// probabilities, so we compute them from a baked-in Elo-style strength table (no second// fetch needed) - a real model, labelled as an estimate.// Approximate team strength (FIFA-ranking-ish, higher = stronger). Unknowns default to 72.const STRENGTH = {Argentina: 94, France: 93, Spain: 92, England: 91, Brazil: 90, Portugal: 89, Netherlands: 88,Germany: 86, Belgium: 85, Croatia: 84, Italy: 84, Uruguay: 82, Colombia: 81, Morocco: 82,Switzerland: 80, Denmark: 80, Japan: 80, "South Korea": 78, Senegal: 79, Serbia: 79, Poland: 78,USA: 78, Mexico: 78, Ecuador: 76, "Czech Republic": 76, Australia: 73, Canada: 74, Paraguay: 73,"Bosnia-Herzegovina": 72, "South Africa": 70, Ghana: 74, Nigeria: 77, Egypt: 76, Peru: 74,};const rating = (name) => STRENGTH[name] != null ? STRENGTH[name] : 72;const FLAGS = {Argentina: "🇦🇷", France: "🇫🇷", Spain: "🇪🇸", England: "🏴", Brazil: "🇧🇷", Portugal: "🇵🇹", Netherlands: "🇳🇱",Germany: "🇩🇪", Belgium: "🇧🇪", Croatia: "🇭🇷", Italy: "🇮🇹", Uruguay: "🇺🇾", Colombia: "🇨🇴", Morocco: "🇲🇦",Switzerland: "🇨🇭", Denmark: "🇩🇰", Japan: "🇯🇵", "South Korea": "🇰🇷", Senegal: "🇸🇳", Serbia: "🇷🇸", Poland: "🇵🇱",USA: "🇺🇸", Mexico: "🇲🇽", Ecuador: "🇪🇨", "Czech Republic": "🇨🇿", Australia: "🇦🇺", Canada: "🇨🇦", Paraguay: "🇵🇾","Bosnia-Herzegovina": "🇧🇦", "South Africa": "🇿🇦", Ghana: "🇬🇭", Nigeria: "🇳🇬", Egypt: "🇪🇬", Peru: "🇵🇪",};const flag = (name) => FLAGS[name] || "⚽";// Elo expectancy -> {home, draw, away} probabilities. Neutral venue (WC), so no home bonus.function probs(home, away) {// Scale each strength point to ~14 Elo points so the rating gaps actually differentiate.const diff = (rating(home) - rating(away)) * 14;const e = 1 / (1 + Math.pow(10, -diff / 400)); // home expected result 0..1let pDraw = 0.27 - Math.min(0.16, Math.abs(e - 0.5) * 0.44); // closer game -> more drawspDraw = Math.max(0.09, pDraw);const pHome = (1 - pDraw) * e;const pAway = (1 - pDraw) * (1 - e);const r = (x) => Math.round(x * 100);let h = r(pHome), d = r(pDraw);let a = 100 - h - d; // keep the three summing to exactly 100return { h, d, a };}const FALLBACK = [{ home: "Spain", away: "Argentina", hs: "1", as: "0", status: "FT · FINAL", date: "2026-07-19" },{ home: "Brazil", away: "Morocco", hs: "1", as: "1", status: "FT", date: "2026-06-13" },{ home: "USA", away: "Paraguay", hs: "4", as: "1", status: "FT", date: "2026-06-13" },{ home: "Mexico", away: "South Africa", hs: "2", as: "0", status: "FT", date: "2026-06-11" },{ home: "South Korea", away: "Czech Republic", hs: "2", as: "1", status: "FT", date: "2026-06-12" },];const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];function shortDate(d) {const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(d || "");return m ? MONTHS[Number(m[2]) - 1] + " " + Number(m[3]) : "";}function esc(s) {return String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));}export default async function handler(req) {const t0 = Date.now();let matches = FALLBACK;try {const res = await fetch("https://www.thesportsdb.com/api/v1/json/3/eventsseason.php?id=4429&s=2026");if (res.ok) {const data = await res.json();const events = data.events || [];if (events.length) {matches = events.slice(-6).reverse().map((e) => ({ home: e.strHomeTeam, away: e.strAwayTeam, hs: e.intHomeScore, as: e.intAwayScore, status: e.strStatus, date: e.dateEvent }));}}} catch (e) {// upstream flaky - render the cached fallback below}const rows = matches.map((m, i) => {const p = probs(m.home, m.away);const isLive = /1H|2H|HT|LIVE|ET/i.test(m.status || "");const isFT = /FT|Finished|AET|PEN/i.test(m.status || "");const hasScore = m.hs != null && m.hs !== "" && m.as != null && m.as !== "";const score = hasScore ? esc(m.hs) + " – " + esc(m.as) : "vs";const pill = isLive ? '<span class="pill live">● LIVE</span>' : isFT ? '<span class="pill">FT</span>' : "";const md = (i * 0.07).toFixed(2);const bd = (0.18 + i * 0.07).toFixed(2);return ('<div class="match" style="animation-delay:' + md + 's">' +'<div class="head"><span>' + esc(shortDate(m.date)) + "</span>" + pill + "</div>" +'<div class="teams">' +'<span class="team r">' + esc(m.home) + ' <span class="fl">' + flag(m.home) + "</span></span>" +'<span class="score">' + score + "</span>" +'<span class="team l"><span class="fl">' + flag(m.away) + "</span> " + esc(m.away) + "</span>" +"</div>" +'<div class="bar" style="animation-delay:' + bd + 's"><i style="width:' + p.h + '%;background:#0B8A5C"></i><i style="width:' + p.d + '%;background:#d4d4d8"></i><i style="width:' + p.a + '%;background:#F0492A"></i></div>' +'<div class="prob"><span><b>' + p.h + "%</b> " + esc(m.home) + "</span><span>" + p.d + "% draw</span><span>" + esc(m.away) + " <b>" + p.a + "%</b></span></div>" +"</div>");}).join("");const ms = Date.now() - t0;const time = new Date().toISOString().slice(11, 16);const cached = matches === FALLBACK ? " · cached" : "";// Deterministic confetti (no Math.random in the sandbox) for the champions banner.const CONF = ["#F0492A", "#0B0B0C", "#ffffff", "#E6FF55", "#0B8A5C"];let confetti = "";for (let i = 0; i < 18; i++) {confetti +='<i style="left:' + ((i * 5.7 + 3) % 100).toFixed(1) + "%;background:" + CONF[i % CONF.length] +";animation-delay:" + ((i * 0.11) % 1.6).toFixed(2) + "s;animation-duration:" + (1.4 + (i % 5) * 0.28).toFixed(2) + 's"></i>';}const champ ='<div class="champ"><span class="confetti">' + confetti + '</span>' +'<span class="trophy">🏆</span>' +'<span class="champ-txt"><span class="champ-kicker">Champions · 2026 · Final</span>' +'<span class="champ-line">Spain 🇪🇸 won the World Cup, 1-0!</span></span></div>';const html = `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>FIFA World Cup - scores & win probability · QuikRun</title><meta name="description" content="Live FIFA World Cup scores with a model win-probability, server-rendered at the edge by QuikRun." /><link rel="canonical" href="https://quik.run/r/world-cup" /><meta property="og:type" content="website" /><meta property="og:site_name" content="QuikRun" /><meta property="og:title" content="FIFA World Cup - scores & win probability" /><meta property="og:description" content="Live World Cup scores with a model win-probability, rendered at the edge by QuikRun." /><meta property="og:url" content="https://quik.run/r/world-cup" /><meta property="og:image" content="https://quik.run/og/r/world-cup" /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="630" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:image" content="https://quik.run/og/r/world-cup" /><style>* { box-sizing: border-box; margin: 0; padding: 0; }body { background: #f4f4f5; color: #18181b; padding: 32px 16px;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;display: flex; align-items: flex-start; justify-content: center; min-height: 100vh; }.card { width: 100%; max-width: 620px; background: #fff; border-radius: 22px;box-shadow: 0 12px 48px rgba(0,0,0,.09); overflow: hidden; }.chrome { display: flex; align-items: center; gap: 12px; padding: 20px 26px; border-bottom: 1px solid #f1f1f2; }.dots { display: flex; gap: 7px; }.dot { width: 11px; height: 11px; border-radius: 50%; background: #d4d4d8; }.url { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 15px; color: #a1a1aa; }.brand { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; text-decoration: none;color: #71717a; font-size: 13px; font-weight: 600; white-space: nowrap; transition: color .15s ease; }.brand:hover { color: #18181b; }.qmark { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px;border-radius: 6px; background: #E6FF55; color: #0B0B0C; font-weight: 800; font-size: 13px; letter-spacing: -.04em; }.body { padding: 22px 26px 10px; }h1 { font-size: 25px; font-weight: 700; letter-spacing: -.5px; }.sub { color: #a1a1aa; font-size: 13.5px; margin-top: 3px; }.match { padding: 18px 0; border-bottom: 1px solid #f4f4f5; }.match:last-child { border-bottom: 0; }.head { display: flex; align-items: center; justify-content: space-between;font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: #a1a1aa;text-transform: uppercase; letter-spacing: .06em; }.pill { background: #f1f1f2; color: #71717a; border-radius: 999px; padding: 2px 8px; font-weight: 600; }.pill.live { background: #F0492A; color: #fff; }.teams { display: flex; align-items: center; gap: 14px; margin-top: 9px; }.team { flex: 1; font-size: 18px; font-weight: 600; }.team.r { text-align: right; }.team.l { text-align: left; }.score { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }.bar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; margin-top: 13px; background: #f1f1f2; }.bar i { display: block; height: 100%; }.prob { display: flex; justify-content: space-between; margin-top: 8px;font-family: ui-monospace, Menlo, monospace; font-size: 12px; color: #71717a; }.prob b { color: #18181b; }.foot { padding: 16px 26px; font-family: ui-monospace, Menlo, monospace; font-size: 12.5px;color: #a1a1aa; border-top: 1px solid #f1f1f2; }.fl { font-size: 15px; }.champ { position: relative; overflow: hidden; display: flex; align-items: center; gap: 15px;margin: 2px 0 20px; padding: 16px 20px; border-radius: 16px; color: #0B0B0C;background: linear-gradient(120deg, #E6FF55 0%, #C9FF6B 100%);box-shadow: 0 8px 22px rgba(201,255,107,.45); animation: pop .5s cubic-bezier(.2,1.3,.35,1) both; }.trophy { font-size: 38px; line-height: 1; animation: bob 1.3s ease-in-out infinite; }.champ-txt { display: flex; flex-direction: column; }.champ-kicker { font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; opacity: .62; }.champ-line { font-size: 21px; font-weight: 800; letter-spacing: -.02em; margin-top: 2px; }.confetti { position: absolute; inset: 0; pointer-events: none; }.confetti i { position: absolute; top: -12px; width: 7px; height: 11px; border-radius: 1px; opacity: .95;animation-name: fall; animation-timing-function: linear; animation-iteration-count: infinite; }@keyframes pop { from { transform: scale(.92); opacity: 0; } to { transform: none; opacity: 1; } }@keyframes bob { 0%, 100% { transform: translateY(0) rotate(-6deg); } 50% { transform: translateY(-5px) rotate(6deg); } }@keyframes fall { 0% { transform: translateY(0) rotate(0); } 100% { transform: translateY(150px) rotate(400deg); opacity: 0; } }.match { opacity: 0; animation: rise .5s cubic-bezier(.2,.8,.2,1) forwards; }.bar { transform: scaleX(0); transform-origin: left; animation: fill .7s cubic-bezier(.2,.8,.2,1) forwards; }.pill.live { animation: pulse 1.3s ease-in-out infinite; }@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }@keyframes fill { to { transform: scaleX(1); } }@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }@media (max-width: 480px) {body { padding: 16px 10px; }.chrome { padding: 15px 16px; gap: 8px; }.url { font-size: 12.5px; white-space: nowrap; }.brand { gap: 0; font-size: 0; }.body { padding: 18px 16px 8px; }h1 { font-size: 21px; }.sub { font-size: 12px; }.match { padding: 14px 0; }.teams { gap: 8px; }.team { font-size: 15px; }.score { font-size: 17px; }.fl { font-size: 13px; }.prob { font-size: 11px; }.foot { padding: 14px 16px; font-size: 11.5px; }}@media (prefers-reduced-motion: reduce) { .match, .bar { animation: none; opacity: 1; transform: none; } }</style></head><body><div class="card"><div class="chrome"><div class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div><span class="url">quik.run/r/world-cup</span><a class="brand" href="https://quik.run" target="_top" rel="noopener"><span class="qmark">q.</span> Built on QuikRun</a></div><div class="body">${champ}<h1>FIFA World Cup 2026</h1><div class="sub">Scores with a model win probability · green home · grey draw · red away</div>${rows}</div><div class="foot">rendered ${time} UTC · ${matches.length} matches · ${ms} ms${cached} · win% is a model estimate</div></div></body></html>`;return new Response(html, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });}
Fork this and make it yours
Describe a change in plain English and QuikRun deploys it to your own URL.