/* APP — root, nav, smooth scroll, language toggle, tweaks */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "lang": "en", "motionLevel": 60, "palette": "slate", "displayFont": "fraunces" }/*EDITMODE-END*/; const PALETTES = { slate: { bone: "oklch(0.965 0.008 80)", paper: "oklch(0.945 0.012 80)", sand: "oklch(0.895 0.018 80)", sandDeep: "oklch(0.840 0.022 75)", line: "oklch(0.78 0.012 75)", lineSoft: "oklch(0.86 0.010 75)", slate: "oklch(0.34 0.012 250)", slateDeep: "oklch(0.24 0.014 250)", slateInk: "oklch(0.18 0.012 250)", slateBg: "oklch(0.30 0.014 250)", muted: "oklch(0.50 0.010 250)", }, graphite: { bone: "oklch(0.965 0.005 80)", paper: "oklch(0.940 0.008 80)", sand: "oklch(0.890 0.012 80)", sandDeep: "oklch(0.830 0.014 75)", line: "oklch(0.78 0.008 75)", lineSoft: "oklch(0.86 0.006 75)", slate: "oklch(0.34 0.005 270)", slateDeep: "oklch(0.22 0.006 270)", slateInk: "oklch(0.16 0.006 270)", slateBg: "oklch(0.28 0.006 270)", muted: "oklch(0.50 0.005 270)", }, ocean: { bone: "oklch(0.965 0.008 80)", paper: "oklch(0.945 0.012 80)", sand: "oklch(0.895 0.018 80)", sandDeep: "oklch(0.840 0.022 75)", line: "oklch(0.78 0.012 75)", lineSoft: "oklch(0.86 0.010 75)", slate: "oklch(0.36 0.025 230)", slateDeep: "oklch(0.24 0.030 230)", slateInk: "oklch(0.18 0.025 230)", slateBg: "oklch(0.30 0.030 230)", muted: "oklch(0.50 0.018 230)", }, warm: { bone: "oklch(0.960 0.012 75)", paper: "oklch(0.935 0.018 75)", sand: "oklch(0.885 0.025 70)", sandDeep: "oklch(0.825 0.028 65)", line: "oklch(0.76 0.018 70)", lineSoft: "oklch(0.84 0.014 70)", slate: "oklch(0.34 0.012 60)", slateDeep: "oklch(0.22 0.012 60)", slateInk: "oklch(0.18 0.010 60)", slateBg: "oklch(0.28 0.012 60)", muted: "oklch(0.50 0.010 60)", }, }; const FONTS = { fraunces: { display: '"Fraunces", "Cormorant Garamond", Georgia, serif', serif: '"Fraunces", "Cormorant Garamond", Georgia, serif' }, cormorant: { display: '"Cormorant Garamond", "Times New Roman", serif', serif: '"Cormorant Garamond", "Times New Roman", serif' }, }; function App() { const { useTweaks } = window; const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); const lang = tweaks.lang || "en"; const t = window.COPY[lang]; const motionLevel = tweaks.motionLevel ?? 60; const cursorRef = React.useRef(null); const [scrolled, setScrolled] = React.useState(false); React.useEffect(() => { const palette = PALETTES[tweaks.palette] || PALETTES.slate; const root = document.documentElement; root.style.setProperty("--bone", palette.bone); root.style.setProperty("--paper", palette.paper); root.style.setProperty("--sand", palette.sand); root.style.setProperty("--sand-deep", palette.sandDeep); root.style.setProperty("--line", palette.line); root.style.setProperty("--line-soft", palette.lineSoft); root.style.setProperty("--slate", palette.slate); root.style.setProperty("--slate-deep", palette.slateDeep); root.style.setProperty("--slate-ink", palette.slateInk); root.style.setProperty("--slate-bg", palette.slateBg); root.style.setProperty("--muted", palette.muted); const f = FONTS[tweaks.displayFont] || FONTS.fraunces; root.style.setProperty("--display", f.display); root.style.setProperty("--serif", f.serif); }, [tweaks.palette, tweaks.displayFont]); // Lenis smooth scroll React.useEffect(() => { if (motionLevel < 20) return; const Lenis = window.Lenis; if (!Lenis) return; const lenis = new Lenis({ duration: 1.2 + motionLevel / 200, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true, }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } const id = requestAnimationFrame(raf); return () => { lenis.destroy(); cancelAnimationFrame(id); }; }, [motionLevel]); React.useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 80); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); // Custom cursor React.useEffect(() => { const c = cursorRef.current; if (!c) return; let x = 0, y = 0, tx = 0, ty = 0; const onMove = (e) => { tx = e.clientX; ty = e.clientY; }; window.addEventListener("mousemove", onMove); const onEnter = (e) => { if (e.target.closest && (e.target.closest("a, button, .s-tab, .g-item, .channel"))) c.classList.add("lg"); }; const onLeave = () => { c.classList.remove("lg"); }; document.addEventListener("mouseover", onEnter); document.addEventListener("mouseout", onLeave); let raf; const tick = () => { x += (tx - x) * 0.18; y += (ty - y) * 0.18; c.style.transform = `translate(${x}px, ${y}px) translate(-50%, -50%)`; raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { window.removeEventListener("mousemove", onMove); document.removeEventListener("mouseover", onEnter); document.removeEventListener("mouseout", onLeave); cancelAnimationFrame(raf); }; }, []); // Reveal observer React.useEffect(() => { const els = document.querySelectorAll(".reveal, .reveal-mask"); const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) e.target.classList.add("in"); }); }, { threshold: 0.15 }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, [lang]); return ( <>