<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WC 2026 Pool</title>
<!-- ═══════════════════════════════════════════════════════════
STEP 1: Paste your Firebase config here (replace the
placeholder values with your actual project values)
═══════════════════════════════════════════════════════════ -->
<script>
const firebaseConfig = {
apiKey: "AIzaSyBBKi8-yNT0iSHaEiw4qZnMV2PE2ExqzBk",
authDomain: "wc2026-pool-b30ca.firebaseapp.com",
databaseURL: "https://wc2026-pool-b30ca-default-rtdb.firebaseio.com",
projectId: "wc2026-pool-b30ca",
storageBucket: "wc2026-pool-b30ca.firebasestorage.app",
messagingSenderId: "876287521909",
appId: "1:876287521909:web:e6a01d9ffd1eb0ceedf43c"
};
// STEP 2: Change this to your chosen admin password
const ADMIN_PW = 'wc2026admin';
</script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
--green:#1a7a4a;--green-light:#e8f5ee;
--gold:#c9972a;--gold-light:#fdf3e0;
--surface:#fff;--surface2:#f7f7f5;
--border:#e5e3dc;--border2:#d0cec5;
--text:#1a1a18;--text2:#5a5952;--text3:#9a9890;
--red:#c0392b;--red-light:#fdf0ef;
--radius:10px;--radius-sm:7px;
}
body{font-family:'Georgia',serif;background:var(--surface2);color:var(--text);min-height:100vh}
h1,h2,h3,button,label,input,select,.sans{font-family:'Trebuchet MS',sans-serif}
.screen{display:none;min-height:100vh;padding-bottom:90px}
.screen.active{display:block}
.top-bar{background:var(--green);color:#fff;padding:14px 16px 12px;display:flex;align-items:center;gap:10px}
.top-bar h1{font-size:16px;font-weight:700;flex:1}
.back-btn{background:rgba(255,255,255,.2);border:none;color:#fff;padding:5px 10px;border-radius:6px;font-size:13px;cursor:pointer;font-family:'Trebuchet MS',sans-serif}
.badge{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;font-family:'Trebuchet MS',sans-serif}
.badge-green{background:var(--green-light);color:var(--green)}
.badge-gold{background:var(--gold-light);color:#8a6010}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin:10px 12px}
.card-sm{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;margin:6px 12px}
.section-title{font-size:11px;font-weight:700;letter-spacing:.07em;color:var(--text3);text-transform:uppercase;padding:14px 16px 6px;font-family:'Trebuchet MS',sans-serif}
input[type=text],input[type=tel],input[type=password]{width:100%;padding:11px 13px;border:1px solid var(--border2);border-radius:var(--radius-sm);font-size:15px;font-family:'Trebuchet MS',sans-serif;background:var(--surface);color:var(--text);outline:none}
input:focus{border-color:var(--green)}
.btn{width:100%;padding:13px;font-size:15px;font-weight:700;border:none;border-radius:var(--radius);cursor:pointer;font-family:'Trebuchet MS',sans-serif;transition:opacity .15s,transform .1s}
.btn:active{transform:scale(.98)}
.btn-green{background:var(--green);color:#fff}
.btn-green:disabled{background:#b0b0a8;cursor:not-allowed}
.btn-outline{background:transparent;border:1.5px solid var(--border2);color:var(--text2)}
.btn-sm{padding:8px 14px;font-size:13px;border-radius:var(--radius-sm);border:none;cursor:pointer;font-family:'Trebuchet MS',sans-serif;font-weight:700}
.sticky-footer{position:fixed;bottom:0;left:0;right:0;background:var(--surface);border-top:1px solid var(--border);padding:12px 16px;z-index:50}
.match-row{display:grid;grid-template-columns:1fr 44px 1fr;gap:6px;align-items:center;margin:8px 0}
.pick-btn{padding:9px 6px;font-size:12px;font-weight:700;border:1.5px solid var(--border2);border-radius:var(--radius-sm);background:transparent;color:var(--text);cursor:pointer;text-align:center;transition:all .15s;font-family:'Trebuchet MS',sans-serif;line-height:1.2}
.pick-btn.sel-home,.pick-btn.sel-away{background:var(--green);color:#fff;border-color:var(--green)}
.draw-btn{padding:9px 4px;font-size:11px;font-weight:700;border:1.5px solid var(--border2);border-radius:var(--radius-sm);background:transparent;color:var(--text2);cursor:pointer;text-align:center;transition:all .15s;font-family:'Trebuchet MS',sans-serif}
.draw-btn.sel-draw{background:#666;color:#fff;border-color:#666}
.prog-wrap{padding:4px 16px 10px}
.prog-bg{height:4px;background:var(--border);border-radius:2px}
.prog-fill{height:4px;background:var(--green);border-radius:2px;transition:width .3s}
.tab-row{display:flex;gap:4px;padding:8px 12px;overflow-x:auto;white-space:nowrap}
.tab{padding:5px 13px;font-size:12px;font-weight:700;border-radius:20px;border:1.5px solid var(--border2);background:transparent;color:var(--text2);cursor:pointer;font-family:'Trebuchet MS',sans-serif;flex-shrink:0}
.tab.active{background:var(--green);color:#fff;border-color:var(--green)}
.lb-row{display:flex;align-items:center;padding:11px 16px;border-bottom:1px solid var(--border);gap:10px}
.avatar{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;flex-shrink:0;font-family:'Trebuchet MS',sans-serif}
.rank-num{font-size:13px;font-weight:700;color:var(--text3);width:22px;text-align:center;font-family:'Trebuchet MS',sans-serif}
.pts-chip{font-size:14px;font-weight:700;color:var(--green);font-family:'Trebuchet MS',sans-serif}
.hero-section{background:var(--green);color:#fff;padding:28px 20px 24px;text-align:center}
.hero-section h1{font-size:26px;font-weight:700;margin-bottom:4px}
.hero-section p{font-size:14px;opacity:.8;font-family:'Trebuchet MS',sans-serif}
.pt-rule{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid var(--border);font-size:13px;font-family:'Trebuchet MS',sans-serif}
.round-header{background:var(--gold-light);border:1px solid #e8d49a;border-radius:var(--radius-sm);padding:8px 14px;margin:8px 12px;font-size:13px;font-weight:700;color:#8a6010;font-family:'Trebuchet MS',sans-serif}
.spinner{display:inline-block;width:20px;height:20px;border:2.5px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;vertical-align:middle;margin-right:8px}
.spinner-dark{border-color:var(--border);border-top-color:var(--green)}
@keyframes spin{to{transform:rotate(360deg)}}
.toast{position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:#222;color:#fff;padding:10px 20px;border-radius:20px;font-size:13px;font-family:'Trebuchet MS',sans-serif;z-index:200;opacity:0;transition:opacity .3s;pointer-events:none;white-space:nowrap}
.toast.show{opacity:1}
.setup-box{background:#fff3cd;border:1px solid #ffc107;border-radius:var(--radius);padding:14px 16px;margin:12px;font-size:13px;font-family:'Trebuchet MS',sans-serif;line-height:1.6;color:#664d03}
.setup-box strong{font-weight:700}
</style>
</head>
<body>
<div id="toast" class="toast"></div>
<!-- ── SETUP WARNING (shown if Firebase not configured) ── -->
<div id="setup-warning" style="display:none">
<div class="top-bar"><h1>WC 2026 Pool — Setup needed</h1></div>
<div class="setup-box">
<strong>Firebase not configured yet.</strong><br><br>
Open this HTML file in a text editor, find the <code>FIREBASE_CONFIG</code> block near the top, and paste in your Firebase project values. See the setup guide below.
</div>
</div>
<!-- ── HOME ── -->
<div id="s-home" class="screen active">
<div class="hero-section">
<div style="font-size:40px;margin-bottom:8px">⚽</div>
<h1>WC 2026 Pool</h1>
<p>USA · Canada · Mexico</p>
</div>
<div class="card" style="margin-top:16px">
<div class="pt-rule"><span>Group stage correct pick</span><span class="badge badge-green">1 pt</span></div>
<div class="pt-rule"><span>Round of 32 correct pick</span><span class="badge badge-green">2 pts</span></div>
<div class="pt-rule"><span>Quarterfinal correct pick</span><span class="badge badge-green">3 pts</span></div>
<div class="pt-rule"><span>Semifinal correct pick</span><span class="badge badge-green">4 pts</span></div>
<div class="pt-rule" style="border:none"><span>Final correct pick</span><span class="badge badge-gold">6 pts</span></div>
<div style="margin-top:10px;font-size:12px;color:var(--text3);font-family:'Trebuchet MS',sans-serif">Picks hidden until first result is entered. Leaderboard updates live across all devices.</div>
</div>
<div style="padding:16px 12px 0;display:flex;flex-direction:column;gap:8px">
<button class="btn btn-green" onclick="go('s-register')">Enter my picks</button>
<button class="btn btn-outline" onclick="openLeaderboard()">View leaderboard</button>
<button class="btn btn-outline" onclick="go('s-admin-login')">Admin panel</button>
</div>
</div>
<!-- ── REGISTER ── -->
<div id="s-register" class="screen">
<div class="top-bar"><button class="back-btn" onclick="go('s-home')">Back</button><h1>Register</h1></div>
<div class="section-title">Your details</div>
<div class="card-sm">
<div style="margin-bottom:12px">
<label style="font-size:13px;color:var(--text2);display:block;margin-bottom:4px">Full name</label>
<input type="text" id="reg-name" placeholder="e.g. Carlos Mendez" oninput="checkReg()">
</div>
<div>
<label style="font-size:13px;color:var(--text2);display:block;margin-bottom:4px">Phone number</label>
<input type="tel" id="reg-phone" placeholder="+1 602 555 0198" oninput="checkReg()">
</div>
</div>
<div style="padding:0 12px;margin-top:4px;font-size:12px;color:var(--text3);font-family:'Trebuchet MS',sans-serif">Used to contact you if you win. Never shared publicly.</div>
<div style="padding:16px 12px 0">
<button class="btn btn-green" id="btn-reg" disabled onclick="doRegister()">Continue to picks</button>
</div>
</div>
<!-- ── PICKS ── -->
<div id="s-picks" class="screen">
<div class="top-bar"><button class="back-btn" onclick="go('s-home')">Home</button><h1 id="picks-title">My Picks</h1></div>
<div class="prog-wrap">
<div style="display:flex;justify-content:space-between;font-size:12px;color:var(--text3);font-family:'Trebuchet MS',sans-serif;margin-bottom:4px">
<span id="prog-label">0 / 0 picked</span><span id="prog-pct">0%</span>
</div>
<div class="prog-bg"><div class="prog-fill" id="prog-fill" style="width:0%"></div></div>
</div>
<div class="tab-row" id="round-tabs"></div>
<div id="picks-body"></div>
<div class="sticky-footer">
<button class="btn btn-green" id="btn-submit" onclick="submitPicks()">Save picks</button>
<div style="text-align:center;font-size:12px;color:var(--text3);margin-top:6px;font-family:'Trebuchet MS',sans-serif" id="submit-hint">Pick all group games first</div>
</div>
</div>
<!-- ── CONFIRM ── -->
<div id="s-confirm" class="screen">
<div style="padding:40px 20px;text-align:center">
<div style="width:64px;height:64px;background:var(--green-light);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:32px">✓</div>
<h2 style="font-size:20px;font-weight:700;margin-bottom:8px">Picks saved!</h2>
<p style="font-size:14px;color:var(--text2);line-height:1.6;font-family:'Trebuchet MS',sans-serif" id="confirm-msg"></p>
<div style="margin-top:24px;display:flex;flex-direction:column;gap:8px">
<button class="btn btn-green" onclick="go('s-picks')">Edit picks</button>
<button class="btn btn-outline" onclick="openLeaderboard()">View leaderboard</button>
</div>
</div>
</div>
<!-- ── LEADERBOARD ── -->
<div id="s-leaderboard" class="screen">
<div class="top-bar"><button class="back-btn" onclick="go('s-home')">Home</button><h1>Leaderboard</h1></div>
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:12px">
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px;text-align:center">
<div style="font-size:11px;color:var(--text3);font-family:'Trebuchet MS',sans-serif;margin-bottom:2px">PLAYERS</div>
<div style="font-size:24px;font-weight:700;font-family:'Trebuchet MS',sans-serif" id="lb-count">—</div>
</div>
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px;text-align:center">
<div style="font-size:11px;color:var(--text3);font-family:'Trebuchet MS',sans-serif;margin-bottom:2px">RESULTS IN</div>
<div style="font-size:24px;font-weight:700;font-family:'Trebuchet MS',sans-serif" id="lb-results">0</div>
</div>
</div>
<div id="lb-body"><div style="padding:32px;text-align:center"><span class="spinner spinner-dark"></span></div></div>
<div style="padding:8px 12px 0">
<button class="btn btn-outline" onclick="go('s-home')">Home</button>
</div>
</div>
<!-- ── ADMIN LOGIN ── -->
<div id="s-admin-login" class="screen">
<div class="top-bar"><button class="back-btn" onclick="go('s-home')">Back</button><h1>Admin</h1></div>
<div class="card-sm" style="margin-top:16px">
<label style="font-size:13px;color:var(--text2);display:block;margin-bottom:6px">Admin password</label>
<input type="password" id="admin-pw-input" placeholder="Enter password" onkeydown="if(event.key==='Enter')adminLogin()">
<button class="btn btn-green" style="margin-top:10px" onclick="adminLogin()">Unlock</button>
</div>
</div>
<!-- ── ADMIN PANEL ── -->
<div id="s-admin" class="screen">
<div class="top-bar"><button class="back-btn" onclick="go('s-home')">Home</button><h1>Admin Panel</h1></div>
<div class="section-title">Enter match results</div>
<div class="tab-row" id="admin-round-tabs"></div>
<div id="admin-body"></div>
<div class="sticky-footer">
<button class="btn btn-green" id="btn-save-results" onclick="saveResults()">Save results & update scores</button>
</div>
</div>
<!-- Firebase SDKs -->
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-database-compat.js"></script>
<script>
// ── DATA ──────────────────────────────────────────────────────
const GROUPS = {
A:['Qatar','Ecuador','Senegal','Netherlands'],
B:['England','Iran','USA','Wales'],
C:['Argentina','Saudi Arabia','Mexico','Poland'],
D:['France','Australia','Denmark','Tunisia'],
E:['Spain','Costa Rica','Germany','Japan'],
F:['Belgium','Canada','Morocco','Croatia'],
G:['Brazil','Serbia','Switzerland','Cameroon'],
H:['Portugal','Ghana','Uruguay','South Korea']
};
const KO_ROUNDS=[
{id:'r32',label:'Rd of 32',pts:2,count:16},
{id:'qf', label:'Quarters', pts:3,count:8},
{id:'sf', label:'Semis', pts:4,count:4},
{id:'final',label:'Final', pts:6,count:1}
];
const ROUND_PTS={group:1,r32:2,qf:3,sf:4,final:6};
const AVATAR_COLORS=[
['#e8f5ee','#1a7a4a'],['#e6f1fb','#185fa5'],['#faeeda','#854f0b'],
['#fbeaf0','#993556'],['#eeedfe','#534ab7'],['#eaf3de','#3b6d11']
];
function buildGroupMatches(){
const ms=[];
Object.entries(GROUPS).forEach(([g,teams])=>{
for(let i=0;i<teams.length;i++)
for(let j=i+1;j<teams.length;j++)
ms.push({id:`g_${g}_${i}_${j}`,round:'group',group:g,home:teams[i],away:teams[j],allowDraw:true});
});
return ms;
}
function buildKOMatches(slots){
const ms=[],s=slots||{};
const get=k=>s[k]||null;
for(let i=0;i<16;i++) ms.push({id:`r32_${i}`,round:'r32',home:get(`r32_${i}_a`)||`R32 Match ${i+1}A`,away:get(`r32_${i}_b`)||`R32 Match ${i+1}B`,allowDraw:false});
for(let i=0;i<8;i++) ms.push({id:`qf_${i}`, round:'qf', home:get(`qf_${i}_a`) ||`QF Match ${i+1}A`, away:get(`qf_${i}_b`) ||`QF Match ${i+1}B`, allowDraw:false});
for(let i=0;i<4;i++) ms.push({id:`sf_${i}`, round:'sf', home:get(`sf_${i}_a`) ||`SF Match ${i+1}A`, away:get(`sf_${i}_b`) ||`SF Match ${i+1}B`, allowDraw:false});
ms.push({id:'final_0',round:'final',home:get('final_a')||'Finalist A',away:get('final_b')||'Finalist B',allowDraw:false});
return ms;
}
const GROUP_MATCHES = buildGroupMatches();
// ── STATE ─────────────────────────────────────────────────────
let db = null;
let remoteState = {players:{}, results:{}, koSlots:{}};
let currentUser = null;
let myPicks = {};
let activeRound = 'group';
let activeAdminRound = 'group';
let allMatches = [];
let lbUnsubscribe = null;
// ── FIREBASE INIT ─────────────────────────────────────────────
function initFirebase(){
if(FIREBASE_CONFIG.apiKey.includes('PASTE')){
document.getElementById('setup-warning').style.display='block';
document.getElementById('s-home').classList.remove('active');
return false;
}
try{
firebase.initializeApp(FIREBASE_CONFIG);
db = firebase.database();
return true;
}catch(e){
toast('Firebase init failed: '+e.message, 4000);
return false;
}
}
// ── DB HELPERS ────────────────────────────────────────────────
function dbRef(path){ return db.ref('wc2026/'+path); }
async function dbGet(path){
const snap = await dbRef(path).get();
return snap.exists() ? snap.val() : null;
}
async function dbSet(path,val){ await dbRef(path).set(val); }
async function dbUpdate(path,val){ await dbRef(path).update(val); }
// ── UI HELPERS ────────────────────────────────────────────────
function go(id){
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.getElementById(id).classList.add('active');
window.scrollTo(0,0);
}
function toast(msg,dur=2500){
const t=document.getElementById('toast');
t.textContent=msg; t.classList.add('show');
setTimeout(()=>t.classList.remove('show'),dur);
}
function safeId(str){ return str.replace(/[^a-zA-Z0-9_-]/g,'_'); }
// ── REGISTER ──────────────────────────────────────────────────
function checkReg(){
const n=document.getElementById('reg-name').value.trim();
const p=document.getElementById('reg-phone').value.trim();
document.getElementById('btn-reg').disabled=!(n.length>1&&p.length>6);
}
async function doRegister(){
const btn=document.getElementById('btn-reg');
btn.innerHTML='<span class="spinner"></span>Loading...'; btn.disabled=true;
const name=document.getElementById('reg-name').value.trim();
const phone=document.getElementById('reg-phone').value.trim();
currentUser={name,phone,uid:safeId(phone)};
try{
const existing=await dbGet('players/'+currentUser.uid);
myPicks=existing&&existing.picks?{...existing.picks}:{};
const slots=await dbGet('koSlots')||{};
allMatches=[...GROUP_MATCHES,...buildKOMatches(slots)];
document.getElementById('picks-title').textContent=name.split(' ')[0]+"'s Picks";
renderRoundTabs(); renderPicksForRound('group');
go('s-picks');
}catch(e){ toast('Error loading data. Check connection.'); }
btn.textContent='Continue to picks'; btn.disabled=false;
}
// ── PICKS ─────────────────────────────────────────────────────
function groupDone(){ return GROUP_MATCHES.every(m=>myPicks[m.id]); }
function renderRoundTabs(){
const rounds=['group',...KO_ROUNDS.map(r=>r.id)];
const labels={group:'Groups',r32:'Rd of 32',qf:'Quarters',sf:'Semis',final:'Final'};
const unlocked={group:true,r32:groupDone(),qf:groupDone(),sf:groupDone(),final:groupDone()};
document.getElementById('round-tabs').innerHTML=rounds.map(r=>`
<button class="tab${r===activeRound?' active':''}" onclick="switchRound('${r}')"
${unlocked[r]?'':'disabled style="opacity:.4;cursor:not-allowed"'}>${labels[r]}</button>`).join('');
}
function switchRound(r){ activeRound=r; renderPicksForRound(r); }
function renderPicksForRound(r){
activeRound=r;
const matches=allMatches.filter(m=>m.round===r);
let html='';
if(r==='group'){
Object.keys(GROUPS).forEach(g=>{
html+=`<div class="section-title">Group ${g}</div>`;
matches.filter(m=>m.group===g).forEach(m=>{ html+=matchHTML(m); });
});
} else {
const info=KO_ROUNDS.find(x=>x.id===r);
html+=`<div class="round-header">+${info.pts} pts per correct pick</div>`;
const ready=matches.length&&!matches.every(m=>m.home.includes('Match'));
if(!ready){
html+='<div style="padding:24px 16px;text-align:center;font-size:14px;color:var(--text3);font-family:Trebuchet MS,sans-serif">Teams TBD — check back after previous round</div>';
} else {
matches.forEach((m,i)=>{
html+=`<div style="font-size:11px;color:var(--text3);padding:10px 16px 2px;font-family:Trebuchet MS,sans-serif">Match ${i+1}</div>`;
html+=matchHTML(m);
});
}
}
document.getElementById('picks-body').innerHTML=html;
updateProgress(); renderRoundTabs();
}
function matchHTML(m){
const p=myPicks[m.id];
const tbd=m.home.includes('Match')||m.away.includes('Match');
const hEsc=m.home.replace(/"/g,'"'); const aEsc=m.away.replace(/"/g,'"');
return`<div class="card-sm">
<div class="match-row">
<button class="pick-btn${p===m.home?' sel-home':''}" onclick='doPick("${m.id}","${hEsc}")' ${tbd?'disabled':''}>${m.home}</button>
${m.allowDraw
?`<button class="draw-btn${p==='Draw'?' sel-draw':''}" onclick='doPick("${m.id}","Draw")'>Draw</button>`
:`<div style="text-align:center;font-size:11px;color:var(--text3);font-family:Trebuchet MS,sans-serif">vs</div>`}
<button class="pick-btn${p===m.away?' sel-away':''}" onclick='doPick("${m.id}","${aEsc}")' ${tbd?'disabled':''}>${m.away}</button>
</div>
</div>`;
}
function doPick(id,val){ myPicks[id]=val; renderPicksForRound(activeRound); }
function updateProgress(){
const available=allMatches.filter(m=>!m.home.includes('Match')&&!m.away.includes('Match'));
const done=available.filter(m=>myPicks[m.id]).length;
const pct=available.length?Math.round(done/available.length*100):0;
document.getElementById('prog-fill').style.width=pct+'%';
document.getElementById('prog-label').textContent=`${done} / ${available.length} picked`;
document.getElementById('prog-pct').textContent=pct+'%';
const ok=groupDone();
document.getElementById('btn-submit').disabled=!ok;
document.getElementById('submit-hint').textContent=ok?'Tap to save — picks sync instantly':'Pick all 48 group games to unlock knockout rounds';
}
async function submitPicks(){
const btn=document.getElementById('btn-submit');
btn.innerHTML='<span class="spinner"></span>Saving...'; btn.disabled=true;
try{
const results=await dbGet('results')||{};
let score=0;
allMatches.forEach(m=>{
const r=results[m.id];
if(r&&myPicks[m.id]===r) score+=ROUND_PTS[m.round]||1;
});
await dbSet('players/'+currentUser.uid,{
name:currentUser.name, phone:currentUser.phone,
picks:{...myPicks}, score, updatedAt:Date.now()
});
const total=Object.keys(myPicks).length;
document.getElementById('confirm-msg').textContent=
`${currentUser.name}, you've saved ${total} picks. Scores update live as results come in — good luck!`;
go('s-confirm');
}catch(e){ toast('Save failed — check connection'); }
btn.textContent='Save picks'; btn.disabled=false;
}
// ── LEADERBOARD ───────────────────────────────────────────────
function openLeaderboard(){
go('s-leaderboard');
if(lbUnsubscribe) lbUnsubscribe();
const ref=dbRef('/');
const handler=ref.on('value', snap=>{
const data=snap.val()||{};
renderLeaderboard(data.players||{}, data.results||{});
});
lbUnsubscribe=()=>ref.off('value',handler);
}
function renderLeaderboard(players, results){
const arr=Object.values(players).sort((a,b)=>(b.score||0)-(a.score||0));
document.getElementById('lb-count').textContent=arr.length;
document.getElementById('lb-results').textContent=Object.keys(results).length;
const picksVisible=Object.keys(results).length>0;
if(arr.length===0){
document.getElementById('lb-body').innerHTML=
'<div style="padding:32px 16px;text-align:center;color:var(--text3);font-family:Trebuchet MS,sans-serif">No entries yet — be the first!</div>';
return;
}
document.getElementById('lb-body').innerHTML=arr.map((p,i)=>{
const [bg,fg]=AVATAR_COLORS[i%AVATAR_COLORS.length];
const init=p.name.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase();
const medal=i===0?'🥇':i===1?'🥈':i===2?'🥉':'';
return`<div class="lb-row">
<div class="rank-num">${medal||i+1}</div>
<div class="avatar" style="background:${bg};color:${fg}">${init}</div>
<div style="flex:1;font-size:14px;font-weight:700;font-family:'Trebuchet MS',sans-serif">${p.name}</div>
${picksVisible?`<div class="pts-chip">${p.score||0} pts</div>`:`<div style="font-size:18px">🔒</div>`}
</div>`;
}).join('');
}
// ── ADMIN ─────────────────────────────────────────────────────
function adminLogin(){
const pw=document.getElementById('admin-pw-input').value;
if(pw!==ADMIN_PW){ toast('Wrong password'); return; }
go('s-admin');
loadAdminData();
}
async function loadAdminData(){
remoteState.results=await dbGet('results')||{};
remoteState.koSlots=await dbGet('koSlots')||{};
allMatches=[...GROUP_MATCHES,...buildKOMatches(remoteState.koSlots)];
renderAdminTabs(); renderAdminRound('group');
}
function renderAdminTabs(){
const rounds=['group',...KO_ROUNDS.map(r=>r.id)];
const labels={group:'Groups',r32:'Rd of 32',qf:'Quarters',sf:'Semis',final:'Final'};
document.getElementById('admin-round-tabs').innerHTML=rounds.map(r=>`
<button class="tab${r===activeAdminRound?' active':''}" onclick="switchAdminRound('${r}')">${labels[r]}</button>`).join('');
}
function switchAdminRound(r){ activeAdminRound=r; renderAdminRound(r); }
function renderAdminRound(r){
activeAdminRound=r;
allMatches=[...GROUP_MATCHES,...buildKOMatches(remoteState.koSlots)];
const matches=allMatches.filter(m=>m.round===r);
let html='';
if(r!=='group'){
const count=r==='final'?1:KO_ROUNDS.find(x=>x.id===r).count;
html+=`<div class="card" style="margin-top:10px">
<div style="font-size:13px;font-weight:700;margin-bottom:6px">Set advancing teams</div>
<div style="font-size:12px;color:var(--text2);line-height:1.5;margin-bottom:10px">Enter team names as they qualify for this round.</div>`;
if(r==='final'){
html+=slotInputs('final_a','final_b','Finalist A','Finalist B',0);
} else {
for(let i=0;i<count;i++) html+=slotInputs(`${r}_${i}_a`,`${r}_${i}_b`,`Match ${i+1} — Team A`,`Match ${i+1} — Team B`,i);
}
html+=`</div>`;
}
if(r==='group'){
Object.keys(GROUPS).forEach(g=>{
html+=`<div class="section-title">Group ${g}</div>`;
matches.filter(m=>m.group===g).forEach(m=>{ html+=adminMatchHTML(m); });
});
} else {
html+=`<div class="section-title" style="margin-top:4px">Results</div>`;
matches.forEach((m,i)=>{
html+=`<div style="font-size:11px;color:var(--text3);padding:8px 16px 2px;font-family:Trebuchet MS,sans-serif">Match ${i+1}</div>`;
html+=adminMatchHTML(m);
});
}
document.getElementById('admin-body').innerHTML=html;
}
function slotInputs(ka,kb,labelA,labelB,idx){
const s=remoteState.koSlots;
return`<div style="margin-bottom:10px">
<div style="font-size:12px;color:var(--text3);margin-bottom:4px;font-family:'Trebuchet MS',sans-serif">${labelA.split('—')[0].trim()}</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">
<input type="text" id="slot_${ka}" placeholder="Team A" value="${s[ka]||''}" style="font-size:13px;padding:8px 10px">
<input type="text" id="slot_${kb}" placeholder="Team B" value="${s[kb]||''}" style="font-size:13px;padding:8px 10px">
</div>
</div>`;
}
function adminMatchHTML(m){
const cur=remoteState.results[m.id];
const opts=m.allowDraw?[m.home,'Draw',m.away]:[m.home,m.away];
const hEsc=m.home.replace(/"/g,'"');const aEsc=m.away.replace(/"/g,'"');
return`<div class="card-sm">
<div style="font-size:13px;font-weight:700;font-family:'Trebuchet MS',sans-serif;margin-bottom:8px">${m.home} vs ${m.away}</div>
<div style="display:flex;gap:6px;flex-wrap:wrap">
${opts.map(o=>{
const oEsc=o.replace(/"/g,'"');
const sel=cur===o;
return`<button class="btn-sm" style="background:${sel?'var(--green)':'transparent'};color:${sel?'#fff':'var(--text2)'};border:1.5px solid ${sel?'var(--green)':'var(--border2)'};" onclick='setResult("${m.id}","${oEsc}")'>${o}</button>`;
}).join('')}
${cur?`<button class="btn-sm" style="background:var(--red-light);color:var(--red);border:1.5px solid #f5c0bc" onclick='setResult("${m.id}","")'>Clear</button>`:''}
</div>
</div>`;
}
function setResult(id,val){
if(!val) delete remoteState.results[id];
else remoteState.results[id]=val;
renderAdminRound(activeAdminRound);
}
async function saveResults(){
const btn=document.getElementById('btn-save-results');
btn.innerHTML='<span class="spinner"></span>Saving...'; btn.disabled=true;
try{
const r=activeAdminRound;
if(r!=='group'){
const count=r==='final'?1:KO_ROUNDS.find(x=>x.id===r).count;
if(r==='final'){
const a=document.getElementById('slot_final_a');
const b=document.getElementById('slot_final_b');
if(a?.value.trim()) remoteState.koSlots['final_a']=a.value.trim();
if(b?.value.trim()) remoteState.koSlots['final_b']=b.value.trim();
} else {
for(let i=0;i<count;i++){
const ka=`${r}_${i}_a`,kb=`${r}_${i}_b`;
const a=document.getElementById(`slot_${ka}`);
const b=document.getElementById(`slot_${kb}`);
if(a?.value.trim()) remoteState.koSlots[ka]=a.value.trim();
if(b?.value.trim()) remoteState.koSlots[kb]=b.value.trim();
}
}
await dbSet('koSlots',remoteState.koSlots);
allMatches=[...GROUP_MATCHES,...buildKOMatches(remoteState.koSlots)];
}
await dbSet('results',remoteState.results);
await recalcAndSaveScores();
toast('Saved! Scores updated for everyone.');
renderAdminRound(activeAdminRound);
}catch(e){ toast('Save failed: '+e.message,4000); }
btn.textContent='Save results & update scores'; btn.disabled=false;
}
async function recalcAndSaveScores(){
const players=await dbGet('players')||{};
const updates={};
Object.entries(players).forEach(([uid,p])=>{
let score=0;
allMatches.forEach(m=>{
const res=remoteState.results[m.id];
if(res&&p.picks&&p.picks[m.id]===res) score+=ROUND_PTS[m.round]||1;
});
updates[`players/${uid}/score`]=score;
});
if(Object.keys(updates).length) await dbUpdate('/',updates);
}
// ── BOOT ──────────────────────────────────────────────────────
initFirebase();
</script>
</body>
</html>