Quiniela Lebec 2026

/* —- todo el CSS va con prefijo #quiniela-root para no chocar con WordPress —- */ #quiniela-root{ –bg:#07140D; –bg2:#0B2417; –card:#0F2D1C; –card2:#13392550; –line:#1F4D34; –text:#F3FBF6; –chalk:#CFE8D8; –muted:#7FA38D; –gold:#F4C95D; –gold-deep:#D9A93B; –teal:#23C4A8; –red:#E2574C; –silver:#C9D2D6; –bronze:#CC8B58; –r:16px; font-family:’Outfit’,system-ui,-apple-system,Segoe UI,Roboto,sans-serif; color:var(–text); max-width:580px; margin:0 auto; padding:0 14px 60px; -webkit-font-smoothing:antialiased; line-height:1.45; } #quiniela-root *{box-sizing:border-box;} #quiniela-root .qbg{ position:fixed; inset:0; z-index:-1; background: radial-gradient(120% 60% at 50% -10%, #14492E 0%, transparent 60%), linear-gradient(180deg, var(–bg2) 0%, var(–bg) 55%); } #quiniela-root h1,#quiniela-root h2,#quiniela-root h3{margin:0; font-weight:400;} #quiniela-root button{font-family:inherit; cursor:pointer; border:none;} #quiniela-root input{font-family:inherit;} /* —- HERO —- */ #quiniela-root .hero{position:relative; text-align:center; padding:26px 0 16px; overflow:hidden;} #quiniela-root .hero .circle{ position:absolute; left:50%; top:6px; transform:translateX(-50%); width:230px; height:230px; border:1.5px solid var(–line); border-radius:50%; opacity:.5; pointer-events:none; } #quiniela-root .hero .circle:after{ content:””; position:absolute; left:50%; top:0; transform:translateX(-50%); width:1.5px; height:230px; background:var(–line); } #quiniela-root .eyebrow{ font-size:12px; letter-spacing:.32em; color:var(–gold); font-weight:700; text-transform:uppercase; position:relative; } #quiniela-root .hero h1{ font-family:’Anton’,sans-serif; font-size:clamp(40px,13vw,68px); line-height:.9; letter-spacing:.01em; position:relative; margin-top:2px; } #quiniela-root .hero h1 .yr{color:var(–gold); display:block;} #quiniela-root .hero .sub{color:var(–chalk); font-size:13.5px; margin-top:8px; position:relative;} /* —- user chip —- */ #quiniela-root .me{ display:flex; align-items:center; justify-content:space-between; gap:10px; background:var(–card); border:1px solid var(–line); border-radius:var(–r); padding:11px 14px; margin-top:14px; } #quiniela-root .me .who{display:flex; align-items:center; gap:9px; min-width:0;} #quiniela-root .me .av{ width:34px;height:34px;border-radius:50%;flex:none; background:linear-gradient(135deg,var(–teal),#0E8C7A); display:grid;place-items:center;font-weight:800;font-size:15px;color:#04221C; } #quiniela-root .me .nm{font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;} #quiniela-root .me .pts{text-align:right; flex:none;} #quiniela-root .me .pts b{font-family:’Anton’;font-size:24px;color:var(–gold);line-height:1;display:block;} #quiniela-root .me .pts span{font-size:10px;color:var(–muted);letter-spacing:.12em;text-transform:uppercase;} /* —- tabs —- */ #quiniela-root .tabs{ display:flex; gap:6px; margin:16px 0 4px; position:sticky; top:0; z-index:5; padding:8px 0; background:linear-gradient(180deg,var(–bg) 70%, transparent); } #quiniela-root .tabs button{ flex:1; padding:11px 6px; border-radius:11px; font-weight:700; font-size:13.5px; background:var(–card); color:var(–chalk); border:1px solid var(–line); transition:.15s; } #quiniela-root .tabs button.on{background:var(–gold); color:#1A1206; border-color:var(–gold);} /* —- banner —- */ #quiniela-root .banner{ border-radius:12px; padding:11px 14px; font-size:13px; margin:10px 0; font-weight:500; display:flex; gap:9px; align-items:flex-start; } #quiniela-root .banner.warn{background:#3A2A0E; border:1px solid var(–gold-deep); color:#FBE7B6;} #quiniela-root .banner.demo{background:#0E2C3A; border:1px solid var(–teal); color:#BBEFE6;} #quiniela-root .banner.lock{background:#3A1714; border:1px solid var(–red); color:#F6C9C4;} /* —- group + match card —- */ #quiniela-root .grp{margin-top:18px;} #quiniela-root .grp-h{display:flex;align-items:center;gap:10px;margin:0 2px 9px;} #quiniela-root .grp-h .tag{ font-family:’Anton’; font-size:15px; background:var(–card2); color:var(–gold); border:1px solid var(–line); width:30px;height:30px;border-radius:9px; display:grid;place-items:center; } #quiniela-root .grp-h .lbl{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(–muted);font-weight:700;} #quiniela-root .jor{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(–muted);margin:12px 4px 6px;font-weight:700;} #quiniela-root .match{ background:var(–card); border:1px solid var(–line); border-radius:14px; padding:12px 12px; margin-bottom:9px; } #quiniela-root .match.done{border-color:var(–gold-deep);} #quiniela-root .row{display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:8px;} #quiniela-root .team{display:flex; align-items:center; gap:8px; min-width:0;} #quiniela-root .team.r{flex-direction:row-reverse; text-align:right;} #quiniela-root .team .fl{font-size:23px; line-height:1; flex:none;} #quiniela-root .team .tn{font-size:14px; font-weight:600; line-height:1.1;} #quiniela-root .score{display:flex; align-items:center; gap:7px;} #quiniela-root .score input{ width:44px; height:46px; text-align:center; font-family:’Anton’; font-size:24px; background:var(–bg); color:var(–text); border:1.5px solid var(–line); border-radius:10px; padding:0; -moz-appearance:textfield; } #quiniela-root .score input::-webkit-outer-spin-button, #quiniela-root .score input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;} #quiniela-root .score input:focus{outline:none; border-color:var(–gold); box-shadow:0 0 0 3px #F4C95D33;} #quiniela-root .score input:disabled{opacity:.85; border-color:var(–line); background:var(–card2);} #quiniela-root .score .dash{color:var(–muted); font-weight:800;} /* resultado real + puntos ganados */ #quiniela-root .res{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:9px; padding-top:9px; border-top:1px dashed var(–line); font-size:12px; } #quiniela-root .res .real{color:var(–chalk);} #quiniela-root .res .real b{color:var(–gold);} #quiniela-root .pill{font-weight:800; font-size:12px; padding:3px 9px; border-radius:20px;} #quiniela-root .pill.p5{background:var(–gold); color:#1A1206;} #quiniela-root .pill.p2{background:#16463B; color:var(–teal); border:1px solid var(–teal);} #quiniela-root .pill.p0{background:#2A1310; color:#E89B92; border:1px solid #6B302A;} /* —- save bar —- */ #quiniela-root .savebar{position:sticky; bottom:0; padding:12px 0 6px; background:linear-gradient(0deg,var(–bg) 65%, transparent); z-index:5;} #quiniela-root .btn{ width:100%; padding:15px; border-radius:13px; font-weight:800; font-size:15.5px; background:var(–gold); color:#1A1206; transition:.15s; letter-spacing:.01em; } #quiniela-root .btn:active{transform:translateY(1px);} #quiniela-root .btn.ghost{background:transparent; color:var(–gold); border:1.5px solid var(–gold-deep);} #quiniela-root .btn:disabled{opacity:.45;} #quiniela-root .btn.sm{width:auto; padding:9px 16px; font-size:13px;} /* —- leaderboard —- */ #quiniela-root .podium{display:grid; grid-template-columns:1fr 1.15fr 1fr; gap:8px; align-items:end; margin:6px 0 14px;} #quiniela-root .pod{background:var(–card); border:1px solid var(–line); border-radius:13px; padding:13px 8px; text-align:center;} #quiniela-root .pod .medal{font-size:22px;} #quiniela-root .pod .pn{font-weight:700; font-size:13px; margin-top:4px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} #quiniela-root .pod .pp{font-family:’Anton’; font-size:26px; line-height:1; margin-top:3px;} #quiniela-root .pod.g1{border-color:var(–gold); padding-top:20px;} #quiniela-root .pod.g1 .pp{color:var(–gold);} #quiniela-root .pod.g2 .pp{color:var(–silver);} #quiniela-root .pod.g3 .pp{color:var(–bronze);} #quiniela-root .lbrow{ display:flex; align-items:center; gap:11px; padding:11px 13px; border-radius:11px; background:var(–card); border:1px solid var(–line); margin-bottom:7px; } #quiniela-root .lbrow.me-row{border-color:var(–teal); background:#0E2C28;} #quiniela-root .lbrow .rk{font-family:’Anton’; font-size:17px; color:var(–muted); width:24px; text-align:center; flex:none;} #quiniela-root .lbrow .lname{flex:1; font-weight:600; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} #quiniela-root .lbrow .lx{font-size:11px; color:var(–muted);} #quiniela-root .lbrow .lp{font-family:’Anton’; font-size:20px; color:var(–gold); flex:none;} #quiniela-root .empty{text-align:center; color:var(–muted); padding:36px 16px; font-size:14px;} /* —- rules —- */ #quiniela-root .rule{display:flex; gap:13px; align-items:center; background:var(–card); border:1px solid var(–line); border-radius:13px; padding:14px; margin-bottom:10px;} #quiniela-root .rule .num{font-family:’Anton’; font-size:30px; flex:none; width:54px; text-align:center;} #quiniela-root .rule.r5 .num{color:var(–gold);} #quiniela-root .rule.r2 .num{color:var(–teal);} #quiniela-root .rule.r0 .num{color:#E89B92;} #quiniela-root .rule .rt b{display:block; font-size:15px; margin-bottom:2px;} #quiniela-root .rule .rt span{font-size:13px; color:var(–chalk);} /* —- registro / modal —- */ #quiniela-root .gate{background:var(–card); border:1px solid var(–line); border-radius:18px; padding:22px; margin-top:18px;} #quiniela-root .gate h2{font-family:’Anton’; font-size:24px; margin-bottom:4px;} #quiniela-root .gate p{color:var(–chalk); font-size:13.5px; margin:0 0 16px;} #quiniela-root .field{margin-bottom:13px;} #quiniela-root .field label{display:block; font-size:12px; color:var(–muted); margin-bottom:6px; font-weight:600; letter-spacing:.04em;} #quiniela-root .field input{ width:100%; padding:13px 14px; border-radius:11px; font-size:15px; background:var(–bg); color:var(–text); border:1.5px solid var(–line); } #quiniela-root .field input:focus{outline:none; border-color:var(–teal);} #quiniela-root .err{color:#F1A39B; font-size:12.5px; margin-top:8px; min-height:16px;} #quiniela-root .footlink{text-align:center; margin-top:30px; color:var(–muted); font-size:11.5px;} #quiniela-root .footlink a{color:var(–muted); cursor:pointer; text-decoration:underline;} /* —- admin —- */ #quiniela-root .amrow{display:grid; grid-template-columns:1fr auto; gap:10px; align-items:center; background:var(–card); border:1px solid var(–line); border-radius:11px; padding:9px 11px; margin-bottom:7px;} #quiniela-root .amrow .at{font-size:12.5px; font-weight:600; line-height:1.2;} #quiniela-root .amrow .at small{color:var(–muted); font-weight:500;} #quiniela-root .amrow .ai{display:flex; gap:5px; align-items:center;} #quiniela-root .amrow .ai input{width:38px; height:38px; text-align:center; font-family:’Anton’; font-size:18px; background:var(–bg); color:var(–text); border:1.5px solid var(–line); border-radius:9px;} #quiniela-root .amrow .ai button{padding:0 12px; height:38px; border-radius:9px; background:var(–teal); color:#04221C; font-weight:800; font-size:13px;} #quiniela-root .amrow.saved{border-color:var(–gold-deep);} #quiniela-root .toggle{display:flex; gap:10px; align-items:center; justify-content:space-between; background:var(–card); border:1px solid var(–line); border-radius:13px; padding:14px; margin-bottom:12px;} #quiniela-root .toggle .tl b{display:block;} #quiniela-root .toggle .tl span{font-size:12px; color:var(–muted);} #quiniela-root .toast{ position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(20px); background:var(–gold); color:#1A1206; font-weight:700; padding:13px 22px; border-radius:30px; font-size:14px; opacity:0; transition:.25s; z-index:50; box-shadow:0 8px 30px #0008; pointer-events:none; } #quiniela-root .toast.show{opacity:1; transform:translateX(-50%) translateY(0);} #quiniela-root .hidden{display:none !important;} @media (prefers-reduced-motion:reduce){#quiniela-root *{transition:none !important;}}
Polla Mundialista · Premio al líder

