:root {
–green-dark: #004831;
–green-mid: #006747;
–green-light: #1a7a58;
–gold: #FFCD00;
–gold-dark: #c9a200;
–white: #ffffff;
–gray: #aaaaaa;
–red: #e53935;
–birdie: #00c853;
–over: #e53935;
–even: #ffffff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Georgia, serif; background: var(–green-dark); min-height: 100vh; color: var(–white); }
header { background: var(–green-dark); border-bottom: 4px solid var(–gold); padding: 22px 20px 16px; text-align: center; }
header .logo { font-size: 2.2em; margin-bottom: 4px; }
header h1 { color: var(–gold); font-size: 1.8em; letter-spacing: 3px; text-transform: uppercase; }
header p { color: var(–gray); font-size: 0.9em; margin-top: 6px; font-style: italic; }
.container { max-width: 1080px; margin: 0 auto; padding: 24px 16px 40px; }
.status-bar { display: flex; justify-content: space-between; align-items: center; background: rgba(0,0,0,0.4); border: 1px solid rgba(245,230,66,0.3); border-radius: 6px; padding: 8px 16px; margin-bottom: 20px; font-size: 0.82em; color: var(–gray); }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #4caf50; margin-right: 6px; animation: pulse 2s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.refresh-btn { background: var(–gold); color: var(–green-dark); border: none; padding: 5px 14px; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: Georgia, serif; font-size: 0.9em; }
.card { background: var(–green-dark); border: 2px solid var(–gold); border-radius: 8px; overflow: hidden; margin-bottom: 28px; }
.card-header { background: var(–gold); color: var(–green-dark); padding: 10px 20px; font-size: 1.1em; font-weight: bold; text-transform: uppercase; letter-spacing: 1.5px; }
table { width: 100%; border-collapse: collapse; }
th { background: rgba(0,0,0,0.35); padding: 9px 14px; text-align: left; color: var(–gold); font-size: 0.78em; text-transform: uppercase; letter-spacing: 1px; }
td { padding: 12px 14px; border-bottom: 1px solid rgba(255,255,255,0.08); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
.pos-cell { font-size: 1.5em; font-weight: bold; color: var(–gold); width: 48px; text-align: center; }
.pos-cell.first { color: #ffd700; font-size: 1.8em; } .pos-cell.second { color: #c0c0c0; } .pos-cell.third { color: #cd7f32; }
.name-cell { font-size: 1.15em; font-weight: bold; }
.pick-tag { display: inline-block; background: rgba(255,255,255,0.1); border-radius: 3px; padding: 2px 7px; margin: 2px 3px 2px 0; white-space: nowrap; }
.pick-tag .pscore { font-weight: bold; margin-left: 3px; }
.total-cell { text-align: right; font-size: 1.5em; font-weight: bold; width: 80px; }
.golfer-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); }
.golfer-card { padding: 10px 12px; border-right: 1px solid rgba(255,255,255,0.08); border-bottom: 1px solid rgba(255,255,255,0.08); text-align: center; }
.g-name { font-size: 0.82em; font-weight: bold; } .g-owner { font-size: 0.7em; color: var(–gray); margin-bottom: 4px; }
.g-score { font-size: 1.3em; font-weight: bold; margin: 2px 0; } .g-thru { font-size: 0.72em; color: var(–gray); }
.under { color: #00c853; } .over { color: #e53935; } .even { color: #ffffff; } .cut { color: #ff7043; font-style: italic; } .wd { color: #ff9800; font-style: italic; } .dash { color: #555; }
.loading, .error { padding: 40px; text-align: center; color: var(–gray); font-style: italic; }
.error { color: #ef9a9a; }
footer { text-align: center; color: #444; font-size: 0.78em; margin-top: 20px; }
Loading live scores…
↺ Refresh
Fetching scores from Augusta…
Auto-refreshes every 60 seconds · Scores via Masters.com / ESPN
const POOL = {
‘Robby’: [‘Scottie Scheffler’,’Tommy Fleetwood’,’Matt Fitzpatrick’,’Chris Gotterup’,’J.J. Spaun’,’Gary Woodland’],
‘Mikey Christin’: [‘Jon Rahm’,’Bob MacIntyre’,’Hideki Matsuyama’,’Si Woo Kim’,’Shane Lowry’,’Tyrrell Hatton’],
‘Mikey Coe’: [‘Rory McIlroy’,’Justin Rose’,’Akshay Bhatia’,’Jake Knapp’,’Sepp Straka’,’Jacob Bridgeman’],
‘Bobby’: [‘Ludvig Aberg’,’Brooks Koepka’,’Viktor Hovland’,’Sam Burns’,’Max Homa’,’Harris English’],
‘Timmy’: [‘Bryson DeChambeau’,’Xander Schauffele’,’Collin Morikawa’,’Patrick Reed’,’Russell Henley’,’Patrick Cantlay’],
‘Danny’: [‘Cam Young’,’Jordan Spieth’,’Min Woo Lee’,’Justin Thomas’,’Adam Scott’,’Jason Day’],
};
const ALIASES = {
‘ludvig aberg’:’Ludvig Aberg’,’l. aberg’:’Ludvig Aberg’,’victor hovland’:’Viktor Hovland’,
‘colin morikawa’:’Collin Morikawa’,’jj spaun’:’J.J. Spaun’,’j.j. spaun’:’J.J. Spaun’,
‘john rahm’:’Jon Rahm’,’jon rahm’:’Jon Rahm’,’cameron young’:’Cam Young’,
‘robert macintyre’:’Bob MacIntyre’,’bob macintyre’:’Bob MacIntyre’,
};
const CANONICAL = {};
for (const picks of Object.values(POOL)) for (const p of picks) CANONICAL[p.toLowerCase()] = p;
for (const [alt, canon] of Object.entries(ALIASES)) CANONICAL[alt] = canon;
const OWNER = {};
for (const [participant, picks] of Object.entries(POOL)) for (const p of picks) OWNER[p] = participant;
function resolvePlayer(n) { return CANONICAL[n.toLowerCase().trim()] || null; }
function parseScore(raw) {
if (raw === null || raw === undefined) return null;
const s = String(raw).trim().toUpperCase();
if (s === ‘E’ || s === ‘0’) return 0;
if (/^[+-]?d+$/.test(s)) return parseInt(s, 10);
return null;
}
function fmtScore(score, status) {
if (status === ‘CUT’) return { text: ‘CUT’, cls: ‘cut’ };
if (status === ‘WD’) return { text: ‘WD’, cls: ‘wd’ };
if (score === null) return { text: ‘—’, cls: ‘dash’ };
if (score 0) return { text: ‘+’ + score, cls: ‘over’ };
return { text: ‘E’, cls: ‘even’ };
}
async function fetchData() {
const endpoints = [
{ url: ‘
https://www.masters.com/en_US/scores/feeds/2026/scores.json’ ;, parser: parseMasters },
{ url: ‘
https://site.api.espn.com/apis/site/v2/sports/golf/pga/leaderboard?event=401811941’ ;, parser: parseESPN },
{ url: ‘
https://site.api.espn.com/apis/site/v2/sports/golf/pga/leaderboard’ ;, parser: parseESPN },
{ url: ‘
https://cdn.espn.com/core/golf/leaderboard?event=401811941&xhr=1&limit=200’ ;, parser: parseESPN },
];
for (const { url, parser } of endpoints) {
try {
const res = await fetch(url, { headers: { Accept: ‘application/json’ } });
if (!res.ok) continue;
const data = await res.json();
return { data, parser };
} catch(e) { console.warn(‘Fetch failed:’, url, e.message); }
}
return null;
}
function parseMasters(data) {
const result = {};
const players = data?.data?.player || data?.leaderboard?.player || data?.player || [];
for (const p of players) {
const name = p.full_name || ”;
if (!name) continue;
const poolName = resolvePlayer(name);
if (!poolName) continue;
const score = parseScore(p.topar ?? null);
let status = ‘active’;
const s = String(p.status || ”).toUpperCase();
if (s === ‘C’) status = ‘CUT’;
if (s === ‘W’) status = ‘WD’;
if (s === ‘D’) status = ‘DQ’;
const thru = String(p.thru ?? ”);
result[poolName] = { score, status, thru, round: data?.data?.currentRound || ” };
}
return result;
}
function parseESPN(data) {
const result = {};
let competitors = [];
if (Array.isArray(data.leaderboard)) { competitors = data.leaderboard; }
else if (data.events && data.events[0]) {
const evt = data.events[0];
const comp = (evt.competitions || [])[0];
competitors = comp ? (comp.competitors || []) : [];
} else if (data.content && data.content.leaderboard) {
const lb = data.content.leaderboard;
competitors = Array.isArray(lb) ? lb : (lb.rows || []);
}
for (const c of competitors) {
const name = c.athlete?.displayName || c.athlete?.fullName || c.displayName || c.name || ”;
if (!name) continue;
const poolName = resolvePlayer(name);
if (!poolName) continue;
let status = ‘active’;
const sStr = String(c.status?.type?.name || c.status?.type?.shortDetail || c.status || ”).toUpperCase();
if (sStr.includes(‘CUT’)) status = ‘CUT’;
if (sStr.includes(‘WD’) || sStr.includes(‘WITHDRAW’)) status = ‘WD’;
if (sStr.includes(‘DQ’)) status = ‘DQ’;
let score = null;
if (c.score !== undefined) {
score = typeof c.score === ‘object’ && c.score !== null ? parseScore(c.score.value ?? c.score.displayValue) : parseScore(c.score);
} else if (c.totalScore !== undefined) { score = parseScore(c.totalScore); }
let thru = c.thru ?? c.thruHole ?? c.period ?? ”;
if (String(thru) === ’18’) thru = ‘F’;
result[poolName] = { score, status, thru: String(thru), round: c.round || ” };
}
return result;
}
function renderPool(playerData) {
const standings = Object.entries(POOL).map(([participant, picks]) => {
let total = 0, counted = 0;
const details = picks.map(golfer => {
const p = playerData[golfer] || {};
const s = p.score ?? null;
if (s !== null && p.status !== ‘WD’) { total += s; counted++; }
return { golfer, score: s, status: p.status || ‘unknown’, thru: p.thru || ” };
});
return { participant, picks: details, total, counted };
});
standings.sort((a, b) => {
if (a.counted === 0 && b.counted === 0) return 0;
if (a.counted === 0) return 1;
if (b.counted === 0) return -1;
return a.total – b.total;
});
let html = ‘
Pos Participant Picks Total ‘;
standings.forEach((s, i) => {
const rank = i + 1;
const posClass = rank === 1 ? ‘first’ : rank === 2 ? ‘second’ : rank === 3 ? ‘third’ : ”;
const { text: totalText, cls: totalCls } = fmtScore(s.counted > 0 ? s.total : null, null);
const picksHtml = s.picks.map(p => {
const lastName = p.golfer.split(‘ ‘).slice(-1)[0];
const { text: st, cls: sc } = fmtScore(p.score, p.status);
const thruHtml = p.thru ? ‘ ‘ + p.thru + ‘ ‘ : ”;
return ‘‘ + lastName + ‘‘ + st + ‘ ‘ + thruHtml + ‘ ‘;
}).join(”);
html += ‘‘ + rank + ‘ ‘ + s.participant + ‘
‘ + picksHtml + ‘
‘ + totalText + ‘ ‘;
});
html += ‘
‘;
document.getElementById(‘pool-body’).innerHTML = html;
}
function renderGolfers(playerData) {
const allGolfers = Object.entries(POOL).flatMap(([participant, picks]) => picks.map(g => ({ golfer: g, participant })));
const html = allGolfers.map(({ golfer, participant }) => {
const p = playerData[golfer] || {};
const lastName = golfer.split(‘ ‘).slice(-1)[0];
const { text, cls } = fmtScore(p.score ?? null, p.status || null);
const thruStr = p.thru ? ‘Thru ‘ + p.thru : (p.status ? ” : ‘Not started’);
return ‘
‘ + participant + ‘
‘ + lastName + ‘
‘ + text + ‘
‘ + thruStr + ‘
‘;
}).join(”);
document.getElementById(‘golfer-grid’).innerHTML = html;
}
async function loadLeaderboard() {
document.getElementById(‘status-text’).textContent = ‘Fetching live scores…’;
try {
const result = await fetchData();
if (!result) throw new Error(‘All API endpoints failed.’);
const { data, parser } = result;
const playerData = parser(data);
const found = Object.keys(playerData).length;
if (found === 0) {
document.getElementById(‘pool-body’).innerHTML = ‘
Connected but couldn’t match any players. Check console (F12).
‘;
} else {
renderPool(playerData);
renderGolfers(playerData);
}
const now = new Date();
const timeStr = now.toLocaleTimeString([], { hour: ‘2-digit’, minute: ‘2-digit’ });
document.getElementById(‘status-text’).textContent = ‘Live · Updated ‘ + timeStr + ‘ · ‘ + found + ‘/36 players · Auto-refreshes 60s’;
} catch(err) {
document.getElementById(‘pool-body’).innerHTML = ‘
⚠️ Could not load scores: ‘ + err.message + ‘↺ Try Again
‘;
document.getElementById(‘status-text’).textContent = ‘Error loading scores’;
}
}
loadLeaderboard();
setInterval(loadLeaderboard, 60000);