← quik.run
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
Endpointquik.run/r/world-cupRunFork
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..1
let pDraw = 0.27 - Math.min(0.16, Math.abs(e - 0.5) * 0.44); // closer game -> more draws
pDraw = 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 100
return { 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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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) + " &ndash; " + 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 &amp; 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 &amp; 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 &middot; green home &middot; grey draw &middot; red away</div>
${rows}
</div>
<div class="foot">rendered ${time} UTC &middot; ${matches.length} matches &middot; ${ms} ms${cached} &middot; 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.

Fork snippet