// theme.jsx — Paleta "Mitla" + tipografías + motivos zapotecos de la etiqueta de mezcal. // Exporta a window: T, Micro, Greca, StepDiamond, AgaveMark, Reveal, Rule, injectThemeCSS. const T = { bg: '#ece3cf', // crema paper: '#efe7d4', // papel card: '#f3ecdb', // tarjeta (un punto más clara) ink: '#2c4632', // verde agave oscuro soft: '#5a6e54', // verde suave accent: '#b1542e', // terracota deep: '#22351f', // verde profundo (pie / overlays) line: 'rgba(44,70,50,0.22)', display: "'Cormorant Garamond', serif", micro: "'Barlow Semi Condensed', sans-serif", }; // textura de papel sutil (idéntica a la etiqueta) const GRAIN = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.045'/%3E%3C/svg%3E\")"; function injectThemeCSS() { if (document.getElementById('__inviteTheme')) return; const s = document.createElement('style'); s.id = '__inviteTheme'; s.textContent = ` *{margin:0;padding:0;box-sizing:border-box;} html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;} body{background:${T.bg};color:${T.ink};font-family:${T.micro}; background-image:${GRAIN};font-weight:500; -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden;} ::selection{background:${T.accent}33;} a{color:inherit;} .serif{font-family:${T.display};} .ital{font-family:${T.display};font-style:italic;} .micro{font-family:${T.micro};text-transform:uppercase;letter-spacing:.24em;font-weight:600;} /* scroll reveal */ .rv{opacity:0;transform:translateY(26px);transition:opacity 1s cubic-bezier(.2,.7,.2,1),transform 1s cubic-bezier(.2,.7,.2,1);will-change:opacity,transform;} .rv.in{opacity:1;transform:none;} @media (prefers-reduced-motion:reduce){.rv{opacity:1!important;transform:none!important;transition:none!important;}} /* image-slot look */ image-slot{--is-bg:${T.card};--is-fg:${T.soft};--is-border:${T.line};display:block;} .lnk{text-decoration:none;border-bottom:1px solid ${T.accent}66;transition:border-color .3s,color .3s;} .lnk:hover{border-color:${T.accent};color:${T.accent};} input,select,textarea,button{font-family:${T.micro};} `; document.head.appendChild(s); } function Micro({ children, size = 11, ls = '.24em', color = T.soft, weight = 600, as = 'div', style }) { const Tag = as; return ( {children} ); } // ——— Greca escalonada de Mitla (banda tileable) ——— let __gid = 0; function Greca({ color = T.ink, strokeWidth = 1.5, step = 6, ups = 3, pad = 3, double = true, opacity = 0.85, style }) { const id = React.useMemo(() => `gr-${__gid++}`, []); const h = ups * step; const period = 2 * ups * step; let d = `M0 ${h}`, x = 0, y = h; for (let i = 0; i < ups; i++) { x += step; d += ` L${x} ${y}`; y -= step; d += ` L${x} ${y}`; } for (let i = 0; i < ups; i++) { x += step; d += ` L${x} ${y}`; y += step; d += ` L${x} ${y}`; } const totalH = h + pad * 2; return ( {double && } ); } function StepDiamond({ size = 22, color = T.accent, stroke = 1.6, fill = 'none' }) { const s = size / 6; const pts = [[3,0],[4,1],[5,2],[6,3],[5,4],[4,5],[3,6],[2,5],[1,4],[0,3],[1,2],[2,1]] .map(([a,b]) => `${a*s},${b*s}`).join(' '); return ( ); } function AgaveMark({ size = 84, color = T.ink, blades = 11, sw = 1.4 }) { const cx = size / 2, baseY = size * 0.92, spread = 78, arr = []; for (let i = 0; i < blades; i++) { const t = blades === 1 ? 0.5 : i / (blades - 1); const ang = (-spread + t * spread * 2) * Math.PI / 180; const lenFactor = 0.62 + 0.38 * (1 - Math.abs(t - 0.5) * 2) ** 0.9; const len = size * 0.82 * lenFactor; const tipX = cx + Math.sin(ang) * len, tipY = baseY - Math.cos(ang) * len; const w = size * 0.045 * (0.7 + 0.3 * (1 - Math.abs(t - 0.5) * 2)); const perp = ang + Math.PI / 2; const bx1 = cx + Math.cos(perp) * w, by1 = baseY + Math.sin(perp) * w; const bx2 = cx - Math.cos(perp) * w, by2 = baseY - Math.sin(perp) * w; arr.push(); } return {arr}; } // divider con rombo escalonado al centro function Rule({ width = 220, color = T.line, diamond = T.accent, dSize = 20, style }) { return (
); } // Reveal por scroll (IntersectionObserver compartido) let __io; function getIO() { if (__io) return __io; __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 -6% 0px' }); return __io; } // Red de seguridad: si por cualquier razón el observer no dispara (viewport raro, // JS lento, etc.), revela todo lo pendiente para que nunca quede contenido oculto. if (typeof window !== 'undefined' && !window.__rvSafety) { window.__rvSafety = true; const reveal = () => document.querySelectorAll('.rv:not(.in)').forEach((el) => el.classList.add('in')); setTimeout(reveal, 2000); window.addEventListener('load', () => setTimeout(reveal, 1500)); } function Reveal({ children, delay = 0, as = 'div', className = '', style }) { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el) return; el.style.transitionDelay = (delay || 0) + 'ms'; getIO().observe(el); }, [delay]); const Tag = as; return {children}; } Object.assign(window, { T, GRAIN, injectThemeCSS, Micro, Greca, StepDiamond, AgaveMark, Rule, Reveal });