// Shared components: Header, Footer, ImagePlaceholder
const { useState, useEffect, useRef, useCallback, useMemo } = React;
// ============ IMAGE PLACEHOLDER ============
const ImagePlaceholder = ({ label, dark = false, style, aspect, className = '' }) => {
return (
{label}
);
};
// ============ DNA WAVE (nav decoration) — rotating in place via rAF ============
const NavDna = () => {
const W = 240;
const H = 30;
const cy = H / 2;
const amp = 9;
const PERIODS = 3.5; // visible cycles across width
const DURATION = 5.5; // seconds per full rotation
const N_RUNGS = 26;
const SAMPLES = 90; // points on each strand
const topRef = useRef(null);
const botRef = useRef(null);
const rungRefs = useRef([]);
useEffect(() => {
const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const start = performance.now();
let raf;
const draw = (phase) => {
let topD = '', botD = '';
for (let i = 0; i <= SAMPLES; i++) {
const x = (i / SAMPLES) * W;
const arg = (x / W) * PERIODS * 2 * Math.PI + phase;
const s = Math.sin(arg);
topD += (i === 0 ? 'M' : 'L') + x.toFixed(2) + ',' + (cy - amp * s).toFixed(2) + ' ';
botD += (i === 0 ? 'M' : 'L') + x.toFixed(2) + ',' + (cy + amp * s).toFixed(2) + ' ';
}
if (topRef.current) topRef.current.setAttribute('d', topD);
if (botRef.current) botRef.current.setAttribute('d', botD);
for (let i = 0; i < N_RUNGS; i++) {
const x = (i / (N_RUNGS - 1)) * W;
const arg = (x / W) * PERIODS * 2 * Math.PI + phase;
const s = Math.sin(arg);
const ref = rungRefs.current[i];
if (ref) {
ref.setAttribute('y1', (cy - amp * s).toFixed(2));
ref.setAttribute('y2', (cy + amp * s).toFixed(2));
ref.setAttribute('opacity', (Math.abs(s) * 0.8).toFixed(2));
}
}
};
if (reduce) {
draw(0);
return;
}
const tick = (now) => {
const t = ((now - start) / 1000 / DURATION) % 1;
const phase = t * 2 * Math.PI;
draw(phase);
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, []);
return (
);
};
// ============ HEADER ============
const Header = ({ activeSection, currentPage = 'home', onBook, onSecondOpinion, onSearch }) => {
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 12);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const handleNavClick = (e, item) => {
// Full-href links: let browser navigate
if (item.href) return;
e.preventDefault();
// If on home page, scroll to section
if (currentPage === 'home') {
const el = document.getElementById(item.id);
if (el) {
const y = el.getBoundingClientRect().top + window.scrollY - 88;
window.scrollTo({ top: y, behavior: 'smooth' });
}
} else {
// From other pages: navigate to home with hash
window.location.href = `index.html#${item.id}`;
}
setMobileOpen(false);
};
const handleBrandClick = (e) => {
if (currentPage === 'home') {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// otherwise default navigation to index.html
};
return (
{/* Top utility bar */}
{DATA.brand.address.split(',')[0]}
{DATA.brand.address.split(',').slice(1).join(',').trim()}
{/* Nav bar */}
);
};
// ============ STAT TICKER (animated number) ============
const StatNumber = ({ value }) => {
// Just render — the visual emphasis is enough without animation
return {value};
};
// ============ SECTION HEADER ============
const SectionHead = ({ eyebrow, title, sub, action }) => (
{eyebrow &&
{eyebrow}}
{title}
{sub &&
{sub}
}
{action &&
{action}
}
);
// ============ CTA BUTTONS PAIR ============
const HeroCtas = ({ onBook, onSecondOpinion }) => (
);
// Expose globally
Object.assign(window, {
ImagePlaceholder,
Header,
StatNumber,
SectionHead,
HeroCtas,
});