/** * Semantic badges for enum-valued frontmatter fields. * - ConfidenceBand: high/medium/low/speculation * - ClassificationLevel: UNCLASSIFIED/CUI/CONFIDENTIAL/SECRET/TOP SECRET (+caveats) * - EnrichmentStatus: deep/shallow/none * - ContentClass: 11 content_classification enum values * - PageType: cover/body/signature/... 14 types * - Generic chip with optional icon */ import { Eye, EyeOff, FileText, FileImage, FileWarning, ShieldAlert, ShieldCheck, Check, Clock, AlertTriangle, Stamp, PenSquare, Table2, Map as MapIcon, Image as ImageIcon, Calendar, MapPin, Tag, Network, FileSearch, Globe, } from "lucide-react"; import type { ConfidenceBand, ClassificationLevel, EnrichmentStatus, ContentClass, } from "@/lib/fm-types"; /* ── ConfidenceBand ─────────────────────────────────────────── */ const CONF_STYLES: Record = { high: "bg-[rgba(0,255,156,0.12)] text-[#00ff9c] border-[#00ff9c]", medium: "bg-[rgba(127,219,255,0.10)] text-[#7fdbff] border-[#7fdbff]", low: "bg-[rgba(245,197,66,0.10)] text-[#f5c542] border-[#f5c542]", speculation: "bg-[rgba(187,107,217,0.10)] text-[#bb6bd9] border-[#bb6bd9]", }; export function FmConfidence({ band }: { band?: ConfidenceBand }) { if (!band) return null; return ( ▎ {band} ); } /* ── ClassificationLevel ────────────────────────────────────── */ const CLASS_STYLES: Record = { "UNCLASSIFIED": "bg-[rgba(63,222,106,0.10)] text-[#3fde6a] border-[#3fde6a]", "CUI": "bg-[rgba(127,219,255,0.10)] text-[#7fdbff] border-[#7fdbff]", "CONFIDENTIAL": "bg-[rgba(245,197,66,0.10)] text-[#f5c542] border-[#f5c542]", "SECRET": "bg-[rgba(255,138,77,0.12)] text-[#ff8a4d] border-[#ff8a4d]", "TOP SECRET": "bg-[rgba(255,51,68,0.15)] text-[#ff3344] border-[#ff3344] font-bold", }; export function FmClassification({ level, caveats }: { level?: ClassificationLevel; caveats?: string[] }) { if (!level) return null; return ( {level} {caveats && caveats.length > 0 && // {caveats.join(", ")}} ); } /* ── EnrichmentStatus ──────────────────────────────────────── */ const ENR_STYLES: Record = { deep: { cls: "bg-[rgba(0,255,156,0.12)] text-[#00ff9c] border-[#00ff9c]", icon: }, shallow: { cls: "bg-[rgba(127,219,255,0.10)] text-[#7fdbff] border-[#7fdbff]", icon: }, none: { cls: "bg-[rgba(90,102,120,0.10)] text-[#5a6678] border-[#5a6678]", icon: }, }; export function FmEnrichmentBadge({ status }: { status?: EnrichmentStatus }) { if (!status) return null; const s = ENR_STYLES[status]; return ( {s.icon} {status} ); } /* ── ContentClass ──────────────────────────────────────────── */ const CONTENT_ICON: Record = { "text-only": , "contains-photos": , "contains-sketches": , "contains-diagrams": , "contains-maps": , "contains-tables": , "contains-signatures":, "contains-stamps": , "redaction-heavy": , "mixed": , "blank": , }; export function FmContentChip({ kind }: { kind: ContentClass }) { const icon = CONTENT_ICON[kind] ?? ; return ( {icon} {kind} ); } /* ── PageType ──────────────────────────────────────────────── */ export function FmPageTypeChip({ type }: { type?: string }) { if (!type) return null; const accent = type === "redaction-heavy" ? "text-[#ff3344] border-[#ff3344]" : type === "signature" ? "text-[#bb6bd9] border-[#bb6bd9]" : type === "table-page" ? "text-[#1e9eb5] border-[#1e9eb5]" : type === "map" ? "text-[#3fde6a] border-[#3fde6a]" : type === "photo" ? "text-[#ffeb99] border-[#ffeb99]" : type === "sketch" ? "text-[#ff8a4d] border-[#ff8a4d]" : type === "cover" ? "text-[#f5c542] border-[#f5c542]" : type === "blank" ? "text-[#5a6678] border-[#5a6678]" : "text-[#7fdbff] border-[#7fdbff]"; return ( {type} ); } /* ── Generic chips ─────────────────────────────────────────── */ export function FmChip({ icon, label, color = "cyan" }: { icon?: React.ReactNode; label: React.ReactNode; color?: "cyan" | "amber" | "green" | "red" | "violet" | "soft"; }) { const cls = color === "amber" ? "text-[#f5c542] border-[rgba(245,197,66,0.32)] bg-[rgba(245,197,66,0.06)]" : color === "green" ? "text-[#3fde6a] border-[rgba(63,222,106,0.32)] bg-[rgba(63,222,106,0.06)]" : color === "red" ? "text-[#ff3344] border-[rgba(255,51,68,0.32)] bg-[rgba(255,51,68,0.06)]" : color === "violet" ? "text-[#bb6bd9] border-[rgba(187,107,217,0.32)] bg-[rgba(187,107,217,0.06)]" : color === "soft" ? "text-[#8896aa] border-[rgba(136,150,170,0.32)] bg-[rgba(136,150,170,0.04)]" : "text-[#7fdbff] border-[rgba(127,219,255,0.32)] bg-[rgba(127,219,255,0.06)]"; return ( {icon} {label} ); } export function FmStat({ icon, label, value, color = "cyan" }: { icon?: React.ReactNode; label: string; value: React.ReactNode; color?: "cyan" | "amber" | "green" | "red" | "violet" | "soft"; }) { const c = color === "amber" ? "text-[#f5c542]" : color === "green" ? "text-[#3fde6a]" : color === "red" ? "text-[#ff3344]" : color === "violet" ? "text-[#bb6bd9]" : color === "soft" ? "text-[#8896aa]" : "text-[#7fdbff]"; return (
{icon} {label} {value}
); } /* ── Language code ─────────────────────────────────────────── */ const LANG_FLAG: Record = { en: "🇬🇧", pt: "🇧🇷", es: "🇪🇸", fr: "🇫🇷", de: "🇩🇪", ru: "🇷🇺", unknown: "❓", }; export function FmLanguageChip({ code }: { code?: string }) { if (!code) return null; return ( {LANG_FLAG[code] ?? "🌐"} {code} ); } /* ── Date ──────────────────────────────────────────────────── */ export function FmDate({ value }: { value?: string }) { if (!value) return null; return ( {value} ); } /* ── Coordinates ───────────────────────────────────────────── */ export function FmCoordinates({ lat, lon, raw }: { lat?: number | null; lon?: number | null; raw?: string }) { const ll = (lat !== null && lat !== undefined && lon !== null && lon !== undefined) ? `${lat.toFixed(5)}, ${lon.toFixed(5)}` : raw; if (!ll) return null; const href = (lat !== null && lat !== undefined && lon !== null && lon !== undefined) ? `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}#map=8/${lat}/${lon}` : undefined; const inner = ( {ll} ); return href ? {inner} : inner; } /* ── Quality dot (0..1) ────────────────────────────────────── */ export function FmQualityDot({ value, label }: { value?: number; label?: string }) { if (value === undefined || value === null) return null; const pct = Math.round(value * 100); const c = pct >= 90 ? "text-[#00ff9c]" : pct >= 75 ? "text-[#7fdbff]" : pct >= 50 ? "text-[#f5c542]" : "text-[#ff3344]"; return ( {label && {label}} {pct}% ); } /* ── Generic flag chip ─────────────────────────────────────── */ export function FmFlag({ flag }: { flag: string }) { const isWarn = /low|miss|fail|heavy|rotat/i.test(flag); return ( {flag} ); } export function FmTimestamp({ value, label }: { value?: string; label?: string }) { if (!value) return null; return ( {label && {label}:} {value.replace("T", " ").replace("Z", " UTC")} ); }