// app.jsx — CDG main app

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#ff3d00",
  "accent2": "#ffaa00",
  "speed": 1,
  "displayFont": "Space Grotesk",
  "heroVariant": "split",
  "palette": "fire"
}/*EDITMODE-END*/;

const PALETTES = {
  fire:    { accent: "#ff3d00", accent2: "#ffaa00", glow: "rgba(255,61,0,0.55)" },
  acid:    { accent: "#c6ff00", accent2: "#00e5ff", glow: "rgba(198,255,0,0.55)" },
  magma:   { accent: "#ff1744", accent2: "#ff6e40", glow: "rgba(255,23,68,0.55)" },
  voltage: { accent: "#7c4dff", accent2: "#ff4081", glow: "rgba(124,77,255,0.55)" }
};

const FONT_PAIRS = {
  "Space Grotesk": "'Space Grotesk', system-ui, sans-serif",
  "Archivo Black": "'Archivo Black', system-ui, sans-serif",
  "Bebas Neue":    "'Bebas Neue', system-ui, sans-serif",
  "Syne":          "'Syne', system-ui, sans-serif"
};

// ----- Countdown -----
function useCountdown(target) {
  const [t, setT] = useState(() => calc(target));
  useEffect(() => {
    const id = setInterval(() => setT(calc(target)), 1000);
    return () => clearInterval(id);
  }, [target]);
  return t;
}
function calc(target) {
  const diff = Math.max(0, target - Date.now());
  const d = Math.floor(diff / 86400000);
  const h = Math.floor((diff / 3600000) % 24);
  const m = Math.floor((diff / 60000) % 60);
  const s = Math.floor((diff / 1000) % 60);
  return { d, h, m, s };
}
const pad = n => String(n).padStart(2, "0");

// ----- Reveal on scroll -----
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll(".reveal");
    // Mark as pending so they fade in on scroll
    els.forEach(el => {
      const rect = el.getBoundingClientRect();
      // Only hide elements not yet on screen
      if (rect.top > window.innerHeight * 0.9) el.classList.add("pending");
      else el.classList.add("visible");
    });
    const obs = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.remove("pending");
          e.target.classList.add("visible");
        }
      });
    }, { threshold: 0.05, rootMargin: "0px 0px -10% 0px" });
    els.forEach(el => obs.observe(el));
    return () => obs.disconnect();
  }, []);
}

// ----- Parallax -----
function useParallax() {
  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      document.querySelectorAll("[data-parallax]").forEach(el => {
        const r = parseFloat(el.dataset.parallax);
        el.style.transform = `translateY(${y * r}px)`;
      });
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
}

// ----- DATA -----
// Calendar oficial CDG · Temporada 2026 (Tiny + Open 5")
// status se calcula automáticamente desde la fecha (no editar manualmente)
const RACES_RAW = [
  { num: "01", date: "2026-02-22", day: "21–22", month: "FEB", name: "Tinywhoop · 1ra fecha",   sub: "1ra fecha · Inicio de temporada", loc: "Guatemala City",   cat: "TINY",  tag: null    },
  { num: "02", date: "2026-02-22", day: "21–22", month: "FEB", name: "Gemfan LATAM Guatemala", sub: "Open 5\" · 1ra fecha",            loc: "Guatemala City",   cat: "5\"",   tag: "LATAM" },
  { num: "03", date: "2026-04-24", day: "24",    month: "ABR", name: "Tinywhoop · 2da fecha",   sub: "Categoría Tinywhoop",            loc: "Guatemala City",   cat: "TINY",  tag: null    },
  { num: "04", date: "2026-04-25", day: "25",    month: "ABR", name: "Open 5\" · 2da fecha",     sub: "Categoría Open 5\"",              loc: "Guatemala City",   cat: "5\"",   tag: null    },
  { num: "05", date: "2026-05-15", day: "15",    month: "MAY", name: "Tinywhoop · 3ra fecha",   sub: "Categoría Tinywhoop",            loc: "Por confirmar",    cat: "TINY",  tag: null    },
  { num: "06", date: "2026-05-16", day: "16",    month: "MAY", name: "Open 5\" · 3ra fecha",     sub: "MultiGP Regional Qualifier",     loc: "Por confirmar",    cat: "5\"",   tag: "RQ"    },
  { num: "07", date: "2026-05-29", day: "29",    month: "MAY", name: "Tinywhoop · 4ta fecha",   sub: "Categoría Tinywhoop",            loc: "Por confirmar",    cat: "TINY",  tag: null    },
  { num: "08", date: "2026-05-30", day: "30",    month: "MAY", name: "Open 5\" · 4ta fecha",     sub: "Global Qualifier",               loc: "Por confirmar",    cat: "5\"",   tag: "GQ"    },
  { num: "09", date: "2026-07-03", day: "03",    month: "JUL", name: "Tinywhoop · 5ta fecha",   sub: "Categoría Tinywhoop",            loc: "Por confirmar",    cat: "TINY",  tag: null    },
  { num: "10", date: "2026-07-04", day: "04",    month: "JUL", name: "Open 5\" · 5ta fecha",     sub: "Global Qualifier",               loc: "Por confirmar",    cat: "5\"",   tag: "GQ"    },
  { num: "11", date: "2026-09-11", day: "11",    month: "SEP", name: "Tinywhoop · 6ta fecha",   sub: "Categoría Tinywhoop",            loc: "Por confirmar",    cat: "TINY",  tag: null    },
  { num: "12", date: "2026-09-12", day: "12",    month: "SEP", name: "Open 5\" · 6ta fecha",     sub: "Categoría Open 5\"",              loc: "Por confirmar",    cat: "5\"",   tag: null    },
  { num: "13", date: "2026-11-13", day: "13",    month: "NOV", name: "Tinywhoop · 7ma fecha",   sub: "Final Tinywhoop",                loc: "Por confirmar",    cat: "TINY",  tag: null    },
  { num: "14", date: "2026-11-14", day: "14",    month: "NOV", name: "Open 5\" · 7ma fecha",     sub: "Final Open 5\"",                  loc: "Por confirmar",    cat: "5\"",   tag: null    }
];

// Auto-derive status (done / live / upcoming) from today's date AND the real race hours
// Tiny corre 5:00–10:00 PM · 5" corre 8:00 AM–6:00 PM (hora Guatemala, GT-6).
const TODAY = new Date();
const TIME_BY_CAT = { "TINY": "5:00 – 10:00 PM", "5\"": "8:00 AM – 6:00 PM", "AMBAS": "Todo el día" };
const HOURS_BY_CAT = { "TINY": { start: 17, end: 22 }, "5\"": { start: 8, end: 18 }, "AMBAS": { start: 8, end: 22 } };
// Sede por defecto cuando aún no se confirma: 5" → Florencia · Tiny → VIP Location (rotativa)
const DEFAULT_VENUE = { "5\"": "Parque Ecológico Florencia", "TINY": "VIP Location · sede rotativa", "AMBAS": "Centro Español" };
const pad2 = n => String(n).padStart(2, "0");
const RACES = RACES_RAW.map(r => {
  const hrs = HOURS_BY_CAT[r.cat] || { start: 8, end: 18 };
  const startTs = new Date(`${r.date}T${pad2(hrs.start)}:00:00-06:00`).getTime();
  const endTs   = new Date(`${r.date}T${pad2(hrs.end)}:00:00-06:00`).getTime();
  const now = TODAY.getTime();
  let status, label;
  if (now < startTs)      { status = "upcoming"; label = r.sub?.startsWith("Final") ? "Final" : "Próxima"; }
  else if (now <= endTs)  { status = "live";     label = "En vivo"; }
  else                    { status = "done";     label = "Completada"; }
  const loc = (r.loc === "Por confirmar") ? (DEFAULT_VENUE[r.cat] || r.loc) : r.loc;
  return { ...r, loc, status, label, time: TIME_BY_CAT[r.cat] || "Por confirmar", timestamp: startTs, startTs, endTs };
});

