// En la nada — lector
// Componentes principales: Boot, Index, Reader, FichaDrawer, Constellation, Chrome, Tweaks.

const { useState, useEffect, useRef, useCallback, useMemo } = React;

const PHOSPHOR_OPTIONS = {
  verde: "#7CFFB2",
  ambar: "#FFC857",
  blanco: "#E8F0EC",
};

const FONT_OPTIONS = {
  "JetBrains Mono": '"JetBrains Mono", ui-monospace, Menlo, monospace',
  "IBM Plex Mono": '"IBM Plex Mono", ui-monospace, Menlo, monospace',
  "Space Mono": '"Space Mono", ui-monospace, Menlo, monospace',
  "Fira Code": '"Fira Code", ui-monospace, Menlo, monospace',
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "phosphor": "verde",
  "font": "JetBrains Mono",
  "verseSpeedMs": 1100,
  "glow": 10,
  "blur": 14,
  "flicker": true,
  "starDensity": 1.0,
  "showOrbit": true,
  "drone": "cosmos",
  "volume": 0.5,
  "audioOn": false,
  "paginated": false
}/*EDITMODE-END*/;

// ── persistencia ─────────────────────────────────────────
const LS_KEY = "enLaNada.v1";
function lsGet() {
  try { return JSON.parse(localStorage.getItem(LS_KEY) || "{}"); } catch { return {}; }
}
function lsSet(patch) {
  const cur = lsGet();
  const next = { ...cur, ...patch };
  try { localStorage.setItem(LS_KEY, JSON.stringify(next)); } catch { }
  return next;
}

// ── routing por pathname (URLs únicas) ──────────────────
function usePathRoute() {
  const [path, setPath] = useState(window.location.pathname || "/");
  useEffect(() => {
    const onChange = () => setPath(window.location.pathname || "/");
    window.addEventListener("popstate", onChange);
    return () => window.removeEventListener("popstate", onChange);
  }, []);
  const navigate = (p) => {
    if (window.location.pathname === p) return;
    window.history.pushState({}, "", p);
    setPath(p);
  };
  return [path, navigate];
}

// ── chrome ───────────────────────────────────────────────
function Chrome({ onIndex, onMap, onFicha, audioOn, onAudio, currentLabel }) {
  return (
    <>
      <div className="chrome top">
        <div className="chrome-brand" onClick={onIndex} title="ir al índice">
          <span className="dot" />
          <span className="chrome-brand-text glow-text">RESTMASS.ORG</span>
        </div>
        <div className="chrome-nav">
          <span className="btn-link" onClick={onIndex}>
            <span className="lbl-long">[ índice ]</span>
            <span className="lbl-short">[ i ]</span>
          </span>
          <span className="btn-link" onClick={onMap}>
            <span className="lbl-long">[ mapa ]</span>
            <span className="lbl-short">[ m ]</span>
          </span>
          {onFicha && (
            <span className="btn-link chrome-ficha" onClick={onFicha}>[ ficha ]</span>
          )}
        </div>
      </div>
      <div className="chrome bot">
        <div className="chrome-bot-label"><span className="glow-text">{currentLabel}</span></div>
        <div className={`audio-btn ${audioOn ? "on" : ""}`} onClick={onAudio}>
          <span className="bars">
            <span className="bar" /><span className="bar" /><span className="bar" /><span className="bar" />
          </span>
          <span>{audioOn ? "drone · on" : "drone · off"}</span>
        </div>
      </div>
    </>
  );
}

