/* atoms.jsx — shared primitives */ const { useState, useEffect, useRef, useCallback, useMemo } = React; /* Photo block. - If you pass a `src` (e.g. src="assets/mi-foto.jpg") it shows the real photo. - If you DON'T pass a src, it shows the warm gradient placeholder with a label. To add a real photo: drop the image file in the /assets folder, then add src="assets/your-file.jpg" to the matching tag. */ function Photo({ tone = "default", label = "photo", src = "", alt = "", className = "", style = {}, children }) { const cls = `ph ph-${tone} ${className}`.trim(); return (
{src ? {alt : {label}} {children}
); } /* Eyebrow with dot */ function Eyebrow({ children, className = "" }) { return (
{children}
); } /* Reveal-on-scroll wrapper */ function Reveal({ as: As = "div", delay = 0, children, className = "", ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; // Fallback 1: if element is already in viewport on mount, reveal immediately. const r = el.getBoundingClientRect(); if (r.top < window.innerHeight && r.bottom > 0) { el.classList.add("is-in"); return; } let io; if (typeof IntersectionObserver !== "undefined") { io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { el.classList.add("is-in"); io.unobserve(el); } }); }, { threshold: 0.08, rootMargin: "0px 0px -8% 0px" }); io.observe(el); } // Fallback 2: hard timeout — never leave content invisible. const t = setTimeout(() => { el.classList.add("is-in"); io && io.disconnect(); }, 1200); return () => { clearTimeout(t); io && io.disconnect(); }; }, []); const delayCls = delay ? ` delay-${delay}` : ""; const merged = `reveal${delayCls} ${className}`.trim(); return {children}; } /* Arrow icon */ function Arr({ size = 14 }) { return ( ); } /* Plus icon (for FAQ) */ function Plus({ size = 18 }) { return ( ); } /* Section heading row */ function SectionHead({ eyebrow, title, sub, align = "left" }) { return (
{eyebrow && {eyebrow}}

{sub &&

{sub}

}
); } Object.assign(window, { Photo, Eyebrow, Reveal, Arr, Plus, SectionHead });