/* ============================================================ Helpers: icons, Reveal, Counter, MagneticButton, parallax ============================================================ */ const { useState, useEffect, useRef, useCallback } = React; /* ---- Icon set (inline, stroke-based) ---- */ const Icon = ({ name, ...p }) => { const paths = { arrow: <>, phone: , mail: <>, globe: <>, star: , home: <>, building: <>, chef: <>, shield: <>, clock: <>, handshake: <>, sparkle: , check: , x: <>, menu: <>, leaf: <>, pin: <>, users: <>, heart: , target: <>, chevron: , whatsapp: <> }; return ( {paths[name]} ); }; /* ---- Reveal manager: rect-based, no IntersectionObserver dependency ---- Works in any browser (and snapshot/non-scrolling harnesses): an element reveals as soon as its top is within the viewport band, checked on scroll, resize, an initial rAF burst, and a final safety pass. */ const RevealMgr = (() => { const items = new Set(); let raf = 0; const band = () => (window.innerHeight || document.documentElement.clientHeight || 800) * 0.92; const check = () => { const limit = band(); for (const it of [...items]) { const el = it.el; if (!el || !el.isConnected) {items.delete(it);continue;} const r = el.getBoundingClientRect(); if (r.top < limit && r.bottom > -80) {it.cb();items.delete(it);} } }; const schedule = () => {if (raf) return;raf = requestAnimationFrame(() => {raf = 0;check();});}; if (typeof window !== "undefined" && !window.__revealMgrInit) { window.__revealMgrInit = true; window.addEventListener("scroll", schedule, { passive: true }); window.addEventListener("resize", schedule); // initial burst to catch fonts/layout settling let n = 0; (function burst() {check();if (n++ < 90) requestAnimationFrame(burst);})(); // safety net: never leave content permanently hidden setTimeout(() => {for (const it of [...items]) {it.cb();items.delete(it);}}, 3500); } return { add(el, cb) {items.add({ el, cb });schedule();} }; })(); const Reveal = ({ children, delay = 0, as: Tag = "div", className = "", ...rest }) => { const ref = useRef(null); const [seen, setSeen] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; RevealMgr.add(el, () => setSeen(true)); }, []); const d = delay ? ` d${delay}` : ""; return {children}; }; /* ---- Count-up number ---- */ const Counter = ({ to, suffix = "", prefix = "", dur = 1600, decimals = 0 }) => { const ref = useRef(null); const [val, setVal] = useState(0); const started = useRef(false); useEffect(() => { const el = ref.current; if (!el) return; RevealMgr.add(el, () => { if (started.current) return; started.current = true; const t0 = performance.now(); const tick = (t) => { const p = Math.min((t - t0) / dur, 1); const eased = 1 - Math.pow(1 - p, 3); setVal(to * eased); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }); }, [to, dur]); const display = decimals ? val.toFixed(decimals) : Math.round(val).toString(); return {prefix}{display}{suffix}; }; /* ---- Magnetic button wrapper ---- */ const Magnetic = ({ children, strength = 0.32, className = "", ...rest }) => { const ref = useRef(null); const onMove = useCallback((e) => { const el = ref.current;if (!el) return; const r = el.getBoundingClientRect(); const x = (e.clientX - r.left - r.width / 2) * strength; const y = (e.clientY - r.top - r.height / 2) * strength; el.style.transform = `translate(${x}px, ${y}px)`; }, [strength]); const reset = useCallback(() => {if (ref.current) ref.current.style.transform = "";}, []); return ( {children} ); }; /* ---- Parallax-on-scroll wrapper ---- */ const Parallax = ({ children, speed = 0.12, className = "", style = {} }) => { const ref = useRef(null); useEffect(() => { const el = ref.current;if (!el) return; let raf = 0; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const r = el.getBoundingClientRect(); const center = r.top + r.height / 2 - window.innerHeight / 2; el.style.transform = `translateY(${center * -speed}px)`; }); }; window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => {window.removeEventListener("scroll", onScroll);cancelAnimationFrame(raf);}; }, [speed]); return
{children}
; }; const Stars = ({ n = 5 }) => {Array.from({ length: n }).map((_, i) => )}; const Placeholder = ({ label, dark = false }) =>
{label}
; /* ---- PhotoSlot: drag-and-drop, user-fillable image (persists) ---- */ const UNSPLASH = "https://images.unsplash.com/"; const photoUrl = (p, w = 1400) => `${UNSPLASH}${p}?fm=jpg&q=75&w=${w}&auto=format&fit=crop`; const PhotoSlot = ({ id, label, photo, w = 1400, fit = "cover" }) => ; Object.assign(window, { Icon, Reveal, Counter, Magnetic, Parallax, Stars, Placeholder, PhotoSlot });