teaching-site-design-system

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Teaching Site Design System

教学网站设计系统

Token starter (copy this, don't re-derive):
templates/tokens.css
— full
:root
block + dark theme + signature components (day-hero, glass-card, prompt-card, task-list, learning-goal, concept, unit, material, table). Paste into the
<style>
block of
index.html
(vanilla SPA pattern) or use as external
style.css
.
Schema authority: this skill defines visual tokens; data field names live in
_shared/domain-primitives.md
. When styling a component, read that file to know the data shape you're rendering.
Reference implementation:
d:/GitHub/ai-workshop/index.html:11-1700
— the full production CSS this skill's tokens were extracted from.
This skill defines the visual layer of a teaching site: color tokens, typography, geometry, components, dark mode strategy, and — critically — the reasoning behind each decision. The rationale matters more than the literal values; future sites can re-tune the values, but the why survives across projects.
Token起始模板(直接复制,无需重新编写):
templates/tokens.css
— 完整的
:root
代码块 + 暗黑主题 + 标志性组件(day-hero、glass-card、prompt-card、task-list、learning-goal、concept、unit、material、table)。粘贴到
index.html
<style>
块中(原生SPA模式),或作为外部
style.css
使用。
Schema权威来源:本Skill定义视觉Token;数据字段名称位于
_shared/domain-primitives.md
。在为组件设置样式时,请阅读该文件了解要渲染的数据结构。
参考实现
d:/GitHub/ai-workshop/index.html:11-1700
— 本Skill的Token提取自该完整生产环境CSS。
本Skill定义了教学网站的视觉层:颜色Token、排版、几何规范、组件、暗黑模式策略,以及至关重要的——每个决策背后的设计逻辑。设计逻辑比字面数值更重要;未来的网站可以调整数值,但设计的「初衷」会在不同项目中延续。

When to Invoke

何时调用

  • Setting up a new teaching site (read tokens before writing first CSS).
  • Adding a new visual element (badge, card, hero) — check whether an existing pattern covers it before inventing.
  • Tuning dark mode (which token to override, which not to).
  • Producing a corporate edition or ebook (same tokens flow to print CSS).
  • Reviewing visual consistency complaints ("it looks inconsistent here").
  • 搭建新教学网站时(编写第一行CSS前先读取Token)。
  • 添加新视觉元素(徽章、卡片、Hero区域)时——先检查是否已有可用模式,再考虑创建新样式。
  • 调整暗黑模式时(确定哪些Token需要覆盖,哪些不需要)。
  • 制作企业版或电子书时(相同Token可应用于打印CSS)。
  • 排查视觉一致性问题时(比如「这里看起来不一致」)。

The Foundational Decision: OKLCH, Not HEX/RGB

核心决策:使用OKLCH,而非HEX/RGB

css
:root {
  --bg:      oklch(99% 0.002 240);
  --surface: oklch(100% 0 0);
  --fg:      oklch(18% 0.012 250);
  --accent:  oklch(58% 0.18 255);
  --accent-2: oklch(66% 0.14 65);
}
Why OKLCH (not
#1a73e8
or
hsl(...)
):
  1. Perceptually uniform
    oklch(60% ...)
    and
    oklch(70% ...)
    look like an equal step apart; HSL doesn't (its 60→70 yellow jumps visually huge but blue barely changes).
  2. Predictable dark mode — for each light token at
    L=N%
    , the dark equivalent is roughly
    L=(100-N)%
    with the same chroma+hue. Mechanical.
  3. color-mix(in oklab, ...)
    works natively
    — e.g. soft accent tints (
    color-mix(in oklab, var(--accent) 18%, transparent)
    ) compose cleanly, no manual rgba math.
  4. Modern browser support is fine (Chrome 111+, Safari 15.4+, Firefox 113+). For a static teaching site this is acceptable.
If a stakeholder hands you brand colors as hex: convert once to OKLCH and never look back.
css
:root {
  --bg:      oklch(99% 0.002 240);
  --surface: oklch(100% 0 0);
  --fg:      oklch(18% 0.012 250);
  --accent:  oklch(58% 0.18 255);
  --accent-2: oklch(66% 0.14 65);
}
为什么选择OKLCH(而非
#1a73e8
hsl(...)
):
  1. 感知均匀——
    oklch(60% ...)
    oklch(70% ...)
    视觉上的亮度差距是均等的;而HSL做不到(比如60→70的黄色视觉变化极大,但蓝色几乎没变化)。
  2. 可预测的暗黑模式——对于亮度为
    L=N%
    的浅色Token,对应的深色Token亮度约为
    L=(100-N)%
    ,且色度和色相保持一致,实现起来非常机械简单。
  3. 原生支持
    color-mix(in oklab, ...)
    ——例如柔和的强调色淡色(
    color-mix(in oklab, var(--accent) 18%, transparent)
    )可以干净地组合,无需手动计算rgba值。
  4. 现代浏览器支持良好(Chrome 111+、Safari 15.4+、Firefox 113+)。对于静态教学网站来说,这一支持范围完全可以接受。
如果利益相关方提供的品牌颜色是十六进制格式:只需转换一次为OKLCH格式,之后就无需再使用原格式。

Token Architecture (Two-Axis)

Token架构(双轴)

Axis 1: Semantic Surface Tokens

轴1:语义化表层Token

css
--bg              /* page background */
--surface         /* card / panel */
--surface-2       /* nested card / hover state */
--fg              /* primary text */
--muted           /* secondary text */
--muted-2         /* tertiary text / placeholder */
--border          /* default border */
--border-strong   /* emphasis border */
These are structural — they describe role in the layout, not color. Dark mode swaps the values; component CSS doesn't change.
css
--bg              /* 页面背景 */
--surface         /* 卡片/面板 */
--surface-2       /* 嵌套卡片/悬停状态 */
--fg              /* 主文本 */
--muted           /* 次要文本 */
--muted-2         /* 三级文本/占位符 */
--border          /* 默认边框 */
--border-strong   /* 强调边框 */
这些是结构性的——它们描述的是在布局中的角色,而非具体颜色。暗黑模式会替换这些Token的值,但组件的CSS无需修改。

Axis 2: Accent Tokens (Dual Accent Strategy)

轴2:强调色Token(双强调色策略)

css
--accent          /* primary action color — blue/violet typically */
--accent-soft     /* faint tint, for hover backgrounds, badges */
--accent-deep     /* high-contrast variant, for hover text */

--accent-2        /* secondary accent — orange/amber typically */
--accent-2-soft
--accent-2-deep
Why two accents (not one + grayscale): teaching sites have two equally important attention paths:
  • Action path (
    --accent
    ): "click me", "copy this", "navigate here" — typically cool color (blue).
  • Content path (
    --accent-2
    ): "look here, this is rich content" — Day hero numerals, PROMPT badges, callout backgrounds. Typically warm color (orange/amber).
Single-accent designs end up using grayscale for content emphasis, which makes everything feel like a button. Dual-accent separates "things you click" from "things you read carefully".
css
--accent          /* 主要操作色——通常为蓝色/紫色 */
--accent-soft     /* 淡色变体,用于悬停背景、徽章 */
--accent-deep     /* 高对比度变体,用于悬停文本 */

--accent-2        /* 次要强调色——通常为橙色/琥珀色 */
--accent-2-soft
--accent-2-deep
为什么使用双强调色(而非单一强调色+灰度):教学网站有两条同等重要的注意力路径
  • 操作路径
    --accent
    ):「点击我」「复制这个」「导航到这里」——通常为冷色调(蓝色)。
  • 内容路径
    --accent-2
    ):「看这里,这是重要内容」——Day Hero数字、PROMPT徽章、提示框背景。通常为暖色调(橙色/琥珀色)。
