// resultados.jsx — Página dedicada de RESULTADOS OFICIALES (separada del index) // Soporta deep-link desde el calendario: resultados.html?race= o resultados.html# const { useState, useEffect, useRef } = React; // ----- Helpers (mismos que el index, copiados aquí para que la página sea autónoma) ----- const RES_PILOT_DIR = (typeof window !== "undefined" && window.CDG_PILOTS) ? window.CDG_PILOTS : {}; const _RES_PILOTS_NORM = Object.fromEntries( Object.entries(RES_PILOT_DIR).map(([k, v]) => [k.toLowerCase().trim(), v]) ); function pilotMeta(name) { if (!name) return {}; return _RES_PILOTS_NORM[name.toLowerCase().trim()] || {}; } function flagUrl(code) { if (!code || code.length !== 2) return ""; return `https://flagcdn.com/${code.toLowerCase()}.svg`; } function availableYears() { const ys = new Set((window.CDG_RESULTS || []).map(r => r.year).filter(Boolean)); const arr = [...ys].sort((a, b) => b - a); if (arr.length === 0) arr.push(new Date().getFullYear()); return arr; } function pilotHref(name, year, cat) { return `piloto.html?name=${encodeURIComponent(name)}&year=${year}&cat=${encodeURIComponent(cat)}`; } // Icono Instagram reutilizable function IgGlyph({ size = 13 }) { return ( ); } // Avatar del piloto (foto recortada o iniciales) function PilotAvatar({ name, size }) { const meta = pilotMeta(name); const s = size || 54; if (meta.photo) return {name}; const initials = String(name || "?").split(/\s+/).map(w => w[0]).slice(0, 2).join("").toUpperCase(); return
{initials}
; } // Podio visual (top 3) del resultado de la fecha function ResultsPodium({ rows, tab, year, category }) { const top = rows.slice(0, 3); if (top.length < 3) return null; const order = [top[1], top[0], top[2]]; const cat = category === "TINY" ? "tiny" : "five"; return (
{order.map((r, idx) => { const place = idx === 1 ? 1 : (idx === 0 ? 2 : 3); const meta = pilotMeta(r.pilot); const flag = flagUrl(meta.country); const medal = place === 1 ? "ORO" : place === 2 ? "PLATA" : "BRONCE"; const big = tab === "qualification" ? (r.time || "—") : `${r.points ?? 0} pts`; const sub = tab === "qualification" ? `${r.points ?? 0} pts` : "Bracket final"; return (
{place}{medal}
{flag && {meta.country}}{r.pilot}
{big}
{sub}
); })}
); } // Deep-link: ¿a qué carrera apuntamos al cargar? function wantedRaceId() { const params = new URLSearchParams(location.search); const hash = decodeURIComponent((location.hash || "").replace(/^#/, "")); return params.get("race") || hash || null; } function Results() { const allData = window.CDG_RESULTS || []; const years = availableYears(); const currentYear = new Date().getFullYear(); // Resolver deep-link una sola vez const initial = useRef(null); if (initial.current === null) { const wid = wantedRaceId(); const race = wid ? allData.find(r => r.id === wid) : null; initial.current = { year: race ? race.year : (years.includes(currentYear) ? currentYear : years[0]), id: race ? race.id : null, cat: "ALL" }; } const [year, setYear] = useState(initial.current.year); const [catFilter, setCatFilter] = useState("ALL"); const yearData = allData.filter(r => r.year === year); const data = yearData.filter(r => catFilter === "ALL" || r.category === catFilter); const [activeId, setActiveId] = useState(initial.current.id || data[0]?.id); const [tab, setTab] = useState("qualification"); const mainRef = useRef(null); useEffect(() => { if (!data.find(r => r.id === activeId)) { setActiveId(data[0]?.id); } }, [year, catFilter]); // Si venimos de un deep-link, hacer scroll suave al panel una vez montado useEffect(() => { if (initial.current.id && mainRef.current) { const y = mainRef.current.getBoundingClientRect().top + window.scrollY - 90; window.scrollTo({ top: y, behavior: "smooth" }); } }, []); if (allData.length === 0) { return (
RESULTADOS OFICIALES / TEMPORADA {year}

Resultados
próximamente.

Los resultados se publicarán después de cada fecha.

); } const active = data.find(r => r.id === activeId) || data[0]; const rows = (active && active[tab]) || []; const fmtCat = (c) => c === "TINY" ? "Tinywhoop" : "Open 5\""; const catClass = (c) => c === "TINY" ? "cat-tiny-pill" : "cat-five-pill"; return (
RESULTADOS OFICIALES · TEMPORADA {year}{year === currentYear ? " · ACTUAL" : ""} / CDG

Resultados
oficiales.

Temporada

Clasificación (tiempos) y bracket final de cada fecha de la temporada. Selecciona una fecha del calendario y caes directo aquí.

{!active ? (
Selecciona una fecha
) : (<>
{active.round} · {active.date}

{active.name}

{fmtCat(active.category)} {active.location && 📍 {active.location}}
{rows.length === 0 ? (
Sin datos de {tab === "qualification" ? "clasificación" : "bracket final"} para esta fecha.
) : (<> {(() => { // Si hay podio (3+ pilotos), no repetimos el top 3 en la lista de abajo const hasPodium = rows.length >= 3; const tableRows = hasPodium ? rows.slice(3) : rows; return tableRows.length === 0 ? (
El podio completo lo tienes arriba. No hay más posiciones en esta fecha.
) : ( {tab === "qualification" && } {tableRows.map(r => { const meta = pilotMeta(r.pilot); return ( {tab === "qualification" && } ); })}
Pos Piloto HandleTiempoPuntos
{String(r.pos).padStart(2, "0")} {flagUrl(meta.country) && {meta.country}} {r.pilot} {meta.ig ? @{meta.ig} : (r.handle || "—")} {r.time || "—"}{r.points ?? "—"}
); })()} )} )}
); } ReactDOM.createRoot(document.getElementById("res-root")).render();