// Category — overview grid of 8 categories. Each card is a real cover from // the latest case in that category and links to /category/?cat= which // renders the filtered case list view below. const CAT_DESC = { "凶杀": "连环、悬案、无动机与不合逻辑的谋杀。", "UFO": "目击、雷达数据、地面痕迹与电磁异常。", "灵异": "超自然现象、反复出现的异象与集体目击。", "邪教": "团体信仰、仪式、自愿者与内部档案。", "失踪": "无出口、无动机、无痕迹的失踪案件。", "传说": "口头相传、周期性、跨城市的民间叙事。", "禁地": "封闭区域、军用遗址、异常观测点。", "阴谋": "跨国档案、数据空白、被抹除的时间。", }; function latestInCat(k) { return CASES.find(c => c.cat === k) || null; } const CatCard = ({ c, i }) => { const latest = latestInCat(c.k); const unsolved = CASES.filter(x => x.cat === c.k && x.status === "未结").length; const rot = ((i % 4) - 1.5) * 0.15; return (
▸ 类别 {String(i+1).padStart(2,"0")}
{latest ? `LATEST · ${latest.date || latest.id}` : "NO FILES YET"}

{c.zh}

{c.en}

{CAT_DESC[c.k] || ""}

案卷
{c.count}
未结
{unsolved}
最新
{latest ? latest.id : "—"}
进入该类 → OPEN
); }; const CategoryIndex = () => ( <>
▸ SECTION / 分类索引

按性质分门归档

Files filed by nature — and by the particular kind of not-knowing they induce.
档案以八类收纳。点击任一类别进入该类全部案卷。数字显示该类别已归档数量与未结占比。
{CATEGORIES.filter(c => c.k !== "all").map((c, i) => )}
); const CategoryFilteredList = ({ cat, onBack }) => { const meta = CATEGORIES.find(c => c.k === cat); const cases = CASES.filter(c => c.cat === cat) .sort((a, b) => (b.published_ts || 0) - (a.published_ts || 0)); return ( <>
{ e.preventDefault(); onBack(); }} className="mono" style={{ fontSize: 11, letterSpacing: 3, color: "#a89b78", marginBottom: 14, display: "inline-block", }}>← 返回全部分类 / ALL CATEGORIES

{meta ? meta.zh : cat}

{meta ? meta.en : ""} · {CAT_DESC[cat] || ""}
该类案卷 · {cases.length}
其中未结 · {cases.filter(c => c.status === "未结").length}
{cases.length === 0 ? (
该分类下暂无案卷。
) : (
{cases.map((item, i) => ( ))}
)}
); }; const App = () => { useDataReady(); const [cat, setCat] = React.useState(() => { const p = new URLSearchParams(location.search); return p.get("cat") || ""; }); // Keep URL in sync when filter changes so shared links work. React.useEffect(() => { const url = new URL(location.href); if (cat) url.searchParams.set("cat", cat); else url.searchParams.delete("cat"); history.replaceState(null, "", url); }, [cat]); if (!window.DATA_READY) { return (<> ); } return ( <> {cat ? setCat("")} /> : } ); }; ReactDOM.createRoot(document.getElementById("root")).render();