单一强调色的设计最终会用灰度来强调内容,这会让所有元素看起来都像按钮。双强调色可以区分「可点击的元素」和「需要仔细阅读的内容」。

Status Tokens

状态Token

css
--success, --success-soft   /* learning goals ✓, copy success */
--warning, --warning-soft   /* gotchas, optional warnings */
--danger,  --danger-soft    /* errors, destructive actions */
Always provide a
-soft
variant for backgrounds; the base is for text/icons.
css
--success, --success-soft   /* 学习目标✓、复制成功 */
--warning, --warning-soft   /* 注意事项、可选警告 */
--danger,  --danger-soft    /* 错误、破坏性操作 */
始终为每个状态颜色提供
-soft
变体作为背景色;基础色用于文本/图标。

Typography: One Font Stack to Rule Them All

排版:一套字体栈统一所有场景

css
--font-display: "Nunito", "jf-openhuninn-1.1", "jf-openhuninn",
                "Gen Jyuu Gothic", "Taipei Sans TC Beta",
                "PingFang TC", "Noto Sans TC",
                system-ui, sans-serif;

/* All four roles point to the same stack */
--font-body:  var(--font-display);
--font-serif: var(--font-display);
--font-mono:  var(--font-display);
Why one stack for everything on a Chinese-language teaching site:
  1. Latin + 中文 混排在 90%+ 的段落都會出現。每換一種字體就會出現「字體斷裂感」。
  2. The fallback chain handles bilingual rendering — Nunito for Latin glyphs, openhuninn for 中文.
  3. Mono / serif variants are mostly used for spacing / weight effects, not actually different typefaces. Implement via
    font-feature-settings
    ,
    letter-spacing
    ,
    font-weight
    instead of swapping fonts.
