/* app.jsx — main App, reveal observer, tweaks wiring */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#F4EDE4", "#6B1F2A", "#D9A48B", "#1B1614"], "displayFont": "Cormorant Garamond", "ornament": true, "density": "editorial" }/*EDITMODE-END*/; const PALETTES = { "Cream + Burgundy": ["#F4EDE4", "#6B1F2A", "#D9A48B", "#1B1614"], "Bone + Maroon": ["#F1ECE2", "#5A1A22", "#C9A289", "#1A1612"], "Ivory + Ink": ["#FBF7F1", "#1B1614", "#C99A4B", "#3D2E2A"], "Dusty Rose": ["#F4E8E4", "#7A2E3B", "#D9A48B", "#1F1717"], }; function applyPalette(p) { const r = document.documentElement; r.style.setProperty("--bg", p[0]); // derive bg-2 r.style.setProperty("--bg-2", shade(p[0], -4)); r.style.setProperty("--paper", shade(p[0], 5)); r.style.setProperty("--line", shade(p[0], -12)); r.style.setProperty("--line-2", shade(p[0], -22)); r.style.setProperty("--primary", p[1]); r.style.setProperty("--primary-2", shade(p[1], -18)); r.style.setProperty("--blush", p[2]); r.style.setProperty("--blush-2", shade(p[2], 8)); r.style.setProperty("--ink", p[3]); r.style.setProperty("--ink-2", shade(p[3], 18)); r.style.setProperty("--muted", shade(p[3], 50)); } function shade(hex, amt) { const c = hex.replace("#",""); const n = parseInt(c, 16); let r = (n >> 16) + amt, g = ((n >> 8) & 0xFF) + amt, b = (n & 0xFF) + amt; r = Math.max(0, Math.min(255, r)); g = Math.max(0, Math.min(255, g)); b = Math.max(0, Math.min(255, b)); return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // Apply palette + font on change React.useEffect(() => { applyPalette(t.palette); }, [t.palette]); React.useEffect(() => { document.documentElement.style.setProperty("--font-serif", `"${t.displayFont}", "EB Garamond", Georgia, serif`); }, [t.displayFont]); React.useEffect(() => { document.body.classList.toggle("no-ornament", !t.ornament); }, [t.ornament]); React.useEffect(() => { document.body.classList.toggle("density-compact", t.density === "compact"); }, [t.density]); // Reveal-on-scroll observer (additive — elements are visible by default) React.useEffect(() => { const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("is-in"); io.unobserve(e.target); } }); }, { rootMargin: "-40px 0px -10% 0px" }); document.querySelectorAll(".reveal:not(.is-in)").forEach((el) => io.observe(el)); return () => io.disconnect(); }, []); return (