diff --git a/README.md b/README.md index 58172d2..55e45a6 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,22 @@ 不是读书 App,不是笔记工具,不是 AI 聊天产品 —— 更接近一座**可以走进去的、还在生长的碑林**。 -This repository holds the 老树 product front-end. The first page implemented is -**我的树 (My Tree)** — a reader's personal archive, rendered as an abstract, slowly -growing tree. +This repository holds the 老树 product front-end: a shelf of books (`/`), each with +its own biography (`/book/:id`) and one cultivated chapter to actually read +(`/book/:id/read`), plus a reader's personal archive (`/my-tree`). + +Two books are live: + +- **道德經** — ported from the design handoff, at `/book/daodejing`. +- **金剛經** — the 佛家 (Buddhist) book added alongside it: Kumārajīva's 402 + translation, the Sixth Patriarch's awakening at "应无所住而生其心", the 868 + Dunhuang scroll (the earliest dated complete printed book known to survive), + and Kerouac copying its closing gāthā into his notebooks. At + `/book/diamondSutra`, reading chapter 第三十二分 · 应化非真分. + +`论语`, `庄子`, `Meditations`, and `源氏物語` sit on the shelf as honest "待种下" +ghosts — not yet cultivated, per the product's own rule that emptiness should +read as room to grow, not a bug to hide. ## 我的树 (My Tree) @@ -44,13 +57,21 @@ npm run preview # serve the production build | --- | --- | | `src/MyTree.tsx` | The 我的树 page component + its branch/annotation data | | `src/MyTree.css` | Page layout, the SVG tree, hover reveal, growth rings | +| `src/books/types.ts` | Shared `Book` shape: shelf card, biography timeline, one cultivated reading chapter | +| `src/books/daodejing.ts`, `src/books/diamondSutra.ts` | The two live books' full content | +| `src/books/registry.ts` | `books` + `comingSoon` (the shelf's "待种下" ghosts) | +| `src/pages/Bookshelf.tsx` | `/` — 千年书架, books on a shared historical axis | +| `src/pages/BookDetail.tsx` | `/book/:id` — 书籍详情页, a book's biography timeline | +| `src/pages/Reading.tsx` | `/book/:id/read` — 阅读页, the time slider + sentence-strata drawer | +| `src/components/Header.tsx`, `src/hooks/useNightMode.ts` | Shared brand/nav bar and 夜读/昼读 toggle | | `src/theme.css` | Design tokens (墨字朱批 palette), day/night themes, keyframes | -| `APP_NOTES.md` | Detailed notes on this page | -| `design/` | The original Claude Design handoff bundle — chat transcript + all page prototypes. The source of truth for the pages not yet built. | +| `APP_NOTES.md` | Detailed notes on 我的树 specifically | +| `design/` | The original Claude Design handoff bundle — chat transcript + all page prototypes. | ## Not yet built -`design/project/` holds prototypes for the rest of the product: 首页书架, 书籍详情页, -阅读页 (the design's stated priority — its "signature" screen with the time-slider), -落笔流程, 句子房间, 考据, 分享卡, 守树人. `我的树` links out to `/read` and `/inscribe` -as placeholders for those routes. +`design/project/` also holds prototypes for 落笔流程 (writing an annotation), +考据, 分享卡, and 守树人 (the AI companion). Their entry points in the built +pages (抄录本章, 在此落笔, 与守树人细究此节, 留下名字…) are present as honest, +inert stubs — clicking them reveals the "读是自由的,落笔需要一个名字" prompt +rather than a fake flow. diff --git a/package-lock.json b/package-lock.json index e07cb30..c6846f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0", "dependencies": { "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.4" }, "devDependencies": { "@types/react": "^18.3.12", @@ -793,6 +794,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1627,6 +1637,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", diff --git a/package.json b/package.json index eade0c2..0cbd1db 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ }, "dependencies": { "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.4" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/src/MyTree.css b/src/MyTree.css index b2d034f..2f36d9e 100644 --- a/src/MyTree.css +++ b/src/MyTree.css @@ -56,26 +56,6 @@ body[data-night='1']::before { color: var(--ink-light); } -/* Quiet links that warm to full ink on hover. */ -.link-muted { - color: var(--ink-light); - text-decoration: none; - cursor: pointer; - transition: color 0.3s ease; -} -.link-muted:hover { - color: var(--ink); -} - -.link-cinnabar { - color: var(--cinnabar); - text-decoration: none; - transition: color 0.3s ease; -} -.link-cinnabar:hover { - color: var(--seal); -} - .stage { max-width: 640px; margin: 0 auto; diff --git a/src/MyTree.tsx b/src/MyTree.tsx index e773e3c..6273717 100644 --- a/src/MyTree.tsx +++ b/src/MyTree.tsx @@ -178,7 +178,7 @@ export default function MyTree() { 老树