diff --git a/apps/design-lab/.gitignore b/apps/design-lab/.gitignore
new file mode 100644
index 0000000..f06235c
--- /dev/null
+++ b/apps/design-lab/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist
diff --git a/apps/design-lab/colors.html b/apps/design-lab/colors.html
new file mode 100644
index 0000000..a41783f
--- /dev/null
+++ b/apps/design-lab/colors.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ design-lab · colors
+
+
+
+
+
+
diff --git a/apps/design-lab/components.html b/apps/design-lab/components.html
new file mode 100644
index 0000000..d61b923
--- /dev/null
+++ b/apps/design-lab/components.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ design-lab · components
+
+
+
+
+
+
diff --git a/apps/design-lab/index.html b/apps/design-lab/index.html
new file mode 100644
index 0000000..287feee
--- /dev/null
+++ b/apps/design-lab/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ design-lab · index
+
+
+
+
+
+
diff --git a/apps/design-lab/package.json b/apps/design-lab/package.json
new file mode 100644
index 0000000..b687e95
--- /dev/null
+++ b/apps/design-lab/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@stack/design-lab",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.0",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "tailwindcss": "^4.1.0",
+ "typescript": "~5.6.2",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/apps/design-lab/shelf.html b/apps/design-lab/shelf.html
new file mode 100644
index 0000000..40cd7b1
--- /dev/null
+++ b/apps/design-lab/shelf.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ design-lab · shelf
+
+
+
+
+
+
diff --git a/apps/design-lab/src/components/EntryGuide.tsx b/apps/design-lab/src/components/EntryGuide.tsx
new file mode 100644
index 0000000..96dd45a
--- /dev/null
+++ b/apps/design-lab/src/components/EntryGuide.tsx
@@ -0,0 +1,35 @@
+/** 入口梳理 —— 读者画像推荐路径:是推荐不是强制(无锁) */
+import { CHAPTERS, PROFILES } from '../lib/chapters'
+
+export function EntryGuide() {
+ return (
+
+ {PROFILES.map((p) => (
+
+
+
+ {p.icon}
+
+ {p.name}
+
+
+ {p.path.map((id, i) => (
+
+ {i > 0 && →}
+
+ {id === p.entry && ['①', '②', '③'][PROFILES.indexOf(p)]} {CHAPTERS[id].title}
+
+
+ ))}
+
+
{p.note}
+
+ ))}
+
+ )
+}
diff --git a/apps/design-lab/src/components/MemphisCard.tsx b/apps/design-lab/src/components/MemphisCard.tsx
new file mode 100644
index 0000000..f2dfb85
--- /dev/null
+++ b/apps/design-lab/src/components/MemphisCard.tsx
@@ -0,0 +1,119 @@
+/** CourseCard —— 教程的入口载体(孟菲斯骨架 × 拟物纸感)
+ * 卡头三件套=靶子:▲目标 + ○预期结果(16:9 相框) + ●积木点列
+ * 交互:hover/聚焦拿起(浮起+投影加深)并播放 GIF;desc ⇄ GIF 每 8000ms 翻转;
+ * hover/Tab 锁定;触屏与 prefers-reduced-motion 不翻转。 */
+import { useEffect, useRef, useState } from 'react'
+import type { Chapter } from '../lib/types'
+import { Ring, Tri } from './shapes'
+
+const FLIP_MS = 8000
+
+export function BrickDots({ bricks }: { bricks: Chapter['target']['bricks'] }) {
+ return (
+ b.learned).length}/${bricks.length}`}>
+ {bricks.map((b, i) => (
+
+ ))}
+
+ )
+}
+
+export function EntryBadge({ n }: { n: 1 | 2 | 3 }) {
+ return (
+
+ {'①②③'[n - 1]}
+
+ )
+}
+
+export function MemphisCard({ chapter, entry, compact = false }: { chapter: Chapter; entry?: 1 | 2 | 3; compact?: boolean }) {
+ const [gifOn, setGifOn] = useState(false)
+ const [locked, setLocked] = useState(false)
+ const boxRef = useRef(null)
+
+ useEffect(() => {
+ const coarse = matchMedia('(pointer: coarse)').matches
+ const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches
+ if (coarse || reduced) return
+ const io = new IntersectionObserver(
+ ([e]) => {
+ if (e.isIntersecting) setGifOn(true)
+ else setGifOn(false)
+ },
+ { threshold: 0.3 },
+ )
+ if (boxRef.current) io.observe(boxRef.current)
+ return () => io.disconnect()
+ }, [])
+
+ useEffect(() => {
+ if (!gifOn) return
+ const t = setInterval(() => {
+ setFlip((f) => !f)
+ }, FLIP_MS)
+ return () => clearInterval(t)
+ }, [gifOn])
+
+ const [flip, setFlip] = useState(false)
+ const showGif = gifOn && (locked ? true : flip)
+
+ return (
+ setLocked(true)}
+ onMouseLeave={() => setLocked(false)}
+ onFocus={() => setLocked(true)}
+ onBlur={() => setLocked(false)}
+ aria-label={`${chapter.title}:${chapter.target.goal}`}
+ >
+ {entry && }
+ {/* ○ 预期结果:16:9 相框(章节撞色只进这里) */}
+
+ {showGif ? (
+ chapter.gifUrl ? (
+

+ ) : (
+
+ ▶ {chapter.target.expectation}
+
+ )
+ ) : (
+
+ {chapter.target.goal}
+
+ )}
+
+ 预期结果{showGif ? ' · GIF' : ''}
+
+
+
+ {/* 卡头:▲ 目标 + 标题 + ● 积木点列 */}
+
+
+
+ {compact ? chapter.title : `${chapter.emoji} ${chapter.title}`}
+
+ {!compact && }
+
+
+ )
+}
diff --git a/apps/design-lab/src/components/RouteLine.tsx b/apps/design-lab/src/components/RouteLine.tsx
new file mode 100644
index 0000000..f1a0734
--- /dev/null
+++ b/apps/design-lab/src/components/RouteLine.tsx
@@ -0,0 +1,82 @@
+/** RouteLine —— 关系线:线形即含义(实=先后 虚=相关 点=同族)
+ * 拓扑:横平竖直 + R8 直角步进;并行不合并(4px);站点 r6 半嵌卡边
+ * (左贴描边、右骑硬投影外缘);同口多线自动聚合胶囊(线止于胶囊边界)。 */
+import type { ChapterId, Edge } from '../lib/types'
+
+const STATION_COLORS = ['var(--color-clash2a)', 'var(--color-clash1b)', 'var(--color-clash3a)', 'var(--color-clash1a)']
+
+export interface LaneNode {
+ id: ChapterId
+ borderLeft: number // 左描边 x(无影侧,入口贴这里)
+ right: number // 右侧黑影外缘 x(描边 + 7,出口从这里出发)
+ y: number // 卡中线 y
+}
+
+/** 水平出发 → R8 双弯直角步进 → 水平到达;同 y 时直线 */
+function stepPath(x0: number, y0: number, x1: number, y1: number, r = 8): string {
+ if (Math.abs(y1 - y0) < 1) return `M ${x0} ${y0} H ${x1}`
+ const down = y1 > y0
+ const sweep1 = down ? 1 : 0
+ const sweep2 = down ? 0 : 1
+ const cornerX = Math.max(x0 + r, x1 - 2 * r)
+ return `M ${x0} ${y0} H ${cornerX} A ${r} ${r} 0 0 ${sweep1} ${cornerX + r} ${y0 + (down ? r : -r)} V ${y1 - (down ? r : -r)} A ${r} ${r} 0 0 ${sweep2} ${cornerX + 2 * r} ${y1} H ${x1}`
+}
+
+export function RouteLine({
+ edges,
+ nodes,
+ width,
+ height,
+}: {
+ edges: Edge[]
+ nodes: Partial>
+ width: number
+ height: number
+}) {
+ const stationColor = (i: number) => STATION_COLORS[i % STATION_COLORS.length]
+
+ const seqEdges = edges.filter((e) => e.kind === 'seq')
+ const insByTo = new Map()
+ seqEdges.forEach((e) => insByTo.set(e.to, [...(insByTo.get(e.to) ?? []), e]))
+ const firstIn = new Set()
+ insByTo.forEach((list) => firstIn.add(list[0].to))
+
+ let si = 0
+ return (
+
+ )
+}
diff --git a/apps/design-lab/src/components/Ticket.tsx b/apps/design-lab/src/components/Ticket.tsx
new file mode 100644
index 0000000..abc3ab1
--- /dev/null
+++ b/apps/design-lab/src/components/Ticket.tsx
@@ -0,0 +1,83 @@
+/** 票券 —— 只表达完成状态:完成=沿撕线打孔(半圆缺口),未开始=实线无孔。
+ * 裁切式实现:头部色块无独立描边,由票体 overflow-hidden 裁出单一描边圆角。 */
+import type { Chapter } from '../lib/types'
+
+export function Ticket({ chapter, done }: { chapter: Chapter; done: boolean }) {
+ return (
+
+ {/* 头部媒体块:章节色纯填充,无独立边框(由票体裁切) */}
+
{chapter.emoji}
+
+
{chapter.title}
+
+ No. {String(chapter.id).padStart(3, '0')} · {done ? '已完成(打孔)' : '未开始(无孔)'}
+
+
+
+ №{String(chapter.id).padStart(2, '0')}
+
+
+
+ )
+}
+
+/** 指派学习块 —— 普通内容块(非票据):+ 位置/预期/场景三样 + 脱靶标记 */
+export function AssignBlock({
+ title,
+ pos,
+ expect,
+ scenes,
+ miss,
+}: {
+ title: string
+ pos: string
+ expect: string
+ scenes: string[]
+ miss: { how: string; looks: string }
+}) {
+ return (
+
+
+
+
+ 位置:{pos}
+
+
+ 预期:{expect}
+
+
+ 场景:{scenes.join(' / ')}
+
+
+
+
+
+
+
脱靶 · {miss.how}
+
{miss.looks}
+
+
+
+
+ )
+}
diff --git a/apps/design-lab/src/components/shapes.tsx b/apps/design-lab/src/components/shapes.tsx
new file mode 100644
index 0000000..ec9bf64
--- /dev/null
+++ b/apps/design-lab/src/components/shapes.tsx
@@ -0,0 +1,79 @@
+/** 七个语义形状——装饰即语义(取自 ai-era-tutorial 词汇表) */
+import type { CSSProperties } from 'react'
+
+interface ShapeProps {
+ size?: number
+ className?: string
+ style?: CSSProperties
+}
+
+/** ▲ 靶子:目标,卡头打头 */
+export const Tri = ({ size = 24, className, style }: ShapeProps) => (
+
+)
+
+/** ● 积木:清单点列,实心=已学 */
+export const BrickDot = ({ size = 14, on = false, className, style }: ShapeProps & { on?: boolean }) => (
+
+)
+
+/** ○ 预期:可核对的结果 */
+export const Ring = ({ size = 24, className, style }: ShapeProps) => (
+
+)
+
+/** ~ 代理区:横波,交给 agent */
+export const Wave = ({ size = 40, className, style }: ShapeProps) => (
+
+)
+
+/** ▬ 价值区:实心条,过程就地写清 */
+export const Bar = ({ size = 34, className, style }: ShapeProps) => (
+
+)
+
+/** ⌇ 脱靶:竖立颤抖——图形入口,读者追随图形进入阅读 */
+export const Tremble = ({ size = 24, className, style }: ShapeProps) => (
+
+)
+
+/** + 指派学习:位置/预期/场景三样 */
+export const Cross = ({ size = 18, className, style }: ShapeProps) => (
+
+)
+
+/** 脱靶标记:图形 + 文字,无容器(图形是第一视觉) */
+export function MissMark({ how, looks }: { how: string; looks: string }) {
+ return (
+
+ )
+}
diff --git a/apps/design-lab/src/lib/chapters.ts b/apps/design-lab/src/lib/chapters.ts
new file mode 100644
index 0000000..c02842f
--- /dev/null
+++ b/apps/design-lab/src/lib/chapters.ts
@@ -0,0 +1,125 @@
+import type { Assignment, Chapter, ChapterId, Edge, Profile } from './types'
+
+export const CHAPTERS: Record = {
+ 1: {
+ id: 1,
+ title: '快速开始',
+ emoji: '🌐',
+ color: 'var(--color-ch1)',
+ target: {
+ goal: '本地跑起一个可交互的页面',
+ expectation: '改一处代码,浏览器里立刻看到变化',
+ bricks: [
+ { name: '页面骨架', learned: true },
+ { name: '请求与渲染', learned: true },
+ { name: '环境咬合', learned: true },
+ { name: '部署预览', learned: false },
+ ],
+ },
+ },
+ 2: {
+ id: 2,
+ title: 'React',
+ emoji: '⚛️',
+ color: 'var(--color-ch2)',
+ target: {
+ goal: '用组件拼出一个可维护的界面',
+ expectation: '新增一种卡片类型不需要改动旧卡片',
+ bricks: [
+ { name: '组件拼接', learned: true },
+ { name: '状态与副作用', learned: true },
+ { name: '受控数据流', learned: false },
+ { name: '性能边界', learned: false },
+ ],
+ },
+ },
+ 3: {
+ id: 3,
+ title: '更多示例',
+ emoji: '🧱',
+ color: 'var(--color-ch3)',
+ target: {
+ goal: '照着积木清单拼出三个小作品',
+ expectation: '每个作品都能对照预期结果自检',
+ bricks: [
+ { name: '表单页', learned: false },
+ { name: '列表页', learned: false },
+ ],
+ },
+ },
+ 4: {
+ id: 4,
+ title: '更多',
+ emoji: '📦',
+ color: 'var(--color-ch4)',
+ target: {
+ goal: '把示例扩展成自己的第一个完整小站',
+ expectation: '有导航、有数据、能部署',
+ bricks: [{ name: '路由', learned: false }],
+ },
+ },
+ 5: {
+ id: 5,
+ title: '全栈基础',
+ emoji: '🔗',
+ color: 'var(--color-ch5)',
+ target: {
+ goal: '把前端连上真实服务并部署上线',
+ expectation: '别人能通过公网地址访问你的页面',
+ bricks: [{ name: 'HTTP 与接口', learned: false }],
+ },
+ },
+ 6: {
+ id: 6,
+ title: '高级教程',
+ emoji: '🧠',
+ color: 'var(--color-ch6)',
+ target: {
+ goal: '指挥 agent 完成整栈项目',
+ expectation: '你负责靶子与验收,agent 负责过程',
+ bricks: [{ name: '识别力', learned: false }],
+ },
+ },
+}
+
+/** 关系边:seq 实线先后 / rel 虚线相关 / family 点线同族 */
+export const EDGES: Edge[] = [
+ { from: 1, to: 2, kind: 'seq' },
+ { from: 2, to: 5, kind: 'seq' },
+ { from: 2, to: 5, kind: 'family' },
+]
+
+export const PROFILES: Profile[] = [
+ {
+ icon: '🌱',
+ name: '零基础入门',
+ entry: 1,
+ path: [1, 2, 3, 5],
+ note: '完整链按序走;每步先核对预期再前进。',
+ },
+ {
+ icon: '⚡',
+ name: '写过页面的人',
+ entry: 2,
+ path: [2, 3, 6],
+ note: '跳过装环境;从组件拼接进入。',
+ },
+ {
+ icon: '🧠',
+ name: '重度 AI 依赖',
+ entry: 6,
+ path: [6, 5],
+ note: '从指挥 agent 做整件事切入,回头补认知基础。',
+ },
+]
+
+export const ASSIGNMENT: Assignment = {
+ title: '指派学习 · npm',
+ pos: '包管理家族——npm 的坐标与邻居',
+ expect: '能说清 lockfile 锁住了什么',
+ scenes: ['加一个依赖', '换源重装'],
+ miss: {
+ how: '装了但没跑',
+ looks: '命令成功、页面空白——缺「真机验收」这块积木',
+ },
+}
diff --git a/apps/design-lab/src/lib/types.ts b/apps/design-lab/src/lib/types.ts
new file mode 100644
index 0000000..4b88f5f
--- /dev/null
+++ b/apps/design-lab/src/lib/types.ts
@@ -0,0 +1,54 @@
+/** 设计系统数据类型——对齐 ai-era-tutorial 词汇表 */
+
+/** 章程序号(票号=章程序号) */
+export type ChapterId = 1 | 2 | 3 | 4 | 5 | 6
+
+/** 卡头三件套之一:靶子 = ▲目标 + ○预期结果 + ●积木清单 */
+export interface Target {
+ /** ▲ 目标:这一章做成什么(一句话) */
+ goal: string
+ /** ○ 预期结果:做成长什么样、怎么核对(GIF 的 alt) */
+ expectation: string
+ /** ● 积木清单:要用到的零件——只列用到的;实心数 = 已学 */
+ bricks: { name: string; learned: boolean }[]
+}
+
+/** 章节数据(MemphisCard / Ticket 共用) */
+export interface Chapter {
+ id: ChapterId
+ title: string
+ emoji: string
+ color: string // 章节色(只进 16:9 相框与图标瓦)
+ target: Target
+ /** 预期结果 GIF(16:9 无声循环);缺省用静态占位 */
+ gifUrl?: string
+}
+
+/** 关系边——线形即含义 */
+export type EdgeKind = 'seq' | 'rel' | 'family'
+/** seq 实线=先后(唯一带站点);rel 虚线=相关;family 点线=同族 */
+
+export interface Edge {
+ from: ChapterId
+ to: ChapterId
+ kind: EdgeKind
+}
+
+/** 入口画像(推荐路径,非强制——无锁) */
+export interface Profile {
+ icon: string
+ name: string
+ entry: ChapterId // ①②③ 推荐起点
+ path: ChapterId[]
+ note: string
+}
+
+/** 指派学习块三样:位置给坐标、预期当尺子、场景管巩固 */
+export interface Assignment {
+ title: string
+ pos: string
+ expect: string
+ scenes: string[]
+ /** 脱靶标记:可能性 + 长相(撞上时认出缺哪块) */
+ miss: { how: string; looks: string }
+}
diff --git a/apps/design-lab/src/lib/ui.tsx b/apps/design-lab/src/lib/ui.tsx
new file mode 100644
index 0000000..3f0c836
--- /dev/null
+++ b/apps/design-lab/src/lib/ui.tsx
@@ -0,0 +1,34 @@
+/** 页面公共骨架 */
+import type { ReactNode } from 'react'
+import '../styles/tokens.css'
+
+export function Page({ tag, title, desc, children }: { tag: string; title: string; desc: string; children: ReactNode }) {
+ return (
+
+
+ {tag}
+ {title}
+ {desc}
+
+ {children}
+
+
+ )
+}
+
+export function Section({ label, note, children }: { label: string; note?: string; children: ReactNode }) {
+ return (
+
+
+ {label}
+ {note && {note}}
+
+ {children}
+
+ )
+}
diff --git a/apps/design-lab/src/pages/colors/App.tsx b/apps/design-lab/src/pages/colors/App.tsx
new file mode 100644
index 0000000..8f45131
--- /dev/null
+++ b/apps/design-lab/src/pages/colors/App.tsx
@@ -0,0 +1,88 @@
+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 · 卡面/大底' },
+ { c: '#FFFFFF', n: 'stack-1', h: '#FFFFFF · 纸叠亮边' },
+ { c: '#EADFC4', n: 'stack-2', h: '#EADFC4 · 纸叠暗层' },
+]
+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 (
+
+ )
+}
+
+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) => )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 大底鲜艳
+
+
+ 纸底安放正文
+
+
+
+
+ )
+}
diff --git a/apps/design-lab/src/pages/colors/main.tsx b/apps/design-lab/src/pages/colors/main.tsx
new file mode 100644
index 0000000..3dc54c8
--- /dev/null
+++ b/apps/design-lab/src/pages/colors/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import '../../styles/tokens.css'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render()
diff --git a/apps/design-lab/src/pages/components/App.tsx b/apps/design-lab/src/pages/components/App.tsx
new file mode 100644
index 0000000..9e89cf5
--- /dev/null
+++ b/apps/design-lab/src/pages/components/App.tsx
@@ -0,0 +1,44 @@
+import { Page, Section } from '../../lib/ui'
+import { ASSIGNMENT, CHAPTERS } from '../../lib/chapters'
+import { MemphisCard } from '../../components/MemphisCard'
+import { RouteLine } from '../../components/RouteLine'
+import { AssignBlock, Ticket } from '../../components/Ticket'
+import { EntryGuide } from '../../components/EntryGuide'
+
+const nodes = {
+ 1: { id: 1, borderLeft: 40, right: 299, y: 180 },
+ 2: { id: 2, borderLeft: 378, right: 639, y: 108 },
+ 5: { id: 5, borderLeft: 699, right: 848, y: 108 },
+} as const
+
+export function App() {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/design-lab/src/pages/components/main.tsx b/apps/design-lab/src/pages/components/main.tsx
new file mode 100644
index 0000000..3dc54c8
--- /dev/null
+++ b/apps/design-lab/src/pages/components/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import '../../styles/tokens.css'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render()
diff --git a/apps/design-lab/src/pages/index/App.tsx b/apps/design-lab/src/pages/index/App.tsx
new file mode 100644
index 0000000..12405f6
--- /dev/null
+++ b/apps/design-lab/src/pages/index/App.tsx
@@ -0,0 +1,30 @@
+const PAGES = [
+ { href: '/colors.html', name: '01 颜色', desc: '撞色对 · 中性阶 · 章节色 · 反例' },
+ { href: '/symbols.html', name: '02 符号形状', desc: '语义形状 · 词汇表 · 线形 · 票券' },
+ { href: '/components.html', name: '03 组件', desc: 'CourseCard · RouteLine · 票券 · 指派块 · 入口' },
+ { href: '/shelf.html', name: '04 样例', desc: '组合场景——实现验收对照图' },
+]
+
+export function App() {
+ return (
+
+
+ <DesignLab />
+
+ design-lab · 孟菲斯 × 拟物卡
+ Vite MPA:一页一入口(src/pages/xxx/main.tsx)。设计系统四册的可运行版。
+
+
+ )
+}
diff --git a/apps/design-lab/src/pages/index/main.tsx b/apps/design-lab/src/pages/index/main.tsx
new file mode 100644
index 0000000..3dc54c8
--- /dev/null
+++ b/apps/design-lab/src/pages/index/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import '../../styles/tokens.css'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render()
diff --git a/apps/design-lab/src/pages/shelf/App.tsx b/apps/design-lab/src/pages/shelf/App.tsx
new file mode 100644
index 0000000..bfc06f0
--- /dev/null
+++ b/apps/design-lab/src/pages/shelf/App.tsx
@@ -0,0 +1,49 @@
+import { Page } from '../../lib/ui'
+import { ASSIGNMENT, CHAPTERS, EDGES } from '../../lib/chapters'
+import { MemphisCard } from '../../components/MemphisCard'
+import { RouteLine } from '../../components/RouteLine'
+import { AssignBlock } from '../../components/Ticket'
+import { Bar, Cross, MissMark, 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: 299, y: 180 },
+ 2: { id: 2, borderLeft: 378, right: 639, y: 108 },
+ 4: { id: 4, borderLeft: 698, right: 848, y: 108 },
+} as const
+
+const SCATTER = [
+ { el: , x: 470, y: 14 },
+ { el: , x: 830, y: 150 },
+ { el: , x: 640, y: 440 },
+ { el: , x: 828, y: 218 },
+ { el: , x: 40, y: 316 },
+ { el: , x: 262, y: 548 },
+ { el: , x: 96, y: 496 },
+]
+
+export function App() {
+ return (
+
+
+
+ {SCATTER.map((s, i) => (
+
{s.el}
+ ))}
+
+
+
+
+
+
+
代理区:安装交给 agent,只验收产物
+
价值区:过程就地写清
+
+
+
+ 形状散落于空白,每个都有含义:▲ 靶子 · ● 积木 · ○ 预期 · ~ 代理区 · ▬ 价值区 · ⌇ 脱靶 · + 指派;线:实=先后 · 虚=相关 · 点=同族
+
+
+
+ )
+}
diff --git a/apps/design-lab/src/pages/shelf/main.tsx b/apps/design-lab/src/pages/shelf/main.tsx
new file mode 100644
index 0000000..3dc54c8
--- /dev/null
+++ b/apps/design-lab/src/pages/shelf/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import '../../styles/tokens.css'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render()
diff --git a/apps/design-lab/src/pages/symbols/App.tsx b/apps/design-lab/src/pages/symbols/App.tsx
new file mode 100644
index 0000000..6cb49a1
--- /dev/null
+++ b/apps/design-lab/src/pages/symbols/App.tsx
@@ -0,0 +1,102 @@
+import { Page, Section } from '../../lib/ui'
+import { Bar, BrickDot, Cross, Ring, Tremble, Tri, Wave } from '../../components/shapes'
+import { CHAPTERS } from '../../lib/chapters'
+import { Ticket } from '../../components/Ticket'
+
+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: [string, 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/design-lab/src/pages/symbols/main.tsx b/apps/design-lab/src/pages/symbols/main.tsx
new file mode 100644
index 0000000..3dc54c8
--- /dev/null
+++ b/apps/design-lab/src/pages/symbols/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import '../../styles/tokens.css'
+import { App } from './App'
+
+createRoot(document.getElementById('root')!).render()
diff --git a/apps/design-lab/src/styles/tokens.css b/apps/design-lab/src/styles/tokens.css
new file mode 100644
index 0000000..d0a8711
--- /dev/null
+++ b/apps/design-lab/src/styles/tokens.css
@@ -0,0 +1,33 @@
+@import 'tailwindcss';
+
+/* ===== 设计系统 tokens(孟菲斯 × 拟物卡)===== */
+@theme {
+ /* 撞色对(鲜艳的全部来源,相邻必撞) */
+ --color-clash1a: #ff6b6b; /* coral · ch1 */
+ --color-clash1b: #4ecdc4; /* teal · ch4 */
+ --color-clash2a: #ffd93d; /* mustard · ch3 */
+ --color-clash2b: #a78bfa; /* violet · ch6 */
+ --color-clash3a: #ff8fab; /* pink · ch2 */
+ --color-clash3b: #5b8def; /* blue · ch5 */
+
+ /* 中性阶(纸的世界) */
+ --color-ink: #111111;
+ --color-paper: #fff9f0;
+ --color-stack1: #ffffff;
+ --color-stack2: #eadfc4;
+ --color-mute: #8a7a5c;
+ --color-body: #3d3d3d;
+
+ /* 章节色别名 */
+ --color-ch1: #ff6b6b;
+ --color-ch2: #ff8fab;
+ --color-ch3: #ffd93d;
+ --color-ch4: #4ecdc4;
+ --color-ch5: #5b8def;
+ --color-ch6: #a78bfa;
+}
+
+/* 语义形状通用 */
+.sem-shadow {
+ filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.9));
+}
diff --git a/apps/design-lab/symbols.html b/apps/design-lab/symbols.html
new file mode 100644
index 0000000..a316d0b
--- /dev/null
+++ b/apps/design-lab/symbols.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ design-lab · symbols
+
+
+
+
+
+
diff --git a/apps/design-lab/tsconfig.json b/apps/design-lab/tsconfig.json
new file mode 100644
index 0000000..dc5f5b9
--- /dev/null
+++ b/apps/design-lab/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/apps/design-lab/tsconfig.tsbuildinfo b/apps/design-lab/tsconfig.tsbuildinfo
new file mode 100644
index 0000000..1e67feb
--- /dev/null
+++ b/apps/design-lab/tsconfig.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./src/components/EntryGuide.tsx","./src/components/MemphisCard.tsx","./src/components/RouteLine.tsx","./src/components/Ticket.tsx","./src/components/shapes.tsx","./src/lib/chapters.ts","./src/lib/types.ts","./src/lib/ui.tsx","./src/pages/colors/App.tsx","./src/pages/colors/main.tsx","./src/pages/components/App.tsx","./src/pages/components/main.tsx","./src/pages/index/App.tsx","./src/pages/index/main.tsx","./src/pages/shelf/App.tsx","./src/pages/shelf/main.tsx","./src/pages/symbols/App.tsx","./src/pages/symbols/main.tsx","./vite.config.ts"],"version":"5.6.3"}
\ No newline at end of file
diff --git a/apps/design-lab/vite.config.ts b/apps/design-lab/vite.config.ts
new file mode 100644
index 0000000..bac9d41
--- /dev/null
+++ b/apps/design-lab/vite.config.ts
@@ -0,0 +1,17 @@
+import { readdirSync } from 'node:fs'
+import { resolve } from 'node:path'
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+// MPA:根目录每个 *.html 是一个入口,对应 src/pages//main.tsx
+const pages = readdirSync(__dirname).filter((f) => f.endsWith('.html'))
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ build: {
+ rollupOptions: {
+ input: Object.fromEntries(pages.map((f) => [f.replace(/\.html$/, ''), resolve(__dirname, f)])),
+ },
+ },
+})