// shared.jsx, tokens + reusable primitives across the three directions // Loaded BEFORE direction files. Exports to window. // ─── Brand tokens (single source of truth) ─────────────────────────── // Five brand-supplied hex codes plus a soft ink. const PD = { pink: '#FF6FD6', // bubblegum · primary mint: '#41EAD4', // electric mint · primary softPink: '#FFC8E4', // pale pink · surface softMint: '#81F7E5', // pale mint · surface grey: '#F7F7F7', // off-white · resting surface ink: '#1A0F2E', // near-black · type // Legacy keys mapped so the older direction-1 + direction-3 still render // without a full rewrite. (Direction-2 uses the new keys directly.) magenta: '#FF6FD6', cream: '#F7F7F7', paper: '#FFFFFF', peach: '#FFC8E4', lilac: '#FFC8E4', }; // ─── Image placeholder ─────────────────────────────────────────────── // All client imagery is a placeholder right now; this component makes // each slot obvious with its semantic id so the user knows what photo // goes there. Styled by direction. function IMG({ id, label, w, h, style = {}, tone = 'pink', children }) { const tones = { pink: { bg: '#FFD1EF', stripe: PD.pink, ink: PD.magenta }, mint: { bg: '#C2F5EE', stripe: PD.mint, ink: '#0F6F66' }, cream: { bg: PD.cream, stripe: PD.peach, ink: PD.ink }, ink: { bg: PD.ink, stripe: PD.pink, ink: PD.cream }, lilac: { bg: PD.lilac, stripe: PD.magenta,ink: PD.ink }, }; const t = tones[tone] || tones.pink; return (
{children}
<{id}>
{label && (
{label}
)}
); } // ─── Logo lockup (uses the PNG you supplied) ───────────────────────── function Logo({ h = 44, white = false, style = {}, className }) { return The Pixie Dollhouse; } // ─── Scrolling marquee (auto loops, used as a section ribbon) ──────── function Marquee({ items, bg = PD.ink, color = PD.cream, height = 56, speed = 30, sep = '·' }) { // Duplicate the content twice so the keyframe can translateX(-50%) seamlessly const line = [...items, ...items, ...items, ...items]; return (
{line.map((t, i) => ( {t} {sep} ))}
); } // ─── Sticker (decorative element: butterfly, sparkle, heart, etc.) ─── function Sticker({ kind, size = 48, color = PD.pink, style = {}, className }) { const w = size; const sw = { display: 'inline-block', width: w, height: w, className, ...style }; delete sw.className; if (kind === 'butterfly') { // The real mark lifted from the logo, never a drawn stand-in. const mask = 'url("assets/butterfly.png") center / contain no-repeat'; return ( ); } if (kind === 'sparkle') { return ( ); } if (kind === 'heart') { return ( ); } if (kind === 'flower') { return ( ); } if (kind === 'star') { return ( ); } if (kind === 'bow') { return ( ); } return null; } // ─── Pill / button ─────────────────────────────────────────────────── function Pill({ children, bg = PD.pink, color = PD.cream, size = 'md', style = {}, outline = false, className }) { const sizes = { sm: { pad: '8px 16px', fs: 13 }, md: { pad: '14px 28px', fs: 15 }, lg: { pad: '20px 40px', fs: 18 }, xl: { pad: '24px 52px', fs: 22 }, }; const s = sizes[size]; return ( {children} ); } // ─── Section frame for direction 2 (rounded "room" card) ───────────── function Room({ title, color = PD.pink, ink = PD.cream, children, style = {} }) { return (
{title && (
{title}
)} {children}
); } // ─── Netlify-backed email signup ───────────────────────────────────── // Posts to Netlify Forms (form name: "newsletter"). The static detection // stub lives in index.html, Netlify's build bot only parses plain HTML. function SignupForm({ ink = PD.ink, accent = PD.pink, surface = PD.paper }) { const [state, setState] = React.useState('idle'); const [email, setEmail] = React.useState(''); function submit(e) { e.preventDefault(); if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { setState('invalid'); return; } setState('sent'); const body = new URLSearchParams({ 'form-name': 'newsletter', email }); fetch('/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body }).catch(() => {}); } if (state === 'sent') { return (
you're on the list, doll.
check your inbox. First email lands next drop.
); } return (

{ setEmail(e.target.value); if (state === 'invalid') setState('idle'); }} style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent', padding: '12px 18px', fontFamily: 'inherit', fontSize: 15, fontWeight: 600, color: ink, }} />
{state === 'invalid' && (
that email looks off. Try again?
)}
); } Object.assign(window, { PD, IMG, Logo, Marquee, Sticker, Pill, Room, SignupForm });