diff --git a/apps/tutorial/src/pages/design-colors/App.tsx b/apps/tutorial/src/pages/design-colors/App.tsx
new file mode 100644
index 0000000..2a363a2
--- /dev/null
+++ b/apps/tutorial/src/pages/design-colors/App.tsx
@@ -0,0 +1,109 @@
+import { Page, Section } from '../../lib/ui'
+
+const PAIRS = [
+ { a: '#FF6B6B', b: '#4ECDC4', name: '对 1 · coral × teal', use: '主用对:课程相框与主图形' },
+ { a: '#FFD93D', b: '#A78BFA', name: '对 2 · mustard × violet', use: '辅助对:指派与脱靶等警示类' },
+ { a: '#FF8FAB', b: '#5B8DEF', name: '对 3 · pink × blue', use: '点缀对:代理区与散落形状' },
+]
+const NEUTRALS = [
+ { c: '#111111', n: 'ink', h: '#111111 · 描边/字/硬投影' },
+ { c: '#FFF9F0', n: 'paper', h: '#FFF9F0 · 卡面/大底' },
+]
+const CHAPTERS6 = [
+ { c: '#FF6B6B', n: 'ch1 快速开始' }, { c: '#FF8FAB', n: 'ch2 React' }, { c: '#FFD93D', n: 'ch3 更多示例' },
+ { c: '#4ECDC4', n: 'ch4 更多' }, { c: '#5B8DEF', n: 'ch5 全栈基础' }, { c: '#A78BFA', n: 'ch6 高级教程' },
+]
+
+function Chip({ c, n, h }: { c: string; n: string; h: string }) {
+ return (
+
+ )
+}
+
+/* 三条规则的「为什么」:鲜艳如何成立、如何被收束、注意力如何分配 */
+const RULES = [
+ {
+ name: '相邻必撞色(跨对取色)',
+ why: '孟菲斯的鲜艳靠碰撞成立:两个高饱和色块相邻时,只有色相拉开距离,视觉才读得出「两个声音」。同色相邻只是一个声音变大——画面发平发闷,鲜艳互相抵消;跨对取色(coral 挨 teal、mustard 挨 violet)反而把彼此衬得更亮。拿到相邻色块时问一句「它俩是不是同一对」,是,就换掉一个。',
+ badCap: '同色相邻失去碰撞',
+ goodCap: '相邻必撞色(跨对取色)',
+ badDemo:
,
+ goodDemo:
,
+ },
+ {
+ name: '黑描边收束鲜艳',
+ why: '高饱和色放在浅纸上会「振动」:边缘发虚、像悬浮在页面上。2px ink 描边给每块鲜艳一道明确的边界,把颜色关进形状里——这也是拟物卡世界的基础:卡片、形状、站点共用同一粗细的墨线,颜色再艳也是「零件」而不是「光」,缩放与低分屏上轮廓依然清晰。去掉描边,鲜艳就是没裁过的布料。',
+ badCap: '无描边的鲜艳漂浮刺眼',
+ goodCap: '一切鲜艳以 2px ink 描边为界',
+ badDemo:
,
+ goodDemo:
,
+ },
+ {
+ name: '大底纸色,鲜艳只进局部',
+ why: '鲜艳是注意力,铺满屏幕等于没有注意力。大底永远是 paper,鲜艳只进相框、站点、散落形状这些局部(≤10%/屏),视线才有落点、正文才永远可读;大底一旦鲜艳,字被淹没,卡片也失去「纸上的物」的载体感。记住层级:大底是世界,卡片是物,鲜艳是物上的标记。',
+ badCap: '大底鲜艳,字被淹没',
+ goodCap: '大底纸色,鲜艳只进局部',
+ badDemo:
大底鲜艳
,
+ goodDemo:
纸底安放正文
,
+ },
+]
+
+function DoDont({ bad, children, text }: { bad?: boolean; children: React.ReactNode; text: string }) {
+ return (
+
+
+ {bad ? '✗' : '✓'}
+
+ {children}
+
{text}
+
+ )
+}
+
+export function App() {
+ return (
+
+
+
+ {PAIRS.map((p) => (
+
+ ))}
+
+
+
+ {NEUTRALS.map((x) => )}
+
+
+ {CHAPTERS6.map((x) => )}
+
+
+
+ {RULES.map((r, i) => (
+
+
+ {i + 1}
+ {r.name}
+
+
{r.why}
+
+ {r.badDemo}
+ {r.goodDemo}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/tutorial/src/pages/design-colors/main.tsx b/apps/tutorial/src/pages/design-colors/main.tsx
new file mode 100644
index 0000000..a3fd25e
--- /dev/null
+++ b/apps/tutorial/src/pages/design-colors/main.tsx
@@ -0,0 +1,4 @@
+import { createRoot } from 'react-dom/client'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render(
)
diff --git a/apps/tutorial/src/pages/design-components/App.tsx b/apps/tutorial/src/pages/design-components/App.tsx
new file mode 100644
index 0000000..fc0cfbe
--- /dev/null
+++ b/apps/tutorial/src/pages/design-components/App.tsx
@@ -0,0 +1,36 @@
+import { Page, Section } from '../../lib/ui'
+import { ASSIGNMENT, CHAPTERS } from '../../lib/chapters'
+import { MemphisCard } from '../../components/MemphisCard'
+import { AssignBlock, Ticket } from '../../components/Ticket'
+import { EntryGuide } from '../../components/EntryGuide'
+import { Mark } from '../../components/shapes'
+
+export function App() {
+ return (
+
+
+
+
+
+
+
+ 这一步过程就地写清:环境与依赖如何咬合>}
+ />
+
+
+
+
+
+
+ )
+}
diff --git a/apps/tutorial/src/pages/design-components/main.tsx b/apps/tutorial/src/pages/design-components/main.tsx
new file mode 100644
index 0000000..a3fd25e
--- /dev/null
+++ b/apps/tutorial/src/pages/design-components/main.tsx
@@ -0,0 +1,4 @@
+import { createRoot } from 'react-dom/client'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render(
)
diff --git a/apps/tutorial/src/pages/design-examples/App.tsx b/apps/tutorial/src/pages/design-examples/App.tsx
new file mode 100644
index 0000000..5c04bea
--- /dev/null
+++ b/apps/tutorial/src/pages/design-examples/App.tsx
@@ -0,0 +1,46 @@
+/** 组合场景=大部分组件拿出来的真实使用,不做解释性标注:
+ * 入口画像 → 课程卡与关系线 → 进度票券 → 指派块;形状散落于卡片附近空当。 */
+import { Page } from '../../lib/ui'
+import { ASSIGNMENT, CHAPTERS, EDGES } from '../../lib/chapters'
+import { MemphisCard } from '../../components/MemphisCard'
+import { RouteLine } from '../../components/RouteLine'
+import { AssignBlock, Ticket } from '../../components/Ticket'
+import { EntryGuide } from '../../components/EntryGuide'
+import { Scatter } from '../../components/Scatter'
+import { Cross, Ring, Tremble, Tri, Wave } from '../../components/shapes'
+
+/* 场景坐标(节点=卡描边真实边缘):卡1(40,110,250) 卡2(380,44,250,rot) 卡3(700,44,140) */
+const nodes = {
+ 1: { id: 1, borderLeft: 40, right: 290, y: 180 },
+ 2: { id: 2, borderLeft: 380, right: 630, y: 108 },
+ 5: { id: 5, borderLeft: 700, right: 840, y: 108 },
+} as const
+
+export function App() {
+ return (
+
+
+
+
,
+
,
+
,
+
,
+
,
+
,
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/tutorial/src/pages/design-examples/main.tsx b/apps/tutorial/src/pages/design-examples/main.tsx
new file mode 100644
index 0000000..a3fd25e
--- /dev/null
+++ b/apps/tutorial/src/pages/design-examples/main.tsx
@@ -0,0 +1,4 @@
+import { createRoot } from 'react-dom/client'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render(
)
diff --git a/apps/tutorial/src/pages/design-symbols/App.tsx b/apps/tutorial/src/pages/design-symbols/App.tsx
new file mode 100644
index 0000000..5d642b6
--- /dev/null
+++ b/apps/tutorial/src/pages/design-symbols/App.tsx
@@ -0,0 +1,103 @@
+import { Page, Section } from '../../lib/ui'
+import { BrickDot, Cross, Mark, Ring, Tremble, Tri, Wave } from '../../components/shapes'
+import { CHAPTERS } from '../../lib/chapters'
+import { Ticket } from '../../components/Ticket'
+import type { ReactNode } from 'react'
+
+const SHAPES = [
+ { el:
, n: '▲ 靶子', m: '目标,卡头打头', s: '靶子先行' },
+ { el:
, n: '● 积木', m: '清单点列,实=已学', s: '积木清单' },
+ { el:
, n: '○ 预期', m: '可核对的结果', s: '预期结果' },
+ { el:
, n: '~ 代理区', m: '交给 agent', s: '每步先分类' },
+ { el:
过程, n: '荧光标记 价值区', m: '划在文字上,不是散落图形', s: '每步先分类' },
+ { el:
, n: '⌇ 脱靶', m: '图形入口:追随图形进入阅读', s: '脱靶提醒' },
+ { el:
, n: '+ 指派', m: '位置/预期/场景', s: '指派学习' },
+]
+
+const VOCAB: { p: string; rows: [ReactNode, string][] }[] = [
+ { p: '靶子', rows: [['▲ 目标', '做什么'], ['○ 预期结果', '做成长什么样、怎么核对'], ['● 积木清单', '要用到的零件——只列用到的;说零件不说知识']] },
+ { p: '步骤分类', rows: [['~ 代理区', '无学习价值——交给 agent'], [
价值区, '有学习价值——就地写清']] },
+ { p: '指派学习', rows: [['+ 三样(图形入口)', '教材只留三样,讲解交给 agent'], ['└ 位置', '知识体系中的坐标'], ['└ 预期', '实践结果应长什么样——当尺子'], ['└ 场景', '带回 agent 再做的练习——管巩固']] },
+ { p: '脱靶提醒', rows: [['⌇ 图形标记', '不人为挖坑;跟在指派后'], ['└ 可能性 / 长相', '怎么脱靶+脱靶什么样——撞上认出缺哪块']] },
+ { p: '认知基础', rows: [['底座纹·真机验收章', 'env/库/运行环境——脚下的地,不是积木']] },
+]
+
+const LINES = [
+ { d: 'M 8 34 H 60 A 8 8 0 0 0 68 26 V 18 A 8 8 0 0 1 76 10 H 180', w: 2.5, dash: '', cap: 'butt' as const, st: true, n: '实线 = 先后', m: '推荐顺序 · 主线,唯一带站点' },
+ { d: 'M 8 23 H 180', w: 2, dash: '4 2', cap: 'butt' as const, st: false, n: '虚线 = 相关', m: '内容互参 · 无顺序,无站点' },
+ { d: 'M 8 23 H 180', w: 2.4, dash: '0.1 6', cap: 'round' as const, st: false, n: '点线 = 同族', m: '同一积木家族(教一块带一族)' },
+]
+
+export function App() {
+ return (
+
+
+
+ {SHAPES.map((s) => (
+
+
{s.el}
+
{s.n}
+
{s.m}
+
{s.s}
+
+ ))}
+
+
+
+
+
+
+ | 父词 |
+ 图形 · 子词 |
+ 释义(skill 口径) |
+
+
+
+ {VOCAB.map((g) =>
+ g.rows.map(([sub, def], i) => (
+
+ {i === 0 && | {g.p} | }
+ {sub} |
+ {def} |
+
+ )),
+ )}
+
+
+
+
+
+ {LINES.map((l) => (
+
+
+
{l.n}
+
{l.m}
+
+ ))}
+
+
+
波浪带 = 代理交接
+
agent 边界 · 区域标记,不连线
+
+
+
+
+
+
+
+
+ 完成=沿撕线打双孔(半圆缺口);未开始撕线为实线、无孔。头部色块无独立描边,由票体 overflow-hidden 裁出单一描边圆角。票号=章程序号。
+
+
+
+
+ )
+}
diff --git a/apps/tutorial/src/pages/design-symbols/main.tsx b/apps/tutorial/src/pages/design-symbols/main.tsx
new file mode 100644
index 0000000..a3fd25e
--- /dev/null
+++ b/apps/tutorial/src/pages/design-symbols/main.tsx
@@ -0,0 +1,4 @@
+import { createRoot } from 'react-dom/client'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render(
)