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.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 ( + + {edges.map((e, i) => { + const a = nodes[e.from] + const b = nodes[e.to] + if (!a || !b) return null + const d = stepPath(a.right, a.y, b.borderLeft, b.y) + if (e.kind === 'rel') return + if (e.kind === 'family') return + return + })} + {seqEdges.map((e) => { + const a = nodes[e.from] + const b = nodes[e.to] + if (!a || !b) return null + const ins = insByTo.get(e.to)! + const capsule = ins.length > 1 && firstIn.has(e.to) + // 源站点骑出边卡黑影外缘;目标:单线=圆点贴描边,多线=胶囊(只画一次) + return ( + + + {capsule ? ( + + ) : ins.length === 1 ? ( + + ) : null} + + ) + })} + + ) +} 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 ( +
+
+ + + + + {title} +
+
+
+ 位置:{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 ( +
+ +
+ 脱靶 · {how} +

{looks}

+
+
+ ) +} 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 ( +
+
+
{n}
+
{h}
+
+ ) +} + +function DoDont({ bad, children, text }: { bad?: boolean; children: React.ReactNode; text: string }) { + return ( +
+ + {bad ? '✗' : '✓'} + + {children} +

{text}

+
+ ) +} + +export function App() { + return ( + +
+
+ {PAIRS.map((p) => ( +
+
+
+
+
+
{p.name}
+
{p.use}
+
+ ))} +
+
+
+
{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)。设计系统四册的可运行版。

+
+ {PAGES.map((p) => ( + + {p.name} +

{p.desc}

+
+ ))} +
+
+ ) +} 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}
+
+ ))} +
+
+
+ + + + + + + + + + {VOCAB.map((g) => + g.rows.map(([sub, def], i) => ( + + {i === 0 && } + + + + )), + )} + +
父词图形 · 子词释义(skill 口径)
{g.p}{sub}{def}
+
+
+
+ {LINES.map((l) => ( +
+ + + {l.st && <> + + + } + +
{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)])), + }, + }, +})