// Next upcoming race for hero countdown
function nextUpcomingRace() {
  return RACES.find(r => r.status === "upcoming") || RACES[RACES.length - 1];
}

// ¿Hay una fecha corriendo ahora mismo? (status auto = "live", o forzado desde config.liveOverride)
function getActiveRace() {
  const cfg = (typeof window !== "undefined" && window.CDG_CONFIG) || {};
  const live = RACES.find(r => r.status === "live");
  if (live) return { race: live, cfg };
  if (cfg.liveOverride) return { race: nextUpcomingRace(), cfg };
  return null;
}

// Mapea una carrera del calendario a su entrada de resultados (para deep-link)
function resultForRace(race) {
  const list = (typeof window !== "undefined" && window.CDG_RESULTS) || [];
  return list.find(r => r.id && r.id.startsWith(race.date) && r.category === race.cat) || null;
}

const VENUES = {
  tiny: [
    { name: "C.C. Tikal Futura",      loc: "Calz. Roosevelt, Zona 11 · Ciudad de Guatemala",   type: "Indoor",  note: "Explanada comercial bajo techo.", maps: "Tikal Futura, Calzada Roosevelt, Guatemala" },
    { name: "Centro Español",         loc: "Ciudad de Guatemala",                               type: "Ambas",   note: "Club deportivo y social · sede híbrida (Tiny + 5\").", maps: "Centro Español de Guatemala" },
    { name: "Design Center · Sótano", loc: "Blvd. Los Próceres, Zona 10 · Ciudad de Guatemala", type: "Indoor",  note: "Pista técnica en niveles de parqueo.", maps: "Design Center, Boulevard Los Próceres, Zona 10, Guatemala" }
  ],
  open: [
    { name: "Parque Ecológico Florencia",        loc: "Santa Lucía Milpas Altas, Sacatepéquez", type: "Outdoor", note: "Reserva boscosa · Km 35 ruta a Antigua.", maps: "Parque Ecológico Florencia, Santa Lucía Milpas Altas, Sacatepéquez" },
    { name: "Estadio Carlos Enrique Cuc Cuevas", loc: "Santa Lucía Milpas Altas, Sacatepéquez", type: "Outdoor", note: "Estadio municipal.", maps: "Estadio Carlos Enrique Cuc Cuevas, Santa Lucía Milpas Altas" },
    { name: "Finca Cienaguilla",                 loc: "Guatemala",                              type: "Outdoor", note: "Circuito en finca privada.", url: "https://www.instagram.com/fincacienaguillagt/", link: "ig", maps: "Finca Cienaguilla, Guatemala" },
    { name: "Parque Erick Barrondo",             loc: "28 av., Zona 7 · Ciudad de Guatemala",   type: "Outdoor", note: "El parque más grande de la capital.", maps: "Parque Erick Barrondo, Zona 7, Guatemala" },
    { name: "Tribu Terrace",                     loc: "Ciudad de Guatemala",                    type: "Outdoor", note: "Terraza rooftop para eventos.", url: "https://tributerrace.com", link: "web", maps: "Tribu Terrace, Guatemala" },
    { name: "Centro Español",                    loc: "Ciudad de Guatemala",                    type: "Ambas",   note: "Club deportivo y social · sede híbrida (Tiny + 5\").", maps: "Centro Español de Guatemala" }
  ]
};
const mapsUrl = q => `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(q)}`;

const VIDEOS = [
  { title: "Highlights · Volcán Pacaya", sub: "RD 02 · 4 min", live: true,  duration: "LIVE"  },
  { title: "POV · Vuelta rápida", sub: "Lap récord", live: false, duration: "2:14"  },
  { title: "Crash compilation", sub: "Temporada 2026", live: false, duration: "3:48"  },
  { title: "Tras bambalinas", sub: "CDG · 502", live: false, duration: "5:21"  },
  { title: "Tutorial: Setup 5\"", sub: "Guía CDG", live: false, duration: "8:02"  }
];

const SPONSORS = [
  { n: "Expression", s: "Patrocinador oficial" },
  { n: "Herbaclinic", s: "Patrocinador oficial" },
  { n: "Schatten", s: "Patrocinador oficial" },
  { n: "BetaFPV", s: "Tinywhoop" },
  { n: "Gemfan", s: "Hélices" }
];

// ----- DIRECTORIO DE PILOTOS -----
// Mapa central: nombre en results.js → { ig, country }
// ig = solo el usuario (sin @, sin link). country = código ISO 2 letras (GT, SV, MX…)
// Pilotos sin ig todavía → se muestran sin link. Pilotos sin country → sin bandera.
// Directorio central de pilotos → data/pilots.js (window.CDG_PILOTS)
const PILOT_DIR = (typeof window !== "undefined" && window.CDG_PILOTS) ? window.CDG_PILOTS : {};

// Lookup tolerante a mayúsculas/espacios
const _PILOTS_NORM = Object.fromEntries(
  Object.entries(PILOT_DIR).map(([k, v]) => [k.toLowerCase().trim(), v])
);
function pilotMeta(name) {
  if (!name) return {};
  return _PILOTS_NORM[name.toLowerCase().trim()] || {};
}

// Código ISO 2 letras → URL de imagen de bandera (se ve igual en todos los SO,
// a diferencia del emoji que en Windows aparece como letras)
function flagUrl(code) {
  if (!code || code.length !== 2) return "";
  return `https://flagcdn.com/${code.toLowerCase()}.svg`;
}

