/* Sybilhunt website — kit-local primitives.
These mirror the design-system components in /components so the kit
renders standalone (the compiled _ds_bundle.js is built post-turn).
All styling uses the real design tokens from styles.css. */
function Logo({ size = 28, showWordmark = true, onClick }) {
return (
{showWordmark && (
Sybilhunt
)}
);
}
function Button({ variant = 'primary', size = 'md', fullWidth = false, disabled = false, leftIcon, rightIcon, onClick, style = {}, children }) {
const sizes = { sm: { padding: '0 16px', height: 36, fontSize: 14 }, md: { padding: '0 22px', height: 46, fontSize: 15 }, lg: { padding: '0 30px', height: 56, fontSize: 17 } };
const variants = {
primary: { background: 'var(--signal-500)', color: 'var(--text-on-signal)', border: '1px solid transparent', boxShadow: '0 8px 30px var(--signal-glow)' },
secondary: { background: 'transparent', color: 'var(--text-strong)', border: '1px solid var(--border-strong)' },
ghost: { background: 'transparent', color: 'var(--text-body)', border: '1px solid transparent' },
danger: { background: 'var(--coral-500)', color: 'var(--coral-ink)', border: '1px solid transparent', boxShadow: '0 8px 28px var(--coral-glow)' },
};
const s = sizes[size], v = variants[variant];
return (
);
}
function Badge({ tone = 'neutral', size = 'md', dot = false, style = {}, children }) {
const tones = {
neutral: { bg: 'color-mix(in oklab, var(--ink-300) 14%, transparent)', fg: 'var(--text-muted)', dotc: 'var(--ink-300)' },
signal: { bg: 'color-mix(in oklab, var(--signal-500) 16%, transparent)', fg: 'var(--signal-400)', dotc: 'var(--signal-500)' },
live: { bg: 'color-mix(in oklab, var(--coral-500) 16%, transparent)', fg: 'var(--coral-300)', dotc: 'var(--coral-500)' },
info: { bg: 'color-mix(in oklab, var(--cyan-500) 15%, transparent)', fg: 'var(--cyan-300)', dotc: 'var(--cyan-500)' },
gold: { bg: 'color-mix(in oklab, var(--gold-500) 16%, transparent)', fg: 'var(--gold-400)', dotc: 'var(--gold-500)' },
};
const t = tones[tone];
const isLive = tone === 'live' || tone === 'signal';
return (
{dot && }
{children}
);
}
function Card({ padding = 24, interactive = false, glow = false, style = {}, children, ...rest }) {
return (
{ e.currentTarget.style.borderColor = 'var(--border-strong)'; e.currentTarget.style.transform = 'translateY(-3px)'; } : undefined}
onMouseLeave={interactive ? (e) => { e.currentTarget.style.borderColor = 'var(--border-subtle)'; e.currentTarget.style.transform = 'translateY(0)'; } : undefined}
{...rest}>{children}
);
}
function Avatar({ src, name = '', size = 48, ring = false, style = {} }) {
const initials = name.split(' ').map((w) => w[0]).slice(0, 2).join('').toUpperCase();
return (
{src ?
: initials}
);
}
function Stat({ value, label, sublabel, tone = 'default', align = 'left', style = {} }) {
const colors = { default: 'var(--text-strong)', signal: 'var(--signal-400)', gold: 'var(--gold-400)', coral: 'var(--coral-300)' };
return (
{value}
{label &&
{label}
}
{sublabel &&
{sublabel}
}
);
}
function Countdown({ deadline, size = 'md', tone = 'coral', style = {} }) {
const target = React.useMemo(() => new Date(deadline).getTime(), [deadline]);
const [now, setNow] = React.useState(Date.now());
React.useEffect(() => { const id = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(id); }, []);
const diff = Math.max(0, target - now);
const units = [['days', Math.floor(diff / 86400000)], ['hrs', Math.floor((diff % 86400000) / 3600000)], ['min', Math.floor((diff % 3600000) / 60000)], ['sec', Math.floor((diff % 60000) / 1000)]];
const dims = size === 'lg' ? { box: 78, num: 36, gap: 12 } : size === 'sm' ? { box: 44, num: 20, gap: 6 } : { box: 58, num: 26, gap: 9 };
const accent = tone === 'signal' ? 'var(--signal-400)' : 'var(--coral-300)';
return (
{units.map(([label, val]) => (
{String(val).padStart(2, '0')}
{label}
))}
);
}
function PartnerChip({ name, category, accent = 'var(--signal-500)', logoUrl = null, muted = false, onClick = null, style = {} }) {
const mono = name ? name.replace(/[^A-Za-zА-Яа-я0-9]/g, '').slice(0, 2).toUpperCase() : '?';
return (
{ e.currentTarget.style.borderColor = 'var(--border-strong)'; e.currentTarget.style.transform = 'translateY(-2px)'; if (onClick) e.currentTarget.style.boxShadow = '0 8px 22px rgba(0,0,0,0.08)'; }}
onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--border-subtle)'; e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'none'; }}>
{logoUrl ?
{ e.currentTarget.style.display='none'; e.currentTarget.parentNode.innerText = mono; }} /> : mono}
{name}
{category && {category}
}
{onClick &&
→}
);
}
function Dialog({ open, onClose, title, eyebrow, width = 'min(70vw, 920px)', footer, children }) {
React.useEffect(() => {
function onKey(e) { if (e.key === 'Escape' && onClose) onClose(); }
if (open) document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open, onClose]);
if (!open) return null;
return (
e.stopPropagation()} role="dialog" aria-modal="true"
style={{ position: 'relative', width, maxWidth: 920, maxHeight: '84vh', overflowY: 'auto', background: 'var(--surface-panel)', border: '1px solid var(--border-strong)', borderRadius: 'var(--radius-xl)', boxShadow: 'var(--shadow-xl), var(--glow-signal-sm)', padding: 'clamp(28px, 4vw, 52px)', animation: 'sh-dlg-pop var(--dur-slow) var(--ease-bounce)' }}>
{eyebrow &&
{eyebrow}
}
{title &&
{title}
}
{children}
{footer &&
{footer}
}
);
}
Object.assign(window, { Logo, Button, Badge, Card, Avatar, Stat, Countdown, PartnerChip, Dialog });