// Main app — orchestrates everything
const { useState, useEffect, useRef, useCallback } = React;
// Stack diagram component (used inside chapter 10)
function StackDiagram({ lang }) {
const T = window.T;
const nodes = [
{ name: "gentle-ai", label: T(lang, "base", "base") },
{ name: "autoSDD", label: T(lang, "fork", "fork") },
{ name: "Engram fail", label: T(lang, "problema", "problem"), problem: true },
{ name: "+ embeddings", label: T(lang, "fix", "fix") },
{ name: "CLAUDE.md", label: T(lang, "índice", "index") },
{ name: "Stark Kit", label: T(lang, "versionado", "versioned") },
{ name: "e2e-forge", label: T(lang, "empresa", "company") },
{ name: "be-code-kit", label: T(lang, "merge", "merge") },
{ name: "OpenClaw", label: T(lang, "gateway", "gateway") },
{ name: "Hermes", label: T(lang, "salto", "leap") },
{ name: "Cobalt", label: T(lang, "actual", "current") }];
return (
{T(lang, "Evolución de mi stack", "Stack evolution")}
{nodes.map((n, i) =>
{i < nodes.length - 1 && }
)}
);
}
function App() {
const [lang, setLang] = useState("es");
const [activeIdx, setActiveIdx] = useState(0);
const [audioOn, setAudioOn] = useState(false);
const [videoModal, setVideoModal] = useState(null);
const [planState, setPlanState] = useState("balanced");
const [copied, setCopied] = useState(false);
const [toast, setToast] = useState(null);
const [easter, setEaster] = useState(0);
const sectionsRef = useRef([]);
const CHAPTERS = window.CHAPTERS;
const ChapterContent = window.ChapterContent;
const T = window.T;
// Scroll observer to determine active chapter
// Uses scroll position directly — IntersectionObserver fails for tall sections
// that never hit a 40% visibility threshold.
useEffect(() => {
const onScroll = () => {
const sections = sectionsRef.current.filter(Boolean);
if (!sections.length) return;
// The "anchor" line is 35% from the top of the viewport.
// The active section is the last one whose top is above that line.
const anchor = window.innerHeight * 0.35;
let idx = 0;
for (let i = 0; i < sections.length; i++) {
const rect = sections[i].getBoundingClientRect();
if (rect.top <= anchor) idx = i;
else break;
}
setActiveIdx(idx);
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
window.removeEventListener("resize", onScroll);
};
}, []);
// Apply tone to body + particles + audio
useEffect(() => {
const tone = CHAPTERS[activeIdx]?.tone || "neutral";
document.body.dataset.tone = tone;
if (window.__setParticleTone) window.__setParticleTone(tone);
if (window.__audio && window.__audio.isActive()) window.__audio.setTone(tone);
}, [activeIdx]);
// Duck the immersive audio while a video modal is open, restore on close.
// Only restores if audio was on before the modal opened.
useEffect(() => {
if (!window.__audio) return;
if (videoModal) {
if (audioOn && window.__audio.isActive()) {
window.__audio.stop();
}
} else {
// Modal just closed — bring audio back if user had it on
if (audioOn && !window.__audio.isActive()) {
window.__audio.start();
window.__audio.setTone(CHAPTERS[activeIdx]?.tone || "neutral");
}
}
}, [videoModal, audioOn]);
// Reveal-on-scroll
useEffect(() => {
const reveals = document.querySelectorAll(".reveal");
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) e.target.classList.add("visible");
});
}, { threshold: .15 });
reveals.forEach((r) => obs.observe(r));
return () => obs.disconnect();
}, [lang]);
const scrollTo = useCallback((idx) => {
const el = sectionsRef.current[idx];
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}, []);
const openVideo = useCallback((id, title) => {
setVideoModal({ id, title });
}, []);
const copyPrompt = useCallback(async () => {
const text = window.SUPERWHISPER_PROMPT || "";
let ok = false;
// Try modern clipboard API first
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
ok = true;
}
} catch (e) {}
// Fallback: hidden textarea + execCommand
if (!ok) {
try {
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.top = "-9999px";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
ta.setSelectionRange(0, text.length);
ok = document.execCommand("copy");
document.body.removeChild(ta);
} catch (e) {}
}
if (ok) {
setCopied(true);
setToast(T(lang, "Prompt copiado al portapapeles", "Prompt copied to clipboard"));
setTimeout(() => setCopied(false), 2200);
setTimeout(() => setToast(null), 2400);
} else {
setToast(T(lang, "Error al copiar — selecciona manualmente", "Copy failed — select manually"));
setTimeout(() => setToast(null), 2400);
}
}, [lang]);
const toggleAudio = useCallback(() => {
if (audioOn) {
window.__audio.stop();
setAudioOn(false);
} else {
window.__audio.start();
window.__audio.setTone(CHAPTERS[activeIdx]?.tone || "neutral");
setAudioOn(true);
setToast(T(lang, "Modo inmersivo activado", "Immersive mode on"));
setTimeout(() => setToast(null), 1800);
}
}, [audioOn, activeIdx, lang]);
// Easter egg: click brand 5x for konami
const onBrandClick = () => {
setEaster((e) => {
const n = e + 1;
if (n === 5) {
document.body.classList.add("glitch");
setToast(T(lang, "✦ Modo Stark activado ✦", "✦ Stark mode unlocked ✦"));
setTimeout(() => {
document.body.classList.remove("glitch");
setToast(null);
}, 2400);
return 0;
}
return n;
});
};
// Keyboard nav
useEffect(() => {
const onKey = (e) => {
if (videoModal) {
if (e.key === "Escape") setVideoModal(null);
return;
}
if (e.key === "ArrowDown" || e.key === "PageDown") {
if (activeIdx < CHAPTERS.length - 1) {e.preventDefault();scrollTo(activeIdx + 1);}
} else if (e.key === "ArrowUp" || e.key === "PageUp") {
if (activeIdx > 0) {e.preventDefault();scrollTo(activeIdx - 1);}
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [activeIdx, videoModal]);
const progress = (activeIdx + 1) / CHAPTERS.length * 100;
return (
<>
{/* Topbar */}
AI ROADMAP
· {T(lang, "Un viaje", "A journey")}
{/* Sidebar timeline */}
{/* Minimap */}
{CHAPTERS.map((ch, i) =>
)}
{/* Main */}
{CHAPTERS.map((ch, i) => {
const fn = ChapterContent[ch.id];
return (
sectionsRef.current[i] = el}
data-idx={i}
data-tone={ch.tone}
data-screen-label={`${ch.num} ${ch.title.es}`}
id={`ch-${ch.id}`}
className={ch.id === "intro" ? "" : "chapter"}>
{ch.id === "intro" ?
fn(lang, openVideo) :
<>
CHAPTER {ch.num}
{ch.act[lang]}
{ch.title[lang]}
{ch.intro[lang]}
{ch.id === "models" ? fn(lang, openVideo, planState, setPlanState) :
ch.id === "voice" ? fn(lang, openVideo, null, copyPrompt, copied) :
fn(lang, openVideo)}
{/* Stack diagram appears in sovereignty chapter */}
{ch.id === "sovereignty" && }
>
}
);
})}
{/* Video modal */}
{videoModal &&
}
{/* Toast */}
{toast && {toast}
}
{/* Console line easter egg */}
{`> chapter_${String(activeIdx + 1).padStart(2, "0")} loaded`}
_
>);
}
ReactDOM.createRoot(document.getElementById("root")).render();