diff --git a/README.md b/README.md
index b9cfc02..9b7f0e3 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ tools/figures/ 独立 Python 项目,使用 drawsvg 编程制图
- `pnpm start`:在本地预览已构建站点。
- `pnpm check`:依次执行 lint、类型检查、制图与构建。
-站点默认浅色,使用本地系统字体。全站搜索支持正文关键词、`⌘/Ctrl K`、方向键与 Enter;代码块提供复制与反馈。桌面提供分组导航和滚动高亮的页内目录,小屏可展开文档菜单与页内目录。首页入口、草案提示、前后篇导航均引用现有 Markdown 内容和资料目录配置。
+站点默认浅色,使用本地系统字体。全站搜索支持正文关键词、`⌘/Ctrl K`、方向键与 Enter;代码块提供复制与反馈。桌面提供分组导航和滚动高亮的页内目录,小屏可展开文档菜单与页内目录。首页入口、草案提示、前后篇导航均引用现有 Markdown 内容和资料目录配置。顶部提供文档切换,阅读页左侧只展示当前文档,并有独立图标、名称与返回入口;搜索框标明当前文档范围。界面主要通过留白、轻渐变与状态反馈分层,支持减少动态效果的系统偏好。
参考设计截图与研究记录保存在 `design/references`;浏览器验收截图与检查说明保存在 `design/verification`。这些文件不作为站点静态资源发布。
diff --git a/apps/docs/src/components/Icon.tsx b/apps/docs/src/components/Icon.tsx
index ad6424c..41f46c9 100644
--- a/apps/docs/src/components/Icon.tsx
+++ b/apps/docs/src/components/Icon.tsx
@@ -2,10 +2,13 @@ import type { CSSProperties } from "react";
// One stroke style for controls and navigation; labels carry the meaning.
export function Icon({ name, size = 18, style }: {
- name: "search" | "arrow" | "book" | "code" | "menu" | "close";
+ name: "search" | "arrow" | "book" | "code" | "menu" | "close" | "layers" | "nodes" | "chevron";
size?: number; style?: CSSProperties;
}) {
const paths = {
+ layers: <>>,
+ nodes: <>>,
+ chevron: ,
search: <>>,
arrow: ,
book: <>>,
diff --git a/apps/docs/src/components/Shell.tsx b/apps/docs/src/components/Shell.tsx
index 665ea7b..ef4880f 100644
--- a/apps/docs/src/components/Shell.tsx
+++ b/apps/docs/src/components/Shell.tsx
@@ -2,6 +2,7 @@ import Link from "next/link";
import { Icon } from "@/components/Icon";
import { useEffect, useRef, useState, type ReactNode } from "react";
import type { DocLink } from "@/lib/docs";
+import { collectionEntries, collectionIcon, collectionLabel, collectionTone } from "@/lib/collections";
export function Brand() {
@@ -13,10 +14,9 @@ export function Brand() {
- Endor /{" "}
Tabularium
- 文档
+ Docs
);
}
@@ -32,51 +32,36 @@ export function Navigation({
const collection = docs.find((d) => d.slug === current)?.collection;
const scopedDocs = docs.filter((d) => d.collection === collection);
const groups = [...new Set(scopedDocs.map((d) => d.group))];
- const collections = [...new Set(docs.map((d) => d.collection))];
+ const collections = collectionEntries(docs);
return (
);
}
+
export function Shell({
docs,
current,
@@ -96,6 +81,7 @@ export function Shell({
const [selected, setSelected] = useState(0);
const [menu, setMenu] = useState(false);
const collection = docs.find((d) => d.slug === current)?.collection;
+ const collections = collectionEntries(docs);
const results = searchDocs.filter((d) =>
(!collection || d.collection === collection) &&
`${d.title} ${d.description} ${d.searchText ?? ""}`
@@ -134,18 +120,15 @@ export function Shell({
return () => document.removeEventListener("keydown", keyboard);
}, []);
return (
- <>
+
跳到正文
@@ -208,7 +191,7 @@ export function Shell({
{
@@ -264,7 +247,7 @@ export function Shell({
))}
{!results.length && (
- 没有找到相关文档。试试「节点」「SVG」或「pnpm」。
+ 没有找到相关内容,请尝试其他关键词。
)}
@@ -272,6 +255,6 @@ export function Shell({
↑ ↓ 选择 Enter 打开 · Esc 关闭
- >
+
);
}
diff --git a/apps/docs/src/lib/collections.ts b/apps/docs/src/lib/collections.ts
new file mode 100644
index 0000000..49ad668
--- /dev/null
+++ b/apps/docs/src/lib/collections.ts
@@ -0,0 +1,24 @@
+import type { DocLink } from "@/lib/docs";
+
+export function collectionLabel(name: string) {
+ return name.replace(/ 文档$| 源码教程$|问题集$/g, "");
+}
+export function collectionTone(name?: string) {
+ if (name?.includes("Pretext")) return "blue";
+ if (name?.includes("语言设计")) return "violet";
+ if (name?.includes("垃圾回收")) return "amber";
+ return "green";
+}
+export function collectionIcon(name?: string): "book" | "code" | "layers" | "nodes" {
+ if (name?.includes("Pretext")) return "code";
+ if (name?.includes("语言设计")) return "layers";
+ if (name?.includes("垃圾回收")) return "nodes";
+ return "book";
+}
+export function collectionEntries(docs: DocLink[]) {
+ return [...new Set(docs.map((doc) => doc.collection))].map((name) => {
+ const items = docs.filter((doc) => doc.collection === name);
+ const entry = items.find((doc) => doc.slug.endsWith("/00-overview")) ?? items[0];
+ return { name, entry, count: items.length };
+ });
+}
diff --git a/apps/docs/src/pages/docs/[...slug].tsx b/apps/docs/src/pages/docs/[...slug].tsx
index 4667eea..c92dd88 100644
--- a/apps/docs/src/pages/docs/[...slug].tsx
+++ b/apps/docs/src/pages/docs/[...slug].tsx
@@ -18,6 +18,8 @@ import { listDocs, readDoc, type Doc, type DocLink } from "@/lib/docs";
import { Shell, Navigation } from "@/components/Shell";
import { ArchitectureFigure } from "@/components/ArchitectureFigure";
import { CodeBlock } from "@/components/CodeBlock";
+import { collectionEntries, collectionLabel } from "@/lib/collections";
+import { Icon } from "@/components/Icon";
import { headingsFor } from "@/lib/navigation";
type Props = { doc: Doc; docs: DocLink[] };
@@ -49,6 +51,7 @@ export default function DocPage({
const [active, setActive] = useState("");
const siblings = docs.filter((d) => d.collection === doc.collection);
+ const collectionEntry = collectionEntries(docs).find((c) => c.name === doc.collection)!.entry;
const index = siblings.findIndex((d) => d.slug === doc.slug);
useEffect(() => {
const observer = new IntersectionObserver(
@@ -90,9 +93,9 @@ export default function DocPage({
- 文档
- /
- {doc.group}
+ {collectionLabel(doc.collection)}
+
+ {doc.slug === collectionEntry.slug ? "概览" : doc.group === doc.collection ? doc.title : doc.group}
{doc.draft && 草案}
diff --git a/apps/docs/src/pages/index.tsx b/apps/docs/src/pages/index.tsx
index d82369f..56a9f92 100644
--- a/apps/docs/src/pages/index.tsx
+++ b/apps/docs/src/pages/index.tsx
@@ -5,6 +5,7 @@ import { useState } from "react";
import { listDocs, type DocLink } from "@/lib/docs";
import { Shell, Navigation } from "@/components/Shell";
import { Icon } from "@/components/Icon";
+import { collectionEntries, collectionIcon, collectionLabel, collectionTone } from "@/lib/collections";
export const getStaticProps: GetStaticProps<{ docs: DocLink[] }> = async () => ({
props: { docs: await listDocs() },
@@ -17,7 +18,7 @@ export default function Home({ docs }: InferGetStaticPropsType doc.collection))];
const filtered = docs.filter((doc) => (!collection || doc.collection === collection) &&
`${doc.title} ${doc.description}`.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
- const featured = docs.filter((doc) => doc.featured).slice(0, 3);
+ const entries = collectionEntries(docs);
return <>
Tabularium · 资料库
@@ -25,16 +26,16 @@ export default function Home({ docs }: InferGetStaticPropsType
- 资料库
+ 文档中心
系统设计、源码研究与学习笔记。
- 推荐阅读
- {featured.map((doc) =>
-
-
{doc.title.replace("(草案)", "")}
-
{doc.description}
-
+
选择一份文档
+
{entries.map(({ name, entry, count }) =>
+
+
{collectionLabel(name)}
+
{entry.description}
+
{count} 篇
)}
diff --git a/apps/docs/src/styles/globals.css b/apps/docs/src/styles/globals.css
index 9fb3fa8..8c7971f 100644
--- a/apps/docs/src/styles/globals.css
+++ b/apps/docs/src/styles/globals.css
@@ -55,7 +55,7 @@ button {
}
.site-header {
height: 68px;
- border-bottom: 1px solid var(--line);
+ border: 0;
display: flex;
align-items: center;
padding: 0 32px;
@@ -82,9 +82,9 @@ button {
color: #c8ceca;
}
.docs-badge {
- font-size: 10px;
+ font-size: 13px;
padding: 2px 5px;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 4px;
color: var(--muted);
letter-spacing: 0;
@@ -123,7 +123,7 @@ button {
display: flex;
gap: 9px;
align-items: center;
- border: 1px solid #e2e7e3;
+ border: 0;
background: #fafbf9;
border-radius: 7px;
padding: 7px 10px;
@@ -138,7 +138,7 @@ button {
}
.search-trigger kbd {
margin-left: auto;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 3px;
padding: 0 4px;
font-size: 10px;
@@ -163,7 +163,7 @@ button {
grid-template-columns: 238px minmax(0, 1fr);
}
.sidebar {
- border-right: 1px solid var(--line);
+ border: 0;
padding: 32px 22px;
height: calc(100dvh - 68px);
position: sticky;
@@ -200,7 +200,7 @@ button {
padding: 9px 10px;
line-height: 1.6;
color: #637066;
- border-left: 1px solid var(--line);
+ border: 0;
margin-left: 10px;
}
.nav-item:hover {
@@ -225,9 +225,9 @@ button {
padding: 0 48px;
}
.eyebrow { font-size: 13px; font-weight: 500; color: var(--muted); margin: 0; }
-.draft-badge { color: #8c733e; background: #faf6e9; border: 1px solid #eee4c9; border-radius: 4px; padding: 2px 6px; font-size: 12px; white-space: nowrap; }
+.draft-badge { color: #8c733e; background: #faf6e9; border: 0; border-radius: 4px; padding: 2px 6px; font-size: 12px; white-space: nowrap; }
.site-footer {
- border-top: 1px solid var(--line);
+ border: 0;
display: flex;
justify-content: space-between;
gap: 15px;
@@ -324,7 +324,7 @@ button {
SFMono-Regular,
Consolas,
monospace;
- border: 1px solid #e7ece7;
+ border: 0;
background: #f6f8f5;
border-radius: 4px;
padding: 2px 5px;
@@ -344,7 +344,7 @@ button {
min-width: 360px;
margin: 0;
font-size: 13px;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 7px;
}
.prose thead {
@@ -357,7 +357,7 @@ button {
min-width: 100px;
}
.prose img {
- border: 1px solid var(--line);
+ border: 0;
border-radius: 8px;
padding: 12px;
background: #fafbf8;
@@ -366,7 +366,7 @@ button {
.callout {
display: flex;
gap: 12px;
- border: 1px solid #e2e9e0;
+ border: 0;
padding: 15px 18px;
border-radius: 7px;
background: #f8faf6;
@@ -392,7 +392,7 @@ button {
font-size: 16px;
}
.code-block {
- border: 1px solid #e1e7e0;
+ border: 0;
border-radius: 8px;
overflow: hidden;
background: #f9fbf7;
@@ -404,7 +404,7 @@ button {
justify-content: space-between;
align-items: center;
padding: 10px 15px;
- border-bottom: 1px solid #e7ece4;
+ border: 0;
color: #879781;
font:
10px ui-monospace,
@@ -415,7 +415,7 @@ button {
}
.code-toolbar button {
padding: 2px 6px;
- border: 1px solid #dce5d6;
+ border: 0;
border-radius: 4px;
font-size: 11px;
min-height: 28px;
@@ -472,13 +472,13 @@ button {
margin-bottom: 18px;
}
.toc nav {
- border-left: 1px solid var(--line);
+ border: 0;
}
.toc nav a {
display: block;
padding: 7px 0 7px 15px;
margin-left: -1px;
- border-left: 1px solid transparent;
+ border: 0;
color: #69766b;
line-height: 1.6;
}
@@ -494,7 +494,7 @@ button {
display: flex;
gap: 15px;
justify-content: space-between;
- border-top: 1px solid var(--line);
+ border: 0;
padding: 25px 0;
margin-top: 45px;
font-size: 10px;
@@ -510,7 +510,7 @@ button {
margin: 10px 0 45px;
}
.pagination > a {
- border: 1px solid var(--line);
+ border: 0;
padding: 18px;
border-radius: 7px;
}
@@ -533,7 +533,7 @@ button {
}
.search-dialog {
padding: 0;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 12px;
width: min(570px, calc(100vw - 32px));
max-height: 80dvh;
@@ -550,7 +550,7 @@ button {
align-items: center;
gap: 13px;
padding: 17px;
- border-bottom: 1px solid var(--line);
+ border: 0;
}
.search-input-row > span {
font-size: 25px;
@@ -564,7 +564,7 @@ button {
}
.search-input-row button {
font-size: 10px;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 4px;
padding: 3px 5px;
}
@@ -578,7 +578,7 @@ button {
}
.search-result {
display: block;
- border: 1px solid transparent;
+ border: 0;
padding: 13px;
border-radius: 7px;
}
@@ -612,7 +612,7 @@ button {
}
.search-footer {
padding: 12px 20px;
- border-top: 1px solid var(--line);
+ border: 0;
font-size: 10px;
color: #9da797;
display: flex;
@@ -656,7 +656,7 @@ button {
.mobile-toc {
display: block;
margin: 18px 0;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 6px;
padding: 10px 15px;
font-size: 12px;
@@ -699,7 +699,7 @@ button {
top: 62px;
z-index: 25;
background: #fff;
- border-bottom: 1px solid var(--line);
+ border: 0;
}
.mobile-bar button {
display: flex;
@@ -716,7 +716,7 @@ button {
.mobile-navigation {
display: block;
padding: 15px 22px;
- border-bottom: 1px solid var(--line);
+ border: 0;
max-height: 65dvh;
overflow: auto;
}
@@ -833,6 +833,7 @@ button {
*,
*::before,
*::after {
+ animation: none !important;
transition: none !important;
}
}
@@ -852,7 +853,7 @@ button {
.table-scroll {
overflow-x: auto;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 7px;
margin: 24px 0;
}
@@ -981,7 +982,7 @@ button {
.auth-card {
width: min(390px, calc(100vw - 40px));
padding: 36px;
- border: 1px solid var(--line);
+ border: 0;
border-radius: 12px;
background: white;
box-shadow: 0 15px 45px #34523c06;
@@ -1007,7 +1008,7 @@ button {
.auth-card input {
width: 100%;
padding: 13px;
- border: 1px solid #d8e2d7;
+ border: 0;
border-radius: 6px;
font:
24px ui-monospace,
@@ -1060,7 +1061,7 @@ button {
.library-intro p { margin: 0; font-size: 16px; color: var(--muted); }
.featured-section h2, .library-section h2 { font-size: 19px; font-weight: 550; margin: 0 0 22px; }
.featured-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 20px; }
-.featured-link { position: relative; border: 1px solid var(--line); border-radius: 8px; padding: 26px; }
+.featured-link { position: relative; border: 0; border-radius: 8px; padding: 26px; }
.featured-link:hover { border-color: #a4b7aa; background: #fafcfb; }
.featured-link > svg:first-child { color: var(--green); margin-bottom: 18px; }
.featured-link h3 { font-size: 18px; font-weight: 550; line-height: 1.6; margin: 0 0 10px; padding-right: 18px; }
@@ -1071,11 +1072,11 @@ button {
.collection-filters { display: flex; gap: 6px; flex-wrap: wrap; }
.collection-filters button { font-size: 14px; padding: 8px 12px; border-radius: 5px; color: var(--muted); }
.collection-filters button[aria-pressed="true"] { background: #eef4f0; color: var(--green); }
-.library-search { display: flex; align-items: center; gap: 8px; color: var(--muted); border: 1px solid var(--line); border-radius: 6px; padding: 9px 12px; }
+.library-search { display: flex; align-items: center; gap: 8px; color: var(--muted); border: 0; border-radius: 6px; padding: 9px 12px; }
.library-search input { min-width: 0; width: 145px; outline: 0; font-size: 14px; }
.library-search:focus-within { border-color: var(--green); }
-.library-list { padding: 0; margin: 0; list-style: none; border-top: 1px solid var(--line); }
-.library-list li { border-bottom: 1px solid var(--line); }
+.library-list { padding: 0; margin: 0; list-style: none; border: 0; }
+.library-list li { border: 0; }
.library-list a { display: flex; align-items: center; gap: 18px; padding: 20px 4px; }
.library-list a:hover { color: var(--green); background: #fafcfb; }
.library-row-title { flex: 1; font-size: 15px; line-height: 1.7; }
@@ -1105,3 +1106,159 @@ button {
.site-footer { font-size: 13px; }
}
.search-status { padding: 12px 20px 0; font-size: 13px; color: var(--muted); }
+
+/* Shared typography; each document keeps its own identity and reading context. */
+:root { --ink: #202724; --muted: #707873; --green: #27745b; --accent-wash: #e9f2ec; --ease: cubic-bezier(.2,.7,.2,1); }
+body { background: #fdfefd; }
+svg { flex-shrink: 0; }
+[data-tone="blue"] { --green: #456da2; --accent-wash: #eaf0f9; }
+[data-tone="violet"] { --green: #79609e; --accent-wash: #f1ecf8; }
+[data-tone="amber"] { --green: #927346; --accent-wash: #f6f0e6; }
+.site-header { height: 64px; padding: 0 30px; gap: 32px; border-bottom: 1px solid #edf0ee; background: #fdfefded; backdrop-filter: blur(18px); }
+.brand { gap: 10px; }
+.brand-mark i { background: linear-gradient(160deg,#80b69a,#27745b); }
+.docs-badge { color: #819087; padding: 0; font-size: 13px; }
+.top-nav { align-self: stretch; align-items: stretch; gap: 26px; margin-left: 16px; }
+.top-nav a, .top-nav a:nth-child(2) { display: flex; align-items: center; position: relative; white-space: nowrap; transition: color 180ms; }
+.top-nav a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--green); transform: scaleX(0); transform-origin: left; transition: transform 220ms var(--ease); }
+.top-nav a:hover, .top-nav a[aria-current="page"] { color: var(--ink); }
+.top-nav a[aria-current="page"]::after { transform: scaleX(1); }
+.search-trigger { margin-left: auto; width: 218px; padding: 8px 12px; background: #f0f3f1; color: #768078; transition: background 180ms,box-shadow 180ms; }
+.search-trigger:hover { background: #eaf0ec; box-shadow: 0 3px 16px #1c3c2810; }
+.search-trigger span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
+.search-trigger kbd { background: transparent; font-size: 11px; white-space: nowrap; }
+.logout-button { font-size: 12px; }
+.home-layout { grid-template-columns: 272px minmax(0,1fr); }
+.reading-layout { grid-template-columns: 272px minmax(0,1fr) 222px; }
+.sidebar { padding: 30px 26px 40px; height: calc(100dvh - 64px); top: 64px; scrollbar-width: thin; scrollbar-color: #d9e2dc transparent; }
+.nav-back { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: #8a948e; margin: 0 8px 28px; transition: color 160ms; }
+.nav-back:hover { color: var(--green); }
+.collection-identity { display: flex; gap: 12px; align-items: center; color: var(--green); font-size: 16px; font-weight: 550; margin: 0 8px 28px; line-height: 1.5; }
+.collection-nav-link { display: flex; align-items: center; gap: 12px; padding: 12px 10px; margin-bottom: 6px; border-radius: 7px; color: #647169; transition: background 180ms,color 180ms; }
+.collection-nav-link svg:last-child { margin-left: auto; opacity: 0; transform: translateX(-3px); transition: opacity 180ms,transform 180ms; }
+.collection-nav-link:hover { background: #f0f5f1; color: var(--green); }
+.collection-nav-link:hover svg:last-child { opacity: 1; transform: none; }
+.nav-group { margin-top: 28px; }
+.nav-group > p { font-size: 12px; font-weight: 500; color: #909991; margin: 0 10px 10px; }
+.nav-item { position: relative; padding: 8px 10px; margin: 2px 0; border-radius: 5px; line-height: 1.65; color: #68756d; transition: color 160ms,background 160ms; }
+.nav-item:hover { color: var(--green); background: color-mix(in srgb,var(--accent-wash) 45%,transparent); }
+.nav-item[aria-current="page"] { color: var(--green); background: linear-gradient(90deg,var(--accent-wash),transparent); }
+.nav-item[aria-current="page"]::before { content: ""; position: absolute; left: -3px; top: 10px; bottom: 10px; width: 2px; border-radius: 2px; background: var(--green); }
+.home-main { max-width: 1160px; }
+.library-intro { position: relative; padding: 68px 0 46px; isolation: isolate; }
+.library-intro::before { content: ""; position: absolute; z-index: -1; width: 420px; max-width: 100%; height: 240px; top: 0; left: -30px; background: radial-gradient(ellipse at 30% 30%,#eaf3ed99,transparent 70%); pointer-events: none; }
+.library-intro h1 { font-weight: 550; }
+.featured-grid { grid-template-columns: repeat(auto-fit,minmax(min(100%,220px),1fr)); }
+.featured-link { display: flex; flex-direction: column; padding: 24px; border-radius: 12px; background: linear-gradient(145deg,color-mix(in srgb,var(--accent-wash) 65%,white),#fbfcfb 65%); transition: transform 240ms var(--ease),box-shadow 240ms var(--ease); }
+.featured-link:hover { background: linear-gradient(145deg,var(--accent-wash),#fbfcfb 80%); transform: translateY(-4px); box-shadow: 0 14px 36px #1d35220d; }
+.collection-art { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; color: var(--green); background: linear-gradient(145deg,#ffffffc9,var(--accent-wash)); margin-bottom: 26px; box-shadow: 0 2px 8px #25483705; transition: transform 240ms var(--ease); }
+.featured-link:hover .collection-art { transform: translateY(-2px) rotate(-4deg); }
+.featured-link h3 { font-size: 19px; margin: 0 0 12px; padding: 0; }
+.featured-link p { flex: 1; }
+.collection-entry { display: flex; align-items: center; justify-content: space-between; margin-top: 28px; color: var(--green); font-size: 13px; }
+.collection-entry svg { transition: transform 200ms var(--ease); }
+.featured-link:hover .collection-entry svg { transform: translateX(4px); }
+.collection-filters { gap: 4px; }
+.collection-filters button { font-size: 13px; transition: background 180ms,color 180ms; }
+.collection-filters button:hover { color: var(--green); background: #f0f4f1; }
+.collection-filters button[aria-pressed="true"] { background: linear-gradient(120deg,var(--accent-wash),#f2f6f3); }
+.library-search { background: #f1f4f2; }
+.library-search:focus-within { box-shadow: 0 0 0 2px var(--accent-wash); }
+.library-list a { padding: 19px 14px; border-radius: 7px; transition: background 180ms,color 180ms; }
+.library-list a:hover { background: #f2f6f3; }
+.library-row-title { font-size: 14px; }
+.library-row-group { font-size: 12px; color: #929b95; }
+.library-list svg { transition: transform 180ms; }
+.library-list a:hover svg { transform: translateX(3px); }
+.site-footer { padding: 32px 0; font-size: 12px; color: #919a94; }
+.reading-main { max-width: 852px; padding: 38px 44px 0; }
+.breadcrumb { align-items: center; flex-wrap: wrap; font-size: 12px; margin-bottom: 30px; line-height: 1.7; }
+.breadcrumb a { color: #6c7970; }
+.breadcrumb > span:not(.draft-badge) { max-width: 100%; }
+.draft-badge { font-size: 11px; background: #f7f2e9; }
+.prose { line-height: 1.9; --tw-prose-links: var(--green); }
+.prose > p:first-of-type { font-size: 17px; color: #788379; }
+.prose code:not(pre code) { background: #eef2ef; }
+.prose tbody tr, .prose thead tr { border: 0; }
+.prose tbody tr:nth-child(even) { background: #f6f8f6; }
+.prose thead { background: #eef3ef; }
+.prose img { background: transparent; padding: 0; }
+.callout { padding: 18px 20px; font-size: 14px; background: linear-gradient(120deg,#eef4ef,#f8faf7); }
+.callout-draft { background: linear-gradient(120deg,#f7f1e7,#fcfaf5); }
+.code-block { background: #f2f5f2; }
+.code-toolbar { font-size: 12px; background: #eaf0eb88; }
+.code-toolbar button { font-size: 12px; background: #ffffffaa; transition: background 180ms; }
+.code-toolbar button:hover { background: white; }
+.toc { top: 64px; height: calc(100dvh - 64px); padding: 44px 24px 30px 12px; font-size: 12px; }
+.toc > .eyebrow { font-size: 12px; color: #929c94; }
+.toc nav a { padding: 7px 10px; margin: 0; border-radius: 4px; color: #8a968d; transition: color 160ms,background 160ms; }
+.toc nav a:hover, .toc nav a.active { color: var(--green); background: linear-gradient(90deg,var(--accent-wash),transparent); }
+.article-end { margin-top: 30px; font-size: 12px; }
+.pagination > a { background: #f3f6f3; transition: background 180ms,transform 180ms var(--ease); }
+.pagination > a:hover { background: var(--accent-wash); transform: translateY(-2px); }
+.pagination small { font-size: 12px; }
+.search-dialog { background: #fdfefd; box-shadow: 0 24px 90px #192d2330; }
+.search-dialog[open] { animation: surface-in 180ms var(--ease); }
+.search-dialog::backdrop { backdrop-filter: blur(5px); }
+.search-input-row { background: #f3f6f3; padding: 20px; }
+.search-result.selected, .search-result:hover { background: var(--accent-wash); }
+.search-result > span, .search-result small { font-size: 12px; }
+.auth-page { background: radial-gradient(ellipse at 50% 30%,#edf5ef,transparent 60%),#fbfdfb; }
+.auth-card { box-shadow: 0 18px 64px #294d340b; }
+.auth-card input { background: #eff4ef; }
+.auth-card button[type="submit"] { background: linear-gradient(120deg,#3e8969,#2c7057); }
+@keyframes surface-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
+.reading-main, .library-intro { animation: surface-in 320ms var(--ease) both; }
+@media (max-width: 1200px) {
+ .site-header { gap: 22px; padding: 0 24px; }
+ .top-nav { gap: 18px; margin-left: 0; }
+ .search-trigger { width: 180px; }
+ .reading-layout { grid-template-columns: 248px minmax(0,1fr); }
+ .home-layout { grid-template-columns: 248px minmax(0,1fr); }
+ .sidebar { padding-left: 22px; padding-right: 22px; }
+ .toc { display: none; }
+ .home-main { padding: 0 36px; }
+ .mobile-toc { display: block; background: #f3f6f3; }
+}
+@media (min-width: 761px) and (max-width: 980px) {
+ .top-nav { display: flex; gap: 14px; font-size: 12px; }
+ .search-trigger { width: 36px; height: 34px; padding: 8px; justify-content: center; }
+ .search-trigger span, .search-trigger kbd { display: none; }
+ .mobile-bar, .mobile-navigation { display: none; }
+ .sidebar { display: block; }
+}
+@media (max-width: 760px) {
+ html { scroll-padding-top: 165px; }
+ .site-header { height: auto; padding: 0 20px; column-gap: 18px; row-gap: 0; flex-wrap: wrap; }
+ .brand { height: 62px; font-size: 15px; }
+ .top-nav { display: flex; order: 4; width: 100%; overflow-x: auto; gap: 24px; min-width: 0; margin: 0; height: 44px; flex: 0 0 100%; font-size: 13px; scrollbar-width: none; }
+ .top-nav a { padding: 0; }
+ .search-trigger { width: 36px; height: 34px; padding: 8px; justify-content: center; }
+ .search-trigger span, .search-trigger kbd { display: none; }
+ .home-layout, .reading-layout { grid-template-columns: 1fr; }
+ .sidebar { display: none; }
+ .mobile-bar { display: block; top: 106px; background: #fdfefdf0; backdrop-filter: blur(14px); }
+ .mobile-bar button { font-size: 13px; color: var(--green); }
+ .mobile-navigation { padding: 20px 22px; background: #f7faf7; box-shadow: 0 16px 24px #26382c08; }
+ .mobile-navigation .collection-identity, .mobile-navigation .nav-back { margin-bottom: 20px; }
+ .home-main { padding: 0 24px; }
+ .library-intro { padding: 40px 0 34px; }
+ .library-intro h1 { font-size: 32px; }
+ .reading-main { padding: 26px 24px 0; }
+ .prose h1 { font-size: 29px; }
+ .breadcrumb { margin-bottom: 24px; }
+}
+@media (max-width: 520px) {
+ .featured-grid { grid-template-columns: 1fr; }
+ .featured-link { padding: 23px; }
+ .library-section { padding-top: 38px; }
+ .library-list a { padding: 16px 8px; }
+ .prose > p:first-of-type { font-size: 16px; }
+ .pagination strong { font-size: 13px; }
+ .site-footer { font-size: 12px; }
+}
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+ *, *::before, *::after { animation: none !important; transition: none !important; }
+ .featured-link:hover, .featured-link:hover .collection-art, .pagination > a:hover { transform: none; }
+}
diff --git a/design/verification/collection-ui/README.md b/design/verification/collection-ui/README.md
new file mode 100644
index 0000000..c29b9da
--- /dev/null
+++ b/design/verification/collection-ui/README.md
@@ -0,0 +1,21 @@
+# 文档 UI 验收 · 2026-10-28
+
+参考 Supabase Database Overview 与 AI Tools 的桌面界面:共用排版,分别维护文档标识、局部目录与顶部上下文。保留 Tabularium 品牌与现有文档路径。
+
+## 实现
+
+- 顶部切换文档,显示当前选中项。左侧显示当前文档名称、图标、独立目录和返回入口;面包屑连接该文档入口。
+- 文档中心按合集提供入口,不把所有章节铺成卡片。原有筛选、分页和全文搜索保留;阅读页搜索提示显示当前文档范围。
+- 移除导航、卡片、提示、代码、表格和上下篇等区域的多数边框,以留白、浅色表面和轻渐变建立层次。不同文档使用克制的强调色。
+- 入口提供 hover 浮起和箭头反馈;选中项、链接和搜索有过渡;正文及对话框有短暂进入效果。系统设置减少动态效果时关闭动画和过渡。
+
+## 验证
+
+- ESLint、类型检查、Next.js webpack 生产构建通过。认证单元测试 3/3 通过。
+- 对最终生产构建运行原有 auth-cli verify:全部文档、搜索索引、附件、静态资源、404 和认证边界通过。
+- 浏览器开发预览:1440×900 桌面检查首页、Pretext、语言设计;顶部点击切换后局部目录和搜索提示更新。搜索 nonlocal 返回本合集两篇文章,方向键与 Enter 能打开章节。
+- 390×844 检查正文、顶部横向文档切换和本地目录展开;截图未见正文横向溢出。
+- 下图为开发预览截图,左下角 Next.js 开发按钮不进入生产产物。生产产物已完成 HTTP 验收;额外的生产浏览器截图因原开发标签页关闭后连接失效未保存。
+- 不代表远程 CI 已完成,也不代表 PR 已合并或生产环境已部署。
+
+
diff --git a/design/verification/collection-ui/desktop-language.jpg b/design/verification/collection-ui/desktop-language.jpg
new file mode 100644
index 0000000..35a8095
Binary files /dev/null and b/design/verification/collection-ui/desktop-language.jpg differ