/* tweaks.jsx — Carla Salvat theme editor (palette + per-level fonts). Runs on every page that loads it. Choices persist via localStorage ('cs_tweaks_v1') and a small inline boot script in every page
applies them on load so the rest of the site reflects the user's choices. Exposes window.__csOpenTweaks() so a header button can open the panel. */ // ----- Palettes (10) ----- [bg-primary, bg-secondary, ivory, sand, accent] const PALETTES = { 'Forest noir' : ['#0F1410', '#1C2620', '#F5F2EC', '#B8AC9B', '#EFE7DA'], 'Pure mono' : ['#0A0A0A', '#161616', '#F5F5F5', '#999999', '#FFFFFF'], 'Warm earth' : ['#1A1410', '#2A2218', '#F5EDDE', '#B8A582', '#D9A87A'], 'Cool slate' : ['#0E1216', '#1A1F26', '#E8ECF0', '#8A95A5', '#C5D0E0'], 'Ocean depth' : ['#0A1818', '#142828', '#EDF4F2', '#9FB3B0', '#C8D9D5'], 'Champagne noir' : ['#0A0A0A', '#161616', '#F5EFE0', '#C5A572', '#D4AF7A'], 'Olivier sauvage' : ['#15170F', '#20231A', '#F0EAD9', '#A8A07D', '#BF9E5F'], 'Velours rouge' : ['#1A0E0F', '#2A1518', '#F5EBE7', '#B89F95', '#A4544F'], 'Terre cuite' : ['#1A100B', '#2A1A12', '#F5EBE0', '#BFA68C', '#C97D4B'], 'Lavande nuit' : ['#110F18', '#1B1827', '#EFEBF5', '#A39CB8', '#8E7CB5'], }; const FONT_OPTIONS = { display: [ 'EB Garamond', 'Cormorant Garamond', 'Playfair Display', 'DM Serif Display', 'Italiana', 'Marcellus', 'Cinzel', 'Anton', 'Bebas Neue', 'Archivo Black', 'Big Shoulders Display', 'Oswald', ], body: [ 'Manrope', 'Inter', 'DM Sans', 'Outfit', 'Karla', 'Work Sans', 'Lato', 'Source Sans 3', ], tag: [ 'Permanent Marker', 'Caveat Brush', 'Reenie Beanie', 'Shadows Into Light Two', 'Rubik Mono One', 'Indie Flower', 'Dancing Script', 'Great Vibes', 'Sacramento', ], }; const TWEAK_DEFAULTS = { palette: PALETTES['Forest noir'], paletteName: 'Forest noir', fontH1: 'EB Garamond', fontH2: 'EB Garamond', fontH3: 'EB Garamond', fontBody: 'Manrope', fontTag: 'Permanent Marker', }; // ----- Apply tweaks to CSS vars + meta ----- function applyTweaks(t) { const r = document.documentElement; if (t.palette && t.palette.length === 5) { r.style.setProperty('--bg-primary', t.palette[0]); r.style.setProperty('--bg-secondary', t.palette[1]); r.style.setProperty('--ivory', t.palette[2]); r.style.setProperty('--sand', t.palette[3]); r.style.setProperty('--accent', t.palette[4]); r.style.setProperty('--accent-hover', t.palette[4]); r.style.setProperty('--accent-soft', hexToRgba(t.palette[4], 0.06)); r.style.setProperty('--border', hexToRgba(t.palette[2], 0.10)); r.style.setProperty('--border-strong',hexToRgba(t.palette[2], 0.22)); r.style.setProperty('--surface-deep', darken(t.palette[0], 0.4)); } if (t.fontH1) r.style.setProperty('--font-h1', '"' + t.fontH1 + '", serif'); if (t.fontH2) r.style.setProperty('--font-h2', '"' + t.fontH2 + '", serif'); if (t.fontH3) r.style.setProperty('--font-h3', '"' + t.fontH3 + '", serif'); if (t.fontBody) r.style.setProperty('--font-body', '"' + t.fontBody + '", system-ui, sans-serif'); if (t.fontTag) r.style.setProperty('--font-tag', '"' + t.fontTag + '", cursive'); } function hexToRgba(hex, a) { if (!hex || hex[0] !== '#') return hex; let h = hex.slice(1); if (h.length === 3) h = h.split('').map(c => c+c).join(''); const r = parseInt(h.slice(0,2),16), g = parseInt(h.slice(2,4),16), b = parseInt(h.slice(4,6),16); return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; } function darken(hex, ratio) { if (!hex || hex[0] !== '#') return hex; let h = hex.slice(1); if (h.length === 3) h = h.split('').map(c => c+c).join(''); let r = parseInt(h.slice(0,2),16), g = parseInt(h.slice(2,4),16), b = parseInt(h.slice(4,6),16); r = Math.max(0, Math.round(r * (1 - ratio))); g = Math.max(0, Math.round(g * (1 - ratio))); b = Math.max(0, Math.round(b * (1 - ratio))); return '#' + [r,g,b].map(n => n.toString(16).padStart(2,'0')).join(''); } // Expose for the inline boot script on other pages and for header button window.__csApplyTweaks = applyTweaks; window.__csPalettes = PALETTES; window.__csOpenTweaks = function() { window.postMessage({ type: '__activate_edit_mode' }, '*'); }; // ----- React App ----- function App() { const [t, setTweak] = useTweaks(loadInitialTweaks()); React.useEffect(() => { applyTweaks(t); }, [t]); React.useEffect(() => { try { localStorage.setItem('cs_tweaks_v1', JSON.stringify(t)); } catch (e) {} }, [t]); const paletteOptions = Object.entries(PALETTES).map(([name, arr]) => arr); const paletteNames = Object.keys(PALETTES); const fontSelect = (label, value, key, options) => (