// Shared components for Little Bloom Photo Studio
// All components attached to window for cross-file access.
const { useState, useEffect, useRef } = React;
/* ============ Photo URLs (client studio assets) ============ */
const PHOTOS = {
hero: "assets/NG5A0131.jpg", // full boho daybed — hero portrait
maternity1: "assets/NG5A0133.jpg", // daybed wider shot
maternity2: "assets/NG5A0134.jpg", // daybed + window light detail
family1: "assets/NG5A0139.jpg", // cream sofa / macramé
family2: "assets/NG5A0141.jpg", // sofa wide with windows
newborn1: "assets/NG5A0138.jpg", // antique bassinet by windows
newborn2: "assets/NG5A0136-2.jpg", // pampas arrangement close
couple1: "assets/NG5A0142.jpg", // macramé wall detail
couple2: "assets/NG5A0141.jpg",
portrait1: "assets/NG5A0135.jpg", // daybed angled
portrait2: "assets/NG5A0134.jpg",
portrait3: "assets/NG5A0133.jpg",
branding1: "assets/NG5A0141.jpg", // bright minimal sofa
branding2: "assets/NG5A0139.jpg",
studio1: "assets/NG5A0131.jpg",
studio2: "assets/NG5A0139.jpg", // sofa/macramé — CTA background
studio3: "assets/NG5A0133.jpg",
flowers: "assets/NG5A0136.jpg", // pampas arrangement full
flowers2: "assets/NG5A0136-2.jpg",
child1: "assets/NG5A0137.jpg", // pampas wall wide
child2: "assets/NG5A0139.jpg",
photographer:"assets/NG5A0134.jpg", // studio detail (daybed + window)
light: "assets/NG5A0138.jpg", // bassinet / window light
snackbar: "assets/NG5A0144.jpg", // snack bar amenity
};
window.PHOTOS = PHOTOS;
/* ============ Floral Wreath SVG (mini decoration) ============ */
function Wreath({ size = 80, color, half = false }) {
const c = color || "var(--lb-tan)";
// Stylized minimal wreath - delicate botanical line art
return (
);
}
/* ============ Logo (uses extracted PNG) ============ */
function Logo({ size = 56, variant = "full" }) {
// variant: "full" (full wreath logo), "text" (just "Little Bloom"), "monogram" (LB)
if (variant === "text") {
return (
Little Bloom
PHOTO STUDIO
);
}
return
;
}
/* ============ Nav ============ */
function Nav({ page, setPage, dark = false }) {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 60);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const items = [
{ id: 'home', label: 'Home' },
{ id: 'about', label: 'About' },
{ id: 'booking', label: 'Booking & Memberships' },
];
const onSolid = scrolled || open;
const color = onSolid ? 'var(--lb-brown)' : (dark ? 'var(--lb-cream-3)' : 'var(--lb-brown)');
const bg = onSolid ? 'rgba(251,247,236,0.92)' : 'transparent';
const go = (id) => { setPage(id); setOpen(false); window.scrollTo({ top: 0, behavior: 'instant' }); };
return (
{/* Mobile sheet */}
{open && (
{items.map(it => (
))}
)}
);
}
/* ============ Footer ============ */
function Footer({ setPage }) {
return (
);
}
/* ============ Marquee strip ============ */
function Marquee({ items, speed = 60 }) {
// duplicate items for seamless scroll
const arr = [...items, ...items];
return (
{arr.map((t, i) => (
·
{t}
))}
);
}
/* ============ Reveal-on-scroll wrapper ============ */
function Reveal({ children, delay = 0, as: As = 'div', ...rest }) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('in'), delay);
io.unobserve(e.target);
}
});
}, { threshold: 0.12 });
io.observe(el);
return () => io.disconnect();
}, [delay]);
return {children};
}
/* Export to window */
Object.assign(window, { Wreath, Logo, Nav, Footer, Marquee, Reveal, PHOTOS });