"use client"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useMemo, useRef } from "react"; import { ScoreLadder } from "@/components/ScoreLadder"; import { highlightRanges } from "@/lib/citations"; import type { ChunkView } from "@/lib/api"; /** * The Evidence sheet: the full clause behind a citation, with the matched span marked and * the score trail that put it there. * * It slides in as a fresh sheet of paper laid over the page, with a hard ink edge — the * conversation stays where it was, so the reader keeps their place. It is a reference * being held up beside the text, not a navigation away from it. */ export function EvidencePanel({ chunk, answerText, onClose, }: { chunk: ChunkView | null; answerText: string; onClose: () => void; }) { const closeRef = useRef(null); const open = chunk !== null; useEffect(() => { if (!open) return; closeRef.current?.focus(); const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); return ( {chunk ? ( <>

{chunk.law_label} · {chunk.part_title}

Article {chunk.article_no}

{chunk.article_title ? (

{chunk.article_title}

) : chunk.section ? (

{chunk.section}

) : null}
p.{chunk.page_start} {chunk.page_end !== chunk.page_start ? `–${chunk.page_end}` : ""} {chunk.seq_total > 1 ? ( {chunk.seq + 1}/{chunk.seq_total} ) : null}

Quoted verbatim from {chunk.law_title}. Marked spans are the sentences the answer drew on.

) : null}
); } /** The chunk text carries a context header line; the sheet shows the clause itself. */ function stripHeader(text: string): string { const newline = text.indexOf("\n"); return newline === -1 ? text : text.slice(newline + 1); } function Highlighted({ text, answer }: { text: string; answer: string }) { const ranges = useMemo(() => highlightRanges(text, answer), [text, answer]); if (ranges.length === 0) { return

{text}

; } const parts: React.ReactNode[] = []; let cursor = 0; ranges.forEach(([start, end], index) => { if (start > cursor) parts.push(text.slice(cursor, start)); parts.push({text.slice(start, end)}); cursor = end; }); if (cursor < text.length) parts.push(text.slice(cursor)); return

{parts}

; }