// app.jsx — Tweaks panel + dynamic bits for Salha Hakim site // The bulk of the site is in static HTML (index.html). This script // powers the Tweaks panel (accent / font / density / dark / hero layout), // scroll reveals, and the floating theme toggle. const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "purple", "density": "regular", "hero": "left", "fontPair": "instrument", "dark": false, "grain": true }/*EDITMODE-END*/; const FONT_PAIRS = { instrument: { display: '"Instrument Serif", Georgia, serif', body: '"Manrope", ui-sans-serif, system-ui, sans-serif', label: "Instrument × Manrope", }, newsreader: { display: '"Newsreader", Georgia, serif', body: '"Geist", ui-sans-serif, system-ui, sans-serif', label: "Newsreader × Geist", }, serif_mono: { display: '"DM Serif Display", Georgia, serif', body: '"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif', label: "DM Serif × Plex", }, }; function applyTweaks(t) { const root = document.documentElement; root.setAttribute("data-accent", t.accent); root.setAttribute("data-density", t.density); root.setAttribute("data-hero", t.hero); root.setAttribute("data-theme", t.dark ? "dark" : "light"); root.style.setProperty("--font-display", FONT_PAIRS[t.fontPair].display); root.style.setProperty("--font-body", FONT_PAIRS[t.fontPair].body); document.body.classList.toggle("no-grain", !t.grain); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); React.useEffect(() => { applyTweaks(t); }, [t]); // Bind floating theme toggle (always present, even when tweaks off) React.useEffect(() => { const btn = document.getElementById("theme-toggle"); if (!btn) return; const handler = () => setTweak("dark", !t.dark); btn.addEventListener("click", handler); return () => btn.removeEventListener("click", handler); }, [t.dark]); return ( { const map = { "#542d84": "purple", "#eb6624": "orange", "#e43226": "red", "#de1b51": "magenta", "#f1c334": "yellow" }; setTweak("accent", map[v]); }} /> setTweak("dark", v)} /> setTweak("grain", v)} /> ({ value: k, label: FONT_PAIRS[k].label }))} onChange={(v) => setTweak("fontPair", v)} /> setTweak("density", v)} /> setTweak("hero", v)} /> ); } // Grain toggle CSS const grainStyle = document.createElement("style"); grainStyle.textContent = `.no-grain::before { display: none !important; }`; document.head.appendChild(grainStyle); // Mount const rootEl = document.getElementById("tweaks-root"); if (rootEl) ReactDOM.createRoot(rootEl).render(); // ── Scroll reveals (vanilla, no React dependency) const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -60px 0px" }); document.querySelectorAll(".reveal").forEach((el) => io.observe(el)); // ── Nav scroll state const nav = document.querySelector(".nav"); const onScroll = () => nav?.classList.toggle("scrolled", window.scrollY > 12); window.addEventListener("scroll", onScroll, { passive: true }); onScroll();