import { type ReactNode, useEffect } from 'react' import { X } from 'lucide-react' import { num } from '../lib/format' // Modal is a dependency free overlay: a dimmed backdrop plus a centered, scrollable panel. Escape and a // backdrop click close it. The body scroll is locked while it is open so a tall result does not jump. export function Modal({ open, onClose, title, children, }: { open: boolean onClose: () => void title: string children: ReactNode }) { useEffect(() => { if (!open) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', onKey) const prev = document.body.style.overflow document.body.style.overflow = 'hidden' return () => { document.removeEventListener('keydown', onKey) document.body.style.overflow = prev } }, [open, onClose]) if (!open) return null return (
e.stopPropagation()} >

{title}

{children}
) } export function Panel({ children, className = '', title, hint, }: { children: ReactNode className?: string title?: string hint?: string }) { return (
{title && (

{title}

{hint && {hint}}
)}
{children}
) } export function Bar({ value, color }: { value: number; color: string }) { return (
) } export function Badge({ children, color, subtle = false, }: { children: ReactNode color: string subtle?: boolean }) { return ( {children} ) } export function Dot({ color }: { color: string }) { return } export function Stat({ label, value, accent, icon, }: { label: string value: ReactNode accent?: string icon?: ReactNode }) { return (
{label} {icon && {icon}}
{value}
) } // CheckRow renders a met (✓) / unmet (⚠) requirement line. export function CheckRow({ ok, label }: { ok: boolean; label: string }) { return (
{ok ? '✓' : '!'} {label}
) } export function ScoreRow({ name, weight, score, color, }: { name: string weight: string score: string color: string }) { return (
{name} {Math.round(Number(weight) * 100)}%
{num(score).toFixed(0)}%
) }