// ----- HERO -----
function Hero({ t }) {
  // ¿Estamos corriendo? → la portada se convierte en un AVISO en vivo
  const activeInfo = getActiveRace();
  const next = nextUpcomingRace();
  const target = next.timestamp;
  const c = useCountdown(target);
  const countdownLabel = `${next.num} · ${next.name.toUpperCase()} · ${next.day} ${next.month}`;

  // Datos del aviso "en vivo"
  let live = null;
  if (activeInfo) {
    const r = activeInfo.race;
    const cfg = activeInfo.cfg;
    const venue = (cfg.liveVenue && cfg.liveVenue.trim()) || r.loc || "Sede por confirmar";
    live = {
      name: r.name,
      cat: r.cat === "TINY" ? "Tinywhoop" : (r.cat === "5\"" ? "Open 5\"" : "Tiny + Open 5\""),
      date: `${r.day} ${r.month}`,
      time: r.time,
      venue,
      note: (cfg.liveNote && cfg.liveNote.trim()) || ""
    };
  }

  return (
    <section className={`hero ${live ? "is-live" : ""}`} id="hero" data-screen-label="01 Hero">
      <div className="hero-grid" data-parallax="0.15"></div>
      <div className="hero-speedlines"></div>
      <div className="hero-inner">
        <div>
          <div className="eyebrow">
            <span className="num">{live ? "// ESTAMOS CORRIENDO · EN VIVO AHORA" : "// CAMPEONATO DRONES GUATEMALA · TEMPORADA 2026"}</span>
          </div>
          <img src="assets/logo-cdg.png" alt="CDG" style={{ width: "min(560px, 100%)", display: "block", marginBottom: 20, filter: "drop-shadow(0 8px 30px rgba(0,0,0,.6))" }} />
          <h1>
            {live
              ? <><span className="stroke">Estamos</span><span className="accent">Corriendo</span></>
              : <><span className="stroke">Agenda</span><span className="accent">Anual /26</span></>}
          </h1>
          <div className="hero-sub">
            <div>Categoría<b>5" Open</b></div>
            <div>Categoría<b>Tinywhoop</b></div>
            <div>Sedes<b>09 Venues</b></div>
          </div>
          <div className="hero-cta">
            {live
              ? <><a className="btn btn-primary" href="#stream">Ver transmisión <span className="arrow">▸</span></a>
                  <a className="btn btn-ghost" href="#calendario">Ver calendario <span className="arrow">→</span></a></>
              : <><a className="btn btn-primary" href="#registro">Inscríbete <span className="arrow">→</span></a>
                  <a className="btn btn-ghost" href="#stream">Ver stream <span className="arrow">▸</span></a></>}
          </div>
        </div>
        <div className="hero-side">
          {live ? (
            <div className="live-banner">
              <div className="live-banner-head"><span className="live-pip"></span>Estamos corriendo · En vivo</div>
              <div className="live-banner-title">{live.name}</div>
              <div className="live-banner-race">{live.cat} · CDG 2026</div>
              <div className="live-banner-rows">
                <div><span>Fecha</span><b>{live.date}</b></div>
                <div><span>Horario</span><b>{live.time}</b></div>
                <div className="full"><span>Sede</span><b>{live.venue}</b></div>
                {live.note && <div className="full"><span>Nota</span><b>{live.note}</b></div>}
              </div>
              <a className="btn btn-primary" href="#stream">Ver transmisión en vivo <span className="arrow">▸</span></a>
            </div>
          ) : (
            <div className="countdown">
              <div className="countdown-label"><span className="dot"></span>{countdownLabel}</div>
              <div className="countdown-grid">
                <div className="countdown-cell"><div className="countdown-num">{pad(c.d)}</div><div className="countdown-unit">Días</div></div>
                <div className="countdown-cell"><div className="countdown-num">{pad(c.h)}</div><div className="countdown-unit">Horas</div></div>
                <div className="countdown-cell"><div className="countdown-num">{pad(c.m)}</div><div className="countdown-unit">Min</div></div>
                <div className="countdown-cell"><div className="countdown-num">{pad(c.s)}</div><div className="countdown-unit">Seg</div></div>
              </div>
            </div>
          )}
          <div className="drone-stage">
            <div className="hud">
              <span className="tl">ALT 14m</span>
              <span className="tr">VEL 124kph</span>
              <span className="bl">REC ●</span>
              <span className="br">5.8GHz · CH4</span>
            </div>
            <img className="drone-real" src="assets/drone-real.png" alt="Drone FPV de carreras — CDG" />
            <div className="drone-shadow"></div>
          </div>
        </div>
      </div>
      <div className="hero-marquee">
        <div className="hero-marquee-track">
          <span>FPV RACING <span className="star">✦</span> CAMPEONATO 2026 <span className="star">✦</span> 5" OPEN <span className="star">✦</span> TINYWHOOP <span className="star">✦</span> 502 <span className="star">✦</span> ANTIGUA <span className="star">✦</span> SAN PEDRO SULA <span className="star">✦</span> TIKAL <span className="star">✦</span> ESPORTS GAMING <span className="star">✦</span></span>
          <span>FPV RACING <span className="star">✦</span> CAMPEONATO 2026 <span className="star">✦</span> 5" OPEN <span className="star">✦</span> TINYWHOOP <span className="star">✦</span> 502 <span className="star">✦</span> ANTIGUA <span className="star">✦</span> SAN PEDRO SULA <span className="star">✦</span> TIKAL <span className="star">✦</span> ESPORTS GAMING <span className="star">✦</span></span>
        </div>
      </div>
    </section>
  );
}

// ----- ABOUT -----
function About() {
  return (
    <section id="campeonato" data-screen-label="01 Sobre">
      <div className="eyebrow">SOBRE EL CAMPEONATO <span className="num">/ 01</span></div>
      <div className="about-grid reveal">
        <div>
          <h2>La velocidad<br/>tiene nuevo<br/>código de área.</h2>
          <p className="lead" style={{ marginTop: 28 }}>
            Tercera temporada del Campeonato Drones Guatemala. Catorce fechas, dos categorías, un solo objetivo:
            llevar el FPV racing centroamericano a los volcanes, las selvas y las pistas más rápidas del 502.
          </p>
          <div className="classes">
            <div className="class-card">
              <div className="tag">Categoría A</div>
              <h4>5" Open</h4>
              <p>Quads de competición sin restricciones de motor, hasta 6S.</p>
              <div className="specs">
                <div>Frame<b>5"</b></div>
                <div>Bat<b>6S</b></div>
                <div>Peso<b>≤ 800g</b></div>
              </div>
              <div className="class-extra">
                <span>Inscripción<b>Q150 / fecha</b></span>
                <span>Horario<b>8 AM – 6 PM</b></span>
              </div>
            </div>
            <div className="class-card">
              <div className="tag">Categoría B</div>
              <h4>Tinywhoop</h4>
              <p>Micro drones indoor, 1S–2S. Pista técnica y multinivel.</p>
              <div className="specs">
                <div>Frame<b>65–75mm</b></div>
                <div>Bat<b>1–2S</b></div>
                <div>Peso<b>≤ 40g</b></div>
              </div>
              <div className="class-extra">
                <span>Inscripción<b>Q100 / fecha</b></span>
                <span>Horario<b>5 – 10 PM</b></span>
              </div>
            </div>
          </div>
        </div>
        <div className="about-stats">
          <div className="stat"><div className="stat-num">14</div><div className="stat-label">Fechas / 2026</div></div>
          <div className="stat"><div className="stat-num">02</div><div className="stat-label">Categorías</div></div>
          <div className="stat"><div className="stat-num">RQ</div><div className="stat-label">Regional Qualifier</div></div>
          <div className="stat"><div className="stat-num">GQ</div><div className="stat-label">Global Qualifier</div></div>
          <div className="stat"><div className="stat-num">220</div><div className="stat-label">Km/h pico</div></div>
          <div className="stat"><div className="stat-num">5.8</div><div className="stat-label">GHz · 8 canales</div></div>
        </div>
      </div>
    </section>
  );
}

// ----- CALENDAR -----
const MONTH_SHORT = ["ENE","FEB","MAR","ABR","MAY","JUN","JUL","AGO","SEP","OCT","NOV","DIC"];
const MONTH_LONG  = ["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"];
const WEEKDAYS    = ["Lun","Mar","Mié","Jue","Vie","Sáb","Dom"];

