// Shared UI primitives + nav + footer + reveal-on-scroll hook.
const { useEffect, useRef, useState, useMemo } = React;
// ----- Reveal on scroll -----
function useReveal() {
useEffect(() => {
const els = document.querySelectorAll(
'.reveal:not(.in), .reveal-left:not(.in), .reveal-right:not(.in), .reveal-scale:not(.in)'
);
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('in');
io.unobserve(e.target);
}
});
}, { threshold: 0.10 });
els.forEach((el) => io.observe(el));
return () => io.disconnect();
});
}
// ----- Buttons -----
const PrimaryButton = ({ children, className = "", icon, ...rest }) => (
);
const GhostButton = ({ children, className = "", icon, ...rest }) => (
);
// ----- Eyebrow label -----
const Eyebrow = ({ children, className = "" }) => (
{/* Close button */}
{/* Drag canvas */}
startDrag(e.clientX, e.clientY)}
onMouseMove={e => moveDrag(e.clientX, e.clientY)}
onMouseUp={endDrag}
onMouseLeave={endDrag}
onTouchStart={e => { e.preventDefault(); if (e.touches.length === 1) startDrag(e.touches[0].clientX, e.touches[0].clientY); }}
onTouchMove={e => { e.preventDefault(); if (e.touches.length === 1) moveDrag(e.touches[0].clientX, e.touches[0].clientY); }}
onTouchEnd={endDrag}
>
{/* Controls bar */}
applyZoom(e.target.value)}
className="fp-slider"
style={{ background: sliderBg }}
/>
{Math.round(zoom * 100)}%
{zoom > 1 && (
)}
,
document.body
);
};
// Expose
Object.assign(window, {
useReveal, PrimaryButton, GhostButton, Eyebrow, SectionTitle, ArchPh, Logo, Nav, Marquee, Footer, NAV, ThemeToggle, ModalPortal, FloorPlanPopup,
});