// ── boot screen ──────────────────────────────────────────
function Boot({ onDone }) {
  const [step, setStep] = useState(0);
  const lines = [
    { t: "> initializing receiver...", dim: true },
    { t: "> calibrating background radiation: 2.725 K", dim: true },
    { t: "> opening signal: enlanada/cosmogonia", dim: true },
    { t: "" },
    { t: "EN LA NADA", dim: false, big: true },
    { t: "Archivo de poemas — borradores en órbita.", dim: true },
    { t: "" },
    { t: "[ pulsa cualquier tecla para entrar ]", dim: true, blink: true },
  ];
  useEffect(() => {
    if (step >= lines.length) return;
    const t = setTimeout(() => setStep(step + 1), step === 4 ? 600 : 280);
    return () => clearTimeout(t);
  }, [step]);
  useEffect(() => {
    const handler = () => onDone();
    window.addEventListener("keydown", handler, { once: true });
    window.addEventListener("click", handler, { once: true });
    return () => {
      window.removeEventListener("keydown", handler);
      window.removeEventListener("click", handler);
    };
  }, [onDone]);
  return (
    <div className="boot">
      {lines.slice(0, step).map((ln, i) => (
        <div
          key={i}
          className={`boot-line ${ln.dim ? "dim" : ""} ${ln.blink ? "cursor" : ""}`}
          style={{
            fontSize: ln.big ? 56 : undefined,
            letterSpacing: ln.big ? "0.18em" : undefined,
            fontFamily: ln.big ? "var(--serif)" : undefined,
            fontStyle: ln.big ? "italic" : undefined,
          }}
        >
          <span className={ln.big ? "glow-text" : ""}>{ln.t || "\u00a0"}</span>
        </div>
      ))}
    </div>
  );
}

