// ─── ¡eso! site shared building blocks ────────────────────── // Identity tokens, ExclamMark, BleedMarks, photo helpers, scroll-reveal hook. const ESO = { cream: '#FBF7EC', paper: '#F1ECDA', ink: '#1F1A17', red: '#D24B3F', lime: '#D4E04A', muted: '#6B6258', rule: 'rgba(31, 26, 23, 0.18)', }; // ── ExclamMark ───────────────────────────────────────────── // `which` = "open" → ¡, "close" → ! function ExclamMark({ which = 'close', size = 80, color = ESO.red, style = {} }) { const w = size * 0.20; const h = size; const stem = h * 0.62; const dotR = w * 0.34; const dotY = which === 'open' ? h * 0.16 : h * 0.84; const stemY = which === 'open' ? h * 0.30 : 0; return ( ); } // ── BleedMarks ───────────────────────────────────────────── // ¡ top-left + ! bottom-right, split half-off the page edge function BleedMarks({ show = true, color = ESO.lime, size = 320, bleed = 0.5, zIndex = 1, } = {}) { if (!show) return null; const w = size * 0.20; const offX = -w * bleed; return (
); } // ── CoachPhoto ───────────────────────────────────────────── // The Jorge & Ariana photo — two crops: // "wide" → both coaches, full-bleed strip with parallax-ready ratio // "ariana" / "jorge" → single face crop const PHOTO_SRC = 'website-assets/jorge_ariana.png'; function CoachPhoto({ crop = 'wide', height = 360, parallaxY = 0 }) { // 1920x1080 source. Approx face centers in %: // Ariana ~ (24%, 33%), Jorge ~ (75%, 32%) const cropMap = { wide: { bgSize: 'cover', bgPos: '50% 28%' }, ariana: { bgSize: '260% auto', bgPos: '24% 33%' }, jorge: { bgSize: '260% auto', bgPos: '75% 32%' }, }; const c = cropMap[crop] || cropMap.wide; return (
{/* Subtle vignette to anchor type when overlaid */}
); } // ── useReveal ────────────────────────────────────────────── // IntersectionObserver hook — adds .in to elements w/ .reveal once they enter view. function useRevealOnScroll() { React.useEffect(() => { const els = document.querySelectorAll('.reveal'); if (!els.length) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }); } // ── useScrollY ───────────────────────────────────────────── // Light-weight scroll position hook for parallax. Throttled via rAF. function useScrollY() { const [y, setY] = React.useState(0); React.useEffect(() => { let raf = 0; const onScroll = () => { if (raf) return; raf = requestAnimationFrame(() => { setY(window.scrollY || 0); raf = 0; }); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => { window.removeEventListener('scroll', onScroll); if (raf) cancelAnimationFrame(raf); }; }, []); return y; } Object.assign(window, { ESO, ExclamMark, BleedMarks, CoachPhoto, useRevealOnScroll, useScrollY, });