QUINIELAMUNDIAL 2026

Pronostica los marcadores · Suma puntos · Llévate el premio
(function(){ /* ============================================================ 1) CONFIG — EDITA ESTAS 3 LÍNEAS ============================================================ */ const SUPABASE_URL = “TU_SUPABASE_URL”; // ej: https://abcd.supabase.co const SUPABASE_ANON = “TU_SUPABASE_ANON_KEY”; // la clave “anon public” const ADMIN_PASS = “cambia-esta-clave”; // clave para que SOLO tú cargues resultados /* ============================================================ */ const root = document.getElementById(‘quiniela-root’); const $ = s => root.querySelector(s); const DEMO = !SUPABASE_URL || SUPABASE_URL.indexOf(‘TU_’)===0; /* —- almacenamiento seguro (no rompe dentro de la vista previa) —- */ const mem = {}; const store = { get(k){ try{ const v=localStorage.getItem(k); return v==null?(k in mem?mem[k]:null):v; }catch(e){ return k in mem?mem[k]:null; } }, set(k,v){ try{ localStorage.setItem(k,v); }catch(e){ mem[k]=v; } } }; /* —- datos: grupos del sorteo oficial (5 dic 2025) —- */ const GRUPOS = { A:[‘México’,’Sudáfrica’,’Corea del Sur’,’Chequia’], B:[‘Canadá’,’Bosnia y Herzegovina’,’Catar’,’Suiza’], C:[‘Brasil’,’Marruecos’,’Haití’,’Escocia’], D:[‘Estados Unidos’,’Paraguay’,’Australia’,’Turquía’], E:[‘Alemania’,’Curazao’,’Costa de Marfil’,’Ecuador’], F:[‘Países Bajos’,’Japón’,’Suecia’,’Túnez’], G:[‘Bélgica’,’Egipto’,’Irán’,’Nueva Zelanda’], H:[‘España’,’Cabo Verde’,’Arabia Saudí’,’Uruguay’], I:[‘Francia’,’Senegal’,’Irak’,’Noruega’], J:[‘Argentina’,’Argelia’,’Austria’,’Jordania’], K:[‘Portugal’,’RD Congo’,’Uzbekistán’,’Colombia’], L:[‘Inglaterra’,’Croacia’,’Ghana’,’Panamá’] }; const FLAG = { ‘México’:’🇲🇽’,’Sudáfrica’:’🇿🇦’,’Corea del Sur’:’🇰🇷’,’Chequia’:’🇨🇿’, ‘Canadá’:’🇨🇦’,’Bosnia y Herzegovina’:’🇧🇦’,’Catar’:’🇶🇦’,’Suiza’:’🇨🇭’, ‘Brasil’:’🇧🇷’,’Marruecos’:’🇲🇦’,’Haití’:’🇭🇹’,’Escocia’:’🏴󠁧󠁢󠁳󠁣󠁴󠁿’, ‘Estados Unidos’:’🇺🇸’,’Paraguay’:’🇵🇾’,’Australia’:’🇦🇺’,’Turquía’:’🇹🇷’, ‘Alemania’:’🇩🇪’,’Curazao’:’🇨🇼’,’Costa de Marfil’:’🇨🇮’,’Ecuador’:’🇪🇨’, ‘Países Bajos’:’🇳🇱’,’Japón’:’🇯🇵’,’Suecia’:’🇸🇪’,’Túnez’:’🇹🇳’, ‘Bélgica’:’🇧🇪’,’Egipto’:’🇪🇬’,’Irán’:’🇮🇷’,’Nueva Zelanda’:’🇳🇿’, ‘España’:’🇪🇸’,’Cabo Verde’:’🇨🇻’,’Arabia Saudí’:’🇸🇦’,’Uruguay’:’🇺🇾’, ‘Francia’:’🇫🇷’,’Senegal’:’🇸🇳’,’Irak’:’🇮🇶’,’Noruega’:’🇳🇴’, ‘Argentina’:’🇦🇷’,’Argelia’:’🇩🇿’,’Austria’:’🇦🇹’,’Jordania’:’🇯🇴’, ‘Portugal’:’🇵🇹’,’RD Congo’:’🇨🇩’,’Uzbekistán’:’🇺🇿’,’Colombia’:’🇨🇴’, ‘Inglaterra’:’🏴󠁧󠁢󠁥󠁮󠁧󠁿’,’Croacia’:’🇭🇷’,’Ghana’:’🇬🇭’,’Panamá’:’🇵🇦’ }; const fl = n => FLAG[n] || ‘⚽’; /* —- generar los 72 partidos de fase de grupos (round-robin) —- */ const PAIRS=[[0,1],[2,3],[0,2],[3,1],[3,0],[1,2]], JOR=[1,1,2,2,3,3]; function buildMatches(){ const out=[]; let gi=0; for(const g in GRUPOS){ const t=GRUPOS[g]; PAIRS.forEach((p,i)=>out.push({ id: gi*6+i+1, grupo:g, jornada:JOR[i], local:t[p[0]], visitante:t[p[1]], goles_local:null, goles_visitante:null, finalizado:false })); gi++; } return out; } /* —- puntaje: marcador exacto = 5, solo ganador/empate = 2, falla = 0 —- */ function puntos(pl,pv,m){ if(m.goles_local==null||m.goles_visitante==null) return null; if(pl===m.goles_local && pv===m.goles_visitante) return 5; if(Math.sign(pl-pv)===Math.sign(m.goles_local-m.goles_visitante)) return 2; return 0; } /* ============================================================ 2) CAPA DE DATOS (Demo = este navegador / Real = Supabase) ============================================================ */ let DB; const LocalDB = { async init(){ if(!store.get(‘q_matches’)) store.set(‘q_matches’, JSON.stringify(buildMatches())); }, async ensureSeed(){ store.set(‘q_matches’, JSON.stringify(buildMatches())); }, async getConfig(){ const v=store.get(‘q_config’); return v?JSON.parse(v):{abiertas:true}; }, async setConfig(ab){ store.set(‘q_config’, JSON.stringify({abiertas:ab})); }, async getMatches(){ const v=store.get(‘q_matches’); return v?JSON.parse(v):buildMatches(); }, async setResult(id,gl,gv){ const ms=await this.getMatches(); const m=ms.find(x=>x.id===id); if(m){ m.goles_local=gl; m.goles_visitante=gv; m.finalizado=true; } store.set(‘q_matches’,JSON.stringify(ms)); }, async registerParticipant(nombre,tel){ const ps=this._parts(); const id=’d’+Date.now(); ps.push({id,nombre,tel}); store.set(‘q_parts’,JSON.stringify(ps)); return {id,nombre}; }, _parts(){ const v=store.get(‘q_parts’); return v?JSON.parse(v):[]; }, async getMyPredictions(pid){ const v=store.get(‘q_pred_’+pid); return v?JSON.parse(v):{}; }, async savePredictions(pid,obj){ store.set(‘q_pred_’+pid, JSON.stringify(obj)); }, async getLeaderboard(){ const ps=this._parts(), ms=await this.getMatches(); const byId={}; ms.forEach(m=>byId[m.id]=m); return ps.map(p=>{ const pr=JSON.parse(store.get(‘q_pred_’+p.id)||'{}’); let pts=0,ex=0; for(const mid in pr){ const m=byId[mid]; if(!m) continue; const s=puntos(pr[mid].pl,pr[mid].pv,m); if(s===5)ex++; if(s) pts+=s; } return {participant_id:p.id, nombre:p.nombre, puntos:pts, exactos:ex}; }).sort((a,b)=> b.puntos-a.puntos || b.exactos-a.exactos); } }; const SupaDB = (sb)=>({ async init(){}, async ensureSeed(){ await sb.from(‘matches’).upsert(buildMatches().map(m=>({ id:m.id,grupo:m.grupo,jornada:m.jornada,local:m.local,visitante:m.visitante })),{onConflict:’id’,ignoreDuplicates:true}); await sb.from(‘config’).upsert({id:1,abiertas:true},{onConflict:’id’,ignoreDuplicates:true}); }, async getConfig(){ const {data}=await sb.from(‘config’).select(‘abiertas’).eq(‘id’,1).maybeSingle(); return {abiertas: data? data.abiertas : true}; }, async setConfig(ab){ await sb.from(‘config’).upsert({id:1,abiertas:ab},{onConflict:’id’}); }, async getMatches(){ const {data}=await sb.from(‘matches’).select(‘*’).order(‘id’); return data||[]; }, async setResult(id,gl,gv){ await sb.from(‘matches’).update({goles_local:gl,goles_visitante:gv,finalizado:true}).eq(‘id’,id); }, async registerParticipant(nombre,tel){ const {data}=await sb.from(‘participants’).insert({nombre,telefono:tel}).select().single(); return data; }, async getMyPredictions(pid){ const {data}=await sb.from(‘predictions’).select(‘match_id,pred_local,pred_visitante’).eq(‘participant_id’,pid); const o={}; (data||[]).forEach(r=>o[r.match_id]={pl:r.pred_local,pv:r.pred_visitante}); return o; }, async savePredictions(pid,obj){ const rows=Object.keys(obj).map(mid=>({participant_id:pid,match_id:+mid,pred_local:obj[mid].pl,pred_visitante:obj[mid].pv})); if(rows.length) await sb.from(‘predictions’).upsert(rows,{onConflict:’participant_id,match_id’}); }, async getLeaderboard(){ const {data}=await sb.from(‘leaderboard’).select(‘*’); return data||[]; } }); function loadSupabaseLib(){ return new Promise((res,rej)=>{ if(window.supabase) return res(); const s=document.createElement(‘script’); s.src=’https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2′; s.onload=res; s.onerror=()=>rej(new Error(‘No se pudo cargar Supabase’)); document.head.appendChild(s); }); } /* ============================================================ 3) ESTADO + ARRANQUE ============================================================ */ const S = { me:null, matches:[], config:{abiertas:true}, myPred:{}, admin:false }; function toast(msg){ const t=$(‘#toast’); t.textContent=msg; t.classList.add(‘show’); setTimeout(()=>t.classList.remove(‘show’),2200); } async function boot(){ if(DEMO){ DB=LocalDB; } else{ try{ await loadSupabaseLib(); const sb=window.supabase.createClient(SUPABASE_URL,SUPABASE_ANON); DB=SupaDB(sb); } catch(e){ DB=LocalDB; } } await DB.init(); S.config = await DB.getConfig(); S.matches = await DB.getMatches(); if(!S.matches.length && DEMO){ await DB.ensureSeed(); S.matches=await DB.getMatches(); } const saved = store.get(‘q_me’); if(saved){ S.me=JSON.parse(saved); await afterLogin(); } else{ $(‘#gate’).classList.remove(‘hidden’); } } async function afterLogin(){ $(‘#gate’).classList.add(‘hidden’); $(‘#app’).classList.remove(‘hidden’); $(‘#me-nm’).textContent = S.me.nombre; $(‘#me-av’).textContent = S.me.nombre.trim().charAt(0).toUpperCase()||’?’; S.myPred = await DB.getMyPredictions(S.me.id); renderPron(); renderTabla(); renderReglas(); refreshMyPoints(); } /* —- registro —- */ $(‘#g-enter’).addEventListener(‘click’, async ()=>{ const nombre = $(‘#g-nombre’).value.trim(); const tel = $(‘#g-tel’).value.trim(); if(nombre.length b.addEventListener(‘click’, ()=>{ root.querySelectorAll(‘.tabs button’).forEach(x=>x.classList.remove(‘on’)); b.classList.add(‘on’); [‘pron’,’tabla’,’reglas’].forEach(t=> $(‘#view-‘+t).classList.toggle(‘hidden’, t!==b.dataset.tab)); if(b.dataset.tab===’tabla’) renderTabla(); })); /* ============================================================ 4) VISTA: PRONÓSTICOS ============================================================ */ function locked(m){ return !S.config.abiertas || m.finalizado; } function renderPron(){ const v=$(‘#view-pron’); v.innerHTML=”; if(DEMO){ v.insertAdjacentHTML(‘beforeend’, ``); } if(!S.config.abiertas){ v.insertAdjacentHTML(‘beforeend’, ``); } else { v.insertAdjacentHTML(‘beforeend’, ``); } for(const g in GRUPOS){ const ms = S.matches.filter(m=>m.grupo===g); const wrap=document.createElement(‘div’); wrap.className=’grp’; wrap.innerHTML=`
${g}
Grupo ${g}
`; let lastJ=0; ms.forEach(m=>{ if(m.jornada!==lastJ){ lastJ=m.jornada; wrap.insertAdjacentHTML(‘beforeend’,`
Jornada ${m.jornada}
`); } const pr = S.myPred[m.id]||{}; const dis = locked(m)?’disabled’:”; const card=document.createElement(‘div’); card.className=’match’+(m.finalizado?’ done’:”); card.innerHTML=`
${fl(m.local)}${m.local}
${fl(m.visitante)}${m.visitante}
${m.finalizado?resBlock(m,pr):”}`; wrap.appendChild(card); }); v.appendChild(wrap); } if(S.config.abiertas){ const bar=document.createElement(‘div’); bar.className=’savebar’; bar.innerHTML=``; v.appendChild(bar); $(‘#save-pred’).addEventListener(‘click’, savePred); } } function resBlock(m,pr){ const s = (pr.pl!=null)?puntos(pr.pl,pr.pv,m):0; const cls = s===5?’p5′:s===2?’p2′:’p0′; const txt = s===5?’+5 exacto’:s===2?’+2 ganador’:’+0′; const mine = (pr.pl!=null)?`Pusiste ${pr.pl}–${pr.pv}`:’No pronosticaste’; return `
Resultado real: ${m.goles_local}–${m.goles_visitante} · ${mine}
${txt}
`; } async function savePred(){ const obj={…S.myPred}; root.querySelectorAll(‘#view-pron input[data-m]’).forEach(inp=>{ const id=inp.dataset.m, side=inp.dataset.s, val=inp.value.trim(); if(val===”) return; obj[id]=obj[id]||{}; obj[id][side===’l’?’pl’:’pv’]=Math.max(0,Math.min(20,parseInt(val)||0)); }); // solo guardar partidos con AMBOS marcadores const clean={}; for(const id in obj){ if(obj[id].pl!=null && obj[id].pv!=null) clean[id]=obj[id]; } $(‘#save-pred’).disabled=true; $(‘#save-pred’).textContent=’Guardando…’; try{ await DB.savePredictions(S.me.id, clean); S.myPred=clean; toast(‘¡Pronósticos guardados!’); } catch(e){ toast(‘Error al guardar. Reintenta.’); } $(‘#save-pred’).disabled=false; $(‘#save-pred’).textContent=’Guardar mis pronósticos’; refreshMyPoints(); } async function refreshMyPoints(){ const byId={}; S.matches.forEach(m=>byId[m.id]=m); let pts=0; for(const id in S.myPred){ const m=byId[id]; if(!m) continue; const s=puntos(S.myPred[id].pl,S.myPred[id].pv,m); if(s) pts+=s; } $(‘#me-pts’).textContent=pts; } /* ============================================================ 5) VISTA: TABLA DE POSICIONES ============================================================ */ async function renderTabla(){ const v=$(‘#view-tabla’); v.innerHTML=’
Cargando tabla…
‘; let lb=[]; try{ lb=await DB.getLeaderboard(); }catch(e){ lb=[]; } v.innerHTML=”; if(!lb.length){ v.innerHTML=’
Aún no hay participantes. ¡Sé el primero en llenar tu quiniela!
‘; return; } const top=lb.slice(0,3); if(lb.some(x=>x.puntos>0)){ const order=[1,0,2], cls=[‘g1′,’g2′,’g3’], med=[‘🥇’,’🥈’,’🥉’]; const pod=document.createElement(‘div’); pod.className=’podium’; order.forEach(i=>{ const p=top[i]; if(!p){ pod.appendChild(document.createElement(‘div’)); return; } const d=document.createElement(‘div’); d.className=’pod ‘+cls[i]; d.innerHTML=`
${med[i]}
${esc(p.nombre)}
${p.puntos}
`; pod.appendChild(d); }); v.appendChild(pod); } lb.forEach((p,i)=>{ const meRow = String(p.participant_id)===String(S.me.id); const row=document.createElement(‘div’); row.className=’lbrow’+(meRow?’ me-row’:”); row.innerHTML=`
${i+1}
${esc(p.nombre)}${meRow?’ · tú’:”} · ${p.exactos||0} exactos
${p.puntos}
`; v.appendChild(row); }); } const esc = s => String(s).replace(/[&”]/g,c=>({‘&’:’&’,”:’>’,'”‘:’"’}[c])); /* ============================================================ 6) VISTA: REGLAS ============================================================ */ function renderReglas(){ $(‘#view-reglas’).innerHTML=`
5
Marcador exactoAciertas los goles de los dos equipos (ej. 2–1 y quedó 2–1).
2
Solo el ganadorAciertas quién ganó o que fue empate, pero no el marcador exacto.
0
Sin aciertoEl resultado fue distinto al que pronosticaste.
`; } /* ============================================================ 7) ADMIN (organizador) — cargar resultados y abrir/cerrar ============================================================ */ $(‘#admin-link’).addEventListener(‘click’, ()=>{ if(!S.admin){ const p=prompt(‘Clave de organizador:’); if(p!==ADMIN_PASS){ if(p!==null) toast(‘Clave incorrecta’); return; } S.admin=true; } openAdmin(); }); async function openAdmin(){ [‘pron’,’tabla’,’reglas’].forEach(t=>$(‘#view-‘+t).classList.add(‘hidden’)); root.querySelectorAll(‘.tabs button’).forEach(x=>x.classList.remove(‘on’)); let host=$(‘#view-admin’); if(!host){ host=document.createElement(‘div’); host.id=’view-admin’; $(‘#view-reglas’).after(host); } host.classList.remove(‘hidden’); S.matches = await DB.getMatches(); S.config = await DB.getConfig(); let html = ``; html += `
Pronósticos ${S.config.abiertas?’abiertos’:’cerrados’}${S.config.abiertas?’Los jugadores aún pueden editar’:’Ya nadie puede editar’}
`; if(DEMO) html += ``; for(const g in GRUPOS){ html+=`
${g}
Grupo ${g}
`; S.matches.filter(m=>m.grupo===g).forEach(m=>{ html+=`
${fl(m.local)} ${m.local} vs ${m.visitante} ${fl(m.visitante)}
J${m.jornada}${m.finalizado?` · final: ${m.goles_local}–${m.goles_visitante}`:”}
`; }); } host.innerHTML=html; $(‘#adm-toggle’).addEventListener(‘click’, async ()=>{ await DB.setConfig(!S.config.abiertas); S.config.abiertas=!S.config.abiertas; toast(S.config.abiertas?’Pronósticos abiertos’:’Pronósticos cerrados’); openAdmin(); }); const seed=$(‘#adm-seed’); if(seed) seed.addEventListener(‘click’, async ()=>{ await DB.ensureSeed(); S.matches=await DB.getMatches(); toast(‘Partidos reiniciados’); openAdmin(); }); host.querySelectorAll(‘button[data-res]’).forEach(b=> b.addEventListener(‘click’, async ()=>{ const id=+b.dataset.res, gl=$(‘#rl-‘+id).value, gv=$(‘#rv-‘+id).value; if(gl===”||gv===”){ toast(‘Pon ambos marcadores’); return; } await DB.setResult(id, Math.max(0,parseInt(gl)), Math.max(0,parseInt(gv))); S.matches=await DB.getMatches(); toast(‘Resultado guardado’); openAdmin(); refreshMyPoints(); })); } boot(); })();
0
    Carrito
    Tu carrito está vacíoRegresar a la tienda.