# DESIGN.md — 魔芋の松果 · 温馨像素设计规范
> 站名 **魔芋の松果**(Pinecone)。导航页和**所有游戏**共用这一套设计语言。
> 改 `design/tokens.css` 一处,全站一起变。
>
> 可视化参考:打开 `design/styleguide.html`(也可以 `node tools/serve.mjs` 后访问 `/design/styleguide.html`)。
---
## 1. 设计方向:温馨像素 Cozy Pixel
一句话:**傍晚的林间小屋,木桌上放着一台老式掌机。**
| 关键词 | 落到什么上 |
|---|---|
| **像素** | 直角、2px 描边、无模糊硬阴影、整数网格、抖动网点。不用圆角、不用模糊、不用渐变过渡 |
| **自然** | 暖土色系:赭石打底、陶土做主色、苔绿做辅色、蜜黄做点缀。背景不用纯白,文字不用纯黑 |
| **温暖** | 低饱和暖色底 + 高对比暖墨字。留白充足,宁可空着也不塞满 |
### 1.1 为什么不用像素字体
中文没有可靠的像素字重,硬套会让正文难读、小字糊成一团。
所以像素感由**几何**承担——直角、2px 硬描边、无模糊投影、8px 网格、抖动网点、点阵图标;
正文保持系统无衬线的可读性,等宽字体只用在标签、数字、代码这类「机器感」的地方。
这条是硬约束:**不要为了「更像像素」去找像素字体**。
### 1.2 品牌
**站名:魔芋の松果**(英文 **Pinecone**)。`
` 写「魔芋の松果 · 小游戏合集」。
**Logo**:一颗点阵松果,32×32 网格。
| 文件 | 网格 | 用途 |
|---|---|---|
| `design/logo.svg` | 32×32,透明底 | 品牌位、规范页、大尺寸 |
| `design/favicon.svg` | 16×16,奶油底 | 浏览器标签 |
- 由 `tools/make-logo.mjs` **参数化生成**(菱形鳞片格 + 收尖轮廓 + 果柄)。
要换形状就改脚本里的 `CFG` 重跑,**不要手改 SVG**。
- 配色:轮廓 `#5c2c10` / 果身 `#c97330` / 受光 `#e9a860`——和 `--primary` 同族。
- **不做单色版**:单色会丢掉鳞片格,16px 下变成一颗没有纹理的蛋。
- **品牌位**(`.brand` / `.brand-mark` / `.brand-text`,在 `components.css`):
40×40 奶油底方块 + 2px 主色描边 + 32px logo。
奶油底是**固定色**(`--px-paper-100`),不随主题变——夜间主题下它像一扇亮着的窗。
---
## 2. 设计原则
1. **一屏一个焦点**:最大的字、最亮的按钮、唯一的主色块,三者只出现在一处。
2. **主按钮只有一个**:其余降级为描边按钮,再次级用幽灵按钮。
3. **靠字号和颜色分层,不靠边框堆结构**:分割线只用在真正需要断开的地方。
4. **间距只用 4 的倍数**:`4 / 8 / 12 / 16 / 24 / 32 / 48 / 64`,组内 < 组间 < 区块。
5. **硬边优先**:能直角就直角,能硬阴影就不模糊,能纯色就不渐变。
6. **真实内容**:不写「标题一」「示例文字」,写像真游戏的文案。
---
## 3. 色彩
### 3.1 语义色(按角色取用,不要按色相取用)
| 角色 | 变量 | 日间 | 夜间 | 用途 |
|---|---|---|---|---|
| 背景 | `--bg` | `#f4ecdc` | `#1f1813` | 页面底 |
| 背景深 | `--bg-alt` | `#ece0c9` | `#17110d` | 代码块、键帽 |
| 表面 | `--surface` | `#fdf8ee` | `#251d17` | 卡片、输入框、按钮 |
| 表面深 | `--surface-alt` | `#f0e4cf` | `#2e241c` | 悬停态、标签底 |
| 描边 | `--line` | `#d5c3a6` | `#3b2f25` | 2px 边框 |
| 描边强 | `--line-strong` | `#b39d7c` | `#55443a` | 强调边框 |
| 正文 | `--text` | `#2b2116` | `#f3e7d5` | 正文、标题 |
| 次要 | `--text-muted` | `#6a5741` | `#b6a289` | 描述、说明 |
| 弱化 | `--text-dim` | `#7a664c` | `#97846c` | 标签、元信息 |
| 主色 | `--primary` | `#b84d24` | `#e8823f` | 主按钮、选中态、链接 |
| 主色悬停 | `--primary-hover` | `#a8471f` | `#f2955a` | 悬停 |
| 主色上的字 | `--on-primary` | `#fff8ee` | `#1a1410` | 主色块上的文字 |
| 辅色 | `--accent` | `#527030` | `#8fb45c` | 次要强调、成功 |
| 点缀 | `--honey` | `#d3961f` | `#e9b455` | 徽章、高亮(**只作填充**) |
| 点缀上的字 | `--on-honey` | `#2b2116` | `#1a1410` | 蜜黄块上的文字 |
| 危险 | `--danger` | `#a83a52` | `#d4617a` | 错误、删除 |
| 信息 | `--info` | `#356a84` | `#6fa3bd` | 提示、链接备选 |
### 3.2 对比度(已实测,改色必须重测)
| 组合 | 日间 | 夜间 | 要求 |
|---|---|---|---|
| `text` / `bg` | 13.4:1 | 14.4:1 | ≥ 4.5 |
| `text-muted` / `bg` | 5.9:1 | 7.1:1 | ≥ 4.5 |
| `text-dim` / `bg` | 4.7:1 | 4.9:1 | ≥ 4.5 |
| `on-primary` / `primary` | 5.1:1 | 6.7:1 | ≥ 4.5 |
| `primary` / `bg` | 4.3:1 | 6.4:1 | ≥ 3(大字 / 图形) |
| `accent` / `bg` | 4.8:1 | 7.4:1 | ≥ 3 |
| `info` / `bg` | 5.0:1 | 6.4:1 | ≥ 3 |
| `danger` / `bg` | 5.3:1 | 4.8:1 | ≥ 3 |
| `on-honey` / `honey` | 6.1:1 | 9.3:1 | ≥ 4.5 |
### 3.3 用色规则
- **主色只用在关键操作和少量点缀**,大致 60 中性 / 30 辅色 / 10 主色。
- **`--honey` 只能当填充**,不能当文字色(对背景只有 2.2:1)。它上面必须放 `--on-honey`。
- **中性色带褐色相**,不要用纯灰(`#888` 之类)。背景不用 `#fff`,文字不用 `#000`。
- **一个游戏最多引入一个自己的强调色**(`game.json` 的 `accent`),其余全部走语义色。
- 语义色在 `design/tokens.css` 里定义,**游戏里不要重新定义一套**。
---
## 4. 字体
```css
--font-body: system-ui, -apple-system, "Segoe UI", "PingFang SC",
"HarmonyOS Sans SC", "MiSans", "Microsoft YaHei", sans-serif;
--font-mono: ui-monospace, "Cascadia Mono", "SFMono-Regular",
"JetBrains Mono", Consolas, "Liberation Mono", monospace;
```
| 用途 | 字体 | 字号 | 字重 | 其他 |
|---|---|---|---|---|
| 页面大标题 | body | 32 / 24 | 700 | `letter-spacing: -.015em`,行高 1.15 |
| 区块标题 | body | 20 | 700 | 行高 1.35 |
| 卡片标题 | body | 16 | 700 | 行高 1.35 |
| 正文 | body | 16 | 400 | 行高 1.65,一行 20–35 个中文 |
| 次要说明 | body | 14 | 400 | `--text-muted` |
| 卡片描述 | body | 13 | 400 | `--text-muted`,最多 2 行 |
| 小标签 / 数字 / 代码 | **mono** | 12 | 600 | `letter-spacing: .14em`,大写 |
**字号阶梯**:`12 / 13 / 14 / 16 / 20 / 24 / 32 / 40`。一屏最多用 4 档。
**中文排版**:
- 不用斜体;字重用 400 / 500 / 600 / 700,标题最多 700。
- 中英文、数字之间加空格(「iPhone 16」「2048 分」)。
- 标点用全角。
- 标题不要只剩一两个字:调宽度或用 `text-wrap: balance`。
---
## 5. 间距与网格
```css
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
--sp-6: 24px; --sp-8: 32px; --sp-12: 48px; --sp-16: 64px;
```
- 只用这 8 档,**不要写 10px / 18px / 22px 这种**。
- 页面左右留白:桌面 24px,手机 16px。
- 内容最大宽度:导航页 1200px,游戏建议 520–720px。
- 背景是 **8px 像素网格**(`--grid-line`,透明度 3–5%),用来强化像素感,不要加粗。
---
## 6. 形状与层次
```css
--radius: 0; /* 一律直角 */
--border-w: 2px; /* 一律 2px 描边 */
--shadow-off: 3px; /* 硬阴影偏移 */
--shadow: 3px 3px 0 var(--shadow-color);
--shadow-lg: 5px 5px 0 var(--shadow-color);
```
- **圆角永远是 0**。需要「圆」的地方用点阵图标或方块拼。
- **阴影永远是硬边**(`blur = 0`),颜色是 `--shadow-color`,不是黑色半透明。
- **按下态**:元素向右下平移、阴影收掉——像真的按进板子里。
```css
.px-btn:active { transform: translate(2px, 2px); box-shadow: 0 0 0 var(--shadow-color); }
```
- **悬停态**:向左上浮 1–2px,阴影加深一档。
- 深色主题靠**表面色差**分层(`--surface` vs `--bg`),不靠阴影。
---
## 7. 组件
全部在 `design/components.css`,类名统一 `px-` 前缀。
| 组件 | 类名 | 说明 |
|---|---|---|
| 按钮 | `.px-btn` | 基础;`.px-btn--primary` 主按钮;`.px-btn--ghost` 幽灵;`.px-btn--sm` 小号 |
| 图标按钮 | `.px-icon-btn` | 40×40,放 24px 图标 |
| 输入框 | `.px-input` | 聚焦时描边变主色;`.px-input--error` 错误态 |
| 下拉框 | `.px-select` | |
| 卡片 | `.px-card` | 内部结构见下 |
| 徽章 | `.px-badge` | `.px-badge--honey` / `.px-badge--muted` |
| 标签 | `.px-tag` | 分类用 |
| 面板 | `.px-panel` | 带描边和阴影的容器 |
| 状态栏 | `.px-statusbar` | 导航页和游戏页共用的顶栏,见 §7.3 |
| 用户位 | `.px-user` | 顶栏右边:`登录` / `昵称 + 分数` |
| 键帽 | `.px-kbd` | 快捷键提示 |
| 空状态 | `.empty` | 含 `.steps` / `.step-n` |
### 7.1 卡片结构
```html
🐍
```
封面的像素风景由 `--accent` 自动生成:三层天空色带 + 地平线抖动网点 + 地面色块 + 两颗像素星。
**不要**给封面加图片背景盖掉它;要自定义就整块换掉 `.card-cover` 的内容。
### 7.2 交互状态
用户会点的地方都要有:悬停、按下、聚焦(`:focus-visible`)、禁用、空、错误。
禁用一律 `opacity: .45` + `cursor: not-allowed`,不要只改颜色。
### 7.3 状态栏(导航页和游戏页同一条)
一条顶栏,左边「你在哪」,右边**用户位**。**同一个组件,两个位置**:
| 位置 | 左边 | 右边(用户位) |
|---|---|---|
| 导航页 | 品牌 + 搜索 + 榜单 | 没登录 → `登录`;登录了 → `昵称 + 总 rating` |
| 游戏页 | `← 合集` + 游戏名 | 没登录 → `登录`;登录了 → `昵称 + 这个游戏的 rating` |
导航页写在 `index.html` 里;游戏页不用手写,调 SDK:
```html
```
生成的结构:
```html
```
规矩:
- **用户位只有一个**(`.px-user`):没登录是「登录」,登录后是「昵称 + 分数」。
不要拆成两个控件,也不要只在游戏页放个孤零零的分数。
- 分数是蜜黄底 + 墨字,**全站唯一给分数上色的地方**。
- 分数是**对外分**(0–10000),不是 0–1。0–1 只在「我的」面板的小字里出现。
- **没成绩就不显示分数**。没身份 / 没配 endpoint / 还没玩过 → 只显示昵称,不要拿 0 冒充。
- 内容列宽度由 `--statusbar-max` 定:导航页 1200(默认),游戏页写 `--statusbar-max: 640px`。
- 游戏页**不要自己画顶栏**。要加东西就改 `sdk/statusbar.js`,所有游戏一起变。
- 主题键和导航页共用 `lg:nav:theme`:在首页切了夜间,进游戏也是夜间。
- 游戏自己的数值(得分、步数、血量)**不进状态栏**,放在下面的 `.hud` 一行里。
- 点游戏页的用户位会回到 `../index.html#me`,「我的」面板自动打开。
---
## 8. 图标
- 点阵 **12×12**,显示 **24px**(整数 2 倍,像素才不糊)。
- 定义在 `design/pixel-icons.js`,用点阵字符串写,运行时转 SVG。
- 颜色跟随 `currentColor`,所以用 CSS 的 `color` 控制。
- **不要用 emoji 当界面图标**(游戏自己的角色 emoji 除外,那是内容不是图标)。
```html
```
现有图标:`search` `sun` `moon` `dice` `gamepad` `arrow`。加新图标就往 `ICONS` 里加一行点阵。
---
## 9. 动效
```css
--dur-fast: 120ms; --dur: 180ms;
--ease: cubic-bezier(.2, .8, .2, 1);
```
- 只动 `transform` 和 `opacity`,不动 `width` / `top` / `margin`。
- 悬停 120ms,状态切换 180ms,超过 250ms 就嫌慢。
- **不要**做呼吸灯、粒子、持续闪烁——像素风靠静态画面立住。
- 必须尊重 `prefers-reduced-motion`(`tokens.css` 已全局兜底)。
---
## 10. 可访问性
- 正文对比度 ≥ 4.5:1,大字和图标 ≥ 3:1(见 §3.2 实测表)。
- 可点区域至少 **44×44**(图标按钮 40×40 + 外围留白)。
- 焦点必须看得见:`box-shadow: var(--focus-ring)`,不要 `outline: none` 了事。
- 语义标签:`header` `nav` `main` `button` `a`;图片有 `alt`;纯装饰图标 `aria-hidden="true"`。
- 颜色不能是唯一的信息载体(选中态除了变色还要有 `aria-pressed`)。
---
## 11. 怎么让游戏和站点统一(关键)
统一靠三件事,缺一不可:
### 11.1 引入同一套变量和组件
每个游戏的 `index.html` 的 `` 里:
```html
```
- **不要**复制一份 token 到自己目录里改。要改就改 `design/tokens.css`,全站一起变。
- 游戏自己的样式写在 `
得分 0