// Romanesk — Tweaks panel + light interactivity const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": "papier", "tagline": "principal", "fontPair": "cormorant-source", "dark": false, "motion": true, "density": "regular" }/*EDITMODE-END*/; const PALETTES = { papier: { label: "Papier · bordeaux", swatches: ["#f0e6d2", "#1d1916", "#7a2a26"], vars: { "--paper": "oklch(0.962 0.012 80)", "--paper-deep": "oklch(0.928 0.018 78)", "--paper-shade": "oklch(0.895 0.022 76)", "--ink": "oklch(0.185 0.018 60)", "--ink-soft": "oklch(0.36 0.014 60)", "--ink-faint": "oklch(0.55 0.010 60)", "--rule": "oklch(0.82 0.014 72)", "--rule-soft": "oklch(0.88 0.012 74)", "--bordeaux": "oklch(0.42 0.115 22)", "--bordeaux-deep":"oklch(0.32 0.105 22)", "--ocre": "oklch(0.62 0.115 70)", "--ivy": "oklch(0.40 0.060 145)" } }, forest: { label: "Vélin · forêt", swatches: ["#ece6d8", "#1c2118", "#2d5538"], vars: { "--paper": "oklch(0.952 0.014 95)", "--paper-deep": "oklch(0.918 0.018 92)", "--paper-shade": "oklch(0.885 0.020 90)", "--ink": "oklch(0.20 0.018 145)", "--ink-soft": "oklch(0.36 0.018 140)", "--ink-faint": "oklch(0.55 0.012 130)", "--rule": "oklch(0.82 0.014 110)", "--rule-soft": "oklch(0.88 0.012 108)", "--bordeaux": "oklch(0.40 0.085 150)", "--bordeaux-deep":"oklch(0.30 0.070 150)", "--ocre": "oklch(0.60 0.110 80)", "--ivy": "oklch(0.42 0.090 160)" } }, prussian: { label: "Ivoire · Prusse", swatches: ["#f0e9d8", "#0f1c2e", "#b88a3c"], vars: { "--paper": "oklch(0.957 0.015 90)", "--paper-deep": "oklch(0.920 0.018 88)", "--paper-shade": "oklch(0.890 0.022 86)", "--ink": "oklch(0.20 0.05 250)", "--ink-soft": "oklch(0.38 0.045 250)", "--ink-faint": "oklch(0.56 0.030 245)", "--rule": "oklch(0.82 0.018 240)", "--rule-soft": "oklch(0.88 0.014 240)", "--bordeaux": "oklch(0.34 0.110 250)", "--bordeaux-deep":"oklch(0.26 0.100 250)", "--ocre": "oklch(0.66 0.115 78)", "--ivy": "oklch(0.44 0.080 175)" } }, bichrome: { label: "Bichromie", swatches: ["#f4f1ea", "#0f0f0e", "#666"], vars: { "--paper": "oklch(0.965 0.005 80)", "--paper-deep": "oklch(0.935 0.005 80)", "--paper-shade": "oklch(0.905 0.005 80)", "--ink": "oklch(0.15 0.005 60)", "--ink-soft": "oklch(0.40 0.005 60)", "--ink-faint": "oklch(0.62 0.005 60)", "--rule": "oklch(0.85 0.005 70)", "--rule-soft": "oklch(0.90 0.005 72)", "--bordeaux": "oklch(0.20 0.005 60)", "--bordeaux-deep":"oklch(0.10 0.005 60)", "--ocre": "oklch(0.50 0.005 60)", "--ivy": "oklch(0.45 0.005 60)" } } }; const TAGLINES = { principal: { head: ['L\u2019atelier d\u2019\u00e9criture ', { em: 'qui pense avec toi.' }], sub: 'Construis un univers, \u00e9cris-le sur des centaines de pages, dialogue avec une IA qui conna\u00eet ton lore \u2014 sans qu\u2019une seule ligne ne quitte ta machine.' }, fiction: { head: ['L\u2019\u00e9criture fictionnelle, ', { em: 'augment\u00e9e et priv\u00e9e.' }], sub: 'Une IA qui lit ton univers et le respecte. Une base SQLite sur ton disque. Pas de cloud, pas de compte, pas de redevance.' }, univers: { head: ['Construis un univers. ', { em: '\u00c9cris-le. Garde-le.' }], sub: 'Six types de fiches polymorphes, un \u00e9diteur multi-chapitres, une m\u00e9moire vivante du lore. Tout tourne sur ta machine via Ollama.' }, worldbuilding: { head: ['Worldbuilding et roman, ', { em: 'dans un seul outil local.' }], sub: 'Romanesk r\u00e9unit la construction d\u2019univers et la r\u00e9daction longue dans un m\u00eame atelier \u2014 avec une IA en sparring partner, hors ligne.' }, machine: { head: ['Ton univers. Ton manuscrit. ', { em: 'Ta machine.' }], sub: 'Romanesk est local-first par construction. Ton manuscrit ne finira pas dans un dataset \u2014 il restera o\u00f9 tu l\u2019as \u00e9crit.' } }; const FONT_PAIRS = { "cormorant-source": { label: "Cormorant + Source Serif", display: '"Cormorant Garamond", "EB Garamond", Georgia, serif', body: '"Source Serif 4", "Source Serif Pro", Georgia, serif' }, "playfair-lora": { label: "Playfair + Lora", display: '"Playfair Display", Georgia, serif', body: '"Lora", Georgia, serif' }, "ebgaramond-only": { label: "EB Garamond seul", display: '"EB Garamond", Georgia, serif', body: '"EB Garamond", Georgia, serif' }, "spectral-spectral": { label: "Spectral", display: '"Spectral", Georgia, serif', body: '"Spectral", Georgia, serif' } }; // Inject the additional Google Fonts on demand const FONT_HREFS = { "playfair-lora": "https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,500;0,600;1,500&family=Lora:ital,wght@0,400;0,500;1,400&display=swap", "ebgaramond-only": "https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&display=swap", "spectral-spectral": "https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,300;0,400;0,500;1,400&display=swap" }; const loadedFonts = new Set(); function ensureFont(key) { if (loadedFonts.has(key)) return; const href = FONT_HREFS[key]; if (!href) return; const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; document.head.appendChild(link); loadedFonts.add(key); } function applyPalette(key) { const pal = PALETTES[key] || PALETTES.papier; const root = document.documentElement; for (const [k, v] of Object.entries(pal.vars)) root.style.setProperty(k, v); } function applyDark(dark) { document.documentElement.classList.toggle('dark', !!dark); } function applyMotion(on) { document.documentElement.classList.toggle('no-motion', !on); } function applyDensity(d) { const root = document.documentElement; if (d === 'compact') { root.style.setProperty('--section-y', 'clamp(56px, 7vw, 110px)'); } else if (d === 'comfy') { root.style.setProperty('--section-y', 'clamp(100px, 14vw, 200px)'); } else { root.style.removeProperty('--section-y'); } } function applyFontPair(key) { ensureFont(key); const pair = FONT_PAIRS[key] || FONT_PAIRS["cormorant-source"]; document.documentElement.style.setProperty('--serif-display', pair.display); document.documentElement.style.setProperty('--serif-body', pair.body); } function applyTagline(key) { const t = TAGLINES[key] || TAGLINES.principal; const h = document.getElementById('hero-tagline'); const s = document.getElementById('hero-sub'); if (!h || !s) return; h.innerHTML = ''; for (const part of t.head) { if (typeof part === 'string') { h.appendChild(document.createTextNode(part)); } else if (part && part.em) { const span = document.createElement('span'); span.className = 'em'; span.textContent = part.em; h.appendChild(span); } } s.textContent = t.sub; } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); React.useEffect(() => { applyPalette(t.palette); }, [t.palette]); React.useEffect(() => { applyDark(t.dark); }, [t.dark]); React.useEffect(() => { applyMotion(t.motion); }, [t.motion]); React.useEffect(() => { applyDensity(t.density); }, [t.density]); React.useEffect(() => { applyFontPair(t.fontPair); }, [t.fontPair]); React.useEffect(() => { applyTagline(t.tagline); }, [t.tagline]); const paletteSwatches = Object.entries(PALETTES).map(([k, v]) => v.swatches); const paletteKeys = Object.keys(PALETTES); const currentPaletteIndex = paletteKeys.indexOf(t.palette); return ( ({ value: k, label: PALETTES[k].label }))} onChange={(v) => setTweak('palette', v)} /> ({ value: k, label: FONT_PAIRS[k].label }))} onChange={(v) => setTweak('fontPair', v)} /> setTweak('density', v)} /> setTweak('dark', v)} /> setTweak('motion', v)} /> setTweak('tagline', v)} /> ); } const root = ReactDOM.createRoot(document.getElementById('tweaks-root')); root.render(); // ───────────────────────────────────────────────────────────────────────────── // Light interactivity outside the React island // Drop-zone visual feedback (() => { const dz = document.getElementById('dropzone'); if (!dz) return; const stop = (e) => { e.preventDefault(); e.stopPropagation(); }; ['dragenter', 'dragover'].forEach(ev => dz.addEventListener(ev, (e) => { stop(e); dz.classList.add('is-drag'); })); ['dragleave', 'drop'].forEach(ev => dz.addEventListener(ev, (e) => { stop(e); dz.classList.remove('is-drag'); })); })(); // Smooth scroll for in-page anchors document.querySelectorAll('a[href^="#"]').forEach(a => { a.addEventListener('click', (e) => { const id = a.getAttribute('href').slice(1); if (!id) return; const target = document.getElementById(id); if (!target) return; e.preventDefault(); window.scrollTo({ top: target.getBoundingClientRect().top + window.scrollY - 60, behavior: 'smooth' }); }); });