function Calendar() {
  const [view, setView] = useState("grid");   // "grid" (cuadrícula por meses) | "list"
  const [filter, setFilter] = useState("ALL");

  // Agrupar carreras por mes (YYYY-MM)
  const monthsMap = {};
  RACES.forEach(r => { const k = r.date.slice(0, 7); (monthsMap[k] = monthsMap[k] || []).push(r); });
  const monthKeys = Object.keys(monthsMap).sort();

  // Mes por defecto = mes de la próxima fecha, si no el primero
  const next = nextUpcomingRace();
  const defKey = next ? next.date.slice(0, 7) : monthKeys[0];
  const [month, setMonth] = useState(monthKeys.includes(defKey) ? defKey : monthKeys[0]);

  // Carreras del mes seleccionado, ya filtradas por categoría
  const passCat = r => filter === "ALL" || r.cat === filter || r.cat === "AMBAS";
  const monthRaces = (monthsMap[month] || []).filter(passCat);

  const [selId, setSelId] = useState(monthRaces[0]?.num || null);
  useEffect(() => { setSelId((monthsMap[month] || []).filter(passCat)[0]?.num || null); }, [month, filter]);

  // Construir la cuadrícula del mes
  const [yy, mm] = month.split("-").map(Number);
  const monthIdx = mm - 1;
  const firstDow = (new Date(yy, monthIdx, 1).getDay() + 6) % 7;   // Lunes = 0
  const daysInMonth = new Date(yy, monthIdx + 1, 0).getDate();
  const todayKey = `${TODAY.getFullYear()}-${String(TODAY.getMonth() + 1).padStart(2, "0")}-${String(TODAY.getDate()).padStart(2, "0")}`;

  // Mapa día → carreras
  const racesByDay = {};
  monthRaces.forEach(r => {
    const d = parseInt(r.date.slice(8, 10), 10);
    (racesByDay[d] = racesByDay[d] || []).push(r);
  });

  const sel = RACES.find(r => r.num === selId) || null;
  const catName = c => c === "TINY" ? "Tinywhoop" : (c === "5\"" ? "Open 5\"" : "Tiny + Open 5\"");
  const catCls  = c => c === "TINY" ? "tiny" : (c === "AMBAS" ? "both" : "");
  const selRes = sel ? resultForRace(sel) : null;

  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(<div className="calx-cell empty" key={`e${i}`}></div>);
  for (let d = 1; d <= daysInMonth; d++) {
    const dayRaces = racesByDay[d] || [];
    const isToday = `${month}-${String(d).padStart(2, "0")}` === todayKey;
    const selectedHere = dayRaces.some(r => r.num === selId);
    cells.push(
      <div
        key={d}
        className={`calx-cell ${dayRaces.length ? "has-race" : ""} ${selectedHere ? "selected" : ""} ${isToday ? "today" : ""}`}
        onClick={dayRaces.length ? () => setSelId(dayRaces[0].num) : undefined}
      >
        <div className="calx-daynum">{String(d).padStart(2, "0")}</div>
        {dayRaces.map(r => (
          <div
            key={r.num}
            className={`calx-chip ${catCls(r.cat)} ${r.status}`}
            onClick={(e) => { e.stopPropagation(); setSelId(r.num); }}
          >
            <span className="chip-dot"></span>{r.cat === "TINY" ? "Tiny" : r.cat === "5\"" ? "5\"" : "Ambas"}
          </div>
        ))}
      </div>
    );
  }

  return (
    <section id="calendario" data-screen-label="02 Calendario">
      <div className="eyebrow">AGENDA ANUAL · TEMPORADA 2026 <span className="num">/ 02</span></div>
      <div className="cal-head reveal">
        <h2>Catorce fechas.<br/>Dos categorías.</h2>
        <div style={{ display: "flex", gap: 12, flexWrap: "wrap", alignItems: "center" }}>
          <div className="cal-viewtoggle">
            <button className={`cal-vt-btn ${view === "grid" ? "active" : ""}`} onClick={() => setView("grid")}>Cuadrícula</button>
            <button className={`cal-vt-btn ${view === "list" ? "active" : ""}`} onClick={() => setView("list")}>Lista</button>
          </div>
          <div className="cal-filter">
            {[
              { id: "ALL",  label: "Todas" },
              { id: "TINY", label: "Tinywhoop" },
              { id: "5\"",  label: "Open 5\"" }
            ].map(f => (
              <button key={f.id} className={`cal-filter-btn ${filter===f.id?"active":""}`} onClick={()=>setFilter(f.id)}>{f.label}</button>
            ))}
          </div>
        </div>
      </div>
      <div className="cal-legend reveal">
        <span><b className="lg-dot lg-tiny"></b>Tinywhoop</span>
        <span><b className="lg-dot lg-5"></b>Open 5"</span>
        <span><b className="lg-tag rq">RQ</b>MultiGP Regional Qualifier</span>
        <span><b className="lg-tag gq">GQ</b>Global Qualifier</span>
        <span><img src="assets/logo-regional.png" alt="MultiGP Regional Series Central America" style={{ height: 40 }} /></span>
      </div>

      {view === "grid" ? (
        <div className="reveal">
          {/* Selector de meses */}
          <div className="cal-months">
            {monthKeys.map(k => {
              const [ky, km] = k.split("-").map(Number);
              const count = (monthsMap[k] || []).filter(passCat).length;
              return (
                <button key={k} className={`cal-month-btn ${month === k ? "active" : ""}`} onClick={() => setMonth(k)}>
                  <span className="cm-count">{count || ""}</span>
                  <b>{MONTH_SHORT[km - 1]}</b>
                  <span>{ky}</span>
                </button>
              );
            })}
          </div>

          <div className="cal-grid-wrap">
            <div className="calx">
              <div className="calx-weekdays">
                {WEEKDAYS.map(w => <div key={w}>{w}</div>)}
              </div>
              <div className="calx-days">{cells}</div>
            </div>

            {/* Panel de detalle */}
            <div className="cal-detail">
              {!sel ? (
                <div className="cd-empty">— Sin fechas de esta categoría en {MONTH_LONG[monthIdx]} —</div>
              ) : (<>
                <div className={`cd-status ${sel.status}`}>{sel.label}</div>
                <div className="cd-date">{sel.day} {sel.month} · {yy}</div>
                <div className="cd-name">{sel.name}{sel.tag ? <span className={`race-tag tag-${sel.tag.toLowerCase()}`} style={{ marginLeft: 10, verticalAlign: "middle" }}>{sel.tag}</span> : null}</div>
                <dl className="cd-rows">
                  <div><dt>Categoría</dt><dd>{catName(sel.cat)}</dd></div>
                  <div><dt>Horario</dt><dd>{sel.time}</dd></div>
                  <div><dt>Sede</dt><dd className={sel.loc === "Por confirmar" ? "tbc" : ""}>{sel.loc}</dd></div>
                </dl>
                {sel.status === "done" && selRes ? (
                  <a className="btn btn-primary cd-cta" href={`resultados.html?race=${selRes.id}`}>Ver resultados oficiales <span className="arrow">→</span></a>
                ) : sel.status === "done" ? (
                  <div className="cd-hint">Resultados oficiales próximamente.</div>
                ) : sel.status === "live" ? (
                  <a className="btn btn-primary cd-cta" href="#stream">Ver transmisión en vivo <span className="arrow">▸</span></a>
                ) : (
                  <a className="btn btn-ghost cd-cta" href="#registro">Inscríbete a esta fecha <span className="arrow">→</span></a>
                )}
                <div className="cd-hint">{sel.sub}</div>
              </>)}
            </div>
          </div>
        </div>
      ) : (
        <div className="calendar reveal" style={{ marginTop: 40 }}>
          {RACES.filter(passCat).map(r => {
            const res = resultForRace(r);
            const href = (r.status === "done" && res) ? `resultados.html?race=${res.id}` : null;
            const Row = href ? "a" : "div";
            return (
            <Row
              className={`race-row cat-${r.cat==="TINY"?"tiny":r.cat==="5\""?"five":"both"}`}
              key={r.num}
              {...(href ? { href, style: { textDecoration: "none", color: "inherit" } } : {})}
            >
              <div className="race-num">F/{r.num}</div>
              <div className="race-date">{r.month}<b>{r.day}</b></div>
              <div className="race-name-wrap">
                <div className="race-name">
                  {r.name}
                  {r.tag && <span className={`race-tag tag-${r.tag.toLowerCase()}`}>{r.tag}</span>}
                </div>
                <div className="race-sub">{r.sub}</div>
              </div>
              <div className={`race-loc ${r.loc==="Por confirmar"?"tbc":""}`}>
                {r.loc==="Por confirmar" ? <><span className="tbc-dot"></span>Por confirmar</> : r.loc}
              </div>
              <div className={`race-status ${r.status}`}>{r.label}</div>
              <div className="race-arrow">→</div>
            </Row>
            );
          })}
        </div>
      )}
    </section>
  );
}

