// Effects — Reveal-on-scroll wrapper + Marquee brand strip /** * Reveal — wraps children in a div that fades + translates in when * it enters the viewport. Uses IntersectionObserver. No-op for * users with prefers-reduced-motion. */ function Reveal({ children, delay = 0, y = 24, once = true, threshold = 0.12, as: Tag = 'div', style = {} }) { const ref = React.useRef(null); const [visible, setVisible] = React.useState(false); React.useEffect(() => { const node = ref.current; if (!node) return; const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reduce) { setVisible(true); return; } const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { setVisible(true); if (once) io.unobserve(node); } else if (!once) { setVisible(false); } }); }, { threshold, rootMargin: '0px 0px -8% 0px' }); io.observe(node); return () => io.disconnect(); }, [once, threshold]); return ( {children} ); } /** * BrandMarquee — slow, infinite horizontal scroll of brand names. * Pure CSS animation; pauses on hover. */ function BrandMarquee({ items, speed = 60 }) { const isMobile = window.__dch_isMobile; // Repeat the list twice so the loop is seamless const row = (key) => (
{items.map((it, i) => ( {it} ))}
); return (
{row('a')} {row('b')}
{/* fade edges so the loop blends into the background */}
); } const marquee = { wrap: { position: 'relative', overflow: 'hidden', background: '#0A0A0A', borderTop: '1px solid #1A1A1A', borderBottom: '1px solid #1A1A1A', }, track: { display: 'flex', width: 'max-content', animation: 'dchMarquee linear infinite', willChange: 'transform', }, row: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0, paddingRight: 0, }, item: { fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic', fontWeight: 300, fontSize: 32, color: '#C8BFA8', letterSpacing: '0.04em', padding: '0 40px', whiteSpace: 'nowrap', }, dot: { color: '#C4A45A', fontSize: 10, opacity: 0.7, }, fade: { position: 'absolute', top: 0, bottom: 0, width: 120, pointerEvents: 'none', zIndex: 2, }, }; window.Reveal = Reveal; window.BrandMarquee = BrandMarquee;