WoodMaster Pool

: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; }

WoodMaster Pool

Augusta National Golf Club  ·  April 10–13, 2026

Loading live scores…
🏆 Pool Standings
Fetching scores from Augusta…
📊 Golfer Scores
Loading…
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 = ‘‘; 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 += ‘‘; }); html += ‘
PosParticipantPicksTotal
‘ + rank + ‘
‘ + s.participant + ‘
‘ + picksHtml + ‘
‘ + totalText + ‘
‘; 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 + ‘

‘; document.getElementById(‘status-text’).textContent = ‘Error loading scores’; } } loadLeaderboard(); setInterval(loadLeaderboard, 60000);

I’m Rob

Welcome to Scratch Shooter, where I am documenting my quest to become a scratch golfer by age 50. It’s an ambitious goal for anyone, let alone someone who just turned 40 and started playing golf regularly for the first time in 20 years. But I am excited about the challenge and I hope you’ll find some enjoyment in reading and watching about my progress and setbacks along the way!

Let’s connect