/* 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 (
setTweak("palette", v)}
/>
setTweak("displayFont", v)}
/>
setTweak("density", v)}
/>
setTweak("ornament", v)}
/>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render();