/* CaseDetail — renders GET /cases/{case_id} payload. * * Layout: * Desktop : meta rail (left, sticky) + reading column (right) * ≤ 960px : meta collapses above the reading column * * Identity is paramount. Provenance is compact and monospaced. The reading * column uses Source Serif at a comfortable measure. When there is more than * one opinion, a quiet pill row appears between identity and reading; for * single-opinion cases (the common shape) it does not render at all. */ const OPINION_TONE = { majority: { label: "Majority", tone: "neutral" }, combined: { label: "Opinion", tone: "neutral" }, plurality: { label: "Plurality", tone: "neutral" }, concurrence: { label: "Concurrence", tone: "concur" }, "concurrence-in-part": { label: "Concurrence in part", tone: "concur" }, dissent: { label: "Dissent", tone: "dissent" }, "dissent-in-part": { label: "Dissent in part", tone: "dissent" }, "concurrence-in-part-and-dissent-in-part": { label: "Concur/Dissent", tone: "split" }, seriatim: { label: "Seriatim", tone: "neutral" }, }; function opinionTone(code) { if (!code) return { label: "Opinion", tone: "neutral" }; const stripped = code.replace(/^0\d{2}/, ""); return OPINION_TONE[stripped] || { label: window.opinionTypeLabel(code), tone: "neutral" }; } const TONE_STYLE = { neutral: { color: "var(--ink-2)", border: "var(--rule)", bg: "var(--paper)" }, concur: { color: "var(--signal-vec)", border: "var(--signal-vec)", bg: "var(--signal-vec-bg)" }, dissent: { color: "var(--signal-fts)", border: "var(--signal-fts)", bg: "var(--signal-fts-bg)" }, split: { color: "var(--ink-2)", border: "var(--rule)", bg: "var(--paper-2)" }, }; /* ── Pieces ──────────────────────────────────────────────────── */ const MetaRow = ({ k, v, mono = false }) => ( <>
No text on file for this opinion. Use the source link to read the original.
); } const blocks = text.replace(/\r\n/g, "\n").split(/\n{2,}/); return ( <> {blocks.map((block, i) => { const t = block.trim(); if (!t) return null; // Heading-y if short and mostly CAPS (e.g. "I", "II", section headers). const isHead = t.length < 80 && /^[IVX]+\.?$/.test(t.replace(/\s+/g, "")) === false ? false : true; const isRoman = /^[IVX]+\.?$/.test(t.replace(/\s+/g, "")); if (isRoman) { return ({t}
); })} > ); }; /* ── Main component ──────────────────────────────────────────── */ const CaseDetail = ({ caseId, onHome, onBackToResults, lastSearch }) => { const [data, setData] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [activeOpId, setActiveOpId] = React.useState(null); React.useEffect(() => { let cancelled = false; setLoading(true); setError(null); setData(null); window.caseApi.fetchCase(caseId) .then((res) => { if (cancelled) return; setData(res); setActiveOpId(res.opinions[0]?.id ?? null); setLoading(false); }) .catch((err) => { if (cancelled) return; setError(err); setLoading(false); }); return () => { cancelled = true; }; }, [caseId]); if (loading) { return ({error.message}