// app.jsx — Grupo Térreo brand manual main app. // Composes sections, sticky navigation, scroll-spy, and Tweaks panel. const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "fontMain": "Montserrat", "palette": ["#2D4A3E","#C8B89A","#FFFFFF","#6B7B8D","#1A1F1C"], "metaDensity": "media", "logoRoute": "sillar" }/*EDITMODE-END*/; const FONT_OPTIONS = [ { id: "Montserrat", label: "Montserrat", stack: "'Montserrat', system-ui, sans-serif" }, { id: "Manrope", label: "Manrope", stack: "'Manrope', system-ui, sans-serif" }, { id: "Plus Jakarta Sans", label: "Plus Jakarta Sans", stack: "'Plus Jakarta Sans', system-ui, sans-serif" }, { id: "Outfit", label: "Outfit", stack: "'Outfit', system-ui, sans-serif" }, ]; const PALETTE_OPTIONS = [ // [verde, arena, piedra, gris, carbon] · label { name: "Blanco", value: ["#2D4A3E","#C8B89A","#FFFFFF","#6B7B8D","#1A1F1C"] }, { name: "Bosque", value: ["#2D4A3E","#C8B89A","#F2F2EF","#6B7B8D","#1A1F1C"] }, { name: "Oliva", value: ["#4A5D3A","#D4C4A4","#F3F3F0","#7A8174","#25281F"] }, { name: "Salvia", value: ["#5D7461","#DFD8C8","#F4F4F2","#8A9098","#1D2120"] }, { name: "Arcilla", value: ["#3A5045","#D8C2A0","#F2F1EE","#7A6F60","#1E1A14"] }, ]; const NAV_ITEMS = [ { id: "cover", num: "00", label: "Portada" }, { id: "concepto", num: "01", label: "Concepto" }, { id: "logotipo", num: "02", label: "Logotipo" }, { id: "paleta", num: "03", label: "Paleta cromática" }, { id: "tipografia",num: "04", label: "Tipografía" }, { id: "editorial", num: "05", label: "Sistema editorial" }, { id: "papeleria", num: "06", label: "Papelería" }, { id: "web", num: "07", label: "Web corporativa" }, { id: "voz", num: "08", label: "Tono y claim" }, ]; function useScrollSpy(ids) { const [active, setActive] = useState(ids[0]); useEffect(() => { const observer = new IntersectionObserver( (entries) => { // Choose the entry closest to top among intersecting ones const visible = entries.filter(e => e.isIntersecting); if (visible.length) { visible.sort((a,b) => a.boundingClientRect.top - b.boundingClientRect.top); setActive(visible[0].target.id); } }, { rootMargin: "-20% 0px -70% 0px", threshold: 0 } ); ids.forEach(id => { const el = document.getElementById(id); if (el) observer.observe(el); }); return () => observer.disconnect(); }, [ids.join(",")]); return active; } function SideNav({ active, route }) { return ( ); } function applyPalette(palette) { // palette: [verde, arena, piedra, gris, carbon] const [verde, arena, piedra, gris, carbon] = palette; const root = document.documentElement.style; root.setProperty("--verde", verde); root.setProperty("--arena", arena); root.setProperty("--piedra", piedra); root.setProperty("--gris", gris); root.setProperty("--carbon", carbon); // Piedra-2 is a slightly lighter version of piedra root.setProperty("--piedra-2", lighten(piedra, 0.82)); root.setProperty("--linea", hexToRgba(verde, 0.18)); root.setProperty("--linea-soft", hexToRgba(verde, 0.10)); } function applyFont(fontId) { const opt = FONT_OPTIONS.find(f => f.id === fontId) || FONT_OPTIONS[0]; document.documentElement.style.setProperty("--f-main", opt.stack); } // helpers function hexToRgba(hex, a) { const m = hex.replace("#",""); const bigint = parseInt(m.length === 3 ? m.split("").map(c => c+c).join("") : m, 16); const r = (bigint >> 16) & 255, g = (bigint >> 8) & 255, b = bigint & 255; return `rgba(${r},${g},${b},${a})`; } function lighten(hex, amt) { // mix hex with white by `amt` (0..1) const m = hex.replace("#",""); const bigint = parseInt(m.length === 3 ? m.split("").map(c => c+c).join("") : m, 16); let r = (bigint >> 16) & 255, g = (bigint >> 8) & 255, b = bigint & 255; r = Math.round(r + (255 - r) * amt); g = Math.round(g + (255 - g) * amt); b = Math.round(b + (255 - b) * amt); return `rgb(${r},${g},${b})`; } function paletteEquals(a, b) { return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i]); } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const active = useScrollSpy(NAV_ITEMS.map(n => n.id)); // Apply CSS variables whenever palette/font change useEffect(() => { applyPalette(t.palette); }, [t.palette]); useEffect(() => { applyFont(t.fontMain); }, [t.fontMain]); const fontStack = (FONT_OPTIONS.find(f => f.id === t.fontMain) || FONT_OPTIONS[0]).stack; return (
setTweak("logoRoute", v)} />
r.id)} onChange={(v) => setTweak("logoRoute", v)} />
{(LOGO_ROUTES.find(r => r.id === t.logoRoute) || {}).label || ""}
p.value)} onChange={(v) => setTweak("palette", v)} />
{(PALETTE_OPTIONS.find(p => paletteEquals(p.value, t.palette)) || {}).name || "Personalizada"}
f.id)} onChange={(v) => setTweak("fontMain", v)} /> setTweak("metaDensity", v)} />
); } ReactDOM.createRoot(document.getElementById("app")).render();