/* Brand Lens — shared UI primitives (exported to window) */
const { useState, useEffect, useRef, useCallback, useMemo } = React;
/* ---- inline icon set ---- */
const ICON_PATHS = {
check: 'M20 6 9 17l-5-5',
x: 'M18 6 6 18M6 6l12 12',
warn: 'M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z',
upload: 'M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M8 8l4-4 4 4M12 4v12',
shuffle: 'M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5',
trash: 'M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6',
arrow: 'M5 12h14M13 6l6 6-6 6',
eye: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z',
eyeOff: 'M9.9 4.2A9.1 9.1 0 0 1 12 4c6.5 0 10 7 10 7a13.2 13.2 0 0 1-2.2 3M6.6 6.6A13.3 13.3 0 0 0 2 11s3.5 7 10 7a9 9 0 0 0 3.5-.7M2 2l20 20M9.9 9.9a3 3 0 0 0 4.2 4.2',
sparkle: 'M12 3v4M12 17v4M3 12h4M17 12h4M6.3 6.3l2.8 2.8M14.9 14.9l2.8 2.8M17.7 6.3l-2.8 2.8M9.1 14.9l-2.8 2.8',
refresh: 'M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5',
heart: 'M19 14c1.5-1.5 3-3.3 3-5.5A4.5 4.5 0 0 0 12 5 4.5 4.5 0 0 0 2 8.5C2 12 5.5 15 12 21c2.6-2.4 4.8-4.4 7-7Z',
filter: 'M22 3H2l8 9.5V19l4 2v-8.5L22 3Z',
scale: 'M16 6h6M22 6l-3-3M22 6l-3 3M3 6h7M3 12h18M3 18h11',
cards: 'M3 5h18v6H3zM3 15h11v4H3zM18 15h3v4h-3z',
panel: 'M3 4h18v16H3zM10 4v16',
image: 'M3 5h18v14H3zM3 15l5-5 4 4 3-3 6 6M9 9a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z',
share: 'M18 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM6 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM18 22a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM8.6 13.5l6.8 4M15.4 6.5l-6.8 4',
shapes: 'M12 2l4 7H8l4-7ZM17.5 22a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7ZM4 14h7v7H4z',
download: 'M12 3v12M7 11l5 5 5-5M5 21h14',
save: 'M4 4h12l4 4v12H4zM8 4v6h8M8 20v-6h8v6',
};
function Icon({ name, size = 18, stroke = 2, style, className }) {
const d = ICON_PATHS[name] || '';
return (
);
}
/* ---- circular brand-fit score ---- */
function ScoreRing({ score, size = 96, label = 'fit' }) {
const r = (size - 12) / 2;
const c = 2 * Math.PI * r;
const pct = Math.max(0, Math.min(100, score)) / 100;
const col = score >= 70 ? 'var(--st-success)' : score >= 45 ? 'var(--st-warning)' : 'var(--ps-flag)';
return (
{Math.round(score)}
{label}
);
}
/* ---- verdict pill ---- */
function VerdictPill({ verdict, big }) {
const keep = verdict === 'keep';
return (
{keep ? 'Brand fit' : 'Off brand'}
);
}
/* ---- status chip for library cards ---- */
function StatusChip({ rec }) {
let txt, bg, fg, dot;
if (!rec.user) { txt = 'Not reviewed'; bg = 'var(--ps-cloud-gray)'; fg = 'var(--fg-3)'; dot = 'var(--ps-line-strong)'; }
else {
const agree = (rec.ai && rec.ai.verdict === 'keep') === !!rec.user.like;
if (rec.ai && agree) { txt = 'Aligned'; bg = '#EAF6F0'; fg = '#1F7A52'; dot = 'var(--st-success)'; }
else { txt = 'Diverged'; bg = 'var(--ps-flag-soft)'; fg = 'var(--ps-flag-ink)'; dot = 'var(--ps-flag)'; }
}
return (
{txt}
);
}
/* ---- pass/warn/fail reason row ---- */
function ReasonRow({ r }) {
const map = {
pass: { c: 'var(--st-success)', i: 'check' },
warn: { c: 'var(--st-warning)', i: 'warn' },
fail: { c: 'var(--ps-flag)', i: 'x' },
};
const m = map[r.status] || map.warn;
return (
);
}
/* ---- use-channel chips (renders any short label) ---- */
function UseChips({ uses }) {
if (!uses || !uses.length) return null;
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
return (
{uses.map((u, i) => (
{cap(u)}
))}
);
}
/* ---- category control (auto / photo / social / clip-art) ---- */
const CAT_META = {
auto: { label: 'Auto', icon: 'sparkle' },
photography: { label: 'Photo', icon: 'image' },
social: { label: 'Social', icon: 'share' },
illustration: { label: 'Clip-art', icon: 'shapes' },
};
function CategoryControl({ value, onChange }) {
return (
{['auto', 'photography', 'social', 'illustration'].map((k) => {
const m = CAT_META[k]; const on = value === k;
return (
);
})}
);
}
function CategoryBadge({ category, small }) {
const m = CAT_META[category] || CAT_META.photography;
return (
{m.label}
);
}
/* ---- buttons ---- */
function Btn({ kind = 'ghost', icon, children, style, ...p }) {
const base = {
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
fontFamily: 'var(--font-sans)', fontSize: 14, fontWeight: 600, cursor: 'pointer',
borderRadius: 10, padding: '10px 16px', border: '1px solid transparent',
transition: 'all .15s var(--ease-standard)', lineHeight: 1, whiteSpace: 'nowrap',
};
const kinds = {
primary: { background: 'var(--ps-primary-blue)', color: '#fff' },
dark: { background: 'var(--ps-navy)', color: '#fff' },
ghost: { background: 'var(--ps-cloud-gray)', color: 'var(--ps-primary-blue)', borderColor: 'var(--ps-line)' },
danger: { background: 'var(--ps-flag-soft)', color: 'var(--ps-flag-ink)', borderColor: '#F0D2CD' },
teal: { background: 'var(--ps-secondary-teal)', color: 'var(--ps-navy)' },
};
return (
);
}
/* ---- spinner ---- */
function Spinner({ size = 18, color = 'currentColor' }) {
return (
);
}
Object.assign(window, { Icon, ScoreRing, VerdictPill, StatusChip, ReasonRow, UseChips, CategoryControl, CategoryBadge, CAT_META, Btn, Spinner,
useState, useEffect, useRef, useCallback, useMemo });