Exception: ebook PDF for formal print may use a true serif (Source Han Serif) — that's the only place to break this rule.
css
--font-display: "Nunito", "jf-openhuninn-1.1", "jf-openhuninn",
                "Gen Jyuu Gothic", "Taipei Sans TC Beta",
                "PingFang TC", "Noto Sans TC",
                system-ui, sans-serif;

/* 四个角色都指向同一字体栈 */
--font-body:  var(--font-display);
--font-serif: var(--font-display);
--font-mono:  var(--font-display);
为什么中文教学网站要使用同一字体栈覆盖所有场景
  1. 90%以上的段落都会出现拉丁文字+中文混排。每更换一种字体就会出现「字体断裂感」。
  2. 回退链可处理双语渲染——Nunito用于拉丁字符,openhuninn用于中文。
  3. 等宽/衬线变体主要用于间距/字重效果,而非实际使用不同字体。可通过
    font-feature-settings
    letter-spacing
    font-weight
    实现,无需切换字体。
例外情况:正式印刷的电子书PDF可使用真正的衬线字体(Source Han Serif)——这是唯一可以打破此规则的场景。

Geometry Tokens

几何Token

css
--r-sm: 6px;   /* buttons, small chips */
--r-md: 10px;  /* cards, panels */
--r-lg: 14px;  /* heroes, large containers */

--sidebar-w: 268px;
--header-h: 56px;
--content-max: 880px;     /* reading line length cap */

--shadow-lift:
  0 6px 18px -10px oklch(20% 0.02 250 / 0.18),
  0 2px 4px  -2px oklch(20% 0.02 250 / 0.06);
Three radii is enough — more produces visual chaos. Match radius to element scale: button → sm, card → md, hero → lg.
--content-max: 880px
is deliberate — comfortable reading line length is ~60–75 CJK characters. Wider feels like documentation, narrower feels like mobile-stuck-on-desktop.
Double-layered shadows (
--shadow-lift
): one wide-soft + one tight-tighter. Single-layer shadows look digital; double-layer mimics physical card lift. Always negate the y-offset (
-10px
,
-2px
) so the shadow doesn't bleed below the element.
css
--r-sm: 6px;   /* 按钮、小标签 */
--r-md: 10px;  /* 卡片、面板 */
--r-lg: 14px;  /* Hero区域、大型容器 */

--sidebar-w: 268px;
--header-h: 56px;
--content-max: 880px;     /* 阅读行长度上限 */

--shadow-lift:
  0 6px 18px -10px oklch(20% 0.02 250 / 0.18),
  0 2px 4px  -2px oklch(20% 0.02 250 / 0.06);
三个圆角值足够——过多的圆角会导致视觉混乱。圆角值要与元素尺寸匹配:按钮→sm,卡片→md,Hero区域→lg。
**
--content-max: 880px
**是经过深思熟虑的——舒适的阅读行长度约为60–75个CJK字符。太宽会像文档,太窄会像桌面端强制使用移动端布局。
双层阴影
--shadow-lift
):一层宽而柔和 + 一层紧凑。单层阴影看起来很数字化;双层阴影模拟实体卡片的悬浮感。始终将y偏移设为负值(
-10px
-2px
),这样阴影不会溢出到元素下方。

Signature Components

标志性组件

1. Day Hero Numeral

1. Day Hero数字