function Venues() {
  const groups = [
    { key: "tiny", label: "Tinywhoop", sub: "Indoor · técnico",   list: VENUES.tiny, cls: "tiny" },
    { key: "open", label: "Open 5\"", sub: "Outdoor · velocidad", list: VENUES.open, cls: "" }
  ];
  return (
    <section id="venues" data-screen-label="04 Venues">
      <div className="eyebrow">VENUES · SEDES 2026 <span className="num">/ 04</span></div>
      <h2 className="reveal" style={{ maxWidth: 900 }}>Donde se<br/>vuela.</h2>
      <p className="lead reveal" style={{ marginTop: 18, maxWidth: 720 }}>
        Los trazados cambian en cada fecha — estas son las <span className="accent">sedes</span> donde corre el campeonato.
      </p>
      <div className="venues reveal">
        {groups.map(g => (
          <div className={`venue-group ${g.cls}`} key={g.key}>
            <div className="vg-head">
              <h3>{g.label}</h3>
              <span>{g.sub}</span>
            </div>
            <div className="venue-grid">
              {g.list.map((v, i) => (
                <div className="venue-card" key={i}>
                  <div className="vc-top">
                    <span className="vc-idx">{String(i + 1).padStart(2, "0")}</span>
                    <span className={`vc-type ${v.type === "Indoor" ? "indoor" : v.type === "Ambas" ? "ambas" : "outdoor"}`}>{v.type}</span>
                  </div>
                  {v.url
                    ? <a className="vc-name vc-name-link" href={v.url} target="_blank" rel="noopener noreferrer">{v.name}<span className="vc-link-ico">{v.link === "ig" ? <svg className="ig-ico" viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><rect x="2.5" y="2.5" width="19" height="19" rx="5.5" fill="none" stroke="currentColor" strokeWidth="2"/><circle cx="12" cy="12" r="4.2" fill="none" stroke="currentColor" strokeWidth="2"/><circle cx="17.3" cy="6.7" r="1.4" fill="currentColor"/></svg> : "↗"}</span></a>
                    : <div className="vc-name">{v.name}</div>}
                  {v.loc && <div className="vc-loc">{v.loc}</div>}
                  {v.note && <div className="vc-note">{v.note}</div>}
                  {v.maps && (
                    <a className="vc-maps" href={mapsUrl(v.maps)} target="_blank" rel="noopener noreferrer">
                      <svg viewBox="0 0 24 24" width="13" height="13" aria-hidden="true"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/><circle cx="12" cy="9" r="2.5" fill="currentColor"/></svg>
                      Cómo llegar
                    </a>
                  )}
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
      <div className="venue-vip reveal">
        <div className="vv-left">
          <span className="vv-tag">VIP LOCATION</span>
          <h3>Sede rotativa</h3>
          <p>Esta localidad <b>cambia cada fecha</b>. Confirmamos la ubicación exacta a través de nuestras redes — escríbenos y te pasamos el punto exacto de cada evento.</p>
        </div>
        <div className="vv-right">
          <a href="https://instagram.com/cdgfpv" target="_blank" rel="noopener noreferrer" className="vv-btn">Instagram <span className="arrow">→</span></a>
          <a href="https://www.tiktok.com/@cdgfpv" target="_blank" rel="noopener noreferrer" className="vv-btn ghost">TikTok <span className="arrow">→</span></a>
        </div>
      </div>
    </section>
  );
}

// ----- RANKING HELPERS -----
// Auto-compute ranking from window.CDG_RESULTS
function computeRanking(year, category) {
  const data = (window.CDG_RESULTS || []).filter(r => r.year === year && r.category === category);
  return rankRaces(data);
}
// Computa standings a partir de una lista arbitraria de carreras (para comparar fechas)
function rankRaces(data) {
  const map = new Map(); // handle/name -> { name, handle, points, races, bestQual, bestBracket }

  const accumulate = (rows, isQual) => {
    rows.forEach(row => {
      const key = (row.handle || row.pilot).toUpperCase();
      const cur = map.get(key) || {
        name: row.pilot,
        handle: row.handle || "",
        points: 0,
        races: new Set(),
        bestQual: null,
        bestBracket: null,
        qualPts: 0,
        bracketPts: 0,
        podiums: 0
      };
      const pts = Number(row.points || 0);
      cur.points += pts;
      if (isQual) cur.qualPts += pts;
      else cur.bracketPts += pts;
      if (row.pos === 1 || row.pos === 2 || row.pos === 3) cur.podiums += 1;

      // Track best time (ignora placeholders en cero / vacíos)
      if (row.time) {
        const t = parseTime(row.time);
        if (Number.isFinite(t)) {
          if (isQual && (cur.bestQual === null || t < cur.bestQual.ms)) {
            cur.bestQual = { ms: t, str: row.time };
          }
          if (!isQual && (cur.bestBracket === null || t < cur.bestBracket.ms)) {
            cur.bestBracket = { ms: t, str: row.time };
          }
        }
      }
      map.set(key, cur);
    });
  };

  data.forEach(race => {
    race.races = race.id;
    accumulate(race.qualification || [], true);
    accumulate(race.bracket || [], false);
    // mark races counted
    [...(race.qualification || []), ...(race.bracket || [])].forEach(row => {
      const key = (row.handle || row.pilot).toUpperCase();
      const cur = map.get(key);
      if (cur) cur.races.add(race.id);
    });
  });

  return [...map.values()]
    .map(p => ({ ...p, racesCount: p.races.size }))
    .sort((a, b) => b.points - a.points || (a.bestQual?.ms ?? Infinity) - (b.bestQual?.ms ?? Infinity));
}

function parseTime(str) {
  // "0:42.18" → ms ; tiempos vacíos o en cero (placeholders de bracket) = inválidos
  if (!str || typeof str !== "string") return Infinity;
  const m = str.match(/(\d+):(\d+(?:\.\d+)?)/);
  if (!m) return Infinity;
  const ms = parseInt(m[1]) * 60000 + parseFloat(m[2]) * 1000;
  return ms > 0 ? ms : Infinity;
}

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

// Enlace a la página de detalle del piloto (historial qualy + bracket)
function pilotHref(name, year, cat) {
  return `piloto.html?name=${encodeURIComponent(name)}&year=${year}&cat=${encodeURIComponent(cat)}`;
}

// Orden cronológico de una carrera (para saber cuál es la fecha más reciente)
const MONTHS_ES = { ENE:0, FEB:1, MAR:2, ABR:3, MAY:4, JUN:5, JUL:6, AGO:7, SEP:8, OCT:9, NOV:10, DIC:11 };
function raceSortKey(race) {
  const d = race.date || "";
  let mo = 0, day = 1;
  const mm = d.match(/\b(ENE|FEB|MAR|ABR|MAY|JUN|JUL|AGO|SEP|OCT|NOV|DIC)\b/i);
  if (mm) mo = MONTHS_ES[mm[1].toUpperCase()] || 0;
  const dm = d.match(/\d+/);
  if (dm) day = parseInt(dm[0], 10);
  return (race.year || 0) * 10000 + mo * 100 + day;
}

// Devuelve el movimiento de cada piloto respecto a la fecha anterior:
//  { KEY: { move:'up'|'down'|'same'|'new', amt:Number } }
function rankingMoves(year, category) {
  const data = (window.CDG_RESULTS || []).filter(r => r.year === year && r.category === category);
  if (data.length < 2) return {};
  const sorted = [...data].sort((a, b) => raceSortKey(a) - raceSortKey(b));
  const maxKey = Math.max(...sorted.map(raceSortKey));
  const prevRaces = sorted.filter(r => raceSortKey(r) < maxKey);
  if (prevRaces.length === 0) return {};
  const cur = rankRaces(sorted);
  const prev = rankRaces(prevRaces);
  const prevPos = {};
  prev.forEach((p, i) => { prevPos[(p.handle || p.name).toUpperCase()] = i; });
  const moves = {};
  cur.forEach((p, i) => {
    const key = (p.handle || p.name).toUpperCase();
    const pp = prevPos[key];
    if (pp === undefined) moves[key] = { move: "new", amt: 0 };
    else if (pp > i)      moves[key] = { move: "up",   amt: pp - i };
    else if (pp < i)      moves[key] = { move: "down", amt: i - pp };
    else                  moves[key] = { move: "same", amt: 0 };
  });
  return moves;
}

// Avatar del piloto (foto recortada o iniciales como respaldo)
function PilotAvatar({ name, size }) {
  const meta = pilotMeta(name);
  const s = size || 54;
  if (meta.photo) {
    return <img className="pilot-avatar" src={meta.photo} alt={name} loading="lazy" style={{ width: s, height: s }} />;
  }
  const initials = String(name || "?").split(/\s+/).map(w => w[0]).slice(0, 2).join("").toUpperCase();
  return <div className="pilot-avatar pa-fallback" style={{ width: s, height: s, fontSize: s * 0.36 }}>{initials}</div>;
}

// Indicador de movimiento ▲/▼/– respecto a la fecha anterior
function MoveIndicator({ info }) {
  if (!info) return null;
  if (info.move === "up")   return <span className="rk-move up" title={`Subió ${info.amt}`}>▲{info.amt > 1 ? <i>{info.amt}</i> : null}</span>;
  if (info.move === "down") return <span className="rk-move down" title={`Bajó ${info.amt}`}>▼{info.amt > 1 ? <i>{info.amt}</i> : null}</span>;
  if (info.move === "new")  return <span className="rk-move new" title="Nuevo en el ranking">NEW</span>;
  return <span className="rk-move same" title="Sin cambios">–</span>;
}

// ----- RANKING TEASER (el ranking completo vive en ranking.html) -----
function RankingTeaser() {
  const years = availableYears();
  const currentYear = new Date().getFullYear();
  const defaultYear = years.includes(currentYear) ? currentYear : years[0];
  const [year] = useState(defaultYear);
  const [category, setCategory] = useState("5\"");
  const ranking = computeRanking(year, category);
  const moves = rankingMoves(year, category);
  const catLabel = category === "TINY" ? "Tinywhoop" : "Open 5\"";
  const keyOf = p => (p.handle || p.name).toUpperCase();
  const podium = ranking.slice(0, 3);
  const order = [podium[1], podium[0], podium[2]].filter(Boolean);
  const rankingUrl = `ranking.html?cat=${category === "TINY" ? "TINY" : "5\""}`;

  return (
    <section id="pilotos" data-screen-label="03 Ranking">
      <div className="eyebrow">RANKING ANUAL DE PILOTOS <span className="num">/ 03</span></div>
      <div className="rkt-head reveal">
        <h2>Los más<br/>rápidos del <span className={category === "TINY" ? "" : "accent"} style={category === "TINY" ? { color: "#00c2ff" } : null}>502</span>.</h2>
        <div className="rkt-right">
          <p className="lead" style={{ maxWidth: 420 }}>
            La <span className="accent">clasificación general</span> de la temporada, fecha a fecha. Mira el podio, los movimientos y la tabla completa.
          </p>
          <div className="ranking-cat" style={{ marginTop: 18 }}>
            <button className={`rcat-btn ${category === "5\"" ? "active" : ""}`} onClick={() => setCategory("5\"")}>Open 5"<span className="rcat-sub">Full size</span></button>
            <button className={`rcat-btn tiny ${category === "TINY" ? "active" : ""}`} onClick={() => setCategory("TINY")}>Tinywhoop<span className="rcat-sub">Tiny size</span></button>
          </div>
        </div>
      </div>

      {ranking.length === 0 ? (
        <div className="ranking-empty reveal"><div className="re-icon">—</div><div className="re-text">Sin resultados de {catLabel} para {year}.</div></div>
      ) : (
        <div className="rkt-podium reveal">
          {order.map(p => {
            const place = ranking.indexOf(p) + 1;
            const meta = pilotMeta(p.name);
            const flag = flagUrl(meta.country);
            const mv = moves[keyOf(p)];
            return (
              <a className={`pod-card place-${place} ${category === "TINY" ? "tiny" : ""}`} href={pilotHref(p.name, year, category)} key={keyOf(p)}>
                <div className="pod-rank">{place}<span className="pod-medal">{place === 1 ? "ORO" : place === 2 ? "PLATA" : "BRONCE"}</span></div>
                <div className="pod-ava-wrap">
                  <PilotAvatar name={p.name} size={place === 1 ? 118 : 92} cls="pod-ava" />
                  {mv && <div className="pod-move"><MoveIndicator info={mv} big /></div>}
                </div>
                <div className="pod-name">{flag && <img className="rk-flag" src={flag} alt={meta.country} loading="lazy" />}{p.name}</div>
                <div className="pod-pts">{p.points}<span>pts</span></div>
                <div className="pod-stats">
                  <span><b>{p.podiums}</b> {p.podiums === 1 ? "podio" : "podios"}</span>
                  <span><b>{p.racesCount}</b> {p.racesCount === 1 ? "fecha" : "fechas"}</span>
                </div>
              </a>
            );
          })}
        </div>
      )}

      <div className="rkt-cta reveal">
        <a className="btn btn-primary" href={rankingUrl}>Ver ranking completo <span className="arrow">→</span></a>
        <span className="rkt-cta-note">{catLabel} · {ranking.length} {ranking.length === 1 ? "piloto" : "pilotos"} · Temporada {year}</span>
      </div>
    </section>
  );
}

// ----- GALLERY -----
function Gallery() {
  return (
    <section id="galeria" data-screen-label="05 Galería">
      <div className="eyebrow">GALERÍA · POV / HIGHLIGHTS <span className="num">/ 05</span></div>
      <div className="gallery-soon reveal">
        <div className="gs-pip"><span className="dot"></span>Próximamente</div>
        <h2>Galería<br/>en camino.</h2>
        <p className="lead">
          Estamos preparando los mejores <span className="accent">POV, highlights y crashes</span> de la temporada.
          Vuelve pronto — esta sección se llena con material de cada fecha.
        </p>
        <a className="btn btn-ghost" href="https://www.instagram.com/cdgfpv" target="_blank" rel="noopener noreferrer">
          Míralo en Instagram <span className="arrow">→</span>
        </a>
      </div>
    </section>
  );
}

// ----- STREAM -----
function Stream() {
  const cfg = window.CDG_CONFIG || {};
  const url = (cfg.streamUrl || "").trim();
  const label = cfg.streamLabel || "TRANSMISIÓN OFICIAL · 4K";
  const subtitle = cfg.streamSubtitle || "";

  // Extract YouTube video ID or channel handle for embed
  const embedUrl = getYouTubeEmbed(url);

  return (
    <section id="stream" data-screen-label="06 Stream">
      <div className="eyebrow">TRANSMISIÓN EN VIVO <span className="num">/ 06</span></div>
      <h2 className="reveal" style={{ maxWidth: 900 }}>Stream<br/>oficial.</h2>
      <div className="stream-single reveal">
        {embedUrl ? (
          <div className="stream-frame">
            <iframe
              src={embedUrl}
              title="CDG Stream en vivo"
              frameBorder="0"
              allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
              allowFullScreen
            ></iframe>
            <div className="stream-corner">
              <div className="stream-live">EN VIVO</div>
              <div className="stream-corner-label">{label}{subtitle && <span className="scs"> · {subtitle}</span>}</div>
            </div>
          </div>
        ) : (
          <div className="stream-frame stream-empty">
            <div className="stream-frame-bg"></div>
            <div className="stream-center">
              <div className="label">PRÓXIMA TRANSMISIÓN</div>
              <div className="title">EN<br/><span className="accent">VIVO</span></div>
              <div className="stream-empty-msg">El link del stream se configura en <code>data/config.js</code></div>
            </div>
          </div>
        )}
        {url && (
          <a className="btn btn-ghost stream-link" href={url} target="_blank" rel="noopener noreferrer">
            Abrir en YouTube <span className="arrow">↗</span>
          </a>
        )}
      </div>
    </section>
  );
}

function getYouTubeEmbed(url) {
  if (!url) return null;
  // Channel live: youtube.com/@handle/live → use channel's live embed via channel id requires API, use /live URL as embed
  // Best approach: try video id patterns
  const patterns = [
    /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/live\/|youtube\.com\/v\/)([A-Za-z0-9_-]{11})/
  ];
  for (const p of patterns) {
    const m = url.match(p);
    if (m) return `https://www.youtube.com/embed/${m[1]}?autoplay=0&rel=0`;
  }
  // Channel handle live (e.g. youtube.com/@CanalCDG/live or youtube.com/channel/UCXXX/live)
  const handleMatch = url.match(/youtube\.com\/(@[A-Za-z0-9_.-]+)\/live/);
  if (handleMatch) {
    // YouTube doesn't allow embedding the @handle/live page directly via iframe;
    // safest fallback: embed using the channel handle stream redirect via livestream URL
    return `https://www.youtube.com/embed/live_stream?channel=${handleMatch[1].replace("@","")}`;
  }
  const channelMatch = url.match(/youtube\.com\/channel\/([A-Za-z0-9_-]+)/);
  if (channelMatch) {
    return `https://www.youtube.com/embed/live_stream?channel=${channelMatch[1]}`;
  }
  return null;
}

// ----- SPONSORS -----
function Sponsors() {
  return (
    <section id="patrocinadores" data-screen-label="07 Patrocinadores">
      <div className="eyebrow">PATROCINADORES OFICIALES <span className="num">/ 07</span></div>
      <h2 className="reveal" style={{ maxWidth: 900 }}>Los que hacen<br/>posible el ruido.</h2>
      <div className="sponsors reveal" style={{ marginTop: 60 }}>
        {SPONSORS.map(s => (
          <div className="sponsor" key={s.n}>{s.n}<span>{s.s}</span></div>
        ))}
      </div>
    </section>
  );
}

// ----- REGISTER -----
function Register() {
  const [cat, setCat] = useState("5\" Open");
  const [submitted, setSubmitted] = useState(false);
  return (
    <section id="registro" data-screen-label="08 Registro">
      <div className="eyebrow">REGISTRO · TEMPORADA 26 <span className="num">/ 08</span></div>
      <div className="register-wrap reveal">
        <div className="register-grid">
          <div>
            <h2>Inscríbete<br/>al 502.</h2>
            <p className="lead" style={{ marginTop: 24 }}>
              Cupos limitados por categoría. La inscripción es <span className="accent">por fecha</span> e incluye placa de competidor, telemetría, pase de pits para 2 personas y kit oficial CDG·26.
            </p>
            <div className="register-info">
              <dl>
                <dt>Tinywhoop</dt><dd>Q. 100 / carrera</dd>
                <dt>Open 5"</dt><dd>Q. 150 / carrera</dd>
                <dt>Menores</dt><dd>Acompañados por un tutor</dd>
                <dt>Sede</dt><dd>Hangar CDG · Z.10 Capital</dd>
                <dt>Contacto</dt><dd><a href="mailto:cdgfpv@gmail.com" style={{ color: "inherit", textDecoration: "none" }}>cdgfpv@gmail.com</a></dd>
              </dl>
            </div>
          </div>
          <form className="register-form" onSubmit={e => {
            e.preventDefault();
            const f = e.target;
            const g = n => (f.elements[n]?.value || "").trim();
            const subject = `Inscripción CDG·26 — ${cat} — ${g("nombre")} ${g("apellido")}`;
            const body =
              `Quiero inscribirme al Campeonato Drones Guatemala 2026.\n\n` +
              `Categoría: ${cat}\n` +
              `Nombre: ${g("nombre")} ${g("apellido")}\n` +
              `Handle FPV: ${g("handle")}\n` +
              `Email: ${g("email")}\n` +
              `Teléfono: ${g("telefono")}\n` +
              `Departamento: ${g("depto")}\n` +
              `Cuota: ${cat === "Tinywhoop" ? "Q100" : "Q150"} / carrera\n`;
            window.location.href = `mailto:cdgfpv@gmail.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
            setSubmitted(true);
          }}>
            <div className="field">
              <label>Categoría</label>
              <div className="toggle-row">
                <div className={`toggle-pill ${cat==="5\" Open"?"active":""}`} onClick={()=>setCat("5\" Open")}>5" Open</div>
                <div className={`toggle-pill ${cat==="Tinywhoop"?"active":""}`} onClick={()=>setCat("Tinywhoop")}>Tinywhoop</div>
              </div>
            </div>
            <div className="field-row">
              <div className="field"><label>Nombre</label><input name="nombre" type="text" placeholder="Tu nombre" required /></div>
              <div className="field"><label>Apellido</label><input name="apellido" type="text" placeholder="Tu apellido" required /></div>
            </div>
            <div className="field"><label>Handle FPV</label><input name="handle" type="text" placeholder="@tu_usuario" required /></div>
            <div className="field-row">
              <div className="field"><label>Email</label><input name="email" type="email" placeholder="piloto@cdg.gt" required /></div>
              <div className="field"><label>Teléfono</label><input name="telefono" type="tel" placeholder="+502 0000 0000" required /></div>
            </div>
            <div className="field">
              <label>Departamento</label>
              <select name="depto" defaultValue="">
                <option value="" disabled>Seleccionar…</option>
                <option>Guatemala</option><option>Sololá</option><option>Quetzaltenango</option>
                <option>Alta Verapaz</option><option>Petén</option><option>Escuintla</option>
                <option>Sacatepéquez</option><option>Otro</option>
              </select>
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginTop: 8, fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--fg-dim)" }}>
              <span>Cuota {cat}</span>
              <b style={{ color: "var(--accent)", fontFamily: "var(--display)", fontSize: 18 }}>{cat === "Tinywhoop" ? "Q100" : "Q150"} <span style={{ color: "var(--fg-mute)", fontSize: 10 }}>/ carrera</span></b>
            </div>
            <button type="submit" className="btn btn-primary" style={{ marginTop: 8 }}>
              {submitted ? "Abrimos tu correo ✓" : "Enviar inscripción"} <span className="arrow">→</span>
            </button>
          </form>
        </div>
      </div>
    </section>
  );
}

// ----- NAV + FOOTER -----
function Nav() {
  const [open, setOpen] = useState(false);
  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);
  const links = [
    { href: "#campeonato",     label: "Campeonato", num: "01" },
    { href: "#calendario",     label: "Calendario", num: "02" },
    { href: "resultados.html", label: "Resultados", num: "03" },
    { href: "ranking.html",    label: "Ranking",    num: "04" },
    { href: "global.html",     label: "Global (GQ)", num: "05" },
    { href: "#venues",         label: "Venues",     num: "06" },
    { href: "noticias.html",   label: "Noticias",   num: "07" }
  ];
  return (
    <nav className={`nav ${open ? "open" : ""}`}>
      <a href="#hero" className="nav-logo-real" onClick={() => setOpen(false)}>
        <img src="assets/logo-cdg.png" alt="Campeonato Drones Guatemala" />
      </a>
      <div className="nav-links">
        {links.map(l => <a key={l.href} href={l.href} data-num={l.num} onClick={() => setOpen(false)}>{l.label}</a>)}
      </div>
      <a href="#registro" className="nav-cta" onClick={() => setOpen(false)}>Inscribirme →</a>
      <button
        className="nav-burger"
        aria-label={open ? "Cerrar menú" : "Abrir menú"}
        aria-expanded={open}
        onClick={() => setOpen(v => !v)}
      >
        <span></span><span></span><span></span>
      </button>
      <div className="nav-backdrop" onClick={() => setOpen(false)}></div>
    </nav>
  );
}

function Footer() {
  return (
    <footer>
      <div className="footer-top">
        <div>
          <a href="#hero" className="nav-logo-real" style={{ height: 48, marginBottom: 16 }}>
            <img src="assets/logo-cdg.png" alt="CDG" />
          </a>
          <p style={{ color: "var(--fg-dim)", fontSize: 13, maxWidth: 360, lineHeight: 1.6 }}>
            Liga oficial de carreras FPV en Guatemala. Tercera temporada, dos categorías: Open 5" y Tinywhoop.
            Sancionado por MultiGP · Regional Series Central America.
          </p>
          <img src="assets/logo-regional.png" alt="MultiGP Regional Series Central America" style={{ height: 64, marginTop: 16, opacity: 0.85 }} />
        </div>
        <div>
          <h4>Liga</h4>
          <ul>
            <li><a href="#campeonato">Reglamento</a></li>
            <li><a href="#calendario">Calendario</a></li>
            <li><a href="ranking.html">Pilotos</a></li>
            <li><a href="#venues">Venues</a></li>
          </ul>
        </div>
        <div>
          <h4>Comunidad</h4>
          <ul>
            <li><a href="https://www.instagram.com/cdgfpv" target="_blank" rel="noopener noreferrer">Instagram</a></li>
            <li><a href="https://www.tiktok.com/@cdgfpv" target="_blank" rel="noopener noreferrer">TikTok</a></li>
            <li><a href="https://www.facebook.com/p/Campeonato-Drones-Guatemala-100090083211265/" target="_blank" rel="noopener noreferrer">Facebook</a></li>
            <li><a href="https://www.youtube.com/@CDGFPV" target="_blank" rel="noopener noreferrer">YouTube</a></li>
          </ul>
        </div>
        <div>
          <h4>Contacto</h4>
          <ul>
            <li><a href="mailto:cdgfpv@gmail.com">cdgfpv@gmail.com</a></li>
          </ul>
        </div>
      </div>
      <div className="footer-bottom">
        <div>© 2026 CDG · Liga independiente</div>
        <div>HECHO EN EL 502 · 14.6349° N, 90.5069° W</div>
      </div>
      <div className="footer-big">CDG·26 / FPV·GT</div>
    </footer>
  );
}

// ----- MAIN APP -----
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useReveal();
  useParallax();

  // Apply tweaks to CSS variables
  useEffect(() => {
    const pal = PALETTES[t.palette] || PALETTES.fire;
    const root = document.documentElement;
    // Palette overrides accent unless user explicitly changed accent
    root.style.setProperty("--accent", t.accent || pal.accent);
    root.style.setProperty("--accent-2", t.accent2 || pal.accent2);
    root.style.setProperty("--accent-glow", pal.glow);
    root.style.setProperty("--speed", String(t.speed));
    root.style.setProperty("--display", FONT_PAIRS[t.displayFont] || FONT_PAIRS["Space Grotesk"]);
  }, [t]);

  return (
    <>
      <Nav />
      <Hero t={t} />
      <About />
      <Calendar />
      <RankingTeaser />
      <Venues />
      <Gallery />
      <Stream />
      <Sponsors />
      <Register />
      <Footer />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Paleta" />
        <TweakRadio label="Preset"
          value={t.palette}
          options={["fire", "magma", "acid", "voltage"]}
          onChange={(v) => {
            const pal = PALETTES[v];
            setTweak({ palette: v, accent: pal.accent, accent2: pal.accent2 });
          }}
        />
        <TweakColor label="Acento"  value={t.accent}  onChange={v => setTweak("accent", v)} />
        <TweakColor label="Acento 2" value={t.accent2} onChange={v => setTweak("accent2", v)} />

        <TweakSection label="Movimiento" />
        <TweakSlider label="Velocidad anim." value={t.speed} min={0.3} max={2.5} step={0.1} unit="x"
          onChange={v => setTweak("speed", v)} />

        <TweakSection label="Tipografía" />
        <TweakSelect label="Display"
          value={t.displayFont}
          options={Object.keys(FONT_PAIRS)}
          onChange={v => setTweak("displayFont", v)}
        />
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