// ── índice ───────────────────────────────────────────────
function Index({ onOpen, leidos }) {
  const sections = Object.keys(window.SECCIONES);
  return (
    <div className="idx">
      <div className="idx-header">
        <div className="idx-eyebrow">
          <span className="idx-eyebrow-prompt">&gt;</span> archivo
          <span className="idx-eyebrow-sep"> · </span>
          {sections.map(k => window.SECCIONES[k].nombre.toLowerCase()).join(" · ")}
        </div>
        <h1 className="idx-title glow-text">En la nada</h1>
        <div className="idx-blurb">
          <p className="idx-blurb-lead">
            Borradores de un libro de poemas<br />
            sobre lo que precede al ruido.
          </p>
          <p className="idx-blurb-body">
            La luz primera, los habitantes del vacío,
            los principios que aún no tienen nombre.
          </p>
          <p className="idx-blurb-cta">
            <span className="idx-cta-mark">·</span>
            pulsa un poema para abrirlo
            <span className="idx-cta-mark">·</span>
          </p>
        </div>
      </div>

      {sections.map((key, idx) => {
        const sec = window.SECCIONES[key];
        const poems = window.POEMS.filter(p => p.seccion === key)
          .sort((a, b) => a.orden - b.orden);
        return (
          <div className="idx-section" key={key}>
            <div className="idx-sec-meta">
              <div className="idx-sec-num">{`§ ${String(idx + 1).padStart(2, "0")}`}</div>
              <div className="idx-sec-name glow-text">{sec.nombre}</div>
              <div className="idx-sec-desc">{sec.descripcion}</div>
            </div>
            <div className="idx-poem-list">
              {poems.map((p) => {
                const disabled = !p.texto;
                const read = !!leidos[p.slug];
                return (
                  <div
                    key={p.slug}
                    className={`idx-poem ${disabled ? "disabled" : ""} ${read ? "read" : ""}`}
                    onClick={() => !disabled && onOpen(p.slug)}
                  >
                    <span className="idx-poem-num">{String(p.orden).padStart(2, "0")}</span>
                    <span className="idx-poem-title glow-text">{p.titulo}</span>
                    <span className="idx-poem-status">{p.estado}</span>
                    <span className="idx-poem-arrow">→</span>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
      <div className="idx-footer">
        <div className="idx-footer-line">
          <span className="idx-footer-mark">·</span> cierre de transmisión · v0.2 <span className="idx-footer-mark">·</span>
        </div>
        <div className="idx-footer-author">
          <span className="idx-footer-author-name">Oscar González</span>
          <span className="idx-footer-mark"> · </span>
          <a className="idx-footer-author-mail" href="mailto:oscargonzalezmoreno@gmail.com">oscargonzalezmoreno@gmail.com</a>
        </div>
      </div>
    </div>
  );
}

// ── adaptación de cadencia ───────────────────────────────
function pauseFor(verse, baseMs) {
  const trimmed = verse.replace(/[\t]/g, "").trim();
  if (trimmed === "") return baseMs * 0.55; // línea vacía: respiro corto
  // longitud
  const len = trimmed.length;
  let mult = 1.0;
  if (len < 10) mult = 1.0;
  else if (len < 28) mult = 1.05;
  else if (len < 56) mult = 1.2;
  else mult = 1.35;
  // puntuación final
  if (/\.\.\.$/.test(trimmed)) mult += 0.45;
  else if (/[.!?]$/.test(trimmed)) mult += 0.35;
  else if (/[,;:]$/.test(trimmed)) mult += 0.12;
  // sangría: cuanto más profunda, más pausa antes (eco/aire)
  const tabs = (verse.match(/^\t+/) || [""])[0].length;
  if (tabs >= 4) mult += 0.30;
  else if (tabs >= 2) mult += 0.18;
  else if (tabs >= 1) mult += 0.08;
  return Math.max(280, baseMs * mult);
}

// ── agrupado en estrofas (separadas por línea vacía) ─────
function toStanzas(verses) {
  const groups = [];
  let cur = [];
  verses.forEach((v, i) => {
    if (v.trim() === "") {
      if (cur.length) { groups.push({ lines: cur, sep: true }); cur = []; }
    } else {
      cur.push({ text: v, idx: i });
    }
  });
  if (cur.length) groups.push({ lines: cur, sep: false });
  return groups;
}

// ── reader ───────────────────────────────────────────────
function Reader({ poem, onIndex, speedMs, blurPx, paginated, drone, onFinish, onExport }) {
  const verses = useMemo(() => poem.texto.split("\n"), [poem]);
  const stanzas = useMemo(() => toStanzas(verses), [verses]);
  const [shown, setShown] = useState(0);
  const [stanzaIdx, setStanzaIdx] = useState(0);
  const readerRef = useRef(null);

  // reset al cambiar de poema
  useEffect(() => {
    setShown(0);
    setStanzaIdx(0);
    if (readerRef.current) readerRef.current.scrollTop = 0;
  }, [poem.slug, paginated]);

  // versos visibles según modo
  const visibleSet = useMemo(() => {
    if (!paginated) return null; // null = usar 'shown'
    const set = new Set();
    let maxIdx = -1;
    for (let i = 0; i <= stanzaIdx && i < stanzas.length; i++) {
      stanzas[i].lines.forEach(l => {
        set.add(l.idx);
        if (l.idx > maxIdx) maxIdx = l.idx;
      });
    }
    // incluir líneas en blanco entre estrofas ya visibles (separador de estrofa)
    verses.forEach((v, i) => {
      if (v.trim() === "" && i <= maxIdx) set.add(i);
    });
    return set;
  }, [paginated, stanzaIdx, stanzas, verses]);

  // avance auto en modo continuo
  useEffect(() => {
    if (paginated || !poem.texto) return;
    if (shown >= verses.length) {
      onFinish && onFinish();
      return;
    }
    const v = verses[shown];
    const dur = pauseFor(v, speedMs);
    const t = setTimeout(() => {
      setShown(s => s + 1);
      // drone reactivo: pequeño acento al avanzar verso (no en líneas vacías)
      if (v.trim() !== "" && drone && drone.isRunning()) {
        const tabs = (v.match(/^\t+/) || [""])[0].length;
        const intensity = 0.5 + Math.min(0.6, tabs * 0.12) + (/\.$/.test(v.trim()) ? 0.15 : 0);
        drone.accent(intensity);
      }
    }, dur);
    return () => clearTimeout(t);
  }, [shown, verses, speedMs, paginated, poem.texto, drone, onFinish]);

  // En modo continuo, mantener el último verso revelado dentro del campo visual.
  useEffect(() => {
    if (paginated || !readerRef.current || shown < 1) return;
    const node = readerRef.current;
    const target = Math.max(0, node.scrollHeight - node.clientHeight + 140);
    requestAnimationFrame(() => {
      node.scrollTo({ top: target, behavior: "smooth" });
    });
  }, [shown, paginated]);

  // paginado: avance manual
  const advance = useCallback(() => {
    if (!paginated) return;
    setStanzaIdx(i => {
      const next = Math.min(i + 1, stanzas.length - 1);
      if (drone && drone.isRunning()) drone.accent(0.7);
      if (next === stanzas.length - 1) onFinish && onFinish();
      return next;
    });
  }, [paginated, stanzas.length, drone, onFinish]);
  const back = useCallback(() => {
    setStanzaIdx(i => Math.max(0, i - 1));
  }, []);

  useEffect(() => {
    if (!paginated) return;
    const onKey = (e) => {
      if (e.key === " " || e.key === "ArrowRight" || e.key === "Enter") { e.preventDefault(); advance(); }
      else if (e.key === "ArrowLeft") { e.preventDefault(); back(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [paginated, advance, back]);

  return (
    <div className="reader" onClick={paginated ? advance : undefined} style={{ cursor: paginated ? "pointer" : "default" }}>
      <div className="reader-head">
        <span className="btn-link reader-back" onClick={onIndex}>← volver</span>
        <h1 className="reader-title glow-text">{poem.titulo}</h1>
      </div>

      <div ref={readerRef} className="reader-scroll">
        {poem.texto ? (
          <div className={`poem glow-text ${paginated ? "paginated" : ""}`}>
            {verses.map((v, i) => {
              const visible = paginated ? visibleSet.has(i) : i < shown;
              if (!paginated && !visible && i > shown + 2) return null;
              return (
                <span
                  key={i}
                  className={`verse ${visible ? "in" : ""} ${v.trim() === "" ? "empty" : ""}`}
                  style={{
                    filter: visible ? "blur(0)" : `blur(${blurPx}px)`,
                    transitionDuration: `${Math.max(800, speedMs * 1.4)}ms`,
                  }}
                >
                  {v || "\u00a0"}
                </span>
              );
            })}
          </div>
        ) : (
          <div className="empty-poem">— pendiente de transcripción —</div>
        )}

        {paginated && poem.texto && (
          <div className="pager" onClick={(e) => e.stopPropagation()}>
            <span className="btn-link pager-btn" onClick={back}>← anterior</span>
            <div className="pager-progress">
              <div className="pager-dots">
                {stanzas.map((_, i) => (
                  <span
                    key={i}
                    className={`pager-dot${i <= stanzaIdx ? " on" : ""}${i === stanzaIdx ? " current" : ""}`}
                  />
                ))}
              </div>
              <div className="pager-count">
                estrofa {String(stanzaIdx + 1).padStart(2, "0")} / {String(stanzas.length).padStart(2, "0")}
              </div>
            </div>
            <span className="btn-link pager-btn" onClick={advance}>siguiente →</span>
          </div>
        )}

      </div>
    </div>
  );
}

// ── ficha drawer ─────────────────────────────────────────
function FichaDrawer({ poem, show, onClose, onExport }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    if (show) window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [show, onClose]);
  if (!poem) return null;
  return (
    <div className={`ficha-drawer ${show ? "show" : ""}`}>
      <div className="close-x" onClick={onClose}>[ cerrar · esc ]</div>
      <div className="ficha-head">
        <div className="ficha-eyebrow">
          <span className="ficha-eyebrow-prompt">&gt;</span> ficha técnica
        </div>
        <h2 className="ficha-title">{poem.titulo}</h2>
        <div className="ficha-body">
          {window.SECCIONES[poem.seccion].nombre.toLowerCase()} · {poem.estado}
          {poem.tema && ` · ${poem.tema}`}
        </div>
        <div className="ficha-rule" />
      </div>
      <div className="ficha-row"><div className="ficha-key">slug</div><div className="ficha-val">{poem.slug}</div></div>
      <div className="ficha-row"><div className="ficha-key">sección</div><div className="ficha-val">{window.SECCIONES[poem.seccion].nombre}</div></div>
      <div className="ficha-row"><div className="ficha-key">orden</div><div className="ficha-val">{poem.orden}</div></div>
      <div className="ficha-row"><div className="ficha-key">estado</div><div className="ficha-val">{poem.estado}</div></div>
      <div className="ficha-row">
        <div className="ficha-key">tags</div>
        <div className="ficha-val">{(poem.tags || []).map(t => <span key={t} className="ficha-tag">{t}</span>)}</div>
      </div>
      {poem.dudas && poem.dudas.length > 0 && (
        <div className="ficha-row">
          <div className="ficha-key">dudas</div>
          <div className="ficha-val" style={{ fontSize: 12 }}>
            {poem.dudas.map((d, i) => <div key={i} style={{ marginBottom: 4 }}>· {d}</div>)}
          </div>
        </div>
      )}
      <div className="ficha-actions">
        <button onClick={() => onExport(poem, "md")}>↓ exportar .md (con frontmatter)</button>
        <button onClick={() => onExport(poem, "txt")}>↓ exportar .txt</button>
      </div>
    </div>
  );
}

// ── cheatsheet ───────────────────────────────────────────
function Cheats({ show, onClose }) {
  useEffect(() => {
    if (!show) return;
    const onKey = (e) => { if (e.key === "Escape" || e.key === "?" || e.key === "/") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [show, onClose]);
  return (
    <div className={`cheats ${show ? "show" : ""}`} onClick={onClose}>
      <h4>atajos</h4>
      <div><kbd>i</kbd> ficha del poema</div>
      <div><kbd>m</kbd> mapa estelar</div>
      <div><kbd>esc</kbd> volver al índice</div>
      <div><kbd>↑</kbd><kbd>↓</kbd> scroll</div>
      <div><kbd>←</kbd><kbd>→</kbd> estrofa anterior / siguiente (modo paginado)</div>
      <div><kbd>?</kbd> mostrar / ocultar esta ayuda</div>
    </div>
  );
}

// ── exportar poema ───────────────────────────────────────
function exportPoem(poem, format) {
  let content;
  let filename;
  if (format === "md") {
    const fm = [
      "---",
      `titulo: ${poem.titulo}`,
      `slug: ${poem.slug}`,
      `seccion: ${poem.seccion}`,
      `orden: ${poem.orden}`,
      `estado: ${poem.estado}`,
      poem.dudas && poem.dudas.length ? `dudas:\n${poem.dudas.map(d => `  - "${d.replace(/"/g, '\\"')}"`).join("\n")}` : null,
      `tags: [${(poem.tags || []).join(", ")}]`,
      "---",
      "",
      "```poem",
      poem.texto || "",
      "```",
      "",
    ].filter(Boolean).join("\n");
    content = fm;
    filename = `${poem.slug}.md`;
  } else {
    content = `${poem.titulo}\n${"=".repeat(poem.titulo.length)}\n\n${poem.texto || ""}\n`;
    filename = `${poem.slug}.txt`;
  }
  const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

// ── App ──────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [path, navigate] = usePathRoute();
  const persisted = useRef(lsGet());
  // Skip boot si visita directa a un poema/mapa o si ya visitó antes
  const _deepLink = path !== "/" && path !== "/index";
  const [booted, setBooted] = useState(!!persisted.current.visited || _deepLink);
  const [showFicha, setShowFicha] = useState(false);
  const [leidos, setLeidos] = useState(persisted.current.leidos || {});
  const [showCheats, setShowCheats] = useState(false);
  const droneRef = useRef(null);

  // restaurar tweaks persistidos en primera carga
  useEffect(() => {
    const p = persisted.current.tweaks;
    if (p && typeof p === "object") {
      Object.entries(p).forEach(([k, v]) => { if (v !== undefined) setTweak(k, v); });
    }
  }, []);
  // guardar tweaks cuando cambian (sin audioOn que es transitorio)
  useEffect(() => {
    const { audioOn, ...rest } = t;
    lsSet({ tweaks: rest });
  }, [t]);

  // aplicar variables CSS según tweaks
  useEffect(() => {
    const root = document.documentElement;
    const ph = PHOSPHOR_OPTIONS[t.phosphor] || PHOSPHOR_OPTIONS.verde;
    root.style.setProperty("--phosphor", ph);
    root.style.setProperty("--phosphor-dim", hexToRgba(ph, 0.55));
    root.style.setProperty("--phosphor-faint", hexToRgba(ph, 0.22));
    root.style.setProperty("--phosphor-ghost", hexToRgba(ph, 0.08));
    root.style.setProperty("--glow", `${t.glow}px`);
    root.style.setProperty("--mono", FONT_OPTIONS[t.font] || FONT_OPTIONS["JetBrains Mono"]);
  }, [t.phosphor, t.glow, t.font]);

  // drone audio
  useEffect(() => { if (!droneRef.current) droneRef.current = new window.Drone(); }, []);
  useEffect(() => {
    if (!droneRef.current) return;
    if (t.audioOn) droneRef.current.start(t.drone);
    else droneRef.current.stop();
  }, [t.audioOn]);
  useEffect(() => { droneRef.current && droneRef.current.setVoice(t.drone); }, [t.drone]);
  useEffect(() => { droneRef.current && droneRef.current.setVolume(t.volume); }, [t.volume]);

  // boot done
  const onBootDone = useCallback(() => {
    setBooted(true);
    lsSet({ visited: true });
    if (path === "/" || path === "") navigate("/index");
  }, [path, navigate]);

  // si ya había visitado, ir al índice si la url está vacía
  useEffect(() => {
    if (booted && (path === "/" || path === "")) navigate("/index");
  }, [booted, path, navigate]);

  // marcar leído al terminar
  const markRead = useCallback((slug) => {
    setLeidos(prev => {
      if (prev[slug]) return prev;
      const next = { ...prev, [slug]: Date.now() };
      lsSet({ leidos: next });
      return next;
    });
  }, []);

  // routing
  let view = booted ? "index" : "boot";
  let currentPoem = null;
  if (booted) {
    if (path.startsWith("/poema/")) {
      const slug = decodeURIComponent(path.slice("/poema/".length).replace(/\/$/, ""));
      currentPoem = window.POEMS.find((p) => p.slug === slug) || null;
      view = currentPoem ? "reader" : "index";
    } else if (path.startsWith("/mapa")) {
      view = "constellation";
    } else {
      view = "index";
    }
  }

  const currentLabel = view === "reader" && currentPoem
    ? `${window.SECCIONES[currentPoem.seccion].nombre.toLowerCase()} / ${currentPoem.slug}`
    : view === "constellation" ? "mapa estelar"
      : view === "index" ? "índice" : "boot";

  // teclado global
  useEffect(() => {
    const onKey = (e) => {
      if (e.target && /input|textarea|select/i.test(e.target.tagName)) return;
      if (e.key === "i" || e.key === "I") {
        if (currentPoem) setShowFicha((s) => !s);
      } else if (e.key === "m" || e.key === "M") {
        navigate("/mapa");
      } else if (e.key === "?" || e.key === "/") {
        setShowCheats((s) => !s);
      } else if (e.key === "Escape") {
        if (showFicha) setShowFicha(false);
        else if (showCheats) setShowCheats(false);
        else if (view === "reader") navigate("/index");
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [currentPoem, navigate, showFicha, showCheats, view]);

  return (
    <div className={`stage ${t.flicker ? "flicker" : ""}`}>
      <Cosmos
        density={t.starDensity}
        phosphor={PHOSPHOR_OPTIONS[t.phosphor] || PHOSPHOR_OPTIONS.verde}
        showOrbit={t.showOrbit && view !== "boot"}
        photonRate={view === "reader" ? (window.matchMedia("(max-width: 720px)").matches ? 0 : 0.22) : 0.10}
      />

      <div className="content">
        {view !== "boot" && (
          <Chrome
            onIndex={() => navigate("/index")}
            onMap={() => navigate("/mapa")}
            onFicha={view === "reader" ? () => setShowFicha(true) : null}
            audioOn={t.audioOn}
            onAudio={() => setTweak("audioOn", !t.audioOn)}
            currentLabel={currentLabel}
          />
        )}

        {view === "boot" && <Boot onDone={onBootDone} />}
        {view === "index" && <Index onOpen={(slug) => navigate(`/poema/${slug}`)} leidos={leidos} />}
        {view === "constellation" && <Constellation onOpen={(slug) => navigate(`/poema/${slug}`)} leidos={leidos} />}
        {view === "reader" && currentPoem && (
          <Reader
            poem={currentPoem}
            onIndex={() => navigate("/index")}
            speedMs={t.verseSpeedMs}
            blurPx={t.blur}
            paginated={t.paginated}
            drone={droneRef.current}
            onFinish={() => markRead(currentPoem.slug)}
            onExport={exportPoem}
          />
        )}
      </div>

      <div className="crt-scan" />
      <div className="crt-noise" />
      <div className="crt-vignette" />

      <FichaDrawer poem={currentPoem} show={showFicha} onClose={() => setShowFicha(false)} onExport={exportPoem} />
      <Cheats show={showCheats} onClose={() => setShowCheats(false)} />

      {view !== "boot" && (
        <span className="help-btn" onClick={() => setShowCheats(s => !s)}>[ ? · atajos ]</span>
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Lectura" />
        <TweakRadio label="Modo" value={t.paginated ? "paginado" : "scroll"}
          options={["scroll", "paginado"]}
          onChange={(v) => setTweak("paginated", v === "paginado")} />
        <TweakSlider label="Velocidad de versos" value={t.verseSpeedMs} min={400} max={2400} step={50} unit="ms"
          onChange={(v) => setTweak("verseSpeedMs", v)} />

        <TweakSection label="Fósforo" />
        <TweakRadio label="Color" value={t.phosphor}
          options={["verde", "ambar", "blanco"]}
          onChange={(v) => setTweak("phosphor", v)} />
        <TweakSlider label="Glow" value={t.glow} min={0} max={28} step={1} unit="px"
          onChange={(v) => setTweak("glow", v)} />
        <TweakSlider label="Blur de aparición" value={t.blur} min={0} max={32} step={1} unit="px"
          onChange={(v) => setTweak("blur", v)} />
        <TweakToggle label="Flicker CRT" value={t.flicker}
          onChange={(v) => setTweak("flicker", v)} />

        <TweakSection label="Cosmos" />
        <TweakSlider label="Densidad estrellas" value={t.starDensity} min={0} max={2.5} step={0.1}
          onChange={(v) => setTweak("starDensity", v)} />
        <TweakToggle label="Órbita decorativa" value={t.showOrbit}
          onChange={(v) => setTweak("showOrbit", v)} />

        <TweakSection label="Tipografía" />
        <TweakSelect label="Fuente mono" value={t.font}
          options={Object.keys(FONT_OPTIONS)}
          onChange={(v) => setTweak("font", v)} />

        <TweakSection label="Drone" />
        <TweakToggle label="Activo" value={t.audioOn}
          onChange={(v) => setTweak("audioOn", v)} />
        <TweakRadio label="Voz" value={t.drone}
          options={["cosmos", "phosphor", "void"]}
          onChange={(v) => setTweak("drone", v)} />
        <TweakSlider label="Volumen" value={t.volume} min={0} max={1} step={0.05}
          onChange={(v) => setTweak("volume", v)} />

        <TweakSection label="Datos" />
        <TweakButton label="Borrar progreso" onClick={() => {
          if (confirm("Borrar marca de leídos y volver al boot?")) {
            try { localStorage.removeItem(LS_KEY); } catch { }
            location.reload();
          }
        }} />
      </TweaksPanel>
    </div>
  );
}

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