// app.jsx — main App with Tweaks
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "cyan",
"fontPair": "geist",
"density": "regular",
"grid": true
}/*EDITMODE-END*/;
const ACCENT_PRESETS = {
cyan: { a: "oklch(0.82 0.14 200)", b: "oklch(0.74 0.16 285)" },
violet: { a: "oklch(0.74 0.17 290)", b: "oklch(0.78 0.16 340)" },
amber: { a: "oklch(0.82 0.16 70)", b: "oklch(0.74 0.18 30)" },
mint: { a: "oklch(0.84 0.14 160)", b: "oklch(0.78 0.13 200)" },
};
const FONT_PAIRS = {
geist: { sans: '"Geist", system-ui, sans-serif', mono: '"Geist Mono", ui-monospace, monospace' },
manrope: { sans: '"Manrope", system-ui, sans-serif', mono: '"JetBrains Mono", ui-monospace, monospace' },
satoshi: { sans: '"Plus Jakarta Sans", system-ui, sans-serif', mono: '"IBM Plex Mono", ui-monospace, monospace' },
};
const DENSITY = {
compact: { container: 1180, gutter: '40px', section: '90px', radius: 12 },
regular: { container: 1240, gutter: 'clamp(20px, 4vw, 56px)', section: 'clamp(80px, 11vw, 140px)', radius: 16 },
comfy: { container: 1320, gutter: 'clamp(28px, 5vw, 72px)', section: 'clamp(100px, 13vw, 180px)', radius: 20 },
};
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
// Apply tweaks as CSS vars on :root
React.useEffect(() => {
const root = document.documentElement;
const acc = ACCENT_PRESETS[t.accent] || ACCENT_PRESETS.cyan;
root.style.setProperty('--accent', acc.a);
root.style.setProperty('--accent-2', acc.b);
const f = FONT_PAIRS[t.fontPair] || FONT_PAIRS.geist;
root.style.setProperty('--font-sans', f.sans);
root.style.setProperty('--font-mono', f.mono);
const d = DENSITY[t.density] || DENSITY.regular;
root.style.setProperty('--container', d.container + 'px');
root.style.setProperty('--gutter', d.gutter);
document.body.classList.toggle('no-grid', !t.grid);
}, [t.accent, t.fontPair, t.density, t.grid]);
// Reveal-on-scroll observer (run once on mount)
React.useEffect(() => {
const els = document.querySelectorAll('.reveal');
// Above-the-fold reveals: show immediately so first paint isn't blank
const vh = window.innerHeight;
els.forEach(el => {
const r = el.getBoundingClientRect();
if (r.top < vh * 0.9) el.classList.add('in');
});
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('in');
io.unobserve(e.target);
}
});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
els.forEach(el => { if (!el.classList.contains('in')) io.observe(el); });
return () => io.disconnect();
}, []);
return (
<>
{
const key = v === '#5cd1e6' ? 'cyan'
: v === '#9b7bd9' ? 'violet'
: v === '#f0b86b' ? 'amber' : 'mint';
setTweak('accent', key);
}}
/>
setTweak('grid', v)}/>
setTweak('fontPair', v)}
/>
setTweak('density', v)}
/>
>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();