import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { COLOR } from "@/lib/constants"; import { cn } from "@/lib/utils"; import { ChevronDown } from "lucide-react"; import { useState } from "react"; interface Props { lines: string[]; } // ALL_CAPS_WORD: — no lowercase letters in key const CAPS_RE = /^([A-Z][A-Z0-9_]+):\s*([\s\S]*)/; // Title Case Word: — starts capital, must contain at least one lowercase const TITLE_RE = /^([A-Z][a-zA-Z0-9][a-zA-Z0-9 ]*):\s*([\s\S]*)/; type ParsedLine = | { kind: "fact"; text: string } | { kind: "caps"; key: string; value: string } | { kind: "title"; key: string; value: string }; const PALETTE: Array<{ bg: string; text: string; border: string; dot: string }> = [ { bg: "rgba(52,211,153,0.08)", text: "#34d399", border: "rgba(52,211,153,0.25)", dot: "#34d399" }, { bg: "rgba(245,158,11,0.08)", text: "#f59e0b", border: "rgba(245,158,11,0.25)", dot: "#f59e0b" }, { bg: "rgba(14,165,233,0.08)", text: "#38bdf8", border: "rgba(14,165,233,0.25)", dot: "#38bdf8" }, { bg: "rgba(236,72,153,0.08)", text: "#f472b6", border: "rgba(236,72,153,0.25)", dot: "#f472b6" }, { bg: "rgba(168,85,247,0.08)", text: "#c084fc", border: "rgba(168,85,247,0.25)", dot: "#c084fc" }, { bg: "rgba(239,68,68,0.08)", text: "#f87171", border: "rgba(239,68,68,0.25)", dot: "#f87171" }, { bg: "rgba(34,197,94,0.08)", text: "#4ade80", border: "rgba(34,197,94,0.25)", dot: "#4ade80" }, { bg: "rgba(251,146,60,0.08)", text: "#fb923c", border: "rgba(251,146,60,0.25)", dot: "#fb923c" }, ]; function hashPalette(word: string): number { let h = 5381; for (let i = 0; i < word.length; i++) h = ((h * 33) ^ word.charCodeAt(i)) >>> 0; return h % PALETTE.length; } function toLabel(key: string): string { const s = key.toLowerCase().replace(/_/g, " "); return s.charAt(0).toUpperCase() + s.slice(1); } function parseLine(line: string): ParsedLine { const caps = CAPS_RE.exec(line); if (caps) return { kind: "caps", key: caps[1], value: caps[2].trim() }; const title = TITLE_RE.exec(line); if (title && /[a-z]/.test(title[1])) { return { kind: "title", key: title[1], value: title[2].trim() }; } return { kind: "fact", text: line }; } interface CapsGroup { key: string; items: string[]; } interface Parsed { titlePairs: Array<{ key: string; value: string }>; facts: string[]; capsGroups: CapsGroup[]; } function parse(lines: string[]): Parsed { const titlePairs: Array<{ key: string; value: string }> = []; const facts: string[] = []; const capsMap = new Map(); const capsOrder: string[] = []; for (const line of lines) { const p = parseLine(line); if (p.kind === "title") { titlePairs.push({ key: p.key, value: p.value }); } else if (p.kind === "caps") { if (!capsMap.has(p.key)) { capsMap.set(p.key, []); capsOrder.push(p.key); } capsMap.get(p.key)?.push(p.value); } else { facts.push(p.text); } } return { titlePairs, facts, capsGroups: capsOrder.map((k) => ({ key: k, items: capsMap.get(k) ?? [] })), }; } // ─── Metadata table (Title Case: pairs) ────────────────────────────────────── function MetadataCard({ pairs }: { pairs: Array<{ key: string; value: string }> }) { if (pairs.length === 0) return null; return (
{pairs.map(({ key, value }, i) => (
{key}
{value || }
))}
); } // ─── Collapsible section (ALL_CAPS: groups + Facts) ─────────────────────────── interface SectionStyle { bg: string; text: string; border: string; } const FACTS_STYLE: SectionStyle = { bg: COLOR.accentDim, text: "#a5b4fc", border: COLOR.accentBorder, }; function CollapsibleSection({ label, count, style, children, }: { label: string; count: number; style: SectionStyle; children: React.ReactNode; }) { const [open, setOpen] = useState(true); return (
{children}
); } function ItemList({ items }: { items: string[] }) { return ( ); } // ─── Export ─────────────────────────────────────────────────────────────────── export function PeerCardViewer({ lines }: Props) { if (!lines || lines.length === 0) { return (

No card set.

); } const { titlePairs, facts, capsGroups } = parse(lines); return (
{facts.length > 0 && ( )} {capsGroups.map((g) => { const p = PALETTE[hashPalette(g.key)]; return ( ); })}
); }