// 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 = "" }) => (
{children}
); // ----- Section heading ----- const SectionTitle = ({ kicker, title, sub, align = "left" }) => (
{kicker && {kicker}}

{title}

{sub &&

{sub}

}
); // ----- Marker placeholder (architectural) ----- const ArchPh = ({ variant = 1, label, className = "" }) => (
{/* layered architectural shapes */} {variant === 1 && ( {[...Array(20)].map((_,i)=>())} )} {variant === 2 && ( )} {variant === 3 && ( {[...Array(18)].map((_,i)=>())} )} {variant === 4 && ( )} {label && (
{label}
)}
); // ----- Logo ----- const Logo = ({ className = "", onClick }) => (
AMG Logo
AMG
Ashok Malhotra Group
); // ----- Theme toggle ----- const ThemeToggle = ({ colorMode, toggle }) => ( ); // ----- Nav ----- const NAV = [ { id: 'home', label: 'Home' }, { id: 'commercial', label: 'Commercial' }, { id: 'residential', label: 'Residential' }, { id: 'brands', label: 'Brands' }, { id: 'leadership', label: 'Leadership' }, { id: 'contact', label: 'Contact' }, ]; const Nav = ({ page, setPage, colorMode, toggleColorMode }) => { const [scrolled, setScrolled] = useState(false); const [mobileOpen, setMobileOpen] = useState(false); useEffect(() => { const onS = () => setScrolled(window.scrollY > 16); onS(); window.addEventListener('scroll', onS, { passive: true }); return () => window.removeEventListener('scroll', onS); }, []); const go = (id) => { setPage(id); setMobileOpen(false); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const unscrolledCls = colorMode === 'light' ? 'py-2 bg-white/40 backdrop-blur-md border border-black/10 shadow-[0_4px_24px_rgba(0,0,0,0.10)]' : 'py-2 bg-black/30 backdrop-blur-md border border-white/10 shadow-[0_4px_32px_rgba(0,0,0,0.45)]'; return (
go('home')} />
WhatsApp
{/* mobile sheet */} {mobileOpen && (
{NAV.map((n) => ( ))} Chat on WhatsApp
)}
); }; // ----- Marquee strip ----- const Marquee = ({ items }) => (
{[...items, ...items, ...items].map((t, i) => (
{t}
))}
); // ----- Footer ----- const Footer = ({ setPage }) => { const go = (id) => { setPage(id); window.scrollTo({ top: 0, behavior: 'smooth' }); }; return ( ); }; // ----- Centered modal portal (escapes Framer Motion transform stacking context) ----- const ModalPortal = ({ onClose, children }) => { React.useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, []); return ReactDOM.createPortal(
{ if (e.target === e.currentTarget) onClose(); }} >
{children}
, document.body ); }; // ── Floor-plan zoom popup ────────────────────────────────────────────────── const FloorPlanPopup = ({ src, alt, onClose }) => { const [zoom, setZoom] = useState(1); const [pan, setPan] = useState({ x: 0, y: 0 }); // Use refs for drag state to avoid stale-closure issues in event handlers const draggingRef = useRef(false); const [cursor, setCursor] = useState('default'); const lastPos = useRef(null); const zoomRef = useRef(1); const panRef = useRef({ x: 0, y: 0 }); useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = prev; }; }, []); const applyZoom = (v) => { const z = Math.min(4, Math.max(1, +v)); zoomRef.current = z; setZoom(z); if (z <= 1) { panRef.current = { x: 0, y: 0 }; setPan({ x: 0, y: 0 }); } setCursor(z > 1 ? 'grab' : 'default'); }; const startDrag = (x, y) => { if (zoomRef.current <= 1) return; draggingRef.current = true; lastPos.current = { x, y }; setCursor('grabbing'); }; const moveDrag = (x, y) => { if (!draggingRef.current || !lastPos.current) return; const dx = x - lastPos.current.x; const dy = y - lastPos.current.y; lastPos.current = { x, y }; panRef.current = { x: panRef.current.x + dx, y: panRef.current.y + dy }; setPan({ ...panRef.current }); }; const endDrag = () => { draggingRef.current = false; lastPos.current = null; setCursor(zoomRef.current > 1 ? 'grab' : 'default'); }; const sliderBg = `linear-gradient(to right, #D4A020 ${((zoom-1)/3*100).toFixed(1)}%, rgba(250,248,244,0.18) ${((zoom-1)/3*100).toFixed(1)}%)`; return ReactDOM.createPortal(
{/* 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} > {alt}
{/* 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, });