// app.jsx — Copa Culinaria Carozzi 2026 — main composition const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#E2231A", "#2E2A6B", "#0B0B0B"] }/*EDITMODE-END*/; const PALETTES = [ // [accent, secondary, background-dark] ["#E2231A", "#2E2A6B", "#0B0B0B"], // Carozzi original ["#D7372A", "#1E1B45", "#080808"], // Deeper red, deeper navy ["#C8102E", "#0F1E3F", "#0A0A0A"], // Crimson + ink blue ["#E85A2C", "#2A2A4C", "#0F0F0E"], // Warmer orange-red + slate (chef edition) ]; function App(){ const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // Apply palette to CSS vars useEffect(()=>{ const [red, navy, black] = t.palette; const root = document.documentElement; root.style.setProperty('--c-red', red); // derive a deeper hover from the red root.style.setProperty('--c-red-deep', shade(red, -16)); root.style.setProperty('--c-navy', navy); root.style.setProperty('--c-navy-deep', shade(navy, -10)); root.style.setProperty('--c-black', black); root.style.setProperty('--c-carbon', shade(black, 4)); }, [t.palette]); // Scroll-reveal observer for [data-reveal] elements useEffect(()=>{ const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; if (reduce){ document.querySelectorAll('[data-reveal]').forEach(el => el.classList.add('is-visible')); return; } const io = new IntersectionObserver((entries)=>{ entries.forEach(entry => { if (entry.isIntersecting){ const el = entry.target; const delay = parseInt(el.getAttribute('data-reveal-delay') || '0', 10); if (delay > 0) el.style.transitionDelay = delay + 'ms'; el.classList.add('is-visible'); io.unobserve(el); } }); }, {rootMargin:'0px 0px -8% 0px', threshold:.08}); // Initial scan + a small re-scan after Babel mounts everything const scan = () => document.querySelectorAll('[data-reveal]:not(.is-visible)').forEach(el => io.observe(el)); scan(); const t1 = setTimeout(scan, 400); const t2 = setTimeout(scan, 1200); // Watch for dynamically-added reveal elements (e.g. form steps) const mo = new MutationObserver(()=> scan()); mo.observe(document.body, {childList:true, subtree:true}); return ()=> { io.disconnect(); mo.disconnect(); clearTimeout(t1); clearTimeout(t2); }; }, []); // Si la URL trae un ancla (ej: index.html#premios), hacer scroll a esa // sección una vez que React terminó de montar las secciones. // Esto permite que los enlaces desde la galería lleguen al lugar correcto. useEffect(()=>{ const id = window.location.hash ? window.location.hash.replace('#','') : ''; if (!id) return; let intentos = 0; const irASeccion = () => { const el = document.getElementById(id); if (el){ window.scrollTo({top: el.offsetTop - 60, behavior:'smooth'}); return true; } return false; }; // La sección puede no existir todavía (React aún montando). // Reintentamos algunas veces hasta que aparezca. const intervalo = setInterval(()=>{ intentos++; if (irASeccion() || intentos > 40){ // ~6 segundos máximo clearInterval(intervalo); } }, 150); return ()=> clearInterval(intervalo); }, []); return (
); } // Helper to lighten/darken a hex color function shade(hex, percent){ const h = hex.replace('#',''); const r = parseInt(h.slice(0,2),16); const g = parseInt(h.slice(2,4),16); const b = parseInt(h.slice(4,6),16); const f = percent/100; const m = (c) => { const v = f < 0 ? c * (1+f) : c + (255-c)*f; return Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2,'0'); }; return '#' + m(r) + m(g) + m(b); } ReactDOM.createRoot(document.getElementById('root')).render();