// ---- Shared UI primitives ------------------------------------------- const { useState, useEffect, useRef } = React; // Thin, elegant line icons (1.6 stroke), inherit currentColor const ICONS = { feather: "M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5zM16 8 2 22M17.5 15H9", calendar:"M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", home: "M3 9.5 12 3l9 6.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z", sparkle: "M12 3l1.9 5.6L19.5 10l-5.6 1.9L12 17l-1.9-5.1L4.5 10l5.6-1.4zM19 4v3M5 18v2.5M18 18v2", heart: "M20.8 5.6a5.5 5.5 0 0 0-7.8 0L12 6.6l-1-1a5.5 5.5 0 1 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8z", leaf: "M11 20A7 7 0 0 1 4 13c0-5 5-9 16-9 0 9-4 14-9 14zM4 21c2-5 6-9 11-11", truck: "M1 4h13v12H1zM14 8h4l3 3v5h-7M5.5 19a2 2 0 1 0 0-.1M17.5 19a2 2 0 1 0 0-.1", cart: "M2 3h2.2l2 13h11.6l2-9H6M9 21a1 1 0 1 0 0-.1M18 21a1 1 0 1 0 0-.1", star: "M12 2.5l2.9 6.1 6.6.9-4.8 4.6 1.2 6.6L12 18.6 6.1 21.3l1.2-6.6L2.5 9.5l6.6-.9z", check: "M20 6 9 17l-5-5", chevron: "M9 18l6-6-6-6", chevronD:"M6 9l6 6 6-6", search: "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.3-4.3", user: "M20 21a8 8 0 0 0-16 0M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", close: "M18 6 6 18M6 6l12 12", plus: "M12 5v14M5 12h14", minus: "M5 12h14", arrow: "M5 12h14M13 6l6 6-6 6", moon: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z", sun: "M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z", book: "M4 4.5A2.5 2.5 0 0 1 6.5 2H20v18H6.5A2.5 2.5 0 0 0 4 22.5zM4 19.5A2.5 2.5 0 0 1 6.5 17H20", bookmark:"M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z", users: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM22 21v-2a4 4 0 0 0-3-3.87M16 3.13A4 4 0 0 1 16 11", ruler: "M3 8l5-5 13 13-5 5zM8 7l2 2M11 4l3 3M5 10l3 3", heartline:"M19.5 5.5a4.5 4.5 0 0 0-7 .5l-.5.6-.5-.6a4.5 4.5 0 1 0-6.8 5.9L12 20l7.3-7.6a4.5 4.5 0 0 0 .2-6.9z", quote: "M7 7H4a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h3v3a3 3 0 0 1-3 3M20 7h-3a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h3v3a3 3 0 0 1-3 3", arrowUpRight:"M7 17 17 7M8 7h9v9", image: "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2zM8.5 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM21 16l-4.5-4.5L7 21", }; function Icon({ name, size = 22, stroke = 1.6, fill = false, style }) { const d = ICONS[name] || ""; return ( ); } // Brand logos (recolored asset variants live in /assets). brandAsset() resolves // to a bundled blob URL (window.__resources) when present, else the file path — // so the same code works in normal preview and in the standalone export. function brandAsset(type, variant) { const id = (type === "mark" ? "logoMark_" : "logoWord_") + variant; return (window.__resources && window.__resources[id]) || ("assets/logo-" + type + "-" + variant + ".svg"); } function LogoMark({ color = "brown", height = 40, style }) { return Journella; } function LogoWord({ color = "brown", height = 26, style }) { return Journella; } function Stars({ value = 5, size = 16, color = "var(--gold-deep)" }) { return ( {[0,1,2,3,4].map(i => ( ))} ); } function Button({ children, variant = "primary", size = "md", onClick, type = "button", style, disabled, full }) { const base = { fontFamily: "var(--ui)", fontWeight: 600, letterSpacing: ".01em", border: "1px solid transparent", borderRadius: 999, cursor: disabled ? "default" : "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 9, transition: "transform .18s var(--ease), background .2s, color .2s, border-color .2s, box-shadow .2s", width: full ? "100%" : "auto", opacity: disabled ? 0.5 : 1, whiteSpace: "nowrap", }; const sizes = { sm: { padding: "9px 18px", fontSize: 13 }, md: { padding: "14px 28px", fontSize: 15 }, lg: { padding: "18px 38px", fontSize: 16 }, }; const variants = { primary: { background: "var(--espresso)", color: "var(--cream)" }, terracotta:{ background: "var(--terracotta)", color: "var(--cream)" }, outline: { background: "transparent", color: "var(--espresso)", borderColor: "var(--espresso)" }, ghost: { background: "var(--cream-2)", color: "var(--espresso)", borderColor: "transparent" }, }; return ( ); } // Marquee strip — gentle scrolling text, like the reference shop's ribbon function Marquee({ items, speed = 38, style }) { const content = items.concat(items); return (
{content.map((t, i) => ( {t} ))}
); } // Section heading: small kicker + serif headline function SectionHead({ kicker, title, sub, center, light }) { return (
{kicker &&
{kicker}
}

{title}

{sub &&

{sub}

}
); } // Reveal-on-scroll: visible by DEFAULT (never stuck hidden); the entrance // animation is added as an enhancement when the element enters the viewport. function Reveal({ children, delay = 0, style }) { const ref = useRef(null); const [anim, setAnim] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); if (r.top < (window.innerHeight || 800) + 60 && r.bottom > -60) { setAnim(true); return; } const io = new IntersectionObserver((es) => { es.forEach(e => { if (e.isIntersecting) { setAnim(true); io.disconnect(); } }); }, { threshold: 0.1 }); io.observe(el); return () => io.disconnect(); }, []); return (
{children}
); } Object.assign(window, { Icon, ICONS, brandAsset, LogoMark, LogoWord, Stars, Button, Marquee, SectionHead, Reveal });