// 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}
{label && (
{label}
)}
);
}
// ─── Logo lockup (uses the PNG you supplied) ─────────────────────────
function Logo({ h = 44, white = false, style = {}, className }) {
return
;
}
// ─── 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 (
);
}
Object.assign(window, { PD, IMG, Logo, Marquee, Sticker, Pill, Room, SignupForm });