// Shared primitives
const { useState, useEffect, useRef, useMemo } = React;
function SectionEyebrow({ label }) {
return (
{label}
);
}
function Halo({ size = 'lg', opacity = 0.85, color = 'blue' }) {
const dims = {
sm: { w: 800, h: 380 },
md: { w: 1100, h: 520 },
lg: { w: 1400, h: 700 },
xl: { w: 1800, h: 900 },
}[size];
return (
);
}
// Inline arrow icon
const IconArrow = ({ size = 14 }) => (
);
const IconCheck = ({ size = 14 }) => (
);
const IconPhone = ({ size = 14 }) => (
);
const IconClose = ({ size = 14 }) => (
);
const IconStar = ({ size = 14 }) => (
);
// Reveal-on-scroll
function useReveal() {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
// If already in viewport on mount, reveal synchronously
const rect = el.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
el.classList.add('in');
return;
}
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {
el.classList.add('in');
obs.disconnect();
}
}, { threshold: 0.12 });
obs.observe(el);
// Fallback: ensure reveal after 1.2s no matter what
const t = setTimeout(() => { el.classList.add('in'); obs.disconnect(); }, 1200);
return () => { obs.disconnect(); clearTimeout(t); };
}, []);
return ref;
}
function Reveal({ children, delay = 0, as: Tag = 'div', ...props }) {
const ref = useReveal();
return (
{children}
);
}
Object.assign(window, { SectionEyebrow, Halo, IconArrow, IconCheck, IconPhone, IconClose, IconStar, Reveal, useReveal });