css
.day-hero-numeral {
  font-family: var(--font-serif);
  font-size: clamp(120px, 16vw, 200px);
  font-weight: 500;
  line-height: 0.85;
  letter-spacing: -0.05em;
  color: var(--accent-2);
  text-shadow: 0 1px 0 color-mix(in oklab, var(--accent-2) 18%, transparent);
}
.day-hero:hover .day-hero-numeral {
  color: var(--accent-deep);
  text-shadow:
    0 2px 0 color-mix(in oklab, var(--accent-2) 22%, transparent),
    0 18px 40px color-mix(in oklab, var(--accent) 22%, transparent);
}
Why a giant numeral instead of decorative SVG / illustration for Day headers: an abstract SVG looks like a placeholder ("they forgot to put a real image"). A 200px "D2" reads as intentional, confident, navigational. Specific > abstract.
Why
clamp(120px, 16vw, 200px)
: scales with viewport width but with floor and ceiling so it never feels broken on mobile (would be 120px) or absurd on 4K (capped at 200px).
Why
@keyframes
fade-in, not
opacity: 0 → transition
: if
IntersectionObserver
fails to fire (e.g. CSS
zoom
is in play, see
static-spa-interactions
), the numeral stays at
opacity: 0
forever. With
@keyframes
, the default state is visible; animation only "plays once if triggered".
css
.day-hero-numeral {
  font-family: var(--font-serif);
  font-size: clamp(120px, 16vw, 200px);
  font-weight: 500;
  line-height: 0.85;
  letter-spacing: -0.05em;
  color: var(--accent-2);
  text-shadow: 0 1px 0 color-mix(in oklab, var(--accent-2) 18%, transparent);
}
.day-hero:hover .day-hero-numeral {
  color: var(--accent-deep);
  text-shadow:
    0 2px 0 color-mix(in oklab, var(--accent-2) 22%, transparent),
    0 18px 40px color-mix(in oklab, var(--accent) 22%, transparent);
}
为什么用巨大的数字而非装饰性SVG/插图作为Day标题:抽象SVG看起来像占位符(「他们忘了放真实图片」)。200px的「D2」会被解读为有意图、自信、具有导航性。具体>抽象。
为什么使用
clamp(120px, 16vw, 200px)
:随视窗宽度缩放,但有下限和上限,因此在移动端不会显得过小(最低120px),在4K屏幕上也不会显得夸张(最高200px)。
为什么使用
@keyframes
淡入,而非
opacity: 0 → transition
:如果
IntersectionObserver
触发失败(比如开启了CSS
zoom
,详见
static-spa-interactions
),数字会一直保持
opacity: 0
。使用
@keyframes
时,默认状态是可见的;动画仅在触发时「播放一次」。

2. PROMPT Block (Orange Badge + Dark Code)

2. PROMPT区块(橙色徽章+深色代码区)

css
.prompt-block {
  background: oklch(15% 0.02 250);          /* always dark, even in light mode */
  color: oklch(92% 0.005 250);
  border-left: 4px solid var(--accent-2);
  padding: 14px 16px;
  border-radius: var(--r-md);
}
.prompt-block::before {
  content: 'PROMPT';
  display: inline-block;
  background: var(--accent-2);
  color: white;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-family: var(--font-mono);
  letter-spacing: 0.1em;
  margin-bottom: 8px;
}
Why orange (
--accent-2
) for PROMPT: blue (
--accent
) is reserved for "clickable / navigation". Orange marks "this is a recipe — read carefully and copy verbatim". Three months in, learners associate orange = prompt block automatically.
Why dark background even in light mode: code-y content reads as code-y. Switching light/dark would feel like the prompt is "less serious" in light mode.
css
.prompt-block {
  background: oklch(15% 0.02 250);          /* 始终为深色,即使在浅色模式下 */
  color: oklch(92% 0.005 250);
  border-left: 4px solid var(--accent-2);
  padding: 14px 16px;
  border-radius: var(--r-md);
}
.prompt-block::before {
  content: 'PROMPT';
  display: inline-block;
  background: var(--accent-2);
  color: white;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-family: var(--font-mono);
  letter-spacing: 0.1em;
  margin-bottom: 8px;
}
为什么PROMPT使用橙色
--accent-2
):蓝色(
--accent
)被保留用于「可点击/导航」元素。橙色标记「这是操作指南——请仔细阅读并原样复制」。三个月后,学习者会自动将橙色与PROMPT区块关联。
为什么浅色模式下也使用深色背景:代码类内容看起来就应该像代码。切换明暗模式会让PROMPT在浅色模式下显得「不够正式」。

