/* ============================================================
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 (
);
};
/* ---- 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 });