/* global React */
const { useState, useEffect, useRef, useMemo, useCallback } = React;
// ─────────────────────────────────────────────────────────────────
// Button
// ─────────────────────────────────────────────────────────────────
function Button({
variant = 'primary',
size = 'md',
fullWidth = false,
loading = false,
icon,
iconRight,
children,
onClick,
disabled,
type = 'button',
className = '',
...rest
}) {
const base = {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
borderRadius: '12px',
fontWeight: 600,
border: 'none',
transition: 'transform 0.15s ease, background 0.2s ease, opacity 0.2s ease',
width: fullWidth ? '100%' : 'auto',
opacity: disabled ? 0.5 : 1,
cursor: disabled ? 'not-allowed' : 'pointer',
letterSpacing: '-0.01em',
whiteSpace: 'nowrap',
};
const sizes = {
md: { height: '48px', padding: '0 24px', fontSize: '15px' },
lg: { height: '56px', padding: '0 32px', fontSize: '17px' },
};
const variants = {
primary: { background: 'var(--color-signal)', color: 'var(--color-charcoal)' },
secondary: { background: 'transparent', color: 'var(--color-trust)', border: '2px solid var(--color-trust)' },
ghost: { background: 'transparent', color: 'var(--color-charcoal)' },
};
const [hover, setHover] = useState(false);
const hoverStyle = !disabled && hover ? {
primary: { background: '#E8581F', transform: 'scale(1.02)' },
secondary: { background: 'rgba(30,58,95,0.06)' },
ghost: { background: 'rgba(26,26,26,0.05)' },
}[variant] : {};
return (
);
}
// ─────────────────────────────────────────────────────────────────
// Card
// ─────────────────────────────────────────────────────────────────
function Card({ variant = 'default', children, style = {}, className = '', ...rest }) {
const variants = {
default: { background: 'var(--color-hanji)', color: 'var(--color-charcoal)' },
highlighted: { background: 'var(--color-trust)', color: 'var(--color-ivory)' },
plain: { background: 'var(--color-ivory)', color: 'var(--color-charcoal)' },
};
return (
{children}
);
}
// ─────────────────────────────────────────────────────────────────
// Badge
// ─────────────────────────────────────────────────────────────────
function Badge({ variant = 'neutral', children, style = {} }) {
const variants = {
orange: { background: 'rgba(255,106,44,0.10)', color: 'var(--color-signal)' },
sage: { background: 'rgba(184,197,166,0.30)', color: 'var(--color-charcoal)' },
trust: { background: 'rgba(30,58,95,0.10)', color: 'var(--color-trust)' },
neutral: { background: 'rgba(26,26,26,0.05)', color: 'rgba(26,26,26,0.7)' },
onTrust: { background: 'rgba(255,255,255,0.12)', color: 'var(--color-ivory)' },
};
return (
{children}
);
}
// ─────────────────────────────────────────────────────────────────
// Section wrapper (with reveal-on-scroll)
// ─────────────────────────────────────────────────────────────────
function Section({ id, bg = 'ivory', children, style = {}, dense = false }) {
const ref = useRef(null);
const [seen, setSeen] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) { setSeen(true); obs.disconnect(); }
});
}, { rootMargin: '-80px 0px' });
obs.observe(el);
return () => obs.disconnect();
}, []);
const bgClass = `bg-${bg}`;
return (
);
}
// ─────────────────────────────────────────────────────────────────
// Section header
// ─────────────────────────────────────────────────────────────────
function SectionHeader({ eyebrow, title, subtitle, align = 'center' }) {
return (
{eyebrow && (
{eyebrow}
)}
{title}
{subtitle && (
{subtitle}
)}
);
}
// ─────────────────────────────────────────────────────────────────
// StatPill
// ─────────────────────────────────────────────────────────────────
function StatPill({ number, label, source, emphasis = false, color = 'signal' }) {
const numColor = color === 'signal' ? 'var(--color-signal)'
: color === 'trust' ? 'var(--color-trust)'
: 'inherit';
return (
{number}
{label}
{source &&
{source}
}
);
}
// ─────────────────────────────────────────────────────────────────
// Input
// ─────────────────────────────────────────────────────────────────
function Input({ label, error, helperText, style = {}, ...rest }) {
const [focus, setFocus] = useState(false);
return (
);
}
function Textarea({ label, error, ...rest }) {
const [focus, setFocus] = useState(false);
return (
);
}
// Export to window so other JSX scripts can use these
Object.assign(window, {
Button, Card, Badge, Section, SectionHeader, StatPill, Input, Textarea,
});