3. Glass Card (
glass-card
)

3. 玻璃卡片(
glass-card

css
.glass-card {
  background: color-mix(in oklab, var(--surface) 60%, transparent);
  backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid color-mix(in oklab, var(--border) 80%, transparent);
  border-radius: var(--r-md);
  padding: 16px 20px;
  box-shadow: var(--shadow-lift);
}
Why translucent + backdrop-filter: on a page with rich background (gradient, dotted pattern, illustrations), opaque cards feel heavy. Glass cards let content breathe, signal "this floats on top of the document".
Caveat: requires
backdrop-filter
browser support. Safari needs
-webkit-backdrop-filter
prefix. On unsupported browsers, the fallback is just the 60%-opacity color — degrades gracefully.
css
.glass-card {
  background: color-mix(in oklab, var(--surface) 60%, transparent);
  backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid color-mix(in oklab, var(--border) 80%, transparent);
  border-radius: var(--r-md);
  padding: 16px 20px;
  box-shadow: var(--shadow-lift);
}
为什么使用半透明+背景模糊:在有丰富背景(渐变、点状图案、插图)的页面上,不透明卡片会显得厚重。玻璃卡片让内容更透气,传递出「漂浮在文档上方」的信号。
注意事项:需要浏览器支持
backdrop-filter
。Safari需要添加
-webkit-backdrop-filter
前缀。在不支持的浏览器上,回退方案是仅显示60%透明度的颜色——降级效果优雅。

4. Task Checkbox (Three-State Visual)

4. 任务复选框(三态视觉)

css
.task-checkbox {
  width: 18px; height: 18px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  transition: all 120ms ease;
}
.task-item:hover .task-checkbox { border-color: var(--accent); }
.task-item.done .task-checkbox {
  background: var(--success);
  border-color: var(--success);
}
.task-item.done .task-label { text-decoration: line-through; color: var(--muted); }
Three states: default (empty + strong border), hover (accent border, hint of intent), done (filled green + strikethrough). The hover state matters — without it, learners aren't sure the checkbox is clickable.
css
.task-checkbox {
  width: 18px; height: 18px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  transition: all 120ms ease;
}
.task-item:hover .task-checkbox { border-color: var(--accent); }
.task-item.done .task-checkbox {
  background: var(--success);
  border-color: var(--success);
}
.task-item.done .task-label { text-decoration: line-through; color: var(--muted); }
三种状态:默认(空+粗边框)、悬停(强调色边框,提示可点击)、已完成(绿色填充+删除线)。悬停状态很重要——没有它,学习者不确定复选框是否可点击。

5. Goal Badge (Green ✓)

5. 目标徽章(绿色✓)

css
.learning-goal::before {
  content: '✓';
  display: inline-block;
  width: 20px; height: 20px;
  background: var(--success-soft);
  color: var(--success);
  border-radius: 999px;
  text-align: center;
  line-height: 20px;
  margin-right: 8px;
  font-weight: bold;
}
Use the green-on-light-green disc consistently for "learning outcomes" / "you'll be able to ...". Don't reuse for "completed" — that's
task-checkbox
's job. Green has two distinct semantic uses which must look different: goal (badge prefix) vs done (filled checkbox).
css
.learning-goal::before {
  content: '✓';
  display: inline-block;
  width: 20px; height: 20px;
  background: var(--success-soft);
  color: var(--success);
  border-radius: 999px;
  text-align: center;
  line-height: 20px;
  margin-right: 8px;
  font-weight: bold;
}
始终使用浅绿底绿字的圆形徽章标记「学习成果」/「你将能够...」。不要将其复用为「已完成」标记——那是
task-checkbox
的职责。绿色有两种不同的语义用途,必须区分开来:目标(徽章前缀)与已完成(填充复选框)。

Dark Mode Strategy

暗黑模式策略

css
:root { /* light tokens here */ }

[data-theme="dark"] {
  --bg:      oklch(16% 0.014 250);
  --surface: oklch(19.5% 0.014 250);
  --fg:      oklch(96% 0.005 250);
  --border:  oklch(28% 0.014 250);
  /* accents usually stay the same — accent-deep may flip to brighter */
  --accent-deep: oklch(72% 0.18 255);
}
Use
[data-theme]
attribute, not
prefers-color-scheme
: a manual toggle is the source of truth. Auto-following OS theme means the user can't decide per-site. The toggle reads/writes localStorage.
What flips in dark mode:
  • ✅ All surface tokens (bg / surface / fg / muted / border)
  • accent-deep
    (the high-contrast variant) — flip to a brighter L value
  • ❌ Status colors (success / danger / warning) — these are semantic, keep their hue; the
    -soft
    variant naturally darkens because of the surface change
  • accent-2
    — the dual-accent identity should survive dark mode (the orange brand stays orange)
What stays the same:
  • All geometry tokens (radii, sidebar width, shadow definitions — though shadow opacity may need tuning)
  • All component layouts and structure
The rule of thumb: token values change, component CSS does not. If you find yourself writing
[data-theme="dark"] .task-checkbox { ... }
, the abstraction has leaked — refactor the component to use tokens for the property that's varying.
css
:root { /* 浅色模式Token */ }

[data-theme="dark"] {
  --bg:      oklch(16% 0.014 250);
  --surface: oklch(19.5% 0.014 250);
  --fg:      oklch(96% 0.005 250);
  --border:  oklch(28% 0.014 250);
  /* 强调色通常保持不变——accent-deep可能会调亮 */
  --accent-deep: oklch(72% 0.18 255);
}
使用
[data-theme]
属性,而非
prefers-color-scheme
:手动切换是最终的判断标准。自动跟随系统主题意味着用户无法按站点自定义。切换按钮会读取/写入localStorage。
暗黑模式中需要切换的内容
  • ✅ 所有表层Token(bg/surface/fg/muted/border)
  • accent-deep
    (高对比度变体)——调亮亮度值
  • ❌ 状态颜色(success/danger/warning)——这些是语义化的,保持色相不变;
    -soft
    变体会随背景变化自然变暗
  • accent-2
    ——双强调色的标识应在暗黑模式下保留(橙色品牌色保持橙色)
保持不变的内容
  • 所有几何Token(圆角、侧边栏宽度、阴影定义——不过阴影透明度可能需要调整)
  • 所有组件布局和结构
经验法则:Token值变化,组件CSS不变。如果你发现自己在写
[data-theme="dark"] .task-checkbox { ... }
,说明抽象层已经失效——重构组件,使用Token来控制变化的属性。

Cross-Format Token Reuse (Web → Ebook)

跨格式Token复用(网页→电子书)

The ebook print CSS (
course-ebook-publishing
) reads the same tokens. Specifically:
css
/* style-ebook.css (print only) */
@media print {
  :root {
    /* Override only what print needs differently */
    --bg: white;
    --surface: white;
    /* But colors of badges, prompts, headings stay the same */
  }
  .prompt-block { /* same .prompt-block style — looks identical to web */ }
  .learning-goal::before { /* same green disc */ }
}
Why: a learner who sees the web first, then opens the PDF handbook, should feel "same product". If web is orange-PROMPT and PDF is gray-PROMPT, the visual brand is shattered.
The corporate edition (
course-corporate-edition
) does the same — even with brand-customised assets, the design tokens remain.
电子书打印CSS(
course-ebook-publishing
读取相同的Token。具体如下:
css
/* style-ebook.css(仅打印) */
@media print {
  :root {
    /* 仅覆盖打印需要的不同设置 */
    --bg: white;
    --surface: white;
    /* 但徽章、提示框、标题的颜色保持不变 */
  }
  .prompt-block { /* 相同的.prompt-block样式——与网页外观一致 */ }
  .learning-goal::before { /* 相同的绿色圆形徽章 */ }
}
原因:先浏览网页,再打开PDF手册的学习者应该觉得「这是同一个产品」。如果网页是橙色PROMPT而PDF是灰色PROMPT,视觉品牌就会被破坏。
企业版(
course-corporate-edition
)也是如此——即使有品牌定制资源,设计Token也保持不变。

Three Decisions That Sound Small But Aren't

三个看似微小却至关重要的决策

Decision 1:
color-mix(in oklab, X 18%, transparent)
for soft tints

决策1:使用
color-mix(in oklab, X 18%, transparent)
生成柔和淡色

Don't hand-write
rgba(...)
. Use
color-mix
so tints derive from the source token:
css
background: color-mix(in oklab, var(--accent-2) 18%, transparent);
When you eventually re-tune
--accent-2
, every tint follows. With manual rgba, you'd have 50 places to update.
不要手动编写
rgba(...)
。使用
color-mix
让淡色从源Token派生:
css
background: color-mix(in oklab, var(--accent-2) 18%, transparent);
当你最终调整
--accent-2
时,所有淡色都会自动跟随变化。如果手动使用rgba,你需要修改50个地方。

Decision 2: No grid system, just
--content-max

决策2:不使用网格系统,仅用
--content-max

Avoid bringing in a 12-column grid framework. For a teaching site with linear reading flow,
max-width: var(--content-max); margin: 0 auto
is enough. Columns appear locally via
grid-template-columns
inside specific components (figure grid, tool gallery) — but the page itself is a stream.
避免引入12列网格框架。对于线性阅读流程的教学网站,
max-width: var(--content-max); margin: 0 auto
就足够了。列布局仅在特定组件内部使用
grid-template-columns
实现(如图形网格、工具库)——但页面本身是流式布局。

Decision 3: Animation budget = "almost none"

决策3:动画预算=「几乎为零」

Allowed:
  • fade-in
    on scroll (one-time per element)
  • Hover micro-interactions (color / shadow shift, 120–200ms)
  • Sidebar slide (transform, 200ms)
NOT allowed:
  • Page transitions between sections
  • Decorative idle animations
  • Anything > 400ms
Teaching content is dense; animation is a distraction tax. Spend the attention budget on content, not motion.
允许的动画:
  • 滚动时的淡入(每个元素仅触发一次)
  • 悬停微交互(颜色/阴影变化,120–200ms)
  • 侧边栏滑动(transform,200ms)
不允许的动画:
  • 页面间过渡动画
  • 装饰性闲置动画
  • 任何超过400ms的动画
教学内容密度高;动画会分散注意力。把注意力预算花在内容上,而非动效上。

Anti-Patterns

反模式

  • Inventing a new color for "this one section" — pull from existing tokens or refactor to add a properly-named token. Drive-by hex values rot the system.
  • Using
    --accent
    for content-emphasis (like PROMPT badge)
    — collapses the dual-accent semantic. Use
    --accent-2
    .
  • Different font for code blocks — see typography section. Use the same stack with
    font-weight
    /
    letter-spacing
    for differentiation, or accept the visual cost.
  • Manually styling dark mode per component — see dark mode section. If a component needs special dark-mode handling, the component is using a non-tokenised value somewhere.
  • Forgetting
    -soft
    variants
    — every accent / status color needs a soft variant. Without it, designers reach for ad-hoc opacity values.
  • 为「某个特定区域」发明新颜色——从现有Token中选取,或重构添加一个命名规范的新Token。随意使用十六进制值会破坏系统。
  • 使用
    --accent
    强调内容(如PROMPT徽章)
    ——会混淆双强调色的语义。应使用
    --accent-2
  • 代码块使用不同字体——参见排版部分。使用同一字体栈,通过
    font-weight
    /
    letter-spacing
    区分,或接受视觉差异。
  • 为每个组件手动设置暗黑模式样式——参见暗黑模式部分。如果组件需要特殊的暗黑模式处理,说明该组件在某处使用了非Token化的值。
  • 忘记添加
    -soft
    变体
    ——每个强调色/状态色都需要
    -soft
    变体。没有它,设计师会使用临时的透明度值。

Hand-off

交接说明

When this skill is consumed by another skill (SPA / interactions / ebook / corporate):
  • Reference
    :root
    tokens, never raw values.
  • If you find yourself writing a literal
    #...
    hex or
    rgb(...)
    , you've broken the system — either it belongs as a new token, or you're recreating an existing one.
  • New components should compose existing patterns (glass-card + prompt-block + task-list) before inventing.
The token file (the
:root { ... }
block) is the single source of truth. Print it on the wall. Re-tune values when the brand evolves, but never widen the token set casually.
当其他Skill(SPA/交互/电子书/企业版)使用本Skill时:
  • 引用
    :root
    Token,而非原始值。
  • 如果你发现自己在写字面量
    #...
    十六进制值或
    rgb(...)
    ,说明你破坏了系统——要么应该添加一个新Token,要么你在重复创建已有的Token。
  • 新组件应先组合现有模式(glass-card+prompt-block+task-list),再考虑创建新样式。
Token文件(
:root { ... }
代码块)是唯一的事实来源。把它贴在墙上。当品牌升级时调整数值,但不要随意扩大Token集合。