// 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;