setHovered(1)}
+ onMouseLeave={() => setHovered(null)}
+ >
+
+
setHovered(2)}
+ onMouseLeave={() => setHovered(null)}
+ >
+
+
setHovered(5)}
+ onMouseLeave={() => setHovered(null)}
+ >
+
+
diff --git a/apps/design-lab/src/pages/shelf/main.tsx b/apps/design-lab/src/pages/shelf/main.tsx
index 3dc54c8..7d878af 100644
--- a/apps/design-lab/src/pages/shelf/main.tsx
+++ b/apps/design-lab/src/pages/shelf/main.tsx
@@ -1,5 +1,5 @@
+import '@stack/design-system/styles/tokens.css'
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
index 97df6df..2ebfe26 100644
--- a/apps/design-lab/src/pages/symbols/App.tsx
+++ b/apps/design-lab/src/pages/symbols/App.tsx
@@ -1,7 +1,7 @@
import { Page, Section } from '../../lib/ui'
-import { BrickDot, Cross, Mark, Ring, Tremble, Tri, Wave } from '../../components/shapes'
+import { BrickDot, Cross, Mark, Ring, Tremble, Tri, Wave } from '@stack/design-system'
import { CHAPTERS } from '../../lib/chapters'
-import { Ticket } from '../../components/Ticket'
+import { Ticket } from '@stack/design-system'
import type { ReactNode } from 'react'
const SHAPES = [
diff --git a/apps/design-lab/src/pages/symbols/main.tsx b/apps/design-lab/src/pages/symbols/main.tsx
index 3dc54c8..7d878af 100644
--- a/apps/design-lab/src/pages/symbols/main.tsx
+++ b/apps/design-lab/src/pages/symbols/main.tsx
@@ -1,5 +1,5 @@
+import '@stack/design-system/styles/tokens.css'
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
deleted file mode 100644
index 76061f5..0000000
--- a/apps/design-lab/src/styles/tokens.css
+++ /dev/null
@@ -1,69 +0,0 @@
-@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-mute: #8a7a5c;
- --color-body: #3d3d3d;
-
- /* 统一硬投影——唯一投影配方,所有基础组件复用(rest / 拿起) */
- --shadow-hard: 5px 5px 0 var(--color-ink);
- --shadow-hard-lift: 8px 8px 0 var(--color-ink);
- /* 同配方的 drop-shadow 形态:给挖空轮廓用(票券)——box-shadow 会被 mask 裁掉 */
- --drop-shadow-hard: 5px 5px 0 var(--color-ink);
-
- /* 章节色别名 */
- --color-ch1: #ff6b6b;
- --color-ch2: #ff8fab;
- --color-ch3: #ffd93d;
- --color-ch4: #4ecdc4;
- --color-ch5: #5b8def;
- --color-ch6: #a78bfa;
-}
-
-/* 票券打孔(完成票专用),三件分工:
- * 挖空 = 票体 mask 差集(两端 1/4 圆硬停剪切,孔穿透露出页面底色);
- * 阴影 = 外层 wrapper drop-shadow-hard——drop-shadow 跟随 alpha 轮廓,
- * 孔挖到哪阴影咬到哪(box-shadow 会被 mask 的 border-box 裁切整体吃掉,不可用);
- * 孔缘 = .pk-arc 半圆环补 ink 边线(mask 只减形不补线),圆心骑撕线两端。 */
-.tk-pk {
- --hole-x: 172.75px; /* 撕线中心:216 − 右描边 2 − stub 42 + 撕线半宽 */
-}
-.ticket-pk {
- -webkit-mask-image:
- radial-gradient(circle 7px at var(--hole-x) 0, transparent 6px, #000 6.5px),
- radial-gradient(circle 7px at var(--hole-x) 100%, transparent 6px, #000 6.5px);
- -webkit-mask-composite: source-in;
- mask-image:
- radial-gradient(circle 7px at var(--hole-x) 0, transparent 6px, #000 6.5px),
- radial-gradient(circle 7px at var(--hole-x) 100%, transparent 6px, #000 6.5px);
- mask-composite: intersect;
-}
-/* 孔缘弧线:16px 圆环(描边带 r6–8),clip-path 只留票内一半,不进阴影轮廓 */
-.pk-arc {
- position: absolute;
- left: calc(var(--hole-x) - 8px);
- width: 16px;
- height: 16px;
- border: 2px solid var(--color-ink);
- border-radius: 9999px;
- pointer-events: none;
-}
-.pk-arc-t {
- top: -8px;
- clip-path: inset(50% 0 0 0);
-}
-.pk-arc-b {
- bottom: -8px;
- clip-path: inset(0 0 50% 0);
-}
diff --git a/apps/tutorial/package.json b/apps/tutorial/package.json
index 392c980..9d13eee 100644
--- a/apps/tutorial/package.json
+++ b/apps/tutorial/package.json
@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@stack/design-system": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/apps/tutorial/src/components/ChapterPage.tsx b/apps/tutorial/src/components/ChapterPage.tsx
index 69308fe..1cf7981 100644
--- a/apps/tutorial/src/components/ChapterPage.tsx
+++ b/apps/tutorial/src/components/ChapterPage.tsx
@@ -6,10 +6,8 @@
import type { CSSProperties } from 'react'
import { Page, Section } from '../lib/ui'
import { CHAPTERS, CONTENT } from '../lib/chapters'
-import type { ChapterId } from '../lib/types'
-import { AssignBlock } from './Ticket'
-import { BrickDots } from './MemphisCard'
-import { Mark, Ring, Tri, Wave } from './shapes'
+import type { ChapterId } from '@stack/design-system'
+import { AssignBlock, BrickDots, Mark, Ring, Tri, Wave } from '@stack/design-system'
const ALL: ChapterId[] = [1, 2, 3, 4, 5, 6]
diff --git a/apps/tutorial/src/components/EntryGuide.tsx b/apps/tutorial/src/components/EntryGuide.tsx
deleted file mode 100644
index 3ef8846..0000000
--- a/apps/tutorial/src/components/EntryGuide.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/** 入口梳理 —— 读者画像推荐路径:是推荐不是强制(无锁) */
-import { CHAPTERS, PROFILES } from '../lib/chapters'
-
-export function EntryGuide() {
- return (
-
- {PROFILES.map((p) => (
-
-
-
- {p.icon}
-
- {p.name}
-
-
-
{p.note}
-
- ))}
-
- )
-}
diff --git a/apps/tutorial/src/components/Scatter.tsx b/apps/tutorial/src/components/Scatter.tsx
deleted file mode 100644
index bde9a9e..0000000
--- a/apps/tutorial/src/components/Scatter.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-/** 散落形状摆放——「卡片附近空当 + 随机」:
- * 网格采样容器空白 → 过滤出距内容盒([data-comp])≤ near 的空当(不胡乱撒)→
- * 种子随机挑位(彼此保距)+空当内随机偏移+随机旋转。
- * 种子固定 → 每次渲染一致(视觉基线可测);容器重排(ResizeObserver)自动重算。 */
-import { useLayoutEffect, useRef, useState, type ReactNode } from 'react'
-
-function mulberry32(seed: number) {
- let t = seed >>> 0
- return () => {
- t = (t + 0x6d2b79f5) >>> 0
- let x = Math.imul(t ^ (t >>> 15), 1 | t)
- x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x
- return ((x ^ (x >>> 14)) >>> 0) / 4294967296
- }
-}
-
-type Place = { x: number; y: number; rot: number }
-
-export function Scatter({
- shapes,
- seed = 7,
- near = 150, // 空当须距某内容盒 ≤ near——只散在卡片附近
- margin = 34, // 与内容盒保持的最小间隙(≥ 最大形状半对角)
- step = 30, // 采样网格密度
- minGap = 80, // 形状彼此最小间距
-}: {
- shapes: ReactNode[]
- seed?: number
- near?: number
- margin?: number
- step?: number
- minGap?: number
-}) {
- const layerRef = useRef
(null)
- const [places, setPlaces] = useState([])
-
- useLayoutEffect(() => {
- const layer = layerRef.current
- const host = layer?.parentElement
- if (!layer || !host) return
-
- const place = () => {
- const hb = host.getBoundingClientRect()
- const rects = [...host.querySelectorAll('[data-comp]')].map((el) => el.getBoundingClientRect())
- if (!rects.length || !hb.width) return
- const dist = (x: number, y: number, r: DOMRect) =>
- Math.hypot(Math.max(r.left - x, 0, x - r.right), Math.max(r.top - y, 0, y - r.bottom))
- const cand: { x: number; y: number }[] = []
- for (let x = 36; x < hb.width - 36; x += step)
- for (let y = 36; y < hb.height - 36; y += step) {
- const px = hb.left + x
- const py = hb.top + y
- if (rects.every((r) => dist(px, py, r) >= margin) && rects.some((r) => dist(px, py, r) <= near))
- cand.push({ x, y })
- }
- const rnd = mulberry32(seed)
- for (let i = cand.length - 1; i > 0; i--) {
- const j = Math.floor(rnd() * (i + 1))
- ;[cand[i], cand[j]] = [cand[j], cand[i]]
- }
- const taken: Place[] = []
- for (const c of cand) {
- if (taken.length >= shapes.length) break
- if (taken.some((t) => Math.hypot(t.x - c.x, t.y - c.y) < minGap)) continue
- taken.push({ x: c.x + (rnd() - 0.5) * 14, y: c.y + (rnd() - 0.5) * 14, rot: Math.round((rnd() - 0.5) * 52) })
- }
- setPlaces(taken)
- }
-
- place()
- const ro = new ResizeObserver(place)
- ro.observe(host)
- return () => ro.disconnect()
- }, [seed, near, margin, step, minGap, shapes.length])
-
- return (
-
- {places.map((p, i) => (
-
- {shapes[i]}
-
- ))}
-
- )
-}
diff --git a/apps/tutorial/src/components/Ticket.tsx b/apps/tutorial/src/components/Ticket.tsx
deleted file mode 100644
index dd11741..0000000
--- a/apps/tutorial/src/components/Ticket.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/** 票券 —— 只表达完成状态:完成=沿撕线打孔(半圆缺口),未开始=实线无孔。
- * 外层 wrapper 出阴影(drop-shadow 跟随挖空后的轮廓,box-shadow 会被 mask 裁掉);
- * 内层票体裁切式:头部色块无独立描边,由票体 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')}
-
-
- {/* 孔缘弧线:mask 只减形不补线,弧线补上挖空边缘的 ink 描边 */}
- {done && (
- <>
-
-
- >
- )}
-
- )
-}
-
-/** 指派学习块 —— 普通内容块(非票据):+ 位置/预期/场景三样 + 脱靶标记 */
-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/tutorial/src/components/shapes.tsx b/apps/tutorial/src/components/shapes.tsx
deleted file mode 100644
index 998abfb..0000000
--- a/apps/tutorial/src/components/shapes.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-/** 七个语义形状——装饰即语义(取自 ai-era-tutorial 词汇表) */
-import type { CSSProperties, ReactNode } 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 Mark = ({ children, className }: { children: ReactNode; className?: string }) => (
-
- {children}
-
-)
-
-/** ⌇ 脱靶:竖立颤抖——图形入口,读者追随图形进入阅读 */
-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/tutorial/src/lib/chapters.ts b/apps/tutorial/src/lib/chapters.ts
index 217bb08..50c5261 100644
--- a/apps/tutorial/src/lib/chapters.ts
+++ b/apps/tutorial/src/lib/chapters.ts
@@ -1,5 +1,5 @@
/** 教程内容数据——大纲见 OUTLINE.md,词汇与结构对齐 ai-era-tutorial skill */
-import type { Assignment, Chapter, ChapterId, Edge, Profile } from './types'
+import type { Assignment, Chapter, ChapterId, Edge, Profile } from '@stack/design-system'
export const CHAPTERS: Record = {
1: {
@@ -432,7 +432,7 @@ export const CONTENT: Record = {
}
/** 指派学习块示例数据(设计系统·组件/样例页演示用) */
-export const ASSIGNMENT: import('./types').Assignment = {
+export const ASSIGNMENT: import('@stack/design-system').Assignment = {
title: '指派学习 · HTTP 协议',
pos: 'Web 通信的地基,旁边是 DNS(找机器)与 TCP(铺线路)',
expect: '能向 agent 讲清一次请求的往返:URL → DNS → 连接 → 请求行/头/体 → 响应 → 渲染',
diff --git a/apps/tutorial/src/lib/types.ts b/apps/tutorial/src/lib/types.ts
deleted file mode 100644
index 4b88f5f..0000000
--- a/apps/tutorial/src/lib/types.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/** 设计系统数据类型——对齐 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/tutorial/src/lib/ui.tsx b/apps/tutorial/src/lib/ui.tsx
index cc3cc41..9fb46fa 100644
--- a/apps/tutorial/src/lib/ui.tsx
+++ b/apps/tutorial/src/lib/ui.tsx
@@ -1,9 +1,8 @@
/** 教程站页面骨架:站头导航跨页常驻(view-transition-name 共享元素,页间不闪烁)。
* 选中项背景色=该章色(与章头 title 横幅统一);书架/设计册选中为 ink。 */
import type { ReactNode } from 'react'
-import type { ChapterId } from './types'
+import type { ChapterId } from '@stack/design-system'
import { CHAPTERS } from './chapters'
-import '../styles/tokens.css'
/** 设计系统四册(自 design-lab 迁入) */
export type DesignKey = 'design-colors' | 'design-typography' | 'design-symbols' | 'design-components' | 'design-examples'
diff --git a/apps/tutorial/src/pages/design-components/App.tsx b/apps/tutorial/src/pages/design-components/App.tsx
index fc0cfbe..8db8e40 100644
--- a/apps/tutorial/src/pages/design-components/App.tsx
+++ b/apps/tutorial/src/pages/design-components/App.tsx
@@ -1,9 +1,9 @@
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'
+import { ASSIGNMENT, CHAPTERS, PROFILES } from '../../lib/chapters'
+import { MemphisCard } from '@stack/design-system'
+import { AssignBlock, Ticket } from '@stack/design-system'
+import { EntryGuide } from '@stack/design-system'
+import { Mark } from '@stack/design-system'
export function App() {
return (
@@ -29,7 +29,7 @@ export function App() {
)
diff --git a/apps/tutorial/src/pages/design-examples/App.tsx b/apps/tutorial/src/pages/design-examples/App.tsx
index 5c04bea..6a471d8 100644
--- a/apps/tutorial/src/pages/design-examples/App.tsx
+++ b/apps/tutorial/src/pages/design-examples/App.tsx
@@ -1,13 +1,9 @@
/** 组合场景=大部分组件拿出来的真实使用,不做解释性标注:
* 入口画像 → 课程卡与关系线 → 进度票券 → 指派块;形状散落于卡片附近空当。 */
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'
+import { ASSIGNMENT, CHAPTERS, EDGES, PROFILES } from '../../lib/chapters'
+import { AssignBlock, EntryGuide, MemphisCard, RouteLine, Scatter, Ticket } from '@stack/design-system'
+import { Cross, Ring, Tremble, Tri, Wave } from '@stack/design-system'
/* 场景坐标(节点=卡描边真实边缘):卡1(40,110,250) 卡2(380,44,250,rot) 卡3(700,44,140) */
const nodes = {
@@ -19,7 +15,7 @@ const nodes = {
export function App() {
return (