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 已合并或生产环境已部署。 + +![桌面语言设计文档](desktop-language.jpg) 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