Kami is born
@@ -0,0 +1,17 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
|
||||
# Font build artifacts
|
||||
assets/fonts/*.otf
|
||||
assets/fonts/Newsreader-italic.woff
|
||||
|
||||
# Template examples with placeholder content, regenerate via: python3 scripts/build.py
|
||||
assets/examples/
|
||||
|
||||
/tmp/
|
||||
*.log
|
||||
@@ -0,0 +1,177 @@
|
||||
# kami · Cheatsheet
|
||||
|
||||
One-page quick reference. Scan before filling a template or tweaking a detail. Full spec in `references/design.en.md`.
|
||||
|
||||
## Eight invariants
|
||||
|
||||
1. Page background `#f5f4ed` (parchment), never pure white
|
||||
2. Single accent: ink-blue `#1B365D`
|
||||
3. All grays **warm-toned** (yellow-brown undertone), no cool blue-gray
|
||||
4. Serif for headlines, sans for body / UI
|
||||
5. Serif weight locked at 500, no bold
|
||||
6. Line-height: headlines 1.1-1.3 / dense 1.4-1.45 / reading 1.5-1.55. **Never 1.6+**
|
||||
7. Tag backgrounds solid hex, no rgba (WeasyPrint double-rectangle bug)
|
||||
8. Depth via ring / whisper shadow, no hard drop shadows
|
||||
|
||||
## Color
|
||||
|
||||
| Role | Hex | Use |
|
||||
|---|---|---|
|
||||
| Parchment | `#f5f4ed` | Page background |
|
||||
| Ivory | `#faf9f5` | Card / lifted container |
|
||||
| Warm Sand | `#e8e6dc` | Button / interactive surface |
|
||||
| Dark Surface | `#30302e` | Dark container |
|
||||
| Deep Dark | `#141413` | Dark page background |
|
||||
| **Brand** | **`#1B365D`** | **Accent · CTA · title underline (≤ 5% of surface)** |
|
||||
| Brand Coral | `#2D5A8A` | Links on dark surfaces |
|
||||
| Near Black | `#141413` | Primary text |
|
||||
| Dark Warm | `#3d3d3a` | Secondary dark / link |
|
||||
| Charcoal | `#4d4c48` | Button text / dense body |
|
||||
| Olive | `#5e5d59` | Subtext · descriptions |
|
||||
| Stone | `#87867f` | Tertiary · metadata |
|
||||
| Warm Silver | `#b0aea5` | Light text on dark surfaces |
|
||||
| Border Cream | `#f0eee6` | Default card border |
|
||||
| Border Warm | `#e8e6dc` | Section divider |
|
||||
| Ring Warm | `#d1cfc5` | Button hover / focus ring |
|
||||
|
||||
**rgba -> solid** (parchment base + ink-blue):
|
||||
|
||||
| Alpha | Solid |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← default tag |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
## Type (print pt)
|
||||
|
||||
| Role | Size | Weight | Line-height |
|
||||
|---|---|---|---|
|
||||
| Display | 36-48 | 500 | 1.10 |
|
||||
| H1 | 18-22 | 500 | 1.20 |
|
||||
| H2 | 14-16 | 500 | 1.25 |
|
||||
| H3 | 12-13 | 500 | 1.30 |
|
||||
| Body Lead | 11 | 400 | 1.55 |
|
||||
| Body | 9.5-10 | 400 | 1.55 |
|
||||
| Body Dense | 9-9.2 | 400 | 1.42 |
|
||||
| Caption | 8.5-9 | 400 | 1.45 |
|
||||
| Label | 7.5-8 | 600 | 1.35 |
|
||||
| Tiny | 7 | 400 | 1.40 |
|
||||
|
||||
Screen (px) ≈ pt × 1.33.
|
||||
|
||||
## Font stack (English)
|
||||
|
||||
```css
|
||||
--serif: "Newsreader", "Source Serif 4", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
```
|
||||
|
||||
## Spacing (4pt base)
|
||||
|
||||
| Tier | Value | Use |
|
||||
|---|---|---|
|
||||
| xs | 2-3pt | Inline |
|
||||
| sm | 4-5pt | Tag padding |
|
||||
| md | 8-10pt | Component interior |
|
||||
| lg | 16-20pt | Between components |
|
||||
| xl | 24-32pt | Section-title margin |
|
||||
| 2xl | 40-60pt | Between major sections |
|
||||
| 3xl | 80-120pt | Between chapters |
|
||||
|
||||
**Page margins (A4)**
|
||||
|
||||
| Document | T · R · B · L |
|
||||
|---|---|
|
||||
| Resume | 11 · 13 · 11 · 13 mm |
|
||||
| One-Pager | 15 · 18 · 15 · 18 mm |
|
||||
| Long Doc | 20 · 22 · 22 · 22 mm |
|
||||
| Letter | 25 mm all sides |
|
||||
| Portfolio | 12 · 15 · 12 · 15 mm |
|
||||
|
||||
## Radius scale
|
||||
|
||||
`4pt -> 6pt -> 8pt (default) -> 12pt -> 16pt -> 24pt -> 32pt (hero)`
|
||||
|
||||
## Common CSS snippets
|
||||
|
||||
### Card
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 8pt;
|
||||
padding: 16pt 20pt;
|
||||
}
|
||||
```
|
||||
|
||||
### Tag (default lightest solid)
|
||||
|
||||
```css
|
||||
.tag {
|
||||
background: #EEF2F7; /* 0.08 equivalent */
|
||||
color: var(--brand);
|
||||
font-size: 8pt; font-weight: 600;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
```
|
||||
|
||||
### Section title (brand underline is the signature move)
|
||||
|
||||
```css
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 24pt 0 10pt 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Metric (data card)
|
||||
|
||||
```css
|
||||
.metric { display: flex; align-items: baseline; gap: 6pt; }
|
||||
.metric-value {
|
||||
font-family: var(--serif); font-size: 16pt; font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-label { font-size: 9pt; color: var(--olive); }
|
||||
```
|
||||
|
||||
### Quote
|
||||
|
||||
```css
|
||||
.quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
}
|
||||
```
|
||||
|
||||
## Quick decisions
|
||||
|
||||
| Need | Use |
|
||||
|---|---|
|
||||
| Headline | serif 500, line-height 1.10-1.30 |
|
||||
| Reading body | sans 400, 9.5-10pt, 1.55 |
|
||||
| Emphasize a number | `color: var(--brand)`, no bold |
|
||||
| Divide two sections | 1pt brand border-bottom, or 0.5pt warm dotted |
|
||||
| Quote | 2pt brand left border + olive color |
|
||||
| Code | ivory bg + 0.5pt border + 6pt radius + mono |
|
||||
| Primary button | brand fill + ivory text |
|
||||
| Secondary button | warm-sand + charcoal |
|
||||
| Chapter start | serif heading + 1pt brand line below |
|
||||
| Cover | Display heading + right-aligned author/date + heavy whitespace |
|
||||
|
||||
Not on the table -> first principles: **serif carries authority, sans carries utility, warm gray carries rhythm, ink-blue carries focus**.
|
||||
@@ -0,0 +1,165 @@
|
||||
# kami · Cheatsheet
|
||||
|
||||
一页纸速查。填模板 / 调细节前扫一眼。完整规范在 `references/design.md`。
|
||||
|
||||
## 八条铁律
|
||||
|
||||
1. 页面背景 `#f5f4ed`(parchment),不用纯白
|
||||
2. 强调色只有油墨蓝 `#1B365D`
|
||||
3. 所有灰**暖调**(yellow-brown undertone),禁冷蓝灰
|
||||
4. 标题 serif,正文/UI sans
|
||||
5. Serif 字重固定 500,不用 bold
|
||||
6. 行距:标题 1.1-1.3 / 密排 1.4-1.45 / 阅读 1.5-1.55。禁 1.6+
|
||||
7. Tag 背景实色 hex,禁 rgba(WeasyPrint 双层矩形 bug)
|
||||
8. 阴影用 ring 或 whisper,不用硬 drop shadow
|
||||
|
||||
## 色板
|
||||
|
||||
| 角色 | Hex | 用途 |
|
||||
|---|---|---|
|
||||
| Parchment | `#f5f4ed` | 页面底 |
|
||||
| Ivory | `#faf9f5` | 卡片 / 浮起容器 |
|
||||
| Warm Sand | `#e8e6dc` | 按钮背景 / 交互面 |
|
||||
| Dark Surface | `#30302e` | 深色容器 |
|
||||
| Deep Dark | `#141413` | 深色页面底 |
|
||||
| **Brand** | **`#1B365D`** | **强调 · CTA · 标题下划线(全文 ≤ 5%)** |
|
||||
| Brand Coral | `#2D5A8A` | 深底上的链接 |
|
||||
| Near Black | `#141413` | 主文字 |
|
||||
| Dark Warm | `#3d3d3a` | 次级深色 / 链接 |
|
||||
| Charcoal | `#4d4c48` | 按钮文字 / 高密度正文 |
|
||||
| Olive | `#5e5d59` | 副文本 · 描述 |
|
||||
| Stone | `#87867f` | 三级文字 · 元信息 |
|
||||
| Warm Silver | `#b0aea5` | 深底上的浅色文字 |
|
||||
| Border Cream | `#f0eee6` | 卡片默认边 |
|
||||
| Border Warm | `#e8e6dc` | section 分隔 |
|
||||
| Ring Warm | `#d1cfc5` | 按钮 hover / focus 环 |
|
||||
|
||||
**rgba -> 实色对照**(底 parchment + 前景油墨蓝):
|
||||
|
||||
| 透明度 | 实色 |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← 默认 tag |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
## 字号(印刷品 pt)
|
||||
|
||||
| 角色 | 字号 | 字重 | line-height |
|
||||
|---|---|---|---|
|
||||
| Display | 36-48 | 500 | 1.10 |
|
||||
| H1 | 18-22 | 500 | 1.20 |
|
||||
| H2 | 14-16 | 500 | 1.25 |
|
||||
| H3 | 12-13 | 500 | 1.30 |
|
||||
| Body Lead | 11 | 400 | 1.55 |
|
||||
| Body | 9.5-10 | 400 | 1.55 |
|
||||
| Body Dense | 9-9.2 | 400 | 1.40 |
|
||||
| Caption | 8.5-9 | 400 | 1.45 |
|
||||
| Label | 7.5-8 | 600 | 1.35 |
|
||||
| Tiny | 7 | 400 | 1.40 |
|
||||
|
||||
屏幕(px)≈ pt × 1.33。
|
||||
|
||||
## 间距(4pt 基)
|
||||
|
||||
| 级 | 值 | 用途 |
|
||||
|---|---|---|
|
||||
| xs | 2-3 pt | 同行内 |
|
||||
| sm | 4-5 pt | tag padding |
|
||||
| md | 8-10 pt | 组件内部 |
|
||||
| lg | 16-20 pt | 组件之间 |
|
||||
| xl | 24-32 pt | section 标题 margin |
|
||||
| 2xl | 40-60 pt | 大 section 之间 |
|
||||
| 3xl | 80-120 pt | 长文档章节之间 |
|
||||
|
||||
**页面 margin(A4)**
|
||||
|
||||
| 文档 | 上右下左 |
|
||||
|---|---|
|
||||
| Resume | 9 mm 13 mm 9 mm 13 mm |
|
||||
| One-Pager | 15 / 18 / 15 / 18 mm |
|
||||
| Long Doc | 20 / 22 / 22 / 22 mm |
|
||||
| Letter | 25 mm 全周 |
|
||||
| Portfolio | 12 / 15 / 12 / 15 mm |
|
||||
|
||||
## 圆角尺度
|
||||
|
||||
`4 pt -> 6 pt -> 8 pt(默认)-> 12 pt -> 16 pt -> 24 pt -> 32 pt(hero)`
|
||||
|
||||
## 常用 CSS 片段
|
||||
|
||||
### Card
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 8pt;
|
||||
padding: 16pt 20pt;
|
||||
}
|
||||
```
|
||||
|
||||
### Tag(默认极淡实色)
|
||||
|
||||
```css
|
||||
.tag {
|
||||
background: #EEF2F7; /* 0.08 等效 */
|
||||
color: var(--brand);
|
||||
font-size: 8pt; font-weight: 500;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
letter-spacing: 0.05pt;
|
||||
}
|
||||
```
|
||||
|
||||
### Section Title(品牌色下划线是签名式样)
|
||||
|
||||
```css
|
||||
.section-title {
|
||||
font-family: serif;
|
||||
font-size: 14pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 24pt 0 10pt 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Metric(数据卡)
|
||||
|
||||
```css
|
||||
.metric { display: flex; align-items: baseline; gap: 6pt; }
|
||||
.metric-value {
|
||||
font-family: serif; font-size: 16pt; font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-label { font-size: 9pt; color: var(--olive); }
|
||||
```
|
||||
|
||||
### Quote
|
||||
|
||||
```css
|
||||
.quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
}
|
||||
```
|
||||
|
||||
## 决策速查
|
||||
|
||||
| 想做 | 怎么做 |
|
||||
|---|---|
|
||||
| 大标题 | serif 500,line-height 1.10-1.30 |
|
||||
| 正文阅读 | sans 400,9.5-10pt,1.55 |
|
||||
| 强调数字 | `color: var(--brand)`,不加粗 |
|
||||
| 分两段 | 1pt 品牌色 border-bottom,或 0.5pt 暖灰虚线 |
|
||||
| 引用 | 左 2pt 品牌实线 + olive 色 |
|
||||
| 代码 | ivory 底 + 0.5pt border + 6pt 圆角 + mono |
|
||||
| 主按钮 | 品牌色填充 + ivory 字 |
|
||||
| 次按钮 | warm-sand 底 + charcoal 字 |
|
||||
| 章节开始 | serif 标题 + 下方 1pt 品牌色实线 |
|
||||
| 封面 | 单页 Display 标题 + 右对齐作者/日期 + 大量留白 |
|
||||
|
||||
不在表里 -> 回原则:**serif 承担权威,sans 承担功能,暖灰承担节奏,油墨蓝承担焦点**。
|
||||
@@ -0,0 +1,110 @@
|
||||
# kami 交接文档
|
||||
|
||||
给**维护** kami 的人看。**使用** kami(做文档)-> 读 `README.md` / `SKILL.md`。
|
||||
|
||||
## 一句话定位
|
||||
|
||||
Kaku(写代码)· Waza(练习惯)· **Kami(出文档)** 三部曲之一。把 Anthropic parchment 视觉语言 + 真实文档排版迭代的踩坑,封装成覆盖 6 种文档的模板 + 规范库。
|
||||
|
||||
**不是**:纯简历工具、Waza 的子集、Web UI framework。
|
||||
|
||||
## 设计铁律
|
||||
|
||||
全部在 `references/design.md` 开头的"设计宣言"段。任何改动之前先确认是否真的要违反那 8 条。
|
||||
|
||||
## 文件职责
|
||||
|
||||
| 文件 | 对谁 | 改动频率 |
|
||||
|---|---|---|
|
||||
| `SKILL.md` | Claude 运行时 | 低(只改路由表) |
|
||||
| `README.md` | 外部用户 | 低(改 install / preview) |
|
||||
| `HANDOFF.md` | 维护者(你) | 中 |
|
||||
| `CHEATSHEET.md` | Claude + 用户 | 低 |
|
||||
| `references/design.md` | Claude + 维护者 | 低(改就是改设计语言) |
|
||||
| `references/writing.md` | Claude | 低 |
|
||||
| `references/production.md` | Claude + 维护者 | 中(新踩坑追加) |
|
||||
| `assets/templates/*.html` / `slides.py` | Claude 填内容 | 中 |
|
||||
| `assets/examples/*.pdf` | README preview | 每次样式改动 |
|
||||
| `scripts/build.py` | 维护者 + CI | 低 |
|
||||
|
||||
## 字体分发
|
||||
|
||||
**主字体 TsangerJinKai02-W04.ttf 是商业字体**。user-supplied only,不进 skill 分发、不进 git。
|
||||
|
||||
**本地开发姿势**:
|
||||
- 把 `.ttf` 放到 `assets/fonts/` 下
|
||||
- 所有 CN 模板和 CN demo 的 `<style>` 里都有 `@font-face { src: url("../fonts/TsangerJinKai02-W04.ttf") }`,相对路径挂载
|
||||
- 有字体 -> 自动用真字体;没字体 -> fallback 链自动接手(Source Han Serif SC -> Noto Serif CJK SC -> Songti SC -> Georgia)
|
||||
|
||||
**打包 skill 前**:确认 `assets/fonts/*.ttf` 不在 git 里(`.gitignore` 已排除)。打 zip 或 `.skill` 时也要跳过。
|
||||
|
||||
**用户侧**:README / HANDOFF 里写清楚"如需主字体,把仓耳今楷 02 W04 的 .ttf 放进 `assets/fonts/`"。没字体也能跑,只是视觉降一档到 Charter / Source Han Serif。
|
||||
|
||||
## 关键踩坑索引(详情在 production.md Part 4)
|
||||
|
||||
改动前值得回看的高风险点:
|
||||
|
||||
1. **Tag 双层矩形**——rgba 半透明触发。必须实色 hex
|
||||
2. **薄 border + 圆角双圈**——border < 1pt + border-radius 触发
|
||||
3. **简历 2 页溢出**——字体 fallback / line-height / 字号动一点都会爆
|
||||
4. **break-inside 在 flex 失效**——要包一层 block wrapper
|
||||
5. **height: 100vh 在 @page 不准**——改用 mm 明确值
|
||||
6. **SVG marker `orient="auto"` 不生效**——WeasyPrint 不旋转 marker,手绘 chevron path
|
||||
|
||||
## 改动的标准姿势
|
||||
|
||||
### 改字号 / 色 / 间距
|
||||
改 `references/design.md` + 所有模板 `<style>` 里对应变量。跑 `scripts/build.py` 生成全部 PDF,对比页数没变。
|
||||
|
||||
### 改模板内容
|
||||
**保留 CSS 不动**,只改 body。跑 `build.py` 验证页数。简历必须严格 2 页。
|
||||
|
||||
### 加新模板
|
||||
1. Copy 最接近的现有模板做起点
|
||||
2. 保持与 `design.md` 完全一致(不另创色板 / 字号)
|
||||
3. 在 `SKILL.md` 路由表加一行
|
||||
4. 在 `assets/examples/` 放一份生成好的 PDF
|
||||
5. 若出现新独特 CSS 结构,追加到 `production.md` Part 4
|
||||
|
||||
## 验证流程
|
||||
|
||||
```bash
|
||||
# 1. 生成所有
|
||||
python3 scripts/build.py
|
||||
|
||||
# 2. CSS 扫描(rgba / 冷灰 / 1.6+ line-height)
|
||||
python3 scripts/build.py --check
|
||||
|
||||
# 3. 字体是否真的加载了
|
||||
pdffonts assets/examples/resume.pdf | head
|
||||
|
||||
# 4. 隐私扫描(改动后)
|
||||
# 把 KEYWORDS 换成自己真实姓名 / 公司 / 电话等,避免误泄漏到模板
|
||||
for kw in YOUR_NAME YOUR_COMPANY YOUR_PHONE YOUR_SCHOOL; do
|
||||
grep -rn "$kw" .
|
||||
done
|
||||
```
|
||||
|
||||
**期望页数**:one-pager 1 / letter 1 / resume 2(严格)/ long-doc 7 ± 2 / portfolio 6 ± 2。
|
||||
|
||||
## 打包与发布
|
||||
|
||||
```bash
|
||||
cd /path/to/kami/parent
|
||||
zip -rq kami.zip kami/ -x '*/.*' # 忽略 .DS_Store 等
|
||||
```
|
||||
|
||||
或用 Anthropic 官方 skill-creator 的 `package_skill`。
|
||||
|
||||
## 待办 / V2 候选
|
||||
|
||||
1. **打磨现有模板的边缘情况**(最高优先级)——portfolio 的图片占位 -> 真实 `<img>`;resume 在 Noto Serif fallback 下的 2 页严格性
|
||||
2. **slides.py 补图文混排 + 数据图表版式**(两个高频场景缺口)
|
||||
3. **新模板候选**:学术论文 / 证书 / 菜单 / 邀请函
|
||||
4. **scripts/inspect.py** 视觉回归(对比修改前后的 PDF 渲染像素)
|
||||
|
||||
**不做**:打包商业字体(授权风险);加第二种强调色(违反克制);加暗色变体(印刷品用不上)。
|
||||
|
||||
---
|
||||
|
||||
**最后一条建议**:kami 的精神是"克制"。每次想加一个颜色、一个字重、一个阴影之前,先问"不加行不行"。多数时候答案是"行"。
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Tw93
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,110 @@
|
||||
<div align="center">
|
||||
<img src="assets/images/logo.svg" width="120" />
|
||||
<h1>Kami</h1>
|
||||
<p><b>The paper your deliverables land on.</b></p>
|
||||
<a href="https://github.com/tw93/kami/stargazers"><img src="https://img.shields.io/github/stars/tw93/kami?style=flat-square" alt="Stars"></a>
|
||||
<a href="https://github.com/tw93/kami/releases"><img src="https://img.shields.io/github/v/tag/tw93/kami?label=version&style=flat-square" alt="Version"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="License"></a>
|
||||
<a href="https://twitter.com/HiTw93"><img src="https://img.shields.io/badge/follow-Tw93-red?style=flat-square&logo=Twitter" alt="Twitter"></a>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
## Why
|
||||
|
||||
Kami (紙, かみ) is the Japanese word for paper: the quiet surface on which a finished idea finally lands.
|
||||
|
||||
Most document design drifts into two failure modes: generic corporate gray, or SaaS hype gradients. Neither reads like something a person actually made with care.
|
||||
|
||||
Kami holds one design idea across every format: warm parchment canvas, a single ink-blue accent, serif for authority, sans for utility, editorial whitespace tuned for print. Inspired by Anthropic's visual language.
|
||||
|
||||
It is part of the `Kaku · Waza · Kami` trilogy: Kaku (書く) is how you write code, Waza (技) is the habits you practice, Kami (紙) is the paper your work ships on.
|
||||
|
||||
## Install
|
||||
|
||||
**Claude Code**
|
||||
|
||||
```bash
|
||||
npx skills add tw93/kami -a claude-code -g -y
|
||||
```
|
||||
|
||||
**Codex**
|
||||
|
||||
```bash
|
||||
npx skills add tw93/kami -a codex -g -y
|
||||
```
|
||||
|
||||
**Claude Desktop**
|
||||
|
||||
[Download ZIP](https://github.com/tw93/kami/archive/refs/heads/main.zip), open Customize > Skills > "+" > Create skill, upload the ZIP.
|
||||
|
||||
## See it
|
||||
|
||||
Just tell Claude what you need: "帮我生成一份白皮书", "生成一份项目方案", "build me a resume", "写一份推荐信", "make a slide deck for my talk", "帮我做一份作品集". The skill auto-triggers, no slash command needed.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-tesla.pdf"><img src="assets/demos/demo-tesla.png" alt="Tesla company one-pager"></a>
|
||||
<br><b>One-Pager</b> · 中文
|
||||
<br><sub>Tesla 公司介绍 · 单页 · <a href="assets/demos/demo-tesla.pdf">PDF</a></sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-agent-slides.pdf"><img src="assets/demos/demo-agent-slides-1.png" alt="Agent keynote cover" /></a>
|
||||
<a href="assets/demos/demo-agent-slides.pdf"><img src="assets/demos/demo-agent-slides-3.png" alt="Agent keynote content slide" /></a>
|
||||
<br><b>Slides</b> · English
|
||||
<br><sub>Agent keynote, 6 slides · <a href="assets/demos/demo-agent-slides.pdf">PDF</a></sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-musk-resume.pdf"><img src="assets/demos/demo-musk-resume.png" alt="Elon Musk resume"></a>
|
||||
<br><b>Resume</b> · English
|
||||
<br><sub>Founder CV, 2 pages strict · <a href="assets/demos/demo-musk-resume.pdf">PDF</a></sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-kaku.pdf"><img src="assets/demos/demo-kaku.png" alt="Kaku portfolio"></a>
|
||||
<br><b>Portfolio</b> · 中文
|
||||
<br><sub>Kaku 项目作品集 · 6 页 · <a href="assets/demos/demo-kaku.pdf">PDF</a></sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Design
|
||||
|
||||
Six document types, each with Chinese and English variants: One-Pager, Long Doc, Letter, Portfolio, Resume, and Slides. Three inline SVG diagram primitives also ship. Kami picks the right variant based on the language you write in.
|
||||
|
||||
Eight invariants hold across every document:
|
||||
|
||||
1. Page background `#f5f4ed` parchment, not pure white
|
||||
2. Single accent color: ink-blue `#1B365D`
|
||||
3. All neutrals warm-toned. No `#6b7280`, no `#888`
|
||||
4. English: serif for headlines and body. Chinese: serif headlines, sans body. Sans for UI elements only
|
||||
5. Serif weight locked at 500. No bold headlines
|
||||
6. Line-heights: tight headlines 1.1 to 1.3, dense body 1.4 to 1.45, reading body 1.5 to 1.55. Never 1.6+
|
||||
7. Tag backgrounds must be solid hex. `rgba()` triggers a WeasyPrint double-rectangle bug
|
||||
8. Depth via ring shadow or whisper shadow. No hard drop shadows
|
||||
|
||||
**Chinese fonts**: TsangerJinKai02 serif + Source Han Sans. TsangerJinKai is a commercial font, for commercial use please obtain a license from [tsanger.cn](https://tsanger.cn). Fallback: Noto Serif CJK SC, Songti SC, Georgia.
|
||||
|
||||
**English fonts**: Newsreader serif for headlines and body + Inter sans for UI elements only, both OFL open source. Fallback: Charter/Georgia, Helvetica Neue/Arial.
|
||||
|
||||
Full spec: [references/design.md](references/design.md), [references/design.en.md](references/design.en.md). Cheatsheet: [CHEATSHEET.md](CHEATSHEET.md), [CHEATSHEET.en.md](CHEATSHEET.en.md).
|
||||
|
||||
## Background
|
||||
|
||||
I invest in US equities and regularly ask AI to generate analysis reports. The output always looked like a default Google Doc: bland, inconsistent, forgettable. I can't stand ugly documents, especially when every report comes out looking different from the last one. So I kept tweaking the typography, colors, and spacing until I had something I actually wanted to read.
|
||||
|
||||
Then I was invited to present a talk based on my article "The Agent You Don't Know: Principles, Architecture, and Engineering Practice" and needed a slide deck that matched the same standard. That round pushed the system further, adding inline SVG diagrams, a consistent warm palette, and tighter editorial rhythm. Eventually it was doing enough that I pulled it into its own package. That became kami: one visual language I like, applied to everything I ship.
|
||||
|
||||
## Support
|
||||
|
||||
- If kami helped you, [share it](https://twitter.com/intent/tweet?url=https://github.com/tw93/kami&text=Kami%20-%20A%20quiet%20design%20system%20for%20professional%20documents.) with friends or give it a star.
|
||||
- Got ideas or bugs? Open an issue or PR.
|
||||
- I have two cats, TangYuan and Coke. If you think kami delights your life, you can feed them <a href="https://miaoyan.app/cats.html?name=Kami" target="_blank">canned food 🥩</a>.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://miaoyan.app/cats.html?name=Kami"><img src="https://cdn.jsdelivr.net/gh/tw93/MiaoYan@main/assets/sponsors.svg" width="1000" loading="lazy" /></a>
|
||||
</div>
|
||||
|
||||
## License
|
||||
|
||||
MIT License. Feel free to use kami and contribute.
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: kami
|
||||
description: 'Typeset any professional document — resumes, one-pagers, white papers, letters, portfolios, slide decks — in a warm parchment design system with ink-blue accent, serif-led hierarchy, and tight editorial spacing. Full bilingual support: Chinese docs use TsangerJinKai02 + Source Han, English docs use Newsreader + Inter (Silicon Valley editorial stack). Triggers on "做 PDF / 排版 / 生成报告 / 一页纸 / 白皮书 / 作品集 / 正式信件 / 简历 / PPT / slides / 高质量文档 / 好看的排版", or "build me a resume / make a one-pager / design a slide deck / turn this into a PDF / make this presentable / polish typography", and when raw content is handed over to be "typeset, designed, made presentable".'
|
||||
---
|
||||
|
||||
# kami · 紙
|
||||
|
||||
**紙 · かみ** — the paper your deliverables land on.
|
||||
|
||||
One design language across six document types: warm parchment canvas, ink-blue accent, serif-led hierarchy, tight editorial rhythm. Inspired by Anthropic's product visuals.
|
||||
|
||||
Part of `Kaku · Waza · Kami` — Kaku writes code, Waza drills habits, **Kami delivers documents**.
|
||||
|
||||
## Step 1 · Decide the language
|
||||
|
||||
**Match the user's language**. If they write in Chinese -> use the Chinese templates (`.html`, Chinese references). If they write in English -> use the English templates (`-en.html`, `.en.md` references).
|
||||
|
||||
When ambiguous (e.g. a one-word command like "resume"), ask a one-liner rather than guess.
|
||||
|
||||
| User language | Templates | References | Cheatsheet |
|
||||
|---|---|---|---|
|
||||
| Chinese (primary) | `*.html` / `slides.py` | `references/*.md` | `CHEATSHEET.md` |
|
||||
| English | `*-en.html` / `slides-en.py` | `references/*.en.md` | `CHEATSHEET.en.md` |
|
||||
|
||||
## Step 2 · Pick the document type
|
||||
|
||||
| User says | Document | CN template | EN template |
|
||||
|---|---|---|---|
|
||||
| "one-pager / 方案 / 执行摘要 / exec summary" | One-Pager | `one-pager.html` | `one-pager-en.html` |
|
||||
| "white paper / 白皮书 / 长文 / 年度总结 / technical report" | Long Doc | `long-doc.html` | `long-doc-en.html` |
|
||||
| "formal letter / 信件 / 辞职信 / 推荐信 / memo" | Letter | `letter.html` | `letter-en.html` |
|
||||
| "portfolio / 作品集 / case studies" | Portfolio | `portfolio.html` | `portfolio-en.html` |
|
||||
| "resume / resume / CV / 简历" | Resume | `resume.html` | `resume-en.html` |
|
||||
| "slides / PPT / deck / 演示" | Slides | `slides.py` | `slides-en.py` |
|
||||
|
||||
If unsure, ask a one-liner about the scenario rather than guess.
|
||||
|
||||
### Diagrams (primitives, not a 7th doc type)
|
||||
|
||||
When the user asks for **a diagram inside** a long-doc / portfolio / slide (not a standalone document), route to `assets/diagrams/` rather than a template:
|
||||
|
||||
| User says | Diagram | Template |
|
||||
|---|---|---|
|
||||
| "架构图 / architecture / 系统图 / components diagram" | Architecture | `assets/diagrams/architecture.html` |
|
||||
| "流程图 / flowchart / 决策流 / branching logic" | Flowchart | `assets/diagrams/flowchart.html` |
|
||||
| "象限图 / quadrant / 优先级矩阵 / 2×2 matrix" | Quadrant | `assets/diagrams/quadrant.html` |
|
||||
|
||||
Read `references/diagrams.md` / `diagrams.en.md` before drawing — it has the selection guide, kami token map, and the AI-slop anti-pattern table. Extract the `<svg>` block from the template and drop it into a `<figure>` inside long-doc / portfolio.
|
||||
|
||||
Before drawing, always ask: **would a well-written paragraph teach the reader less than this diagram?** If no, don't draw.
|
||||
|
||||
## Step 2.5 · Distill raw content (if applicable)
|
||||
|
||||
Skip this step if the user already provides structured content (clear sections, bullet points, metrics in place).
|
||||
|
||||
When the user hands over **raw material** (meeting notes, brain dump, existing doc in different format, chat transcript, scattered points):
|
||||
|
||||
1. **Extract**: pull out every factual claim, number, date, name, and action item
|
||||
2. **Classify**: map each extract to the target template's sections (see `references/writing.md` for section structure per doc type)
|
||||
3. **Gap-check**: list what the template needs but the raw content doesn't have — present as a compact table
|
||||
4. **Ask once**: share the gap table with the user. Do not guess to fill gaps.
|
||||
|
||||
Example gap-check:
|
||||
|
||||
| Template needs | Found | Missing |
|
||||
|---|---|---|
|
||||
| 4 metric cards | "8 years", "50-person team" | 2 more quantifiable results |
|
||||
| 3-5 core projects | 2 mentioned | at least 1 more with outcome |
|
||||
|
||||
Then proceed to Step 3 with structured, distilled content.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 · Load the right amount of spec
|
||||
|
||||
Pick the tier that matches the task. Default to the lowest tier that covers the work.
|
||||
|
||||
| Tier | When | Read |
|
||||
|---|---|---|
|
||||
| **Content-only** | Updating text, swapping bullets, translating an existing doc. CSS stays untouched. | `CHEATSHEET.md` only (170 lines) |
|
||||
| **Layout tweak** | Adjusting spacing, moving sections, changing font size within spec. CSS touched. | `CHEATSHEET.md` + template (tokens already inline) |
|
||||
| **New document** | Building from scratch or from raw content. | Full design spec + writing spec + template |
|
||||
| **Troubleshoot** | Rendering bug, font issue, page overflow. | `production.md` (+ design spec if CSS is the cause) |
|
||||
| **Diagram** | Embedding SVG in a doc. | `diagrams.md` only (has its own token map) |
|
||||
|
||||
You can always escalate mid-task if the work turns out to need more than the initial tier.
|
||||
|
||||
The full spec files for reference:
|
||||
- Design: `references/design.md` (CN) / `references/design.en.md` (EN)
|
||||
- Writing: `references/writing.md` / `writing.en.md`
|
||||
- Production: `references/production.md` / `production.en.md`
|
||||
- Diagrams: `references/diagrams.md` / `diagrams.en.md`
|
||||
|
||||
## Step 4 · Fill content into the template
|
||||
|
||||
- Copy the template into your working directory; don't write HTML from scratch
|
||||
- **CSS stays untouched**, only edit the body
|
||||
- Content follows `writing.md` / `writing.en.md`: data over adjectives, distinctive phrasing over industry clichés
|
||||
|
||||
## Step 5 · Build & verify
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py --verify # build all + page count + font check + placeholder check
|
||||
python3 scripts/build.py --verify resume-en # single target full verification
|
||||
python3 scripts/build.py --check # CSS rule violations only (fast, no build)
|
||||
```
|
||||
|
||||
Visual anomalies (tag double rectangle, font fallback, page break issues) -> `production.md` / `production.en.md` Part 4.
|
||||
|
||||
## Fonts
|
||||
|
||||
**Chinese**
|
||||
- Main serif: TsangerJinKai02-W04.ttf (commercial, user-supplied)
|
||||
- Fallback chain baked into templates: Source Han Serif SC -> Noto Serif CJK SC -> Songti SC -> Georgia
|
||||
|
||||
**English**
|
||||
- Main serif: Newsreader (Google Fonts, open source, Silicon Valley editorial feel)
|
||||
- Main sans: Inter (open source, Silicon Valley default — Linear, Vercel, Notion adjacent)
|
||||
- Fallback: Charter (macOS) / Georgia (cross-platform), Helvetica Neue / system-ui
|
||||
|
||||
Font files next to HTML and `@font-face` relative paths is the most stable setup.
|
||||
|
||||
## Feedback protocol
|
||||
|
||||
When the user gives **vague visual feedback** ("looks off", "不对劲", "spacing weird", "too cramped", "not elegant"):
|
||||
|
||||
Do not guess. Ask back using kami vocabulary, with current values included.
|
||||
|
||||
| User says | Ask about |
|
||||
|---|---|
|
||||
| "太挤了" / "too cramped" | Which element? Line-height (current: X)? Padding (current: Y)? Page margin? |
|
||||
| "太松了" / "too loose" | Same direction, reversed |
|
||||
| "颜色不对" / "color feels wrong" | Which element? Brand orange overused? A gray reading too cool? |
|
||||
| "不够好看" / "not polished" | Font rendering? Alignment? Whitespace distribution? Hierarchy unclear? |
|
||||
| "看着不专业" / "unprofessional" | Content wording? Or layout (alignment, consistency)? |
|
||||
|
||||
Template response: "X is currently set to Y. Would you like (a) [specific alternative within spec] or (b) [another option]?"
|
||||
|
||||
Never say "I'll adjust the spacing" without naming the exact property and its new value.
|
||||
|
||||
---
|
||||
|
||||
## When not to use this skill
|
||||
|
||||
- User explicitly wants Material / Fluent / Tailwind default — different design language
|
||||
- Need dark / cyberpunk / futurist aesthetic (this is deliberately anti-future)
|
||||
- Need saturated multi-color (this has one accent)
|
||||
- Need cartoon / animation / illustration style (this is editorial)
|
||||
- Web dynamic app UI (this is for print / static documents)
|
||||
|
||||
---
|
||||
|
||||
Next: **apply Step 3's tier table to decide what to read**, then copy the matching template and start filling.
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,562 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
SLIDES DEMO · Agent development · English
|
||||
Landscape A4, one slide per page, kami palette.
|
||||
Content distilled from the article "Things You Don't Know About Agents"
|
||||
(tw93.fun, 2026-03-21).
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Things You Don't Know About Agents · Slides</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../fonts/Newsreader.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../fonts/Newsreader.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter-500.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter-600.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../fonts/JetBrainsMono.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../fonts/JetBrainsMono.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 0;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Charter", Georgia, serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.slide {
|
||||
width: 297mm;
|
||||
height: 210mm;
|
||||
padding: 18mm 22mm;
|
||||
background: var(--parchment);
|
||||
break-after: page;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.slide:last-child { break-after: auto; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
letter-spacing: 2.5pt;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
.page-num {
|
||||
position: absolute;
|
||||
bottom: 10mm;
|
||||
right: 22mm;
|
||||
font-family: var(--mono);
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.footer-mark {
|
||||
position: absolute;
|
||||
bottom: 10mm;
|
||||
left: 22mm;
|
||||
font-family: var(--mono);
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* --- Cover --- */
|
||||
.cover {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 32pt;
|
||||
align-items: center;
|
||||
}
|
||||
.cover-left { display: flex; flex-direction: column; }
|
||||
.cover .mark {
|
||||
font-family: var(--mono);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 3pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 22pt;
|
||||
}
|
||||
.cover h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 42pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -1.5pt;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover .sub {
|
||||
font-family: var(--serif);
|
||||
font-size: 15pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 34ch;
|
||||
}
|
||||
.cover .line {
|
||||
width: 60pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 20pt 0 16pt 0;
|
||||
}
|
||||
.cover .meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.4pt;
|
||||
}
|
||||
.cover-visual { display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
/* --- Content slide --- */
|
||||
.slide-head {
|
||||
margin-bottom: 10pt;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.slide-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 38pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.6pt;
|
||||
color: var(--near-black);
|
||||
max-width: 24ch;
|
||||
margin-top: 6pt;
|
||||
}
|
||||
.brand-line {
|
||||
width: 60pt;
|
||||
height: 1.5pt;
|
||||
background: var(--brand);
|
||||
margin: 14pt 0 18pt 0;
|
||||
}
|
||||
|
||||
.body-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 28pt;
|
||||
}
|
||||
.body-single {
|
||||
max-width: 70ch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.claim {
|
||||
font-family: var(--serif);
|
||||
font-size: 17pt;
|
||||
line-height: 1.45;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.claim .hl { color: var(--brand); }
|
||||
|
||||
ul.pts {
|
||||
list-style: none;
|
||||
}
|
||||
ul.pts li {
|
||||
font-family: var(--sans);
|
||||
font-size: 12.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
padding-left: 18pt;
|
||||
position: relative;
|
||||
margin-bottom: 9pt;
|
||||
}
|
||||
ul.pts li::before {
|
||||
content: "-";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand);
|
||||
}
|
||||
ul.pts li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* kami-native callout: transparent + single brand left bar */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 0 8pt 16pt;
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
line-height: 1.5;
|
||||
color: var(--olive);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
.callout .hl { color: var(--brand); }
|
||||
|
||||
/* kami-native metrics: transparent baseline flex row */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 20pt;
|
||||
margin-top: 20pt;
|
||||
padding-top: 12pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8pt;
|
||||
}
|
||||
.metric .v {
|
||||
font-family: var(--serif);
|
||||
font-size: 32pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric .l {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* --- Diagram column --- */
|
||||
.diagram-col {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-top: -20pt;
|
||||
}
|
||||
.diagram-col svg { width: 96%; }
|
||||
|
||||
/* --- Ending slide --- */
|
||||
.ending { justify-content: center; align-items: center; text-align: center; }
|
||||
.ending h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 52pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
letter-spacing: -0.8pt;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.ending .sub {
|
||||
font-family: var(--serif);
|
||||
font-size: 17pt;
|
||||
color: var(--olive);
|
||||
max-width: 44ch;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.ending .line {
|
||||
width: 60pt;
|
||||
height: 1.5pt;
|
||||
background: var(--brand);
|
||||
margin: 22pt auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═══════════ 01 · Cover ═══════════ -->
|
||||
<section class="slide cover">
|
||||
<div class="cover-left">
|
||||
<div class="mark">Keynote · 2026</div>
|
||||
<h1>Things you don't know about Agents</h1>
|
||||
<div class="sub">Loops, harness, context, memory: what actually moves the needle in production.</div>
|
||||
<div class="line"></div>
|
||||
<div class="meta">kami slides demo · A4 landscape · 6 slides</div>
|
||||
</div>
|
||||
<div class="cover-visual">
|
||||
<!-- Decorative loop mark: concentric arcs in brand + stone -->
|
||||
<svg viewBox="0 0 260 260" xmlns="http://www.w3.org/2000/svg" style="width: 220pt;">
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<circle cx="130" cy="130" r="72" fill="none" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<circle cx="130" cy="130" r="44" fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<!-- arc segments on outer ring to suggest cycle -->
|
||||
<path d="M 130 30 A 100 100 0 0 1 230 130" fill="none" stroke="#1B365D" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<path d="M 230 130 A 100 100 0 0 1 130 230" fill="none" stroke="#87867f" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<path d="M 130 230 A 100 100 0 0 1 30 130" fill="none" stroke="#87867f" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<path d="M 30 130 A 100 100 0 0 1 130 30" fill="none" stroke="#87867f" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<!-- center label -->
|
||||
<text x="130" y="125" text-anchor="middle" font-size="10" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">Agent</text>
|
||||
<text x="130" y="141" text-anchor="middle" font-size="8" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="2">LOOP</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="page-num">01</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 02 · Agent loop ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">01 · Agent Loop</span>
|
||||
</div>
|
||||
<div class="slide-title">Simple core,<br>complex surroundings</div>
|
||||
<div class="brand-line"></div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">The loop is small. The <span class="hl">infrastructure around it</span> is what keeps it stable across feature growth.</p>
|
||||
<ul class="pts">
|
||||
<li>A working Agent loop fits in about <span class="hl">20 lines</span> of code.</li>
|
||||
<li>Control flow lives in the tools, not in branchy internal state.</li>
|
||||
<li>Workflow vs Agent: predefined paths in code vs model picks paths at runtime.</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
If your loop keeps growing every sprint, you are fixing the wrong layer. The tax is paid <span class="hl">outside</span> the loop.
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagram-col">
|
||||
<!-- Agent loop cycle diagram. viewBox 680×560 (same ratio as reference).
|
||||
4 nodes 200×72 rx=8, centered at NESW with 12-14px inset from viewBox edges.
|
||||
Ring r=192 grazes top/bottom nodes at their inner edges, passes through left/right.
|
||||
Q-curve arcs: control at tangent-axis outer corner, giving true quarter-circle sweep.
|
||||
Chevron marker (open path, not filled polygon). Core is the only focal element. -->
|
||||
<svg viewBox="0 0 680 560" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- surroundings label -->
|
||||
<text x="340" y="12" text-anchor="middle" font-size="9" fill="#87867f" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">TOOLS · HARNESS · EVALS · MEMORY</text>
|
||||
|
||||
<!-- dashed ink-blue ring -->
|
||||
<circle cx="340" cy="280" r="192" fill="none" stroke="#1B365D" stroke-width="1" stroke-dasharray="4 3"/>
|
||||
|
||||
<!-- PLAN: x=240 y=16 w=200 h=72 rx=8, center (340, 52) -->
|
||||
<rect x="240" y="16" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="340" y="52" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">PLAN</text>
|
||||
|
||||
<!-- ACT: x=468 y=244 w=200 h=72 rx=8, center (568, 280) -->
|
||||
<rect x="468" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="568" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">ACT</text>
|
||||
|
||||
<!-- OBSERVE: x=240 y=472 w=200 h=72 rx=8, center (340, 508) -->
|
||||
<rect x="240" y="472" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="340" y="508" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">OBSERVE</text>
|
||||
|
||||
<!-- REFLECT: x=12 y=244 w=200 h=72 rx=8, center (112, 280) -->
|
||||
<rect x="12" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="112" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">REFLECT</text>
|
||||
|
||||
<!-- center: focal CORE text -->
|
||||
<text x="340" y="268" text-anchor="middle" dominant-baseline="central" font-size="16" font-weight="600" fill="#141413" font-family="'Inter', sans-serif">20 LOC</text>
|
||||
<text x="340" y="292" text-anchor="middle" dominant-baseline="central" font-size="10" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">CORE</text>
|
||||
|
||||
<!-- clockwise arcs (no marker-end; WeasyPrint ignores orient="auto").
|
||||
Arrowheads drawn manually as chevron paths below each arc. -->
|
||||
<!-- PLAN right-mid (440,52) → ACT top-mid (568,244) via (568,52) -->
|
||||
<path d="M 440 52 Q 568 52 568 244" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<!-- chevron ↓ at (568, 244) -->
|
||||
<path d="M 560 236 L 568 244 L 576 236" fill="none" stroke="#5e5d59" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- ACT bottom-mid (568,316) → OBSERVE right-mid (440,508) via (568,508) -->
|
||||
<path d="M 568 316 Q 568 508 440 508" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<!-- chevron ← at (440, 508) -->
|
||||
<path d="M 448 500 L 440 508 L 448 516" fill="none" stroke="#5e5d59" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- OBSERVE left-mid (240,508) → REFLECT bottom-mid (112,316) via (112,508) -->
|
||||
<path d="M 240 508 Q 112 508 112 316" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<!-- chevron ↑ at (112, 316) -->
|
||||
<path d="M 104 324 L 112 316 L 120 324" fill="none" stroke="#5e5d59" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- REFLECT top-mid (112,244) → PLAN left-mid (240,52) via (112,52) -->
|
||||
<path d="M 112 244 Q 112 52 240 52" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<!-- chevron → at (240, 52) -->
|
||||
<path d="M 232 44 L 240 52 L 232 60" fill="none" stroke="#5e5d59" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-num">02</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 03 · Harness over models ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">02 · Harness</span>
|
||||
</div>
|
||||
<div class="slide-title">Harness wins<br>over hardware</div>
|
||||
<div class="brand-line"></div>
|
||||
|
||||
<div class="body-single">
|
||||
<p class="claim">More expensive models bring gains <span class="hl">far smaller than expected</span>. Verification, boundaries, feedback, and fallbacks matter more than model capability.</p>
|
||||
<ul class="pts">
|
||||
<li>Upgrade the harness first. If accuracy does not move, then try the model.</li>
|
||||
<li>Evaluation is the only honest signal. Test harness before test model.</li>
|
||||
<li>Smaller model + strong harness routinely beats larger model + weak harness in production.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="v">20</div>
|
||||
<div class="l">lines of code in a working Agent core loop</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="v">4</div>
|
||||
<div class="l">harness layers that matter: verify, bound, feedback, fallback</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="v">10×</div>
|
||||
<div class="l">velocity gains trace to execution discipline, not model swaps</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">03</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 04 · Context ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">03 · Context</span>
|
||||
</div>
|
||||
<div class="slide-title">Density beats length</div>
|
||||
<div class="brand-line"></div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">Long context windows do not fix weak context design. <span class="hl">Context Rot</span> sets in around 300–400K tokens regardless of the model.</p>
|
||||
<ul class="pts">
|
||||
<li>Layer the load: constant, on-demand, runtime, memory, system.</li>
|
||||
<li>Index first, full content on demand. Beats dumping everything up front.</li>
|
||||
<li>Stable prompt prefixes let caching actually pay off.</li>
|
||||
<li>Every token that is not load-bearing is diluting signal.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="diagram-col">
|
||||
<!-- Context: flat vs layered accuracy comparison -->
|
||||
<svg viewBox="0 0 280 230" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background -->
|
||||
<rect width="280" height="230" fill="#f5f4ed"/>
|
||||
<!-- Title -->
|
||||
<text x="140" y="18" text-anchor="middle" font-size="8.5" fill="#87867f" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">LOADING STRATEGY vs ACCURACY</text>
|
||||
<!-- Y axis label -->
|
||||
<text x="14" y="130" text-anchor="middle" font-size="8" fill="#5e5d59" font-family="'Inter', sans-serif" transform="rotate(-90 14 130)">Accuracy</text>
|
||||
<!-- Y axis line -->
|
||||
<line x1="36" y1="30" x2="36" y2="180" stroke="#5e5d59" stroke-width="0.8"/>
|
||||
<!-- Y axis ticks -->
|
||||
<line x1="30" y1="56" x2="36" y2="56" stroke="#5e5d59" stroke-width="0.8"/>
|
||||
<text x="26" y="59" text-anchor="end" font-size="8" fill="#87867f" font-family="'Inter', sans-serif">85%</text>
|
||||
<line x1="30" y1="120" x2="36" y2="120" stroke="#5e5d59" stroke-width="0.8"/>
|
||||
<text x="26" y="123" text-anchor="end" font-size="8" fill="#87867f" font-family="'Inter', sans-serif">53%</text>
|
||||
<!-- X axis -->
|
||||
<line x1="36" y1="180" x2="256" y2="180" stroke="#5e5d59" stroke-width="0.8"/>
|
||||
<!-- bar 1: flat loading -->
|
||||
<rect x="56" y="120" width="68" height="60" fill="#e8e6dc" stroke="none"/>
|
||||
<text x="90" y="112" text-anchor="middle" font-size="8" fill="#87867f" font-family="'Inter', sans-serif">53%</text>
|
||||
<text x="90" y="198" text-anchor="middle" font-size="8.5" fill="#5e5d59" font-family="'Inter', sans-serif">Flat</text>
|
||||
<text x="90" y="209" text-anchor="middle" font-size="7.5" fill="#87867f" font-family="'JetBrains Mono', monospace">loading</text>
|
||||
<!-- bar 2: layered loading (brand) -->
|
||||
<rect x="156" y="56" width="68" height="124" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="190" y="48" text-anchor="middle" font-size="8" fill="#1B365D" font-family="'Inter', sans-serif" font-weight="600">85%</text>
|
||||
<text x="190" y="198" text-anchor="middle" font-size="8.5" fill="#1B365D" font-family="'Inter', sans-serif" font-weight="600">Layered</text>
|
||||
<text x="190" y="209" text-anchor="middle" font-size="7.5" fill="#1B365D" font-family="'JetBrains Mono', monospace">loading</text>
|
||||
<!-- dotted line at 85 -->
|
||||
<line x1="36" y1="56" x2="256" y2="56" stroke="#1B365D" stroke-width="0.6" stroke-dasharray="3 3"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">04</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 05 · Tools + Memory ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">04 · Tools & Memory</span>
|
||||
</div>
|
||||
<div class="slide-title">Put state outside<br>the context</div>
|
||||
<div class="brand-line"></div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">Tools should match Agent goals, not underlying API shapes. Memory lives on disk, not in the window.</p>
|
||||
<ul class="pts">
|
||||
<li><span class="hl">ACI principle</span>: Agent-Computer Interface — design for what the Agent wants to do, not for the HTTP verb.</li>
|
||||
<li>A bad tool description looks like a model failure until you re-read the description.</li>
|
||||
<li>File-based state survives restarts. In-context state does not.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="claim" style="font-size: 14pt;"><span class="hl">Four kinds of memory</span></p>
|
||||
<ul class="pts">
|
||||
<li><span class="hl">Working</span>: the context window — fast, expensive, temporary.</li>
|
||||
<li><span class="hl">Procedural</span>: SKILL.md files — how to behave, loaded lazily.</li>
|
||||
<li><span class="hl">Episodic</span>: JSONL logs — what happened, appendable.</li>
|
||||
<li><span class="hl">Semantic</span>: MEMORY.md — what to remember across sessions, consolidated at thresholds.</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
Cross-session consistency needs explicit consolidation, not hope.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">05</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 06 · Closing ═══════════ -->
|
||||
<section class="slide ending">
|
||||
<h1>Protocol first.<br>Then parallelism.</h1>
|
||||
<div class="line"></div>
|
||||
<div class="sub">Fix your evals before you tweak the Agent. Most of what looks like model failure is infrastructure noise in disguise.</div>
|
||||
<div class="page-num">06</div>
|
||||
<div class="footer-mark">End of Deck</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,676 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
PORTFOLIO TEMPLATE · parchment design system
|
||||
多页 A4 作品集 / 案例集 / 项目集
|
||||
结构:封面 -> About -> 若干项目页 -> Contact
|
||||
每个项目 1-2 页,视觉重于文字
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kaku · 作品集</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 15mm 12mm 15mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
@page:first {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-deep:#a64f33;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
}
|
||||
|
||||
/* ════════════ COVER ════════════ */
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 297mm;
|
||||
padding: 25mm 20mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-top {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.cover-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14pt;
|
||||
}
|
||||
.cover-name {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 58pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
color: var(--near-black);
|
||||
letter-spacing: 2pt;
|
||||
}
|
||||
.cover-tagline {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
max-width: 80%;
|
||||
}
|
||||
.cover-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 18pt 0 4pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ════════════ ABOUT ════════════ */
|
||||
section.about {
|
||||
break-after: page;
|
||||
padding: 10mm 0;
|
||||
}
|
||||
.about-eyebrow {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.about-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.about-lead {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 15pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 24pt;
|
||||
max-width: 90%;
|
||||
}
|
||||
.about-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24pt;
|
||||
}
|
||||
.about-body p {
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10pt;
|
||||
}
|
||||
.about-body h3 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* ════════════ PROJECT PAGE ════════════ */
|
||||
.project {
|
||||
break-before: page;
|
||||
break-after: page;
|
||||
padding: 8mm 0;
|
||||
}
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.project-num {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 42pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
margin-right: 18pt;
|
||||
}
|
||||
.project-title-block {
|
||||
flex: 1;
|
||||
padding-top: 6pt;
|
||||
}
|
||||
.project-type {
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.project-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 19pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.project-subtitle {
|
||||
font-size: 11pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.project-date {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 80pt;
|
||||
padding-top: 10pt;
|
||||
}
|
||||
|
||||
/* Tags row */
|
||||
.project-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.tag {
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
padding: 2pt 7pt;
|
||||
border-radius: 3pt;
|
||||
}
|
||||
|
||||
/* Image / Visual */
|
||||
.project-hero {
|
||||
width: 100%;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.project-hero img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 6pt;
|
||||
border: 0.5pt solid var(--border-soft);
|
||||
}
|
||||
|
||||
/* 3 段式 */
|
||||
.project-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
.project-block h3 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 4pt;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
.project-block p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.45;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.project-block .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Results metrics row */
|
||||
.project-results {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-top: 14pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
}
|
||||
.result-item {
|
||||
flex: 1;
|
||||
}
|
||||
.result-value {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 18pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.result-label {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
margin-top: 2pt;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ════════════ 2-IMAGE VARIANT ════════════ */
|
||||
.project-visuals-2col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.project-visuals-2col .project-hero {
|
||||
height: 80mm;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ════════════ SELECTED WORKS LIST ════════════ */
|
||||
section.more-works {
|
||||
break-before: page;
|
||||
padding-top: 10mm;
|
||||
}
|
||||
.more-works-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.work-item {
|
||||
display: grid;
|
||||
grid-template-columns: 40pt 1fr 80pt;
|
||||
gap: 12pt;
|
||||
padding: 8pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.work-year {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
color: var(--brand);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.work-title-block .title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.work-title-block .desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
margin-top: 1pt;
|
||||
}
|
||||
.work-link {
|
||||
text-align: right;
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
/* ════════════ CONTACT ════════════ */
|
||||
section.contact {
|
||||
break-before: page;
|
||||
padding: 20mm 0;
|
||||
min-height: 230mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.contact-eyebrow {
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.contact-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 48pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 20pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.contact-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin-bottom: 24pt;
|
||||
}
|
||||
.contact-list {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.contact-list strong {
|
||||
color: var(--stone);
|
||||
font-weight: 500;
|
||||
font-size: 10pt;
|
||||
letter-spacing: 0.5pt;
|
||||
margin-right: 12pt;
|
||||
display: inline-block;
|
||||
min-width: 70pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.contact-list a {
|
||||
color: var(--brand);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
background: var(--tag-bg);
|
||||
padding: 0.5pt 3pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--brand);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ════════════ COVER ════════════ -->
|
||||
<div class="cover">
|
||||
<div class="cover-top">SELECTED WORKS · 2025 – 2026</div>
|
||||
|
||||
<div class="cover-center">
|
||||
<div class="cover-name">Kaku<br>書く</div>
|
||||
<div class="cover-tagline">A fast, out-of-the-box terminal built for AI coding.</div>
|
||||
<div class="cover-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="cover-meta">
|
||||
<div>
|
||||
<strong>Terminal Emulator · Open Source</strong><br>
|
||||
macOS · Apple Silicon
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
hello@kaku.app<br>
|
||||
github.com/tw93/Kaku
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════ ABOUT ════════════ -->
|
||||
<section class="about">
|
||||
<div class="about-eyebrow">About</div>
|
||||
<div class="about-title">A terminal tuned for the AI-coding era</div>
|
||||
<div class="about-lead">
|
||||
Kaku is a WezTerm downstream that does less, does it faster, and looks right on macOS from the first launch. No config file required. The built-in AI assistant takes natural-language commands, reads shell errors, and fixes itself.
|
||||
</div>
|
||||
<div class="about-body">
|
||||
<div>
|
||||
<h3>Origin</h3>
|
||||
<p>Built by a small team that used Alacritty, iTerm2, and Ghostty daily and wanted something between them. Fast like Alacritty, configurable like iTerm2, but readable, AI-aware, and polished on macOS out of the box. Rust core, Lua scripting, WezTerm-compatible config file.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Focus</h3>
|
||||
<p>One opinionated default instead of fifty knobs. If a setting only makes sense to 2% of users, it lives in Lua, not in the UI. Shell bootstrap cut to ~100ms. Binary 40% smaller. Chinese, Japanese, Korean glyphs render correctly on first boot because macOS system font rendering is on by default.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PROJECT 01 · Kaku the product ════════════ -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">01</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">Terminal Emulator · Open Source</div>
|
||||
<div class="project-title">A fast, ready-to-go terminal for AI coding</div>
|
||||
<div class="project-subtitle">WezTerm downstream, stripped of everything that was not earning its keep, and tuned for macOS + Apple Silicon first.</div>
|
||||
</div>
|
||||
<div class="project-date">V0.9.0 · 2026.04</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">Rust</span>
|
||||
<span class="tag">Lua</span>
|
||||
<span class="tag">macOS</span>
|
||||
<span class="tag">Open Source</span>
|
||||
</div>
|
||||
|
||||
<!-- Hero image -->
|
||||
<div class="project-hero">
|
||||
<img src="images/kaku-hero.jpg" alt="Kaku terminal screenshot">
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Context</h3>
|
||||
<p>AI coding flipped the terminal from a place you type commands to a place you <span class="hl">collaborate with a model</span>. Existing terminals were still tuned for 2015 workflows: configs buried in TOML, bootstrap times that felt fine when humans were the only users, fonts that rendered Chinese as tofu. Kaku is the answer to what a terminal should feel like when half the commands come from an agent.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Approach</h3>
|
||||
<p>Forked WezTerm, cut the surface area in half, replaced the Lua defaults with a curated set that works on first boot. Polished the macOS integration: JetBrains Mono baked in, theme-aware dark/light, clickable paths, copy-on-select. Bundled a built-in AI assistant that can take natural-language commands and fix its own errors.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Outcome</h3>
|
||||
<p><span class="hl">4.2k stars · 216 forks · 1,082 commits</span>. Binary 40% smaller, resources 20% lighter, shell bootstrap from ~200ms to ~100ms versus upstream. The "Talk to it, it configures itself" use-case replaced dotfiles for most users.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-results">
|
||||
<div class="result-item">
|
||||
<div class="result-value">4.2<span style="font-size: 12pt; color: var(--brand);">k</span></div>
|
||||
<div class="result-label">GitHub stars, 216 forks</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">-40%</div>
|
||||
<div class="result-label">binary size vs upstream WezTerm</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">~100<span style="font-size: 12pt; color: var(--brand);">ms</span></div>
|
||||
<div class="result-label">shell bootstrap, down from ~200ms</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PROJECT 02 · Features ════════════ -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">02</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">Features · Built-in Tooling</div>
|
||||
<div class="project-title">A full workflow, no plugins required</div>
|
||||
<div class="project-subtitle">Lazygit, Yazi, remote filesystem mounting, pane broadcasting, and a curated shell suite ship in the box. Every shortcut is deliberate.</div>
|
||||
</div>
|
||||
<div class="project-date">V0.5 – V0.9</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">Lazygit</span>
|
||||
<span class="tag">Yazi</span>
|
||||
<span class="tag">Shell Suite</span>
|
||||
<span class="tag">SSH</span>
|
||||
<span class="tag">Lua Config</span>
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Terminal multiplexing</h3>
|
||||
<p><span class="hl">Cmd+D</span> splits vertically, <span class="hl">Cmd+Shift+D</span> horizontally. <span class="hl">Cmd+Shift+Enter</span> zooms the active pane. Pane input broadcast via <span class="hl">Cmd+Opt+I</span> (current tab) or <span class="hl">Cmd+Shift+I</span> (all tabs). Navigate with <span class="hl">Cmd+Opt+Arrows</span>. Smart <span class="hl">Cmd+W</span>: closes pane if multiple panes, closes tab if multiple tabs, otherwise hides the app.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Built-in tools</h3>
|
||||
<p><span class="hl">Cmd+Shift+G</span> launches lazygit in the current pane. <span class="hl">Cmd+Shift+Y</span> launches yazi; the <code>y</code> shell wrapper syncs the working directory on exit. <span class="hl">Cmd+Shift+R</span> mounts the current SSH session's remote filesystem via sshfs and opens it in yazi — no manual mount commands.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Shell suite + config</h3>
|
||||
<p>zsh-autosuggestions, zsh-syntax-highlighting, zsh-completions, and <code>z</code> load automatically inside Kaku sessions. Starship, Delta, and Yazi install via <span class="hl">kaku init</span>. Config file at <code>~/.config/kaku/kaku.lua</code> — open with <span class="hl">kaku config</span> or <span class="hl">Cmd+,</span>. Add keybindings by inserting into <code>config.keys</code>, never replacing it.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-results">
|
||||
<div class="result-item">
|
||||
<div class="result-value">15+</div>
|
||||
<div class="result-label">keybindings with no config, all deliberate</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">4</div>
|
||||
<div class="result-label">built-in tools: lazygit, yazi, sshfs, shell suite</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">Lua</div>
|
||||
<div class="result-label">full WezTerm API, insert-only keybinding pattern</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PROJECT 03 · AI Assistant ════════════ -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">03</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">Feature · Kaku AI</div>
|
||||
<div class="project-title">The terminal reads errors and fixes itself</div>
|
||||
<div class="project-subtitle">Two modes: automatic error recovery after any non-zero exit, and natural-language command generation via the <code>#</code> prefix.</div>
|
||||
</div>
|
||||
<div class="project-date">V0.9 · 2026.04</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">Error Recovery</span>
|
||||
<span class="tag">NL to Command</span>
|
||||
<span class="tag">OpenAI-compatible</span>
|
||||
<span class="tag">Cmd+Shift+E</span>
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Error recovery</h3>
|
||||
<p>When a command exits non-zero, Kaku Assistant automatically sends the failed command, exit code, working directory, and git branch to the LLM, and displays a suggested fix inline. Press <span class="hl">Cmd+Shift+E</span> to paste the suggestion — dangerous commands (<code>rm -rf</code>, <code>git reset --hard</code>) are pasted but never auto-executed.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Natural language to command</h3>
|
||||
<p>Type <code># list files modified in the last 7 days</code> and press Enter. Kaku intercepts the line before the shell sees it, sends the query with current directory and git branch context, and injects the resulting command into the prompt ready to review and run. Works in both zsh and fish.</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Provider + safety</h3>
|
||||
<p>Configured via <span class="hl">kaku ai</span> or <code>~/.config/kaku/assistant.toml</code>. Supports any OpenAI-compatible base URL — self-hosted models, DeepSeek, or corporate API gateways via <code>custom_headers</code>. Does not trigger on Ctrl+C exits, help flags, or non-shell foreground processes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-results">
|
||||
<div class="result-item">
|
||||
<div class="result-value">#</div>
|
||||
<div class="result-label">prefix to generate any shell command in plain English</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">E</div>
|
||||
<div class="result-label">Cmd+Shift+E applies the AI fix, one keystroke after any error</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">0</div>
|
||||
<div class="result-label">auto-executed dangerous commands — always pasted for review</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ RELEASE HISTORY ════════════ -->
|
||||
<section class="more-works">
|
||||
<div class="more-works-title">Release Cadence</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">V0.9</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">Kaku AI: error recovery + natural-language commands</div>
|
||||
<div class="desc">Cmd+Shift+E to apply fix. # prefix for NL-to-command. OpenAI-compatible providers.</div>
|
||||
</div>
|
||||
<div class="work-link">2026.04</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">V0.7</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">Shell bootstrap at ~100ms; lazygit + yazi integration</div>
|
||||
<div class="desc">Curated zsh plugin suite. Cmd+Shift+G / Y / R for lazygit, yazi, remote sshfs.</div>
|
||||
</div>
|
||||
<div class="work-link">2026.01</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">V0.5</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">Theme-aware rendering; pane multiplexing defaults</div>
|
||||
<div class="desc">Auto dark/light follows macOS. Broadcast input, zoom pane, smart Cmd+W.</div>
|
||||
</div>
|
||||
<div class="work-link">2025.10</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">V0.3</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">Lua config system; WezTerm API compatibility</div>
|
||||
<div class="desc">kaku config, kaku doctor, kaku init. Full WezTerm keybinding API, insert-only pattern.</div>
|
||||
</div>
|
||||
<div class="work-link">2025.08</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">V0.1</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">Public launch — WezTerm downstream, macOS-first</div>
|
||||
<div class="desc">40% smaller binary, JetBrains Mono, copy-on-select, clickable paths, CJK rendering.</div>
|
||||
</div>
|
||||
<div class="work-link">2025.06</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ CONTACT ════════════ -->
|
||||
<section class="contact">
|
||||
<div class="contact-eyebrow">Get Kaku</div>
|
||||
<div class="contact-title">Kaku is open source.</div>
|
||||
<div class="contact-line"></div>
|
||||
<div class="contact-list">
|
||||
<div><strong>GitHub</strong><a href="https://github.com/tw93/Kaku">github.com/tw93/Kaku</a></div>
|
||||
<div><strong>Website</strong><a href="https://kaku.app">kaku.app</a></div>
|
||||
<div><strong>Install</strong>brew install --cask kaku</div>
|
||||
<div><strong>Stars</strong>4,200+ and counting</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 55 KiB |
@@ -0,0 +1,714 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Elon Musk · Resume</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../fonts/Newsreader.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../fonts/Newsreader.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter-500.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../fonts/Inter-600.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../fonts/JetBrainsMono.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../fonts/JetBrainsMono.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 11mm 13mm 11mm 13mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--border: #e8e6dc;
|
||||
--border-soft: #efede4;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--brand-tint-strong: #E4ECF5;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 9.4pt;
|
||||
line-height: 1.42;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
.mono { font-family: var(--mono); }
|
||||
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 3mm;
|
||||
margin-bottom: 3mm;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
.header > div:first-child { flex-shrink: 0; }
|
||||
.name {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.2pt;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 3mm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alias {
|
||||
font-size: 12.5pt;
|
||||
font-weight: 400;
|
||||
color: var(--stone);
|
||||
}
|
||||
.contact {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.contact .role {
|
||||
color: var(--brand);
|
||||
font-size: 10.5pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.contact .sep { color: var(--border); margin: 0 0.4em; }
|
||||
.contact a { color: var(--dark-warm); }
|
||||
.contact .loc { color: var(--olive); font-weight: 500; }
|
||||
|
||||
/* METRICS */
|
||||
.metrics { display: flex; gap: 7mm; margin-bottom: 3mm; }
|
||||
.metric {
|
||||
flex: 1;
|
||||
padding: 1mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-value .unit {
|
||||
font-size: 10.5pt;
|
||||
margin-left: 0.5mm;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
}
|
||||
.metric-label { font-size: 8.8pt; color: var(--olive); line-height: 1.2; }
|
||||
|
||||
/* SECTION TITLE */
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding: 0 0 1mm 0;
|
||||
margin: 5mm 0 2mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2.5mm;
|
||||
}
|
||||
.section-title .sub {
|
||||
margin-left: auto;
|
||||
font-family: var(--sans);
|
||||
font-weight: 400;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
section:first-of-type .section-title { margin-top: 0; }
|
||||
.page-break > .section-title { margin-top: 0; }
|
||||
|
||||
/* SUMMARY */
|
||||
.summary { font-size: 9.4pt; line-height: 1.48; color: var(--near-black); }
|
||||
.summary .hl { color: var(--brand); }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { display: flex; gap: 7mm; margin-bottom: 2.5mm; }
|
||||
.tl-step { flex: 1; padding: 0.8mm 0; break-inside: avoid; }
|
||||
.tl-top {
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
margin-bottom: 0.6mm;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--brand); line-height: 1;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.tl-body { font-size: 8.8pt; color: var(--olive); line-height: 1.45; }
|
||||
|
||||
/* PROJECTS */
|
||||
.project {
|
||||
padding: 1.6mm 0;
|
||||
border-top: 0.4pt solid var(--border-soft);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.project:first-of-type {
|
||||
border-top: 0.4pt solid var(--border);
|
||||
padding-top: 1.6mm;
|
||||
}
|
||||
.proj-head {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; margin-bottom: 0.9mm;
|
||||
}
|
||||
.proj-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500; color: var(--near-black);
|
||||
}
|
||||
.proj-kind { font-size: 11pt; color: var(--olive); }
|
||||
.proj-role {
|
||||
font-size: 8.4pt; color: var(--brand); font-weight: 600;
|
||||
margin-left: auto; background: #EEF2F7;
|
||||
padding: 0.4mm 1.6mm; border-radius: 2pt; letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.proj-lines { display: table; width: 100%; }
|
||||
.proj-row { display: table-row; }
|
||||
.proj-label {
|
||||
display: table-cell; width: 16mm;
|
||||
font-size: 8.5pt; color: var(--brand);
|
||||
font-weight: 600; letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
padding: 0.4mm 0 0.4mm 0; vertical-align: top;
|
||||
}
|
||||
.proj-text {
|
||||
display: table-cell; font-size: 9.2pt;
|
||||
color: var(--near-black); line-height: 1.45;
|
||||
padding: 0 0 0.4mm 1mm;
|
||||
}
|
||||
.proj-text .hl { color: var(--brand); }
|
||||
|
||||
/* PAGE 2 FORCE BREAK */
|
||||
.page-break { break-before: page; }
|
||||
|
||||
/* OPEN SOURCE */
|
||||
.os-intro {
|
||||
font-size: 9.4pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
margin: 1.6mm 0 2.4mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-intro .hl { color: var(--brand); }
|
||||
.os-intro .strong { color: var(--near-black); font-weight: 500; }
|
||||
|
||||
.os-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 9mm; row-gap: 1.2mm;
|
||||
}
|
||||
.os-item {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2.5mm; padding: 1.2mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt; font-weight: 500;
|
||||
flex-shrink: 0; min-width: 18mm;
|
||||
}
|
||||
.os-name a { color: var(--brand); }
|
||||
.os-desc { font-size: 9pt; color: var(--olive); line-height: 1.4; flex: 1; }
|
||||
.os-star {
|
||||
font-size: 8.8pt; color: var(--dark-warm);
|
||||
font-weight: 600; white-space: nowrap;
|
||||
flex-shrink: 0; min-width: 10mm; text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.os-star.big { color: var(--brand); font-size: 9.2pt; }
|
||||
|
||||
.os-highlight {
|
||||
margin-top: 2.5mm;
|
||||
padding: 1.8mm 3mm;
|
||||
background: var(--brand-tint);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-highlight .hl { color: var(--brand); }
|
||||
.os-highlight .tag {
|
||||
display: inline-block;
|
||||
background: var(--brand); color: var(--ivory);
|
||||
font-size: 7.6pt; font-weight: 600;
|
||||
padding: 0.2mm 1.6mm; border-radius: 2pt;
|
||||
margin-right: 1.5mm; letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
transform: translateY(-0.2mm);
|
||||
}
|
||||
|
||||
/* CONVICTIONS */
|
||||
.convictions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 3mm;
|
||||
margin-top: 1.4mm;
|
||||
}
|
||||
.conv-card { padding: 0 2mm 0 0; break-inside: avoid; }
|
||||
.conv-head {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black); margin-bottom: 1mm;
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.conv-head .year {
|
||||
color: var(--brand); font-weight: 600;
|
||||
font-size: 9pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.conv-body {
|
||||
font-size: 9pt; color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.conv-body .hl { color: var(--brand); }
|
||||
|
||||
/* IMPACT */
|
||||
.handle-strip {
|
||||
padding: 0; margin: 1.4mm 0 2mm 0;
|
||||
display: flex; align-items: baseline; gap: 3.5mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.handle-strip .handle {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500; color: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.handle-strip .follower { font-size: 8.8pt; color: var(--stone); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
||||
.handle-strip .desc { font-size: 8.8pt; color: var(--olive); line-height: 1.45; flex: 1; }
|
||||
|
||||
.impact-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 6mm; }
|
||||
.inf-block-title {
|
||||
margin-top: 2.5mm;
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 1.5mm; padding-bottom: 0.5mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.inf-block-title .small {
|
||||
font-family: var(--sans);
|
||||
font-size: 8.5pt; color: var(--stone);
|
||||
font-weight: 400;
|
||||
}
|
||||
.art-row {
|
||||
padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.talk-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 2mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.art-row:last-of-type, .talk-row:last-of-type { border-bottom: none; }
|
||||
.art-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 2mm;
|
||||
}
|
||||
.art-title, .talk-title {
|
||||
font-size: 9.2pt; color: var(--near-black);
|
||||
font-weight: 500; line-height: 1.4;
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
}
|
||||
.art-stats, .talk-venue {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
line-height: 1.4; margin-top: 0.6mm;
|
||||
}
|
||||
.art-stats .em-brand { color: var(--brand); }
|
||||
.art-date, .talk-date {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* SKILLS */
|
||||
.skill-row {
|
||||
display: flex; gap: 3mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.skill-row:last-of-type { border-bottom: none; }
|
||||
.skill-label {
|
||||
font-size: 8.6pt; font-weight: 600;
|
||||
color: var(--brand); letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
width: 32mm; flex-shrink: 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.skill-body { font-size: 9.2pt; color: var(--near-black); line-height: 1.45; flex: 1; }
|
||||
.skill-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* EDUCATION */
|
||||
.edu-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: baseline; padding: 1.8mm 0;
|
||||
}
|
||||
.edu-row .school {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500; font-size: 11pt;
|
||||
}
|
||||
.edu-row .major { color: var(--olive); font-size: 9.4pt; }
|
||||
.edu-row .date { color: var(--stone); font-variant-numeric: tabular-nums; font-size: 9pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- =====================================================================
|
||||
Usage:
|
||||
- Replace all {{VAR}} placeholders with real content
|
||||
- Drop sections that don't apply (e.g. no open source -> remove that block)
|
||||
- Each project strictly three-part: ROLE / ACTIONS / IMPACT
|
||||
- Must verify: 2 pages exactly (strict)
|
||||
python3 scripts/build.py resume-en
|
||||
- Content strategy: references/writing.en.md
|
||||
===================================================================== -->
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 1 ═══════════ -->
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="name serif">Elon Musk</div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<span class="role">Founder · Chief Engineer · CEO</span>
|
||||
<br>
|
||||
<a href="https://x.com/elonmusk">x.com/elonmusk</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="mailto:em@example.com">em@example.com</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="loc">Austin, TX</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Four strongest numeric badges -->
|
||||
<div class="metrics">
|
||||
<div class="metric"><span class="metric-value serif">30<span class="unit">yrs</span></span><span class="metric-label">building hard tech</span></div>
|
||||
<div class="metric"><span class="metric-value serif">6<span class="unit">cos</span></span><span class="metric-label">founded / co-founded</span></div>
|
||||
<div class="metric"><span class="metric-value serif">400<span class="unit">+</span></span><span class="metric-label">Falcon launches, lifetime</span></div>
|
||||
<div class="metric"><span class="metric-value serif">7M<span class="unit">+</span></span><span class="metric-label">Tesla vehicles delivered</span></div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Summary</div>
|
||||
<div class="summary">
|
||||
Founder and engineer across SpaceX, Tesla, xAI, Neuralink, The Boring Company, and X. 30 years running companies where the product is hard and the supply chain is physical. Long arc: move civilization to multi-planetary, sustainable energy, beneficial AI. Core depth in rocket engineering, mass manufacturing, capital markets, and first-principles problem decomposition.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Experience<span class="sub">1995 — Present · six companies, one reusable rocket, one mass-market EV</span></div>
|
||||
|
||||
<!-- Timeline: three-step long-range arc -->
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">1995</div><div class="tl-head">Internet era</div></div>
|
||||
<div class="tl-body">Zip2 -> X.com -> PayPal. Sold two internet companies before age 32.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2002</div><div class="tl-head">Space + autos</div></div>
|
||||
<div class="tl-body">SpaceX, Tesla, SolarCity. Built physical infrastructure others called impossible.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2023</div><div class="tl-head">AI era</div></div>
|
||||
<div class="tl-body">xAI, Neuralink scaling, X platform ownership. New bet on general intelligence and BCI.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project block — repeat 3–5 times -->
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">SpaceX</span>
|
||||
<span class="proj-kind">· Space launch · reusable rockets</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Founded SpaceX in 2002 to make humanity multi-planetary. First private company to reach orbit (2008), dock with ISS (2012), land and reuse an orbital-class booster (2015), and fly crew (2020). CEO and Chief Engineer from day one.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Vertical integration from propellant to avionics. Merlin and Raptor engines designed in-house. Reusable-booster thesis pursued through 12 failed landings before success. Starship developed publicly with high-cadence prototype testing. Launch cadence scaled from 1 flight per year (2008) to one every 2-3 days (2024).</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">Falcon 9: <span class="hl">400+ successful launches</span>, 95%+ reuse rate, cost-per-kg to orbit reduced by a factor of 10 versus legacy providers. Starlink: <span class="hl">7,000+ satellites</span> in orbit, largest constellation ever deployed. SpaceX valued at $350B (2024 secondary round), now responsible for the majority of US orbital mass launched.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Tesla</span>
|
||||
<span class="proj-kind">· EVs · energy · autonomy</span>
|
||||
<span class="proj-role">ceo</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Joined Tesla as lead investor and chairman in 2004; took over as CEO in 2008 during near-bankruptcy. Built it from a two-car roadster project into the world's most valuable automaker by a factor of two.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Scaled Model S -> 3 -> Y via the Master Plan framework, treating each model as capital to fund the next. Built four Gigafactories on three continents. Shipped FSD as a continuously improving software product, not a fixed-version feature. Vertically integrated batteries, motors, silicon, and charging.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">Tesla: <span class="hl">7M+ vehicles delivered</span> lifetime (1.8M in 2023 alone), first EV to cross 20% gross margin, first automaker to cross $1T market cap. Reshaped the industry such that every major incumbent announced an EV transition strategy within 5 years of Model 3 launch.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">xAI</span>
|
||||
<span class="proj-kind">· Frontier AI · foundation models</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Founded xAI in 2023 with a focus on understanding the true nature of the universe. Shipped Grok within 6 months of incorporation, one of the fastest foundation-model companies to production.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Built Colossus, a 100,000-H100 GPU supercluster, in 122 days from concept to training start. Integrated Grok directly into X to give the model unique real-time data access and the product a real user base from day one.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">Colossus stood up in <span class="hl">122 days</span>, a pace treated as implausible inside the industry. Grok reached ChatGPT-comparable benchmarks within 18 months of company founding, with xAI valued at $50B in 2024 funding.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Neuralink</span>
|
||||
<span class="proj-kind">· Brain-computer interfaces · BCI implants</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Founded 2016 to build high-bandwidth BCI. Shipped the N1 chip with <span class="hl">1,024 electrodes</span> and a precision surgical robot. Secured FDA Breakthrough Device designation and IDE approval (2023) after five years of primate trials.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">First human implanted January 2024; cursor control in 30 days. <span class="hl">Second patient mid-2024</span>, expanding to speech restoration. Valued at $5B (2023).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 2 ═══════════ -->
|
||||
|
||||
<section class="page-break">
|
||||
<div class="section-title">Open Source & Indie Work<span class="sub">1995 — present · Founder-engineer across six hard-tech companies</span></div>
|
||||
|
||||
<div class="os-intro">
|
||||
<span class="hl">First-principles founder across hard tech.</span> Approach: start from the physics, question every received cost assumption, prototype in public. Public reach: <span class="strong">214M followers on X · 125 followers on GitHub</span>.
|
||||
</div>
|
||||
|
||||
<div class="os-grid">
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://spacex.com">SpaceX</a></span>
|
||||
<span class="os-desc">Launch · Starlink · Starship</span>
|
||||
<span class="os-star big">★ $350B</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://tesla.com">Tesla</a></span>
|
||||
<span class="os-desc">EVs · energy · FSD</span>
|
||||
<span class="os-star big">★ $900B</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="os-highlight">
|
||||
<span class="tag">Cadence</span>The Falcon 9 booster that landed on 22 December 2015 was the first orbital-class rocket to return intact and fly again. The industry assumption that rockets were expendable was gone within 24 months.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Judgment & Conviction</div>
|
||||
<div class="convictions">
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">1999</span>Bet on online banking</div>
|
||||
<div class="conv-body">
|
||||
Founded X.com while consumer online banking was niche; merger with Confinity became PayPal. Sold to eBay for $1.5B, with most of the proceeds recycled into SpaceX and Tesla within 24 months.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2008</span>CEO during near-bankruptcy</div>
|
||||
<div class="conv-body">
|
||||
Took Tesla CEO during the 2008 crisis, three weeks from insolvency. Closed a Mercedes partnership that funded Model S development, a decision that made every subsequent Tesla product possible.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2015</span>Committed to reusable boosters</div>
|
||||
<div class="conv-body">
|
||||
Pushed Falcon 9 reusability after <span class="hl">12 consecutive landing failures</span> when board and industry said stop. Succeeded on attempt 13. Reusability is now the assumed baseline across every launch provider.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Public Impact</div>
|
||||
|
||||
<div class="handle-strip">
|
||||
<span class="handle serif">X · <a href="https://tesla.com/blog" style="color: var(--brand); border-bottom: none;">@elonmusk</a></span>
|
||||
<span class="follower">214M followers</span>
|
||||
<span class="desc">Direct channel to 214M followers. Each engineering post, launch video, or product hint moves markets within hours.</span>
|
||||
</div>
|
||||
|
||||
<div class="impact-grid">
|
||||
<div>
|
||||
<div class="inf-block-title">Selected writing<span class="small">Founder-engineer across six hard-tech companies</span></div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://youtube.com/ted" style="text-decoration: none;">Master Plan Part Deux</a>
|
||||
<span class="art-date">2016.07</span>
|
||||
</div>
|
||||
<div class="art-stats">Tesla blog, <span class="em-brand">40M+</span> combined reads; reframed EV strategy for the industry</div>
|
||||
</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://spacex.com" style="text-decoration: none;">First Principles Thinking in Engineering</a>
|
||||
<span class="art-date">2013.03</span>
|
||||
</div>
|
||||
<div class="art-stats">TED Talk · 5.6M views on youtube.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="inf-block-title">Invited talks<span class="small">Founder-engineer across six hard-tech companies</span></div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">Making Humans Multi-Planetary</div>
|
||||
<div class="talk-venue">IAC Guadalajara · 2016</div>
|
||||
</div>
|
||||
<div class="talk-date">2016.09</div>
|
||||
</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">Starship and Mars</div>
|
||||
<div class="talk-venue">Starbase Texas · 2024</div>
|
||||
</div>
|
||||
<div class="talk-date">2024.04</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Core Skills</div>
|
||||
<!-- Five fixed rows, each with at least one brand-color emphasis -->
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Rocket · Engine Design</div>
|
||||
<div class="skill-body">Led Merlin, Raptor, Starship architecture; <span class="em-brand">first reusable orbital booster</span> shipped by any org.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Manufacturing · Scale</div>
|
||||
<div class="skill-body">Built Tesla Fremont, Shanghai, Berlin, Austin; SpaceX vehicle assembly from 0 to <span class="em-brand">100+ launches per year</span>.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Capital · Crisis Mgmt</div>
|
||||
<div class="skill-body">Closed Daimler investment when Tesla was <span class="em-brand">weeks from insolvency</span>; survived five near-bankruptcies across companies.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Public Communication</div>
|
||||
<div class="skill-body">214M followers on X; product announcements routinely move the <span class="em-brand">S&P 500 component</span> price of his companies.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">First Principles · Cost</div>
|
||||
<div class="skill-body">Questioning every accepted cost assumption (rockets, batteries, tunneling) has driven <span class="em-brand">10× cost reductions</span> across four industries.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="break-inside: avoid;">
|
||||
<div class="section-title">Education</div>
|
||||
<div class="edu-row">
|
||||
<div>
|
||||
<span class="school serif">University of Pennsylvania</span>
|
||||
<span class="major"> · Wharton · College of Arts & Sciences · B.S. Economics · B.A. Physics · transferred from Queen's University; enrolled in Stanford physics PhD, left after two days</span>
|
||||
</div>
|
||||
<div class="date">1992 — 1997</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 451 KiB |
@@ -0,0 +1,365 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
ONE-PAGER TEMPLATE · parchment design system
|
||||
单页 A4 方案/报告/执行摘要
|
||||
改完跑:python3 -c "from weasyprint import HTML; HTML('one-pager.html').write_pdf('out.pdf')"
|
||||
目标:页数必须 == 1
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Tesla, Inc. · 公司介绍</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
}
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.header {
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding-bottom: 8pt;
|
||||
margin-bottom: 14pt;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block {
|
||||
flex: 1;
|
||||
}
|
||||
.eyebrow {
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
h1 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 12pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.meta {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.45;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
/* 横向 baseline 布局 —— 和 resume.html 一致的 kami native 姿态。
|
||||
背景透明、无边框,用 typography 本身承担层次,不加容器。 */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 22pt;
|
||||
padding: 6pt 0 8pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
section { margin-bottom: 22pt; break-inside: avoid; }
|
||||
section:last-of-type { margin-bottom: 0; }
|
||||
|
||||
h2 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
padding-bottom: 3pt;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
h2 .sub {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
font-weight: 400;
|
||||
margin-left: 6pt;
|
||||
}
|
||||
|
||||
p { margin-bottom: 6pt; line-height: 1.45; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
.lead {
|
||||
font-size: 13pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== 2-column grid ========== */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18pt;
|
||||
}
|
||||
|
||||
/* ========== 短横线 list(代替默认 bullet) ========== */
|
||||
ul.dash {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.dash li {
|
||||
margin-bottom: 4pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* ========== Timeline (横向进度点) ========== */
|
||||
.timeline {
|
||||
display: flex;
|
||||
gap: 12pt;
|
||||
margin-top: 6pt;
|
||||
}
|
||||
.tl-step {
|
||||
flex: 1;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-body {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ========== Tag ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg); /* 实色!不要 rgba */
|
||||
color: var(--brand);
|
||||
font-size: 7.5pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.1pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* ========== Callout / 高亮块 ========== */
|
||||
/* 参考 resume.html .team-culture:透明底 + 单左侧 brand 竖条。
|
||||
不做容器也不做圆角,气质是"一条旁注"而不是"一个卡片"。 */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 1.8pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
margin: 10pt 0;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout .hl, .callout .em-brand { color: var(--brand); }
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.footer {
|
||||
margin-top: 18pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 8pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== HEADER ========== -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">公司介绍 · Company Brief</div>
|
||||
<h1>把汽车产业从内燃机拖进电气化</h1>
|
||||
<div class="subtitle">电动车、能源、AI 三条主线,共用一套制造哲学。</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
公开资料整理<br>
|
||||
2026.04.18<br>
|
||||
公开版
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== METRICS(可选,3-4 卡最佳) ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">1.8M</div>
|
||||
<div class="metric-label">全球交付量</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">$96B</div>
|
||||
<div class="metric-label">全年营收</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">4</div>
|
||||
<div class="metric-label">超级工厂数</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">2003</div>
|
||||
<div class="metric-label">加州成立</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== LEAD(导语,一段) ========== -->
|
||||
<p class="lead">
|
||||
Tesla 不是一家「做电动车的」公司。它是一家<span class="hl">用第一性原理重造制造业</span>的公司,电动车只是最先被重造的那条产业链。能源、储能、Robotaxi、人形机器人 Optimus,都是同一套哲学在不同产业的延伸。
|
||||
</p>
|
||||
|
||||
<!-- ========== 核心内容(两栏) ========== -->
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>业务主线</h2>
|
||||
<p>营收结构高度集中在汽车,但长期叙事在能源与 AI。</p>
|
||||
<ul class="dash">
|
||||
<li><span class="hl">汽车</span>:Model 3/Y/S/X + Cybertruck,$25k-$100k 价格带</li>
|
||||
<li><span class="hl">能源</span>:Powerwall 家储、Megapack 电网储能,增速超汽车</li>
|
||||
<li><span class="hl">AI</span>:FSD 自动驾驶 + Optimus 人形机器人,共用感知栈</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>信念与方法</h2>
|
||||
<p>三条反行业共识的判断,定义了这家公司的长期战略。</p>
|
||||
<ul class="dash">
|
||||
<li>垂直整合到极致:电池、电机、逆变器、软件、充电网络全部自建</li>
|
||||
<li>产品迭代像软件:OTA 每几周发版,8 年前的 Model S 还在收新功能</li>
|
||||
<li>中国是制造锚点:上海工厂本地化率 95%+,2023 年产出占全球 50%+</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ========== Timeline / Roadmap(可选) ========== -->
|
||||
<section>
|
||||
<h2>时间线 / 路线图<span class="sub">(适用于方案类文档)</span></h2>
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Master Plan 1</div>
|
||||
<div class="tl-head">高端立足</div>
|
||||
<div class="tl-body">2006:先做豪华跑车 Roadster 证明电动车可行,再用利润下沉到 Model S -> 3。</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Master Plan 2</div>
|
||||
<div class="tl-head">规模化普及</div>
|
||||
<div class="tl-body">2016:Model 3 大规模量产、储能产品化、光伏屋顶。从小众酷拉到主流选择。</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Master Plan 3</div>
|
||||
<div class="tl-head">能源 × 自动驾驶</div>
|
||||
<div class="tl-body">2023 之后:自动驾驶网络 + Robotaxi + Optimus。车、屋、工厂三个入口都是 AI 终端。</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ========== 关键里程碑(横向时间点) ========== -->
|
||||
<section>
|
||||
<h2>关键里程碑<span class="sub">从跑车到万亿市值</span></h2>
|
||||
<ul class="dash" style="display: grid; grid-template-columns: 1fr 1fr; column-gap: 18pt; row-gap: 2pt;">
|
||||
<li><span class="hl">2008</span>:Roadster 交付,第一款量产电动跑车</li>
|
||||
<li><span class="hl">2012</span>:Model S 发布,重新定义豪华 EV</li>
|
||||
<li><span class="hl">2017</span>:Model 3 量产,EV 进入主流价位</li>
|
||||
<li><span class="hl">2020</span>:市值超过丰田,成为全球最贵车企</li>
|
||||
<li><span class="hl">2021</span>:首家市值破万亿美元的汽车公司</li>
|
||||
<li><span class="hl">2023</span>:上海工厂年产 95 万辆,全球领先</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ========== CALLOUT(可选)========== -->
|
||||
<div class="callout">
|
||||
「我们不是在做一辆车,我们是在做制造这辆车的机器。」Tesla 团队多次提到的内部共识。理解这句话,就理解为什么它的估值长期按科技公司、而非车企给。
|
||||
</div>
|
||||
|
||||
<!-- ========== FOOTER ========== -->
|
||||
<div class="footer">
|
||||
<span>公开 · 公司介绍</span>
|
||||
<span>kami demo · 1 / 1</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 460 KiB |
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Architecture (kami palette)
|
||||
Components + connections in a system. One focal node, everything else
|
||||
in warm neutrals. Drop the <svg> block into a long-doc or portfolio
|
||||
figure element to embed.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Architecture · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Charter", Georgia, serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · kami diagram</p>
|
||||
<h1>{{System name}} in production</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(20,20,19,0.08)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#5e5d59"/>
|
||||
</marker>
|
||||
<marker id="arrow-brand" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Arrows (behind nodes) -->
|
||||
<line x1="168" y1="240" x2="220" y2="240" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="364" y1="240" x2="416" y2="240" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<line x1="576" y1="224" x2="628" y2="180" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="576" y1="256" x2="628" y2="300" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow labels (masked rect prevents line bleed-through) -->
|
||||
<rect x="172" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="196" y="230" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">HTTPS</text>
|
||||
|
||||
<rect x="368" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="392" y="230" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">SSR</text>
|
||||
|
||||
<rect x="560" y="180" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="588" y="190" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">READ</text>
|
||||
|
||||
<rect x="560" y="288" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="588" y="298" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">QUERY</text>
|
||||
|
||||
<!-- Node 1: Reader (external) -->
|
||||
<rect x="40" y="208" width="128" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="40" y="208" width="128" height="64" rx="6" fill="rgba(94,93,89,0.08)" stroke="#87867f" stroke-width="1"/>
|
||||
<text x="62" y="226" fill="#87867f" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">USER</text>
|
||||
<text x="104" y="246" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Reader</text>
|
||||
<text x="104" y="262" fill="#5e5d59" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">Browser</text>
|
||||
|
||||
<!-- Node 2: Edge / CDN (cloud) -->
|
||||
<rect x="220" y="208" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="220" y="208" width="144" height="64" rx="6" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="242" y="226" fill="#87867f" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">EDGE</text>
|
||||
<text x="292" y="246" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">CDN</text>
|
||||
<text x="292" y="262" fill="#5e5d59" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">cache · SSL</text>
|
||||
|
||||
<!-- Node 3: Origin (FOCAL — brand accent) -->
|
||||
<rect x="416" y="208" width="160" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="416" y="208" width="160" height="64" rx="6" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="438" y="226" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">ORIGIN</text>
|
||||
<text x="496" y="246" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">App Server</text>
|
||||
<text x="496" y="262" fill="#5e5d59" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">render · route</text>
|
||||
|
||||
<!-- Node 4: Store (content) -->
|
||||
<rect x="628" y="128" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="628" y="128" width="144" height="64" rx="6" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="650" y="146" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">BUNDLE</text>
|
||||
<text x="700" y="166" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Content</text>
|
||||
<text x="700" y="182" fill="#5e5d59" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">*.mdx · assets</text>
|
||||
|
||||
<!-- Node 5: Database (store) -->
|
||||
<rect x="628" y="288" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="628" y="288" width="144" height="64" rx="6" fill="rgba(20,20,19,0.05)" stroke="#5e5d59" stroke-width="1"/>
|
||||
<text x="650" y="306" fill="#5e5d59" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">STORE</text>
|
||||
<text x="700" y="326" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Database</text>
|
||||
<text x="700" y="342" fill="#5e5d59" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">postgres · vectors</text>
|
||||
|
||||
<!-- Legend strip -->
|
||||
<line x1="40" y1="390" x2="920" y2="390" stroke="rgba(20,20,19,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="406" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="422" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="60" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Focal · origin</text>
|
||||
|
||||
<rect x="170" y="422" width="14" height="10" rx="2" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="190" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Backend · bundle</text>
|
||||
|
||||
<rect x="320" y="422" width="14" height="10" rx="2" fill="rgba(20,20,19,0.05)" stroke="#5e5d59" stroke-width="1"/>
|
||||
<text x="340" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Store</text>
|
||||
|
||||
<rect x="420" y="422" width="14" height="10" rx="2" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="440" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Cloud</text>
|
||||
|
||||
<rect x="510" y="422" width="14" height="10" rx="2" fill="rgba(94,93,89,0.08)" stroke="#87867f" stroke-width="1"/>
|
||||
<text x="530" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">External</text>
|
||||
|
||||
<line x1="620" y1="428" x2="650" y2="428" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<text x="658" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Standard flow</text>
|
||||
|
||||
<line x1="780" y1="428" x2="810" y2="428" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<text x="818" y="430" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Primary path</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: one ink-blue node per diagram, marking the component the reader should look at first. Every other box stays in warm neutrals so the accent actually means something.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,167 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Flowchart (kami palette)
|
||||
Decision logic with branches. One focal decision, two to four outcomes.
|
||||
Use when the structure IS the point. If a table communicates the
|
||||
same thing, use a table instead.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Flowchart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Charter", Georgia, serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 960px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 820px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Flowchart · kami diagram</p>
|
||||
<h1>{{Question the flow answers}}</h1>
|
||||
|
||||
<svg viewBox="0 0 860 520" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#5e5d59"/>
|
||||
</marker>
|
||||
<marker id="arrow-brand" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- ========== Flow ========== -->
|
||||
<!-- Start → Check -->
|
||||
<line x1="430" y1="72" x2="430" y2="112" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Check → Decision (focal) -->
|
||||
<line x1="430" y1="176" x2="430" y2="216" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Decision → Yes branch (brand) -->
|
||||
<path d="M 360 268 L 200 268 L 200 332" fill="none" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<rect x="170" y="284" width="34" height="14" rx="2" fill="#f5f4ed"/>
|
||||
<text x="187" y="294" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.12em">YES</text>
|
||||
|
||||
<!-- Decision → No branch -->
|
||||
<path d="M 500 268 L 660 268 L 660 332" fill="none" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="636" y="284" width="30" height="14" rx="2" fill="#f5f4ed"/>
|
||||
<text x="651" y="294" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.12em">NO</text>
|
||||
|
||||
<!-- Yes outcome → End -->
|
||||
<path d="M 200 396 L 200 440 L 396 440" fill="none" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- No outcome → End -->
|
||||
<path d="M 660 396 L 660 440 L 464 440" fill="none" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ========== Nodes ========== -->
|
||||
|
||||
<!-- Start (pill) -->
|
||||
<rect x="360" y="40" width="140" height="32" rx="16" fill="rgba(94,93,89,0.08)" stroke="#87867f" stroke-width="1"/>
|
||||
<text x="430" y="60" fill="#141413" font-size="11" font-weight="500" font-family="'Inter', sans-serif" text-anchor="middle">Start</text>
|
||||
|
||||
<!-- Check step -->
|
||||
<rect x="350" y="112" width="160" height="64" rx="6" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="364" y="130" fill="#5e5d59" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">STEP 01</text>
|
||||
<text x="430" y="150" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">{{Describe input}}</text>
|
||||
<text x="430" y="166" fill="#5e5d59" font-size="9" font-family="'Inter', sans-serif" text-anchor="middle">one short line</text>
|
||||
|
||||
<!-- Decision diamond (FOCAL) -->
|
||||
<polygon points="430,216 500,242 500,268 430,320 360,268 360,242"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<text x="430" y="266" fill="#141413" font-size="11" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">{{Decision}}</text>
|
||||
<text x="430" y="282" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.15em">BRANCH</text>
|
||||
|
||||
<!-- Yes outcome -->
|
||||
<rect x="120" y="332" width="160" height="64" rx="6" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="134" y="350" fill="#5e5d59" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">OUTCOME A</text>
|
||||
<text x="200" y="370" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">{{Path taken}}</text>
|
||||
<text x="200" y="386" fill="#5e5d59" font-size="9" font-family="'Inter', sans-serif" text-anchor="middle">what happens next</text>
|
||||
|
||||
<!-- No outcome -->
|
||||
<rect x="580" y="332" width="160" height="64" rx="6" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="594" y="350" fill="#87867f" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">OUTCOME B</text>
|
||||
<text x="660" y="370" fill="#141413" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">{{Alt path}}</text>
|
||||
<text x="660" y="386" fill="#5e5d59" font-size="9" font-family="'Inter', sans-serif" text-anchor="middle">skipped / deferred</text>
|
||||
|
||||
<!-- End (pill) -->
|
||||
<rect x="396" y="424" width="68" height="32" rx="16" fill="rgba(94,93,89,0.08)" stroke="#87867f" stroke-width="1"/>
|
||||
<text x="430" y="444" fill="#141413" font-size="11" font-weight="500" font-family="'Inter', sans-serif" text-anchor="middle">End</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="484" x2="820" y2="484" stroke="rgba(20,20,19,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="500" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<polygon points="180,492 192,498 180,504 168,498" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="202" y="502" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Focal decision</text>
|
||||
|
||||
<rect x="310" y="494" width="14" height="10" rx="2" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="330" y="502" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Step · outcome</text>
|
||||
|
||||
<rect x="440" y="494" width="14" height="10" rx="16" fill="rgba(94,93,89,0.08)" stroke="#87867f" stroke-width="1"/>
|
||||
<text x="460" y="502" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Start · end</text>
|
||||
|
||||
<rect x="560" y="494" width="14" height="10" rx="2" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="580" y="502" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Deferred branch</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">The decision diamond carries the accent. Its two branches diverge into equal weights so the reader sees the question, not an implied answer.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Quadrant (kami palette)
|
||||
Two-axis positioning. 4 quadrants, 6-10 plotted points, 1-2 focal.
|
||||
Classic use: Impact × Effort, Reach × Confidence, Urgency × Importance.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Quadrant · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Charter", Georgia, serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 780px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Quadrant · kami diagram</p>
|
||||
<h1>{{X axis}} × {{Y axis}}</h1>
|
||||
|
||||
<svg viewBox="0 0 820 540" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#5e5d59"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Quadrant zones (subtle background tint on the "preferred" quadrant) -->
|
||||
<rect x="80" y="60" width="660" height="380" fill="none" stroke="rgba(20,20,19,0.10)" stroke-width="0.8"/>
|
||||
<rect x="410" y="60" width="330" height="190" fill="rgba(201,100,66,0.04)"/>
|
||||
|
||||
<!-- Cross axes (through midpoint) -->
|
||||
<line x1="80" y1="250" x2="740" y2="250" stroke="rgba(20,20,19,0.18)" stroke-width="0.8"/>
|
||||
<line x1="410" y1="60" x2="410" y2="440" stroke="rgba(20,20,19,0.18)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axis arrows + labels -->
|
||||
<line x1="80" y1="460" x2="740" y2="460" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="60" y1="440" x2="60" y2="60" stroke="#5e5d59" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<text x="410" y="480" fill="#141413" font-size="11" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle" letter-spacing="0.08em">{{X axis · low to high}}</text>
|
||||
<text x="30" y="250" fill="#141413" font-size="11" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle" letter-spacing="0.08em" transform="rotate(-90 30 250)">{{Y axis · low to high}}</text>
|
||||
|
||||
<!-- Quadrant labels -->
|
||||
<text x="244" y="88" fill="#87867f" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">HIGH-Y · LOW-X</text>
|
||||
<text x="244" y="102" fill="#5e5d59" font-size="10" font-family="'Inter', sans-serif" text-anchor="middle" font-style="italic">incubate</text>
|
||||
|
||||
<text x="576" y="88" fill="#1B365D" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">HIGH-Y · HIGH-X</text>
|
||||
<text x="576" y="102" fill="#1B365D" font-size="10" font-family="'Inter', sans-serif" text-anchor="middle" font-style="italic" font-weight="500">do first</text>
|
||||
|
||||
<text x="244" y="428" fill="#87867f" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">LOW-Y · LOW-X</text>
|
||||
<text x="244" y="420" fill="#87867f" font-size="10" font-family="'Inter', sans-serif" text-anchor="middle" font-style="italic">drop</text>
|
||||
|
||||
<text x="576" y="428" fill="#87867f" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">LOW-Y · HIGH-X</text>
|
||||
<text x="576" y="420" fill="#5e5d59" font-size="10" font-family="'Inter', sans-serif" text-anchor="middle" font-style="italic">delegate</text>
|
||||
|
||||
<!-- Plotted points -->
|
||||
<!-- 1: Top-left (incubate) -->
|
||||
<circle cx="190" cy="180" r="5" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="200" y="184" fill="#141413" font-size="10" font-family="'Inter', sans-serif" font-weight="500">{{Item A}}</text>
|
||||
|
||||
<!-- 2: Top-right FOCAL (do first) -->
|
||||
<circle cx="550" cy="140" r="6.5" fill="#1B365D" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="562" y="145" fill="#1B365D" font-size="11" font-family="'Inter', sans-serif" font-weight="600">{{Item B · focal}}</text>
|
||||
|
||||
<!-- 3: Top-right secondary -->
|
||||
<circle cx="640" cy="200" r="5" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="650" y="204" fill="#141413" font-size="10" font-family="'Inter', sans-serif" font-weight="500">{{Item C}}</text>
|
||||
|
||||
<!-- 4: Top-right -->
|
||||
<circle cx="490" cy="210" r="5" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="500" y="214" fill="#141413" font-size="10" font-family="'Inter', sans-serif">{{Item D}}</text>
|
||||
|
||||
<!-- 5: Bottom-right (delegate) -->
|
||||
<circle cx="580" cy="350" r="4.5" fill="rgba(20,20,19,0.05)" stroke="#5e5d59" stroke-width="1"/>
|
||||
<text x="590" y="354" fill="#5e5d59" font-size="10" font-family="'Inter', sans-serif">{{Item E}}</text>
|
||||
|
||||
<!-- 6: Bottom-left (drop) -->
|
||||
<circle cx="180" cy="380" r="4" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="190" y="384" fill="#87867f" font-size="10" font-family="'Inter', sans-serif">{{Item F}}</text>
|
||||
|
||||
<!-- 7: Bottom-left -->
|
||||
<circle cx="280" cy="340" r="4" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="290" y="344" fill="#87867f" font-size="10" font-family="'Inter', sans-serif">{{Item G}}</text>
|
||||
|
||||
<!-- 8: Near center -->
|
||||
<circle cx="380" cy="270" r="4" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="390" y="274" fill="#141413" font-size="10" font-family="'Inter', sans-serif">{{Item H}}</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="502" x2="780" y2="502" stroke="rgba(20,20,19,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="518" fill="#5e5d59" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<circle cx="180" cy="514" r="5" fill="#1B365D" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="192" y="518" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Focal · do first</text>
|
||||
|
||||
<circle cx="328" cy="514" r="5" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="340" y="518" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Near-focal</text>
|
||||
|
||||
<circle cx="450" cy="514" r="5" fill="#ffffff" stroke="#141413" stroke-width="1"/>
|
||||
<text x="462" y="518" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Standard</text>
|
||||
|
||||
<circle cx="555" cy="514" r="5" fill="rgba(20,20,19,0.05)" stroke="#5e5d59" stroke-width="1"/>
|
||||
<text x="567" y="518" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Secondary</text>
|
||||
|
||||
<circle cx="660" cy="514" r="5" fill="rgba(20,20,19,0.03)" stroke="rgba(20,20,19,0.30)" stroke-width="1"/>
|
||||
<text x="672" y="518" fill="#5e5d59" font-size="8.5" font-family="'Inter', sans-serif">Drop</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">The ink-blue point is where the reader's eye lands. The tinted upper-right quadrant is the preferred region. Everything else recedes so the judgment reads instantly.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER · English · parchment design system
|
||||
A4 formal letter / memo / recommendation / resignation / statement
|
||||
Characteristics: minimal, serif-led, slightly larger body type
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{LETTER_SUBJECT}}</title>
|
||||
<style>
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-500.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a { color: var(--dark-warm); text-decoration: none; }
|
||||
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 24pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
.subject .label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.subject .title {
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.2pt;
|
||||
}
|
||||
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
.body-content p {
|
||||
margin-bottom: 12pt;
|
||||
text-align: left;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.body-content p:first-child { text-indent: 0; }
|
||||
.body-content p.indent { text-indent: 1.5em; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.closing {
|
||||
margin-top: 28pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-family: var(--sans);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Memo variant: sans-led, no indent */
|
||||
body.memo {
|
||||
font-family: var(--sans);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
body.memo .body-content p { text-indent: 0; margin-bottom: 10pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">{{SENDER_NAME}}</div>
|
||||
<div>{{SENDER_ADDRESS / DEPARTMENT / ORGANIZATION}}</div>
|
||||
<div>{{PHONE}} · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="date">{{DATE, e.g. April 18, 2026}}</div>
|
||||
|
||||
<div class="recipient">
|
||||
<div class="to">To</div>
|
||||
<div class="name">{{RECIPIENT_NAME_OR_TITLE}}</div>
|
||||
<div class="org">{{RECIPIENT_ORG / DEPARTMENT}}</div>
|
||||
</div>
|
||||
|
||||
<div class="subject">
|
||||
<div class="label">Re</div>
|
||||
<div class="title">{{One-sentence subject line: what this letter is about.}}</div>
|
||||
</div>
|
||||
|
||||
<div class="salutation">{{Dear {{NAME}},}}</div>
|
||||
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
{{Paragraph 1: state the purpose. Why you are writing and the core intent.
|
||||
No preamble. Two sentences at most so the reader knows what you want from this letter.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 2: elaborate. Background, reasoning, evidence. Use
|
||||
<span class="hl">brand-color emphasis</span> on the single most important point.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 3: be specific. What you want the reader to do, by when,
|
||||
and how to reach you. Give the action a clear exit.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 4 (optional): close. Express anticipation, gratitude, or a courteous sign-off line.}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
{{Closing — "Best regards," / "Sincerely," / "Warm regards,"}}
|
||||
</div>
|
||||
|
||||
<div class="signature">{{HANDWRITTEN_NAME_OR_SIGNATURE}}</div>
|
||||
<div class="signoff-meta">
|
||||
{{TITLE · DEPARTMENT}}<br>
|
||||
{{DATE (optional restatement)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attachments">
|
||||
<span class="label">Enclosures</span>
|
||||
{{List — ① Attachment 1 · ② Attachment 2}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER TEMPLATE · parchment design system
|
||||
A4 正式信件 / 备忘录 / 推荐信 / 辞职信 / 个人声明
|
||||
特点:极简,serif 主导,字号稍大,段距宽松
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{信件主题}}</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
|
||||
/* ========== LETTERHEAD 信头 ========== */
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a {
|
||||
color: var(--dark-warm);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========== META ========== */
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 20pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== SUBJECT ========== */
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
.subject .label {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.8pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.subject .title {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SALUTATION ========== */
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== BODY ========== */
|
||||
.body-content p {
|
||||
margin-bottom: 14pt;
|
||||
text-align: justify;
|
||||
text-justify: inter-ideograph;
|
||||
line-height: 1.55;
|
||||
text-indent: 2em; /* 中文段落首行缩进 */
|
||||
}
|
||||
.body-content p:first-child { text-indent: 2em; }
|
||||
.body-content p.no-indent { text-indent: 0; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== CLOSING ========== */
|
||||
.closing {
|
||||
margin-top: 30pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== ATTACHMENT ========== */
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== 一些特殊变体 ========== */
|
||||
/* 如果是 memo(备忘录),用 sans 字体,去掉首行缩进 */
|
||||
body.memo {
|
||||
font-family: "Source Han Sans SC", -apple-system, sans-serif;
|
||||
line-height: 1.55;
|
||||
}
|
||||
body.memo .body-content p { text-indent: 0; margin-bottom: 10pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== LETTERHEAD(寄件人) ========== -->
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">{{寄件人姓名}}</div>
|
||||
<div>{{寄件人地址 / 部门 / 机构}}</div>
|
||||
<div>{{电话}} · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 日期 ========== -->
|
||||
<div class="date">{{日期 如 2026 年 4 月 18 日}}</div>
|
||||
|
||||
<!-- ========== 收件人 ========== -->
|
||||
<div class="recipient">
|
||||
<div class="to">致</div>
|
||||
<div class="name">{{收件人姓名 / 称谓}}</div>
|
||||
<div class="org">{{收件人机构 / 部门}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 主题 ========== -->
|
||||
<div class="subject">
|
||||
<div class="label">关于</div>
|
||||
<div class="title">{{信件主题,一句话说清}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 称呼 ========== -->
|
||||
<div class="salutation">{{称呼 如 "尊敬的 XX 先生:" / "Dear XX,"}}</div>
|
||||
|
||||
<!-- ========== 正文 ========== -->
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
{{第一段:破题。说明写这封信的缘由和核心意图。
|
||||
不要绕弯,1-2 句话让读者知道你要说什么。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第二段:展开。给出背景、理由、论据。
|
||||
如有 <span class="hl">关键信息</span> 可用高亮突出。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第三段:具体。说清楚你希望对方做什么、
|
||||
何时做、怎么联系。让行动有明确出口。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第四段(可选):收尾。表达期待、致谢或礼貌性结束语。}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========== 敬语 ========== -->
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
{{敬语。中文常用 "此致 / 敬礼!"、"顺颂商祺"。英文 "Best regards," / "Sincerely,"}}
|
||||
</div>
|
||||
|
||||
<div class="signature">{{签名 / 亲笔姓名}}</div>
|
||||
<div class="signoff-meta">
|
||||
{{职位 · 部门}}<br>
|
||||
{{日期复写 · 可选}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 附件(可选) ========== -->
|
||||
<div class="attachments">
|
||||
<span class="label">附件</span>
|
||||
{{附件清单:① 附件名 1 · ② 附件名 2}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,487 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LONG DOCUMENT · English · parchment design system
|
||||
Multi-page A4 white paper / tech report / annual review
|
||||
Flow: cover -> toc -> exec summary -> chapters -> appendix
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{DOC_TITLE}}</title>
|
||||
<style>
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-500.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{DOC_TITLE}}";
|
||||
font-family: "Newsreader", "Charter", Georgia, serif;
|
||||
font-size: 8.5pt;
|
||||
color: #87867f;
|
||||
}
|
||||
}
|
||||
@page:first { @bottom-center { content: ""; } }
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.sans { font-family: var(--sans); }
|
||||
|
||||
/* COVER */
|
||||
.cover {
|
||||
min-height: 240mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40mm 0 0 0;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-eyebrow {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
.cover-title {
|
||||
font-size: 40pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.5pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: var(--sans);
|
||||
font-size: 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 85%;
|
||||
margin-bottom: 30pt;
|
||||
}
|
||||
.cover-divider {
|
||||
width: 60pt;
|
||||
height: 1pt;
|
||||
background: var(--brand);
|
||||
margin: 20pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.55;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.cover-meta strong { color: var(--dark-warm); font-weight: 500; }
|
||||
|
||||
/* TOC */
|
||||
.toc { break-after: page; }
|
||||
.toc h2 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 6pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.toc-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.toc-num {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
min-width: 30pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.toc-title {
|
||||
flex: 1;
|
||||
color: var(--near-black);
|
||||
padding-left: 6pt;
|
||||
}
|
||||
.toc-page {
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* HEADINGS */
|
||||
h1 {
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.3pt;
|
||||
margin: 0 0 10pt 0;
|
||||
padding-bottom: 4pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
h2 {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
margin: 24pt 0 6pt 0;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
h3 {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 18pt 0 4pt 0;
|
||||
color: var(--dark-warm);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.chapter-num {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* PARAGRAPHS */
|
||||
p { margin: 0 0 10pt 0; line-height: 1.55; color: var(--near-black); }
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* LISTS */
|
||||
ul, ol {
|
||||
margin: 6pt 0 10pt 0;
|
||||
padding-left: 20pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* QUOTE */
|
||||
blockquote, .quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
margin: 12pt 0;
|
||||
padding: 4pt 0 4pt 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
blockquote .cite, .quote .cite {
|
||||
display: block;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
|
||||
/* CODE */
|
||||
code {
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
pre {
|
||||
font-family: var(--mono);
|
||||
font-size: 8.5pt;
|
||||
line-height: 1.5;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-soft);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 10pt 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
pre code { background: transparent; padding: 0; font-size: inherit; }
|
||||
|
||||
/* TABLE */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th {
|
||||
text-align: left;
|
||||
font-family: var(--sans);
|
||||
font-weight: 500;
|
||||
color: var(--charcoal);
|
||||
padding: 6pt 8pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
background: var(--ivory);
|
||||
}
|
||||
table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* CALLOUT / TAKEAWAY */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 10pt 14pt;
|
||||
border-radius: 3pt;
|
||||
margin: 12pt 0;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 8pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* TAG */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 8pt;
|
||||
font-weight: 600;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
|
||||
/* FIGURE */
|
||||
figure { margin: 14pt 0; break-inside: avoid; }
|
||||
figure img { max-width: 100%; border-radius: 4pt; }
|
||||
figcaption {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 6pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* CHAPTER BREAK */
|
||||
.chapter { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- COVER -->
|
||||
<section class="cover">
|
||||
<div>
|
||||
<div class="cover-eyebrow">{{EYEBROW — e.g. Technical Report / Annual Review / White Paper}}</div>
|
||||
<div class="cover-title">{{Document title<br>can span two lines}}</div>
|
||||
<div class="cover-sub">{{One-line subtitle — what this is and who it's for.}}</div>
|
||||
<div class="cover-divider"></div>
|
||||
</div>
|
||||
<div class="cover-meta">
|
||||
<strong>{{AUTHOR / TEAM}}</strong><br>
|
||||
{{Version 1.0}} · {{YYYY.MM}}<br>
|
||||
{{PUBLISHER / ORGANIZATION}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TOC -->
|
||||
<section class="toc">
|
||||
<h2>Contents</h2>
|
||||
<div class="toc-item"><span class="toc-num">01</span><span class="toc-title">Executive Summary</span><span class="toc-page">03</span></div>
|
||||
<div class="toc-item"><span class="toc-num">02</span><span class="toc-title">Background & Problem Statement</span><span class="toc-page">04</span></div>
|
||||
<div class="toc-item"><span class="toc-num">03</span><span class="toc-title">Methodology & Findings</span><span class="toc-page">06</span></div>
|
||||
<div class="toc-item"><span class="toc-num">04</span><span class="toc-title">Conclusions & Recommendations</span><span class="toc-page">09</span></div>
|
||||
<div class="toc-item"><span class="toc-num">05</span><span class="toc-title">Appendix</span><span class="toc-page">11</span></div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 01 -->
|
||||
<section>
|
||||
<div class="chapter-num">01 · Executive Summary</div>
|
||||
<h1>Executive Summary</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{Two or three sentences opening the whole thesis. Use <span class="hl">brand-color emphasis</span> to grab attention on the sharpest claim. A reader of only this paragraph should understand what the document argues.}}
|
||||
</p>
|
||||
|
||||
<h2>Key Takeaways</h2>
|
||||
<ul>
|
||||
<li>{{Takeaway 1 — a quantified conclusion in one line.}}</li>
|
||||
<li>{{Takeaway 2 — an insight backed by data.}}</li>
|
||||
<li>{{Takeaway 3 — a forward-looking judgment.}}</li>
|
||||
</ul>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Questions this document answers</div>
|
||||
{{List the three core questions as actual questions — so the reader can decide in ten seconds whether to read on.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 02 -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">02 · Background</div>
|
||||
<h1>Background & Problem Statement</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{Chapter intro — what this chapter is solving, why it matters. One or two sentences.}}
|
||||
</p>
|
||||
|
||||
<h2>Current State</h2>
|
||||
<p>{{Three to five lines describing the status quo. Use <span class="hl">specific figures</span> rather than adjectives.}}</p>
|
||||
|
||||
<h2>The Core Problem</h2>
|
||||
<p>{{State the problem specifically. Use a callout to emphasize a key observation:}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{A short quoted line or key observation. Different in tone from the body so the reader gets a breath.}}
|
||||
</div>
|
||||
|
||||
<h2>Metrics of Success</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dimension</th>
|
||||
<th>Current</th>
|
||||
<th>Target</th>
|
||||
<th>Gap</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>{{DIMENSION_1}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{GAP}}</td></tr>
|
||||
<tr><td>{{DIMENSION_2}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{GAP}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 03 -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">03 · Methodology</div>
|
||||
<h1>Methodology & Findings</h1>
|
||||
|
||||
<p class="lead">{{Chapter intro.}}</p>
|
||||
|
||||
<h2>Approach</h2>
|
||||
<p>{{Describe the methodology. Code or formula examples welcome:}}</p>
|
||||
|
||||
<pre><code>def analyze(data):
|
||||
return transform(data)</code></pre>
|
||||
|
||||
<h2>Key Findings</h2>
|
||||
|
||||
<h3>Finding 1 — {{TITLE}}</h3>
|
||||
<p>{{A paragraph with data: <span class="hl">specific numbers or ratios</span>.}}</p>
|
||||
|
||||
<h3>Finding 2 — {{TITLE}}</h3>
|
||||
<p>{{A paragraph.}}</p>
|
||||
|
||||
<blockquote>
|
||||
{{A quoted passage — user interview, expert perspective, or cited source.}}
|
||||
<span class="cite">— {{SOURCE / PERSON}}, {{DATE}}</span>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 04 -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">04 · Conclusions</div>
|
||||
<h1>Conclusions & Recommendations</h1>
|
||||
|
||||
<p class="lead">{{Chapter intro — a one-line summary of the conclusion, then the recommendations below.}}</p>
|
||||
|
||||
<h2>Core Conclusions</h2>
|
||||
<ol>
|
||||
<li>{{Conclusion 1.}}</li>
|
||||
<li>{{Conclusion 2.}}</li>
|
||||
<li>{{Conclusion 3.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>Recommended Next Steps</h2>
|
||||
<p>{{Concrete, executable recommendations tied to the conclusions.}}</p>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Call to Action</div>
|
||||
{{If the reader does one thing, what is it? Specific enough to start Monday morning.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 05 -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">05 · Appendix</div>
|
||||
<h1>Appendix</h1>
|
||||
|
||||
<h2>A. References</h2>
|
||||
<ul>
|
||||
<li>{{Reference 1}}</li>
|
||||
<li>{{Reference 2}}</li>
|
||||
</ul>
|
||||
|
||||
<h2>B. Glossary</h2>
|
||||
<p><strong>{{TERM}}</strong> — {{definition}}</p>
|
||||
<p><strong>{{TERM}}</strong> — {{definition}}</p>
|
||||
|
||||
<h2>C. Acknowledgements</h2>
|
||||
<p>{{Acknowledgement paragraph.}}</p>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,538 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LONG DOCUMENT TEMPLATE · parchment design system
|
||||
多页 A4 白皮书/技术报告/年度总结
|
||||
结构:封面 -> 目录 -> 执行摘要 -> 正文章节 -> 附录
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{文档标题}}</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{文档标题}}";
|
||||
font-family: "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 8.5pt;
|
||||
color: #87867f;
|
||||
}
|
||||
}
|
||||
|
||||
@page:first {
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: "Source Han Sans SC", "Noto Sans CJK SC",
|
||||
-apple-system, "PingFang SC", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* ========== COVER ========== */
|
||||
.cover {
|
||||
min-height: 240mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40mm 0 0 0;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-eyebrow {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
.cover-title {
|
||||
font-size: 36pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
letter-spacing: 0.5pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 85%;
|
||||
margin-bottom: 30pt;
|
||||
}
|
||||
.cover-divider {
|
||||
width: 60pt;
|
||||
height: 1pt;
|
||||
background: var(--brand);
|
||||
margin: 20pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== TOC(目录)========== */
|
||||
.toc { break-after: page; }
|
||||
.toc h2 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 6pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.toc-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.toc-num {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
min-width: 30pt;
|
||||
}
|
||||
.toc-title {
|
||||
flex: 1;
|
||||
color: var(--near-black);
|
||||
padding-left: 6pt;
|
||||
}
|
||||
.toc-page {
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ========== HEADINGS ========== */
|
||||
h1 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 10pt 0;
|
||||
padding-bottom: 4pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 24pt 0 6pt 0;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 18pt 0 4pt 0;
|
||||
color: var(--dark-warm);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.chapter-num {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* ========== PARAGRAPHS ========== */
|
||||
p {
|
||||
margin: 0 0 10pt 0;
|
||||
line-height: 1.55;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== LISTS ========== */
|
||||
ul, ol {
|
||||
margin: 6pt 0 10pt 0;
|
||||
padding-left: 20pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== QUOTE ========== */
|
||||
blockquote, .quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
margin: 12pt 0;
|
||||
padding: 4pt 0 4pt 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
blockquote .cite, .quote .cite {
|
||||
display: block;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
|
||||
/* ========== CODE ========== */
|
||||
code {
|
||||
font-family: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
pre {
|
||||
font-family: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
font-size: 8.5pt;
|
||||
line-height: 1.5;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-soft);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 10pt 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
pre code { background: transparent; padding: 0; font-size: inherit; }
|
||||
|
||||
/* ========== TABLE ========== */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--charcoal);
|
||||
padding: 6pt 8pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
background: var(--ivory);
|
||||
}
|
||||
table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 10pt 14pt;
|
||||
border-radius: 3pt;
|
||||
margin: 12pt 0;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.takeaway {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway-label {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 8pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure {
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
figure img {
|
||||
max-width: 100%;
|
||||
border-radius: 4pt;
|
||||
}
|
||||
figcaption {
|
||||
font-family: "Source Han Sans SC", sans-serif;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 6pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== EXECUTIVE SUMMARY ========== */
|
||||
.exec-summary {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 6pt;
|
||||
padding: 16pt 20pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.exec-summary h2 {
|
||||
margin-top: 0;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
/* ========== CHAPTER BREAK ========== */
|
||||
.chapter { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═════════════ COVER ═════════════ -->
|
||||
<section class="cover">
|
||||
<div>
|
||||
<div class="cover-eyebrow">{{EYEBROW · 如 "技术报告" / "年度总结" / "白皮书"}}</div>
|
||||
<div class="cover-title">{{文档主标题<br>可以两行}}</div>
|
||||
<div class="cover-sub">{{副标题,一句话说清这份文档是什么 / 为谁而写}}</div>
|
||||
<div class="cover-divider"></div>
|
||||
</div>
|
||||
<div class="cover-meta">
|
||||
<strong>{{作者 / 团队}}</strong><br>
|
||||
{{版本 V1.0}} · {{日期 2026.04}}<br>
|
||||
{{发布方 / 机构}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ 目录 ═════════════ -->
|
||||
<section class="toc">
|
||||
<h2>目录</h2>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">01</span>
|
||||
<span class="toc-title">执行摘要</span>
|
||||
<span class="toc-page">03</span>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">02</span>
|
||||
<span class="toc-title">背景与问题定义</span>
|
||||
<span class="toc-page">04</span>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">03</span>
|
||||
<span class="toc-title">方法与发现</span>
|
||||
<span class="toc-page">06</span>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">04</span>
|
||||
<span class="toc-title">结论与建议</span>
|
||||
<span class="toc-page">09</span>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">05</span>
|
||||
<span class="toc-title">附录</span>
|
||||
<span class="toc-page">11</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 01:执行摘要 ═════════════ -->
|
||||
<section>
|
||||
<div class="chapter-num">01 · Executive Summary</div>
|
||||
<h1>执行摘要</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{一段 2-3 句话的大论点开场。用 <span class="hl">关键词高亮</span> 抓住读者注意力。
|
||||
让读者读这段就能理解整份文档想表达什么。}}
|
||||
</p>
|
||||
|
||||
<h2>核心 Takeaways</h2>
|
||||
<ul>
|
||||
<li>{{Takeaway 1:一句话,可量化的结论}}</li>
|
||||
<li>{{Takeaway 2:有数据的洞察}}</li>
|
||||
<li>{{Takeaway 3:对未来的判断}}</li>
|
||||
</ul>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">本文档回答的问题</div>
|
||||
{{用疑问句列出 3 个本文档的核心问题。让读者立刻 get 到是否需要读完。}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 02:背景 ═════════════ -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">02 · Background</div>
|
||||
<h1>背景与问题定义</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{章节导语:这一章要解决什么问题,为什么重要。1-2 句。}}
|
||||
</p>
|
||||
|
||||
<h2>当前现状</h2>
|
||||
<p>{{3-5 行段落,铺陈当前状况。用 <span class="hl">具体数据</span> 而不是形容词。}}</p>
|
||||
|
||||
<h2>核心问题</h2>
|
||||
<p>{{陈述具体的核心问题。可以用 callout 突出关键观察:}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{一段重要引用或核心观察。和正文语气略有不同,给读者呼吸节奏。}}
|
||||
</div>
|
||||
|
||||
<h2>衡量标准</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>维度</th>
|
||||
<th>当前水平</th>
|
||||
<th>目标水平</th>
|
||||
<th>差距</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{维度 1}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{差距}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{维度 2}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{差距}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 03:方法 ═════════════ -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">03 · Methodology</div>
|
||||
<h1>方法与发现</h1>
|
||||
|
||||
<p class="lead">{{章节导语}}</p>
|
||||
|
||||
<h2>研究方法</h2>
|
||||
<p>{{描述方法论。可以用代码 / 公式示例:}}</p>
|
||||
|
||||
<pre><code>示例代码块
|
||||
def analyze(data):
|
||||
return transform(data)</code></pre>
|
||||
|
||||
<h2>关键发现</h2>
|
||||
|
||||
<h3>发现 1:{{标题}}</h3>
|
||||
<p>{{一段论述。包含数据:<span class="hl">具体数字 / 具体比例</span>。}}</p>
|
||||
|
||||
<h3>发现 2:{{标题}}</h3>
|
||||
<p>{{一段论述。}}</p>
|
||||
|
||||
<blockquote>
|
||||
{{一段引用,可以是用户访谈、专家观点、文献引用}}
|
||||
<span class="cite">— {{来源 / 人物}},{{日期}}</span>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 04:结论 ═════════════ -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">04 · Conclusions</div>
|
||||
<h1>结论与建议</h1>
|
||||
|
||||
<p class="lead">{{章节导语:一句话总结结论,下面展开建议。}}</p>
|
||||
|
||||
<h2>核心结论</h2>
|
||||
<ol>
|
||||
<li>{{结论 1}}</li>
|
||||
<li>{{结论 2}}</li>
|
||||
<li>{{结论 3}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>下一步建议</h2>
|
||||
<p>{{基于结论的具体可执行建议。}}</p>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Call to Action</div>
|
||||
{{如果读者要做一件事,是什么?具体到可以周一早上就开始行动。}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 05:附录 ═════════════ -->
|
||||
<section class="chapter">
|
||||
<div class="chapter-num">05 · Appendix</div>
|
||||
<h1>附录</h1>
|
||||
|
||||
<h2>A. 参考资料</h2>
|
||||
<ul>
|
||||
<li>{{参考文献 1}}</li>
|
||||
<li>{{参考文献 2}}</li>
|
||||
</ul>
|
||||
|
||||
<h2>B. 术语表</h2>
|
||||
<p><strong>{{术语}}</strong>:{{定义}}</p>
|
||||
<p><strong>{{术语}}</strong>:{{定义}}</p>
|
||||
|
||||
<h2>C. 致谢</h2>
|
||||
<p>{{致谢段落}}</p>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,344 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
ONE-PAGER · English · parchment design system
|
||||
Single A4 proposal / report / executive summary
|
||||
Build: python3 scripts/build.py one-pager-en
|
||||
Target: exactly 1 page
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{DOC_TITLE}}</title>
|
||||
<style>
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-500.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.48;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
|
||||
.header {
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding-bottom: 8pt;
|
||||
margin-bottom: 14pt;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* METRICS — horizontal baseline, no containers (matches resume aesthetic). */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 16pt;
|
||||
margin-bottom: 18pt;
|
||||
padding: 4pt 0 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
section { margin-bottom: 18pt; break-inside: avoid; }
|
||||
section:last-of-type { margin-bottom: 0; }
|
||||
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 13.5pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
padding-bottom: 3pt;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
h2 .sub {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
font-weight: 400;
|
||||
margin-left: 6pt;
|
||||
}
|
||||
|
||||
p { margin-bottom: 6pt; line-height: 1.48; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
.lead {
|
||||
font-size: 12.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18pt;
|
||||
}
|
||||
|
||||
ul.dash {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.dash li {
|
||||
position: relative;
|
||||
padding-left: 12pt;
|
||||
margin-bottom: 4pt;
|
||||
line-height: 1.48;
|
||||
}
|
||||
ul.dash li::before {
|
||||
content: "-";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
gap: 12pt;
|
||||
margin-top: 6pt;
|
||||
}
|
||||
.tl-step { flex: 1; break-inside: avoid; }
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-body {
|
||||
font-size: 8.8pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 7.5pt;
|
||||
font-weight: 600;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* CALLOUT — transparent, single brand bar. No filled container. */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 1.8pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
margin: 10pt 0;
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout .hl, .callout .em-brand { color: var(--brand); }
|
||||
|
||||
.footer {
|
||||
margin-top: 18pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 8pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">{{EYEBROW — e.g. Proposal / Report / Exec Summary}}</div>
|
||||
<h1>{{Document headline — verb-led, fits in two lines, bookish.}}</h1>
|
||||
<div class="subtitle">{{One-line subtitle or the single sharpest claim.}}</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
{{AUTHOR}}<br>
|
||||
{{YYYY.MM.DD}}<br>
|
||||
{{VERSION / STATUS}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">{{LABEL}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">{{LABEL}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">{{LABEL}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">{{LABEL}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead">
|
||||
{{~30-40 words. The one paragraph that sets the whole document's tone. Use <span class="hl">brand-color emphasis</span> on the sharpest claim or number. Everything below is in service of this.}}
|
||||
</p>
|
||||
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>{{Section one}}</h2>
|
||||
<p>{{One or two sentences expanding the claim.}}</p>
|
||||
<ul class="dash">
|
||||
<li>{{Short bullet: a data point, observation, or judgment.}}</li>
|
||||
<li>{{Short bullet with <span class="hl">key figure</span>.}}</li>
|
||||
<li>{{Short bullet.}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{{Section two}}</h2>
|
||||
<p>{{One or two sentences.}}</p>
|
||||
<ul class="dash">
|
||||
<li>{{Short bullet.}}</li>
|
||||
<li>{{Short bullet.}}</li>
|
||||
<li>{{Short bullet.}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Roadmap<span class="sub">three-step arc for proposals</span></h2>
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Phase 1</div>
|
||||
<div class="tl-head">{{STAGE_TITLE}}</div>
|
||||
<div class="tl-body">{{One-line explanation.}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Phase 2</div>
|
||||
<div class="tl-head">{{STAGE_TITLE}}</div>
|
||||
<div class="tl-body">{{One-line explanation.}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">Phase 3</div>
|
||||
<div class="tl-head">{{STAGE_TITLE}}</div>
|
||||
<div class="tl-body">{{One-line explanation.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="callout">
|
||||
{{Key quote / critical note / the single takeaway that must not be missed.}}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>{{CONFIDENTIALITY — internal / public / draft}}</span>
|
||||
<span>{{PAGE / CONTACT}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,359 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
ONE-PAGER TEMPLATE · parchment design system
|
||||
单页 A4 方案/报告/执行摘要
|
||||
改完跑:python3 -c "from weasyprint import HTML; HTML('one-pager.html').write_pdf('out.pdf')"
|
||||
目标:页数必须 == 1
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{文档标题}}</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
}
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.header {
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding-bottom: 8pt;
|
||||
margin-bottom: 14pt;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block {
|
||||
flex: 1;
|
||||
}
|
||||
.eyebrow {
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
h1 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.45;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
/* 横向 baseline 布局 —— 和 resume.html 一致的 kami native 姿态。
|
||||
背景透明、无边框,用 typography 本身承担层次,不加容器。 */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 18pt;
|
||||
padding: 4pt 0 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 20pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.2pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
section { margin-bottom: 18pt; break-inside: avoid; }
|
||||
section:last-of-type { margin-bottom: 0; }
|
||||
|
||||
h2 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
padding-bottom: 3pt;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
h2 .sub {
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
font-weight: 400;
|
||||
margin-left: 6pt;
|
||||
}
|
||||
|
||||
p { margin-bottom: 6pt; line-height: 1.45; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== 2-column grid ========== */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18pt;
|
||||
}
|
||||
|
||||
/* ========== 短横线 list(代替默认 bullet) ========== */
|
||||
ul.dash {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.dash li {
|
||||
position: relative;
|
||||
padding-left: 12pt;
|
||||
margin-bottom: 4pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
ul.dash li::before {
|
||||
content: "-";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* ========== Timeline (横向进度点) ========== */
|
||||
.timeline {
|
||||
display: flex;
|
||||
gap: 12pt;
|
||||
margin-top: 6pt;
|
||||
}
|
||||
.tl-step {
|
||||
flex: 1;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-body {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ========== Tag ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg); /* 实色!不要 rgba */
|
||||
color: var(--brand);
|
||||
font-size: 7.5pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.1pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* ========== Callout / 高亮块 ========== */
|
||||
/* 参考 resume.html .team-culture:透明底 + 单左侧 brand 竖条。
|
||||
不做容器也不做圆角,气质是"一条旁注"而不是"一个卡片"。 */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 1.8pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
margin: 10pt 0;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout .hl, .callout .em-brand { color: var(--brand); }
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.footer {
|
||||
margin-top: 18pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 8pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== HEADER ========== -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">{{EYEBROW · 如 "产品方案" / "项目提案" / "执行摘要"}}</div>
|
||||
<h1>{{文档主标题(serif,2 行内,动词+名词结构最好)}}</h1>
|
||||
<div class="subtitle">{{一行副标题 / 一句核心论点}}</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
{{作者名}}<br>
|
||||
{{日期 YYYY.MM.DD}}<br>
|
||||
{{版本号 / 状态}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== METRICS(可选,3-4 卡最佳) ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">{{标签描述}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">{{标签描述}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">{{标签描述}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">{{标签描述}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== LEAD(导语,一段) ========== -->
|
||||
<p class="lead">
|
||||
{{一段 40-60 字的核心论点导语。用「<span class="hl">关键词</span>」高亮重点。这段定调整个文档。}}
|
||||
</p>
|
||||
|
||||
<!-- ========== 核心内容(两栏) ========== -->
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>{{section 标题 1}}</h2>
|
||||
<p>{{这里写 1-2 句段落,展开论点。}}</p>
|
||||
<ul class="dash">
|
||||
<li>{{短 bullet:数据/观察/判断}}</li>
|
||||
<li>{{短 bullet:带 <span class="hl">关键数字</span> 的论据}}</li>
|
||||
<li>{{短 bullet}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>{{section 标题 2}}</h2>
|
||||
<p>{{1-2 句段落。}}</p>
|
||||
<ul class="dash">
|
||||
<li>{{短 bullet}}</li>
|
||||
<li>{{短 bullet}}</li>
|
||||
<li>{{短 bullet}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ========== Timeline / Roadmap(可选) ========== -->
|
||||
<section>
|
||||
<h2>时间线 / 路线图<span class="sub">(适用于方案类文档)</span></h2>
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">阶段 1</div>
|
||||
<div class="tl-head">{{阶段标题}}</div>
|
||||
<div class="tl-body">{{一句解释}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">阶段 2</div>
|
||||
<div class="tl-head">{{阶段标题}}</div>
|
||||
<div class="tl-body">{{一句解释}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">阶段 3</div>
|
||||
<div class="tl-head">{{阶段标题}}</div>
|
||||
<div class="tl-body">{{一句解释}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== CALLOUT(可选)========== -->
|
||||
<div class="callout">
|
||||
{{关键引用 / 重要提示 / 核心 takeaway。比正文更需要引起注意的内容放这里。}}
|
||||
</div>
|
||||
|
||||
<!-- ========== FOOTER ========== -->
|
||||
<div class="footer">
|
||||
<span>{{机密级别 / 内部 / 公开 等}}</span>
|
||||
<span>{{页码 / 联系方式}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,595 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
PORTFOLIO · English · parchment design system
|
||||
Multi-page A4 portfolio / case studies
|
||||
Structure: cover -> About -> project pages -> Contact
|
||||
Each project 1-2 pages, visuals over words
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{NAME}} · Portfolio</title>
|
||||
<style>
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-500.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 15mm 12mm 15mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
@page:first { margin: 0; }
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-deep:#a64f33;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.48;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
|
||||
/* COVER */
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 297mm;
|
||||
padding: 25mm 20mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-top {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
.cover-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14pt;
|
||||
}
|
||||
.cover-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 64pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.02;
|
||||
color: var(--near-black);
|
||||
letter-spacing: -1pt;
|
||||
}
|
||||
.cover-tagline {
|
||||
font-family: var(--serif);
|
||||
font-size: 18pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
max-width: 80%;
|
||||
}
|
||||
.cover-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 18pt 0 4pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ABOUT */
|
||||
section.about {
|
||||
break-after: page;
|
||||
padding: 10mm 0;
|
||||
}
|
||||
.about-eyebrow {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.about-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 34pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.5pt;
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.about-lead {
|
||||
font-family: var(--serif);
|
||||
font-size: 15pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 24pt;
|
||||
max-width: 90%;
|
||||
}
|
||||
.about-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24pt;
|
||||
}
|
||||
.about-body p { line-height: 1.5; margin-bottom: 10pt; }
|
||||
.about-body h3 {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* PROJECT PAGE */
|
||||
.project {
|
||||
break-before: page;
|
||||
break-after: page;
|
||||
padding: 8mm 0;
|
||||
}
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.project-num {
|
||||
font-family: var(--serif);
|
||||
font-size: 48pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
margin-right: 18pt;
|
||||
letter-spacing: -1pt;
|
||||
}
|
||||
.project-title-block { flex: 1; padding-top: 6pt; }
|
||||
.project-type {
|
||||
font-family: var(--sans);
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.project-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.project-subtitle {
|
||||
font-size: 11pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.project-date {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 80pt;
|
||||
padding-top: 10pt;
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.project-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.tag {
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 8pt;
|
||||
font-weight: 600;
|
||||
padding: 2pt 8pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Hero image */
|
||||
.project-hero {
|
||||
width: 100%;
|
||||
height: 110mm;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-soft);
|
||||
border-radius: 6pt;
|
||||
margin-bottom: 14pt;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--stone);
|
||||
font-size: 10pt;
|
||||
}
|
||||
.project-hero img { width: 100%; height: 100%; object-fit: cover; }
|
||||
|
||||
/* 3-column body */
|
||||
.project-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
.project-block h3 {
|
||||
font-family: var(--serif);
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 4pt;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
.project-block p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.project-block .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* Results */
|
||||
.project-results {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-top: 14pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
}
|
||||
.result-item { flex: 1; }
|
||||
.result-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 20pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.result-label {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
margin-top: 2pt;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 2-image variant */
|
||||
.project-visuals-2col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.project-visuals-2col .project-hero {
|
||||
height: 80mm;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* SELECTED WORKS */
|
||||
section.more-works {
|
||||
break-before: page;
|
||||
padding-top: 10mm;
|
||||
}
|
||||
.more-works-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 16pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.work-item {
|
||||
display: grid;
|
||||
grid-template-columns: 40pt 1fr 80pt;
|
||||
gap: 12pt;
|
||||
padding: 8pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.work-year {
|
||||
font-family: var(--serif);
|
||||
color: var(--brand);
|
||||
font-size: 11pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.work-title-block .title {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.work-title-block .desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
margin-top: 1pt;
|
||||
}
|
||||
.work-link {
|
||||
text-align: right;
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* CONTACT */
|
||||
section.contact {
|
||||
break-before: page;
|
||||
padding: 20mm 0;
|
||||
min-height: 230mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.contact-eyebrow {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.contact-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 52pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -1pt;
|
||||
margin-bottom: 20pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.contact-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin-bottom: 24pt;
|
||||
}
|
||||
.contact-list {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.contact-list strong {
|
||||
font-family: var(--sans);
|
||||
color: var(--stone);
|
||||
font-weight: 500;
|
||||
font-size: 10pt;
|
||||
letter-spacing: 1pt;
|
||||
margin-right: 12pt;
|
||||
display: inline-block;
|
||||
min-width: 80pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.contact-list a {
|
||||
color: var(--brand);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- COVER -->
|
||||
<div class="cover">
|
||||
<div class="cover-top">{{YEAR_RANGE — e.g. Selected Works 2023–2026}}</div>
|
||||
|
||||
<div class="cover-center">
|
||||
<div class="cover-name">{{NAME}}<br>Portfolio</div>
|
||||
<div class="cover-tagline">{{One-line self-description or portfolio theme.}}</div>
|
||||
<div class="cover-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="cover-meta">
|
||||
<div>
|
||||
<strong>{{DISCIPLINE / ROLE}}</strong><br>
|
||||
{{LOCATION}}
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
{{EMAIL}}<br>
|
||||
{{WEBSITE / SOCIAL}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ABOUT -->
|
||||
<section class="about">
|
||||
<div class="about-eyebrow">About</div>
|
||||
<div class="about-title">{{Single-line positioning headline.}}</div>
|
||||
<div class="about-lead">
|
||||
{{Two or three lines of introduction, serif italic. Not sales-y. Describe in your own words what you care about and what you do well.}}
|
||||
</div>
|
||||
<div class="about-body">
|
||||
<div>
|
||||
<h3>Background</h3>
|
||||
<p>{{A paragraph on your past experience.}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Focus</h3>
|
||||
<p>{{A paragraph on your current focus or methodology.}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROJECT 01 -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">01</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">{{PROJECT_TYPE — e.g. Product Design / Open Source}}</div>
|
||||
<div class="project-title">{{PROJECT_NAME}}</div>
|
||||
<div class="project-subtitle">{{One-line description of what this project did.}}</div>
|
||||
</div>
|
||||
<div class="project-date">{{DATE — e.g. 2025.04 — 2026.02}}</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">{{Tag 1}}</span>
|
||||
<span class="tag">{{Tag 2}}</span>
|
||||
<span class="tag">{{Tag 3}}</span>
|
||||
</div>
|
||||
|
||||
<div class="project-hero">
|
||||
<!-- Replace with: <img src="project-01-hero.png" alt=""> -->
|
||||
[hero image placeholder — replace with <img>]
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Context</h3>
|
||||
<p>{{Why this project? What problem does it solve? Who is the user?}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Approach</h3>
|
||||
<p>{{How it was done — key decisions, design rationale, technical approach.}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Outcome</h3>
|
||||
<p>{{Results — <span class="hl">figures</span>, feedback, impact.}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-results">
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{NUMBER}}</div>
|
||||
<div class="result-label">{{LABEL}}</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{NUMBER}}</div>
|
||||
<div class="result-label">{{LABEL}}</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{NUMBER}}</div>
|
||||
<div class="result-label">{{LABEL}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROJECT 02 (2-image variant) -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">02</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">{{PROJECT_TYPE}}</div>
|
||||
<div class="project-title">{{PROJECT_NAME}}</div>
|
||||
<div class="project-subtitle">{{One-line description.}}</div>
|
||||
</div>
|
||||
<div class="project-date">{{DATE}}</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">{{Tag}}</span>
|
||||
<span class="tag">{{Tag}}</span>
|
||||
</div>
|
||||
|
||||
<div class="project-visuals-2col">
|
||||
<div class="project-hero">[left image]</div>
|
||||
<div class="project-hero">[right image]</div>
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Context</h3>
|
||||
<p>{{Context.}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Approach</h3>
|
||||
<p>{{Approach.}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Outcome</h3>
|
||||
<p>{{Outcome.}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MORE WORKS -->
|
||||
<section class="more-works">
|
||||
<div class="more-works-title">Selected Works</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2025</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{WORK_TITLE}}</div>
|
||||
<div class="desc">{{One-line description.}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{LINK / STATUS}}</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2024</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{WORK_TITLE}}</div>
|
||||
<div class="desc">{{One-line description.}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{LINK / STATUS}}</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2023</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{WORK_TITLE}}</div>
|
||||
<div class="desc">{{One-line description.}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{LINK / STATUS}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CONTACT -->
|
||||
<section class="contact">
|
||||
<div class="contact-eyebrow">Let's Talk</div>
|
||||
<div class="contact-title">{{One-line opening — e.g. "Open to new collaborations."}}</div>
|
||||
<div class="contact-line"></div>
|
||||
<div class="contact-list">
|
||||
<div><strong>Email</strong><a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
<div><strong>Phone</strong>{{PHONE}}</div>
|
||||
<div><strong>Website</strong><a href="{{URL}}">{{URL}}</a></div>
|
||||
<div><strong>{{PLATFORM}}</strong><a href="{{URL}}">@{{HANDLE}}</a></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,597 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
PORTFOLIO TEMPLATE · parchment design system
|
||||
多页 A4 作品集 / 案例集 / 项目集
|
||||
结构:封面 -> About -> 若干项目页 -> Contact
|
||||
每个项目 1-2 页,视觉重于文字
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{名字}} · 作品集</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 15mm 12mm 15mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
@page:first {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-deep:#a64f33;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#efede4;
|
||||
--tag-bg: #E4ECF5;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
}
|
||||
|
||||
/* ════════════ COVER ════════════ */
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 297mm;
|
||||
padding: 25mm 20mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-top {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.cover-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14pt;
|
||||
}
|
||||
.cover-name {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 58pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
color: var(--near-black);
|
||||
letter-spacing: 2pt;
|
||||
}
|
||||
.cover-tagline {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 18pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
max-width: 80%;
|
||||
}
|
||||
.cover-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 18pt 0 4pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ════════════ ABOUT ════════════ */
|
||||
section.about {
|
||||
break-after: page;
|
||||
padding: 10mm 0;
|
||||
}
|
||||
.about-eyebrow {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.about-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 32pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.about-lead {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 15pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 24pt;
|
||||
max-width: 90%;
|
||||
}
|
||||
.about-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24pt;
|
||||
}
|
||||
.about-body p {
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10pt;
|
||||
}
|
||||
.about-body h3 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* ════════════ PROJECT PAGE ════════════ */
|
||||
.project {
|
||||
break-before: page;
|
||||
break-after: page;
|
||||
padding: 8mm 0;
|
||||
}
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.project-num {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 42pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
margin-right: 18pt;
|
||||
}
|
||||
.project-title-block {
|
||||
flex: 1;
|
||||
padding-top: 6pt;
|
||||
}
|
||||
.project-type {
|
||||
font-size: 8.5pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.project-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.project-subtitle {
|
||||
font-size: 11pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.project-date {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 80pt;
|
||||
padding-top: 10pt;
|
||||
}
|
||||
|
||||
/* Tags row */
|
||||
.project-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.tag {
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
padding: 2pt 7pt;
|
||||
border-radius: 3pt;
|
||||
}
|
||||
|
||||
/* Image / Visual */
|
||||
.project-hero {
|
||||
width: 100%;
|
||||
height: 110mm;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-soft);
|
||||
border-radius: 6pt;
|
||||
margin-bottom: 14pt;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--stone);
|
||||
font-size: 10pt;
|
||||
}
|
||||
.project-hero img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 3 段式 */
|
||||
.project-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
.project-block h3 {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 4pt;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
.project-block p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.45;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.project-block .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Results metrics row */
|
||||
.project-results {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-top: 14pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
}
|
||||
.result-item {
|
||||
flex: 1;
|
||||
}
|
||||
.result-value {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 18pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.result-label {
|
||||
font-size: 8.5pt;
|
||||
color: var(--olive);
|
||||
margin-top: 2pt;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ════════════ 2-IMAGE VARIANT ════════════ */
|
||||
.project-visuals-2col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.project-visuals-2col .project-hero {
|
||||
height: 80mm;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ════════════ SELECTED WORKS LIST ════════════ */
|
||||
section.more-works {
|
||||
break-before: page;
|
||||
padding-top: 10mm;
|
||||
}
|
||||
.more-works-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
}
|
||||
.work-item {
|
||||
display: grid;
|
||||
grid-template-columns: 40pt 1fr 80pt;
|
||||
gap: 12pt;
|
||||
padding: 8pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.work-year {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
color: var(--brand);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.work-title-block .title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.work-title-block .desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
margin-top: 1pt;
|
||||
}
|
||||
.work-link {
|
||||
text-align: right;
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
/* ════════════ CONTACT ════════════ */
|
||||
section.contact {
|
||||
break-before: page;
|
||||
padding: 20mm 0;
|
||||
min-height: 230mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.contact-eyebrow {
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.contact-title {
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC", Georgia, serif;
|
||||
font-size: 48pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 20pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.contact-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin-bottom: 24pt;
|
||||
}
|
||||
.contact-list {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.contact-list strong {
|
||||
color: var(--stone);
|
||||
font-weight: 500;
|
||||
font-size: 10pt;
|
||||
letter-spacing: 0.5pt;
|
||||
margin-right: 12pt;
|
||||
display: inline-block;
|
||||
min-width: 70pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.contact-list a {
|
||||
color: var(--brand);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ════════════ COVER ════════════ -->
|
||||
<div class="cover">
|
||||
<div class="cover-top">{{年份 或 领域标签 · 如 "Selected Works 2023–2026"}}</div>
|
||||
|
||||
<div class="cover-center">
|
||||
<div class="cover-name">{{名字<br>Portfolio}}</div>
|
||||
<div class="cover-tagline">{{一句自我描述 / 作品集主题}}</div>
|
||||
<div class="cover-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="cover-meta">
|
||||
<div>
|
||||
<strong>{{专业 / 角色}}</strong><br>
|
||||
{{所在地}}
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
{{EMAIL}}<br>
|
||||
{{网站 / 社交链接}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════ ABOUT ════════════ -->
|
||||
<section class="about">
|
||||
<div class="about-eyebrow">About</div>
|
||||
<div class="about-title">{{一句自我定位的 headline}}</div>
|
||||
<div class="about-lead">
|
||||
{{2-3 行的自我介绍引言。serif 字体,斜体感,不写太 sales。
|
||||
用自己的语言描述你关心什么、擅长什么。}}
|
||||
</div>
|
||||
<div class="about-body">
|
||||
<div>
|
||||
<h3>经历</h3>
|
||||
<p>{{一段关于你过往经历的概述。}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>关注</h3>
|
||||
<p>{{一段关于你当前的关注点 / 方法论。}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PROJECT 01 ════════════ -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">01</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">{{项目类型 · 如 "Product Design" / "Open Source"}}</div>
|
||||
<div class="project-title">{{项目名称}}</div>
|
||||
<div class="project-subtitle">{{一句话描述这个项目做了什么}}</div>
|
||||
</div>
|
||||
<div class="project-date">{{时间 · 如 "2025.04 — 2026.02"}}</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">{{标签 1}}</span>
|
||||
<span class="tag">{{标签 2}}</span>
|
||||
<span class="tag">{{标签 3}}</span>
|
||||
</div>
|
||||
|
||||
<!-- 主图 -->
|
||||
<div class="project-hero">
|
||||
<!-- 替换为:<img src="project-01-hero.png" alt=""> -->
|
||||
[项目主图占位 · 替换为 <img src="xxx.png">]
|
||||
</div>
|
||||
|
||||
<!-- 三段式正文 -->
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Context</h3>
|
||||
<p>{{为什么做这个项目?要解决什么问题?谁是用户?}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Approach</h3>
|
||||
<p>{{怎么做的?关键决策、设计考量、技术方案。}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Outcome</h3>
|
||||
<p>{{结果是什么?<span class="hl">数据</span>、反馈、影响。}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键数据(可选)-->
|
||||
<div class="project-results">
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{数字}}</div>
|
||||
<div class="result-label">{{标签}}</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{数字}}</div>
|
||||
<div class="result-label">{{标签}}</div>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<div class="result-value">{{数字}}</div>
|
||||
<div class="result-label">{{标签}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PROJECT 02 (双图版式) ════════════ -->
|
||||
<section class="project">
|
||||
<div class="project-header">
|
||||
<div class="project-num">02</div>
|
||||
<div class="project-title-block">
|
||||
<div class="project-type">{{项目类型}}</div>
|
||||
<div class="project-title">{{项目名称}}</div>
|
||||
<div class="project-subtitle">{{一句话描述}}</div>
|
||||
</div>
|
||||
<div class="project-date">{{时间}}</div>
|
||||
</div>
|
||||
|
||||
<div class="project-tags">
|
||||
<span class="tag">{{标签}}</span>
|
||||
<span class="tag">{{标签}}</span>
|
||||
</div>
|
||||
|
||||
<!-- 并排两图 -->
|
||||
<div class="project-visuals-2col">
|
||||
<div class="project-hero">[左图]</div>
|
||||
<div class="project-hero">[右图]</div>
|
||||
</div>
|
||||
|
||||
<div class="project-body">
|
||||
<div class="project-block">
|
||||
<h3>Context</h3>
|
||||
<p>{{背景}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Approach</h3>
|
||||
<p>{{方法}}</p>
|
||||
</div>
|
||||
<div class="project-block">
|
||||
<h3>Outcome</h3>
|
||||
<p>{{结果}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ MORE WORKS(可选) ════════════ -->
|
||||
<section class="more-works">
|
||||
<div class="more-works-title">更多作品</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2025</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{作品标题}}</div>
|
||||
<div class="desc">{{一句描述}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{链接 / 状态}}</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2024</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{作品标题}}</div>
|
||||
<div class="desc">{{一句描述}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{链接 / 状态}}</div>
|
||||
</div>
|
||||
|
||||
<div class="work-item">
|
||||
<span class="work-year">2023</span>
|
||||
<div class="work-title-block">
|
||||
<div class="title">{{作品标题}}</div>
|
||||
<div class="desc">{{一句描述}}</div>
|
||||
</div>
|
||||
<div class="work-link">{{链接 / 状态}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ CONTACT ════════════ -->
|
||||
<section class="contact">
|
||||
<div class="contact-eyebrow">Let's Talk</div>
|
||||
<div class="contact-title">{{欢迎联系的一句话 · 如 "期待新的合作机会"}}</div>
|
||||
<div class="contact-line"></div>
|
||||
<div class="contact-list">
|
||||
<div><strong>Email</strong><a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
<div><strong>Phone</strong>{{电话}}</div>
|
||||
<div><strong>Website</strong><a href="{{URL}}">{{URL}}</a></div>
|
||||
<div><strong>{{其他平台}}</strong><a href="{{URL}}">@{{ID}}</a></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,683 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{NAME}} · Resume</title>
|
||||
<style>
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Newsreader"; src: url("../fonts/Newsreader.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-500.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
@font-face { font-family: "Inter"; src: url("../fonts/Inter-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 11mm 13mm 11mm 13mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--border: #e8e6dc;
|
||||
--border-soft: #efede4;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--brand-tint-strong: #E4ECF5;
|
||||
|
||||
--serif: "Newsreader", "Source Serif 4", "Source Serif Pro", "Charter",
|
||||
Georgia, "Times New Roman", serif;
|
||||
--sans: "Inter", -apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 9.4pt;
|
||||
line-height: 1.42;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
.mono { font-family: var(--mono); }
|
||||
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 3mm;
|
||||
margin-bottom: 3mm;
|
||||
border-bottom: 0.5pt solid var(--brand);
|
||||
}
|
||||
.header > div:first-child { flex-shrink: 0; }
|
||||
.name {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.2pt;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 3mm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alias {
|
||||
font-size: 12.5pt;
|
||||
font-weight: 400;
|
||||
color: var(--stone);
|
||||
}
|
||||
.contact {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.contact .role {
|
||||
color: var(--brand);
|
||||
font-size: 10.5pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.contact .sep { color: var(--border); margin: 0 0.4em; }
|
||||
.contact a { color: var(--dark-warm); }
|
||||
.contact .loc { color: var(--olive); font-weight: 500; }
|
||||
|
||||
/* METRICS */
|
||||
.metrics { display: flex; gap: 7mm; margin-bottom: 3mm; }
|
||||
.metric {
|
||||
flex: 1;
|
||||
padding: 1mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-value .unit {
|
||||
font-size: 10.5pt;
|
||||
margin-left: 0.5mm;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
}
|
||||
.metric-label { font-size: 8.8pt; color: var(--olive); line-height: 1.2; }
|
||||
|
||||
/* SECTION TITLE */
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding: 0 0 1mm 0;
|
||||
margin: 5mm 0 2mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2.5mm;
|
||||
}
|
||||
.section-title .sub {
|
||||
margin-left: auto;
|
||||
font-family: var(--sans);
|
||||
font-weight: 400;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
section:first-of-type .section-title { margin-top: 0; }
|
||||
.page-break > .section-title { margin-top: 0; }
|
||||
|
||||
/* SUMMARY */
|
||||
.summary { font-size: 9.4pt; line-height: 1.48; color: var(--near-black); }
|
||||
.summary .hl { color: var(--brand); }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { display: flex; gap: 7mm; margin-bottom: 2.5mm; }
|
||||
.tl-step { flex: 1; padding: 0.8mm 0; break-inside: avoid; }
|
||||
.tl-top {
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
margin-bottom: 0.6mm;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--brand); line-height: 1;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.tl-body { font-size: 8.8pt; color: var(--olive); line-height: 1.45; }
|
||||
|
||||
/* PROJECTS */
|
||||
.project {
|
||||
padding: 1.6mm 0;
|
||||
border-top: 0.4pt solid var(--border-soft);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.project:first-of-type {
|
||||
border-top: 0.4pt solid var(--border);
|
||||
padding-top: 1.6mm;
|
||||
}
|
||||
.proj-head {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; margin-bottom: 0.9mm;
|
||||
}
|
||||
.proj-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500; color: var(--near-black);
|
||||
}
|
||||
.proj-kind { font-size: 11pt; color: var(--olive); }
|
||||
.proj-role {
|
||||
font-size: 8.4pt; color: var(--brand); font-weight: 600;
|
||||
margin-left: auto; background: #EEF2F7;
|
||||
padding: 0.4mm 1.6mm; border-radius: 2pt; letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.proj-lines { display: table; width: 100%; }
|
||||
.proj-row { display: table-row; }
|
||||
.proj-label {
|
||||
display: table-cell; width: 16mm;
|
||||
font-size: 8.5pt; color: var(--brand);
|
||||
font-weight: 600; letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
padding: 0.4mm 0 0.4mm 0; vertical-align: top;
|
||||
}
|
||||
.proj-text {
|
||||
display: table-cell; font-size: 9.2pt;
|
||||
color: var(--near-black); line-height: 1.45;
|
||||
padding: 0 0 0.4mm 1mm;
|
||||
}
|
||||
.proj-text .hl { color: var(--brand); }
|
||||
|
||||
/* PAGE 2 FORCE BREAK */
|
||||
.page-break { break-before: page; }
|
||||
|
||||
/* OPEN SOURCE */
|
||||
.os-intro {
|
||||
font-size: 9.4pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
margin: 1.6mm 0 2.4mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-intro .hl { color: var(--brand); }
|
||||
.os-intro .strong { color: var(--near-black); font-weight: 500; }
|
||||
|
||||
.os-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 9mm; row-gap: 1.2mm;
|
||||
}
|
||||
.os-item {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2.5mm; padding: 1.2mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt; font-weight: 500;
|
||||
flex-shrink: 0; min-width: 18mm;
|
||||
}
|
||||
.os-name a { color: var(--brand); }
|
||||
.os-desc { font-size: 9pt; color: var(--olive); line-height: 1.4; flex: 1; }
|
||||
.os-star {
|
||||
font-size: 8.8pt; color: var(--dark-warm);
|
||||
font-weight: 600; white-space: nowrap;
|
||||
flex-shrink: 0; min-width: 10mm; text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.os-star.big { color: var(--brand); font-size: 9.2pt; }
|
||||
|
||||
.os-highlight {
|
||||
margin-top: 2.5mm;
|
||||
padding: 1.8mm 3mm;
|
||||
background: var(--brand-tint);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-highlight .hl { color: var(--brand); }
|
||||
.os-highlight .tag {
|
||||
display: inline-block;
|
||||
background: var(--brand); color: var(--ivory);
|
||||
font-size: 7.6pt; font-weight: 600;
|
||||
padding: 0.2mm 1.6mm; border-radius: 2pt;
|
||||
margin-right: 1.5mm; letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
transform: translateY(-0.2mm);
|
||||
}
|
||||
|
||||
/* CONVICTIONS */
|
||||
.convictions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 3mm;
|
||||
margin-top: 1.4mm;
|
||||
}
|
||||
.conv-card { padding: 0 2mm 0 0; break-inside: avoid; }
|
||||
.conv-head {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black); margin-bottom: 1mm;
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.conv-head .year {
|
||||
color: var(--brand); font-weight: 600;
|
||||
font-size: 9pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.conv-body {
|
||||
font-size: 9pt; color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.conv-body .hl { color: var(--brand); }
|
||||
|
||||
/* IMPACT */
|
||||
.handle-strip {
|
||||
padding: 0; margin: 1.4mm 0 2mm 0;
|
||||
display: flex; align-items: baseline; gap: 3.5mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.handle-strip .handle {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500; color: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.handle-strip .follower { font-size: 8.8pt; color: var(--stone); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
||||
.handle-strip .desc { font-size: 8.8pt; color: var(--olive); line-height: 1.45; flex: 1; }
|
||||
|
||||
.impact-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 6mm; }
|
||||
.inf-block-title {
|
||||
margin-top: 2.5mm;
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 1.5mm; padding-bottom: 0.5mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.inf-block-title .small {
|
||||
font-family: var(--sans);
|
||||
font-size: 8.5pt; color: var(--stone);
|
||||
font-weight: 400;
|
||||
}
|
||||
.art-row {
|
||||
padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.talk-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 2mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.art-row:last-of-type, .talk-row:last-of-type { border-bottom: none; }
|
||||
.art-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 2mm;
|
||||
}
|
||||
.art-title, .talk-title {
|
||||
font-size: 9.2pt; color: var(--near-black);
|
||||
font-weight: 500; line-height: 1.4;
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
}
|
||||
.art-stats, .talk-venue {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
line-height: 1.4; margin-top: 0.6mm;
|
||||
}
|
||||
.art-stats .em-brand { color: var(--brand); }
|
||||
.art-date, .talk-date {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* SKILLS */
|
||||
.skill-row {
|
||||
display: flex; gap: 3mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.skill-row:last-of-type { border-bottom: none; }
|
||||
.skill-label {
|
||||
font-size: 8.6pt; font-weight: 600;
|
||||
color: var(--brand); letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
width: 32mm; flex-shrink: 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.skill-body { font-size: 9.2pt; color: var(--near-black); line-height: 1.45; flex: 1; }
|
||||
.skill-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* EDUCATION */
|
||||
.edu-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: baseline; padding: 1.8mm 0;
|
||||
}
|
||||
.edu-row .school {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500; font-size: 11pt;
|
||||
}
|
||||
.edu-row .major { color: var(--olive); font-size: 9.4pt; }
|
||||
.edu-row .date { color: var(--stone); font-variant-numeric: tabular-nums; font-size: 9pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- =====================================================================
|
||||
Usage:
|
||||
- Replace all {{VAR}} placeholders with real content
|
||||
- Drop sections that don't apply (e.g. no open source -> remove that block)
|
||||
- Each project strictly three-part: ROLE / ACTIONS / IMPACT
|
||||
- Must verify: 2 pages exactly (strict)
|
||||
python3 scripts/build.py resume-en
|
||||
- Content strategy: references/writing.en.md
|
||||
===================================================================== -->
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 1 ═══════════ -->
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="name serif">{{NAME}}<span class="alias">{{ALIAS_OR_PREFERRED_NAME}}</span></div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<span class="role">{{ROLE_TITLE — e.g. AI / Agent Engineer}}</span>
|
||||
<br>
|
||||
<a href="{{GITHUB_URL}}">github.com/{{GITHUB_ID}}</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="{{X_URL}}">x.com/{{X_ID}}</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="mailto:{{EMAIL}}">{{EMAIL}}</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="loc">{{CITY}}, {{COUNTRY_OR_REGION}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Four strongest numeric badges -->
|
||||
<div class="metrics">
|
||||
<div class="metric"><span class="metric-value serif">{{NUMBER}}<span class="unit">{{UNIT}}</span></span><span class="metric-label">{{LABEL}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{NUMBER}}<span class="unit">{{UNIT}}</span></span><span class="metric-label">{{LABEL}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{NUMBER}}<span class="unit">{{UNIT}}</span></span><span class="metric-label">{{LABEL}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{NUMBER}}<span class="unit">{{UNIT}}</span></span><span class="metric-label">{{LABEL}}</span></div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Summary</div>
|
||||
<div class="summary">
|
||||
{{~50 words. Structure: current role + seniority + tenure. Team composition (size, seniority mix, cross-functional collaborators). Long-term direction. Three to five core areas of depth.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Experience<span class="sub">{{START_DATE}} — Present · {{KEY_MILESTONE}}</span></div>
|
||||
|
||||
<!-- Timeline: three-step long-range arc -->
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{YEAR}}</div><div class="tl-head">{{STAGE_TITLE}}</div></div>
|
||||
<div class="tl-body">{{One sentence on what this stage meant.}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{YEAR}}</div><div class="tl-head">{{STAGE_TITLE}}</div></div>
|
||||
<div class="tl-body">{{One sentence on what this stage meant.}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{YEAR}}</div><div class="tl-head">{{STAGE_TITLE}}</div></div>
|
||||
<div class="tl-body">{{One sentence on what this stage meant.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project block — repeat 3–5 times -->
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{PROJECT_NAME}}</span>
|
||||
<span class="proj-kind">· {{PROJECT_TYPE}}</span>
|
||||
<span class="proj-role">{{ROLE — e.g. tech lead}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">{{~40 words: what the project is, why it existed, what your seat at the table was.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">{{~55 words: technical approach, key decisions, execution path.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">{{~65 words: numbers-first. Highlight <span class="hl">1-2 key figures</span>.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{PROJECT_NAME}}</span>
|
||||
<span class="proj-kind">· {{PROJECT_TYPE}}</span>
|
||||
<span class="proj-role">{{ROLE}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">{{~40 words.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">{{~55 words.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">{{~65 words, include <span class="hl">key figures</span>.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{PROJECT_NAME}}</span>
|
||||
<span class="proj-kind">· {{PROJECT_TYPE}}</span>
|
||||
<span class="proj-role">{{ROLE}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">{{Project context and your seat.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">{{Execution path.}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">{{Quantifiable outcome with <span class="hl">key figures</span>.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 2 ═══════════ -->
|
||||
|
||||
<section class="page-break">
|
||||
<div class="section-title">Open Source & Indie Work<span class="sub">{{TIME_RANGE}} · {{SUBTITLE}}</span></div>
|
||||
|
||||
<div class="os-intro">
|
||||
<span class="hl">{{One-line positioning statement.}}</span> {{Short sketch of your indie developer identity: design instinct, solo end-to-end delivery, cross-language range, user reception.}} Cumulative GitHub: <span class="strong">{{STARS_TOTAL}} stars · {{FORKS_TOTAL}} forks · {{FOLLOWERS_TOTAL}} followers</span>.
|
||||
</div>
|
||||
|
||||
<div class="os-grid">
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Stack + core positioning + platform.}}</span>
|
||||
<span class="os-star big">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Description.}}</span>
|
||||
<span class="os-star big">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Description.}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Description.}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Description.}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{PROJECT}}</a></span>
|
||||
<span class="os-desc">{{Description.}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="os-highlight">
|
||||
<span class="tag">{{HIGHLIGHT}}</span>{{A single anecdote that sets your work apart: the moment it went viral, the notable person who shared it, the community that formed around it.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Judgment & Conviction</div>
|
||||
<div class="convictions">
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">{{DATE}}</span>{{EVENT_TITLE}}</div>
|
||||
<div class="conv-body">
|
||||
{{Specific call you made or action you took, and the downstream evidence that the judgment was right.}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">{{DATE}}</span>{{EVENT_TITLE}}</div>
|
||||
<div class="conv-body">
|
||||
{{Specific call or action.}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">{{DATE}}</span>{{EVENT_TITLE}}</div>
|
||||
<div class="conv-body">
|
||||
{{Specific call or action.}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Public Impact</div>
|
||||
|
||||
<div class="handle-strip">
|
||||
<span class="handle serif">{{PLATFORM}} · <a href="{{URL}}" style="color: var(--brand); border-bottom: none;">@{{HANDLE}}</a></span>
|
||||
<span class="follower">{{FOLLOWERS}} followers</span>
|
||||
<span class="desc">{{Blog / newsletter / other content product.}}</span>
|
||||
</div>
|
||||
|
||||
<div class="impact-grid">
|
||||
<div>
|
||||
<div class="inf-block-title">Selected writing<span class="small">{{SUBTITLE}}</span></div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="{{URL}}" style="text-decoration: none;">{{ARTICLE_TITLE}}</a>
|
||||
<span class="art-date">{{DATE}}</span>
|
||||
</div>
|
||||
<div class="art-stats">{{Views / likes / impact metric.}}</div>
|
||||
</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="{{URL}}" style="text-decoration: none;">{{ARTICLE_TITLE}}</a>
|
||||
<span class="art-date">{{DATE}}</span>
|
||||
</div>
|
||||
<div class="art-stats">{{Views / likes.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="inf-block-title">Invited talks<span class="small">{{SUBTITLE}}</span></div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">{{TALK_TITLE}}</div>
|
||||
<div class="talk-venue">{{HOST / VENUE}}</div>
|
||||
</div>
|
||||
<div class="talk-date">{{DATE}}</div>
|
||||
</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">{{TALK_TITLE}}</div>
|
||||
<div class="talk-venue">{{HOST / VENUE}}</div>
|
||||
</div>
|
||||
<div class="talk-date">{{DATE}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Core Skills</div>
|
||||
<!-- Five fixed rows, each with at least one brand-color emphasis -->
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{SKILL_LABEL_1}}</div>
|
||||
<div class="skill-body">{{Description with at least <span class="em-brand">one emphasis</span>.}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{SKILL_LABEL_2}}</div>
|
||||
<div class="skill-body">{{Description with at least <span class="em-brand">one emphasis</span>.}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{SKILL_LABEL_3}}</div>
|
||||
<div class="skill-body">{{Description with at least <span class="em-brand">one emphasis</span>.}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{SKILL_LABEL_4}}</div>
|
||||
<div class="skill-body">{{Description with at least <span class="em-brand">one emphasis</span>.}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{SKILL_LABEL_5}}</div>
|
||||
<div class="skill-body">{{Description with at least <span class="em-brand">one emphasis</span>.}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="break-inside: avoid;">
|
||||
<div class="section-title">Education</div>
|
||||
<div class="edu-row">
|
||||
<div>
|
||||
<span class="school serif">{{SCHOOL}}</span>
|
||||
<span class="major"> · {{COLLEGE}} · {{MAJOR}} · {{One-line judgment-flavored note, e.g. "declined grad school, went straight to industry".}}</span>
|
||||
</div>
|
||||
<div class="date">{{DATE_RANGE}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,707 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{姓名}} · 简历</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 9mm 13mm 9mm 13mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--sand: #e8e6dc;
|
||||
--border: #e8e6dc;
|
||||
--border-soft: #efede4;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7; /* solid equivalent of rgba(1B365D, 0.08), avoids tag double-rectangle bug */
|
||||
--brand-tint-strong: #E4ECF5; /* solid equivalent of rgba(1B365D, 0.14) */
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: "TsangerJinKai02", "Noto Serif CJK SC", "DejaVu Serif", "PingFang SC", Georgia, serif;
|
||||
font-size: 9.2pt;
|
||||
line-height: 1.38;
|
||||
letter-spacing: 0.15pt;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
}
|
||||
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 3mm;
|
||||
margin-bottom: 3mm;
|
||||
border-bottom: 0.5pt solid #e3baa9;
|
||||
}
|
||||
.header > div:first-child { flex-shrink: 0; }
|
||||
.name {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 26.5pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1pt;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2.2mm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alias {
|
||||
font-size: 12.5pt;
|
||||
font-weight: 400;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
.alias::before {
|
||||
content: "";
|
||||
}
|
||||
.tagline { font-size: 9.5pt; color: var(--olive); line-height: 1.3; }
|
||||
.tagline .accent { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.contact {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
margin-bottom: -0.5mm;
|
||||
}
|
||||
.contact a { color: var(--dark-warm); }
|
||||
.contact .loc { color: var(--olive); font-weight: 500; }
|
||||
|
||||
/* METRICS */
|
||||
.metrics { display: flex; gap: 6mm; margin-bottom: 3mm; margin-left: 0; margin-right: 0; }
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 1mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1.8mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", Georgia, serif;
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-label { font-size: 8.8pt; color: var(--olive); line-height: 1.2; }
|
||||
|
||||
.metric-value .unit {
|
||||
font-size: 10.5pt;
|
||||
margin-left: 0.3mm;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* SECTION */
|
||||
.section-title {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 12.5pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
border-bottom: 1pt solid var(--brand);
|
||||
padding: 0 0 1mm 0;
|
||||
margin: 5mm 0 1.6mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2mm;
|
||||
}
|
||||
.section-title .sub {
|
||||
margin-left: auto;
|
||||
font-family: "TsangerJinKai02", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-weight: 400;
|
||||
font-size: 8.7pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
section:first-of-type .section-title { margin-top: 0; }
|
||||
.page-break > .section-title { margin-top: 0; }
|
||||
|
||||
/* Unify gap from section-title bottom border to first content's first line = 3mm
|
||||
(section-title margin-bottom 1.8mm + first-child top spacing 1.2mm) */
|
||||
.section-title + .skill-row { padding-top: 1.2mm; }
|
||||
.section-title + .edu-row { padding-top: 1.2mm; }
|
||||
|
||||
/* SUMMARY */
|
||||
.summary { font-size: 9.2pt; line-height: 1.40; color: var(--near-black); }
|
||||
.summary .em-brand { color: var(--brand); }
|
||||
.summary .hl { color: var(--brand); }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { display: flex; gap: 6mm; margin-bottom: 2mm; margin-left: 0; margin-right: 0; }
|
||||
.tl-step {
|
||||
flex: 1; background: transparent;
|
||||
padding: 0.8mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.tl-top {
|
||||
display: flex; align-items: baseline; gap: 1.5mm;
|
||||
margin-bottom: 0.5mm;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", Georgia, serif;
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--brand); line-height: 1;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.tl-body { font-size: 8.8pt; color: var(--olive); line-height: 1.40; letter-spacing: 0; }
|
||||
|
||||
/* PROJECTS */
|
||||
.project {
|
||||
padding: 1.2mm 0 1.2mm 0;
|
||||
border-top: 0.4pt solid var(--border-soft);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.project:first-of-type { border-top: 0.4pt solid var(--border); padding-top: 1.2mm; }
|
||||
.proj-head {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; margin-bottom: 0.75mm;
|
||||
}
|
||||
.proj-name {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 11pt; font-weight: 500; color: var(--near-black);
|
||||
}
|
||||
.proj-kind { font-size: 11pt; color: var(--olive); }
|
||||
.proj-role {
|
||||
font-size: 8.7pt; color: var(--brand); font-weight: 500;
|
||||
margin-left: auto; background: #EEF2F7;
|
||||
padding: 0.4mm 1.2mm; border-radius: 2pt; letter-spacing: 0.05pt;
|
||||
}
|
||||
.proj-lines { display: table; width: 100%; }
|
||||
.proj-row { display: table-row; }
|
||||
.proj-label {
|
||||
display: table-cell; width: 11mm;
|
||||
font-size: 9pt; color: var(--brand);
|
||||
font-weight: 600; letter-spacing: 0.2pt;
|
||||
padding: 0.2mm 0 0.4mm 0; vertical-align: top;
|
||||
}
|
||||
.proj-text {
|
||||
display: table-cell; font-size: 9pt;
|
||||
color: var(--near-black); line-height: 1.40; letter-spacing: 0;
|
||||
padding: 0 0 0.4mm 0.5mm;
|
||||
}
|
||||
.proj-text .hl { color: var(--brand); }
|
||||
|
||||
.team-culture {
|
||||
margin-top: 2mm; padding: 1.5mm 2.5mm;
|
||||
background: transparent;
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; color: var(--olive); line-height: 1.40;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.team-culture .hl { color: var(--brand); }
|
||||
|
||||
/* PAGE 2 FORCE BREAK */
|
||||
.page-break { break-before: page; }
|
||||
|
||||
/* OPEN SOURCE — NEW ENHANCED LAYOUT */
|
||||
.os-intro {
|
||||
font-size: 9.2pt; line-height: 1.40; letter-spacing: 0;
|
||||
color: var(--near-black);
|
||||
padding: 0; margin: 1.2mm 0 2mm 0;
|
||||
background: transparent;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-intro .hl { color: var(--brand); }
|
||||
.os-intro .strong { color: var(--near-black); }
|
||||
|
||||
.os-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 8mm; row-gap: 1mm;
|
||||
}
|
||||
.os-item {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; padding: 1mm 0 1mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-name {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", Georgia, serif;
|
||||
font-size: 10pt; font-weight: 500;
|
||||
flex-shrink: 0; min-width: 14mm;
|
||||
}
|
||||
.os-name a { color: var(--brand); }
|
||||
.os-desc { font-size: 9pt; color: var(--olive); line-height: 1.40; flex: 1; }
|
||||
.os-star {
|
||||
font-size: 8.7pt; color: var(--dark-warm);
|
||||
font-weight: 600; white-space: nowrap; flex-shrink: 0; min-width: 7.5mm; text-align: right;
|
||||
}
|
||||
.os-star.big { color: var(--brand); font-size: 9.2pt; }
|
||||
|
||||
.os-highlight {
|
||||
margin-top: 2.5mm;
|
||||
padding: 1.8mm 2.8mm;
|
||||
background: var(--brand-tint);
|
||||
border: 0.4pt solid #D6E1EE;
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; line-height: 1.40;
|
||||
color: var(--near-black);
|
||||
letter-spacing: 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-highlight .hl { color: var(--brand); }
|
||||
.os-highlight .tag {
|
||||
display: inline-block;
|
||||
background: var(--brand); color: var(--ivory);
|
||||
font-size: 7.8pt; font-weight: 600;
|
||||
padding: 0.2mm 1.5mm; border-radius: 2pt;
|
||||
margin-right: 1.2mm; letter-spacing: 0.2pt;
|
||||
transform: translateY(-0.2mm);
|
||||
}
|
||||
|
||||
/* AI CONVICTION — NEW SECTION */
|
||||
.ai-conviction {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 2.5mm;
|
||||
margin-top: 1.2mm;
|
||||
}
|
||||
.ai-card {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0 2mm 0 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.ai-card-head {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 9.8pt; font-weight: 500;
|
||||
color: var(--near-black); margin-bottom: 0.9mm;
|
||||
display: flex; align-items: baseline; gap: 1.5mm;
|
||||
}
|
||||
.ai-card-head .year {
|
||||
color: var(--brand); font-weight: 600;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.ai-card-body {
|
||||
font-size: 9pt; color: var(--olive);
|
||||
line-height: 1.40; letter-spacing: 0;
|
||||
}
|
||||
.ai-card-body .hl { color: var(--brand); }
|
||||
.ai-card-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* INFLUENCE */
|
||||
.twitter-strip {
|
||||
background: transparent;
|
||||
padding: 0; margin: 1.2mm 0 2mm 0;
|
||||
display: flex; align-items: baseline; gap: 3mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.twitter-strip .handle {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", Georgia, serif;
|
||||
font-size: 9.8pt; font-weight: 500; color: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.twitter-strip .follower { font-size: 8.7pt; color: var(--stone); flex-shrink: 0; }
|
||||
.twitter-strip .desc { font-size: 8.8pt; color: var(--olive); line-height: 1.40; letter-spacing: 0; flex: 1; }
|
||||
|
||||
.influence-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 5.5mm; }
|
||||
.inf-block-title {
|
||||
margin-top: 2.5mm;
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 9.8pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 1.5mm; padding-bottom: 0.5mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.inf-block-title .small {
|
||||
font-family: "TsangerJinKai02", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-size: 8.5pt; color: var(--stone);
|
||||
font-weight: 400; margin-left: 0; letter-spacing: 0.2pt;
|
||||
}
|
||||
.art-row {
|
||||
padding: 1.6mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.talk-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 2mm; padding: 1.6mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.art-row:last-of-type, .talk-row:last-of-type { border-bottom: none; }
|
||||
.art-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 2mm; padding-right: 3mm;
|
||||
}
|
||||
.art-title, .talk-title {
|
||||
font-size: 9pt; color: var(--near-black);
|
||||
font-weight: 500; line-height: 1.40;
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
}
|
||||
.art-stats, .talk-venue {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
line-height: 1.40; margin-top: 0.5mm; padding-left: 0; letter-spacing: 0;
|
||||
}
|
||||
.art-stats .em-brand {
|
||||
color: var(--brand);
|
||||
}
|
||||
.art-date {
|
||||
font-size: 8.8pt; color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 400;
|
||||
}
|
||||
.art-stats .lang-cn, .art-stats .lang-en {
|
||||
display: inline-block;
|
||||
font-size: 8.5pt; font-weight: 600;
|
||||
margin-right: 0.5mm;
|
||||
letter-spacing: 0.2pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
.talk-date {
|
||||
font-size: 8.8pt; color: var(--stone); font-weight: 500;
|
||||
white-space: nowrap; font-variant-numeric: tabular-nums;
|
||||
padding-top: 0.3mm;
|
||||
}
|
||||
|
||||
/* SKILLS */
|
||||
.skill-row {
|
||||
display: flex; gap: 2.5mm; padding: 1.7mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.skill-row:last-of-type { border-bottom: none; }
|
||||
.skill-label {
|
||||
font-size: 9pt; font-weight: 600;
|
||||
color: var(--brand); letter-spacing: 0.3pt;
|
||||
width: 22mm; flex-shrink: 0;
|
||||
line-height: 1.36;
|
||||
}
|
||||
.skill-body { font-size: 9pt; color: var(--near-black); line-height: 1.40; letter-spacing: 0; flex: 1; }
|
||||
.skill-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* EDUCATION */
|
||||
.edu-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: baseline; font-size: 9.5pt; padding: 1.7mm 0;
|
||||
}
|
||||
.edu-row .school {
|
||||
font-family: "TsangerJinKai02", "DejaVu Serif", "Noto Serif CJK SC", Georgia, serif;
|
||||
font-weight: 500; font-size: 10.7pt;
|
||||
}
|
||||
.edu-row .major { color: var(--olive); font-size: 9.5pt; }
|
||||
.edu-row .date { color: var(--stone); font-variant-numeric: tabular-nums; font-size: 9pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- =====================================================================
|
||||
使用说明:
|
||||
- 所有 {{变量}} 替换为实际内容
|
||||
- 删除不适用的 section(如无开源/AI判断/对外影响力)
|
||||
- 项目可加减,每个严格遵守 角色/动作/结果 三段式
|
||||
- 改完验证:必须 2 页
|
||||
python3 -c "from weasyprint import HTML; HTML('resume.html').write_pdf('out.pdf')"
|
||||
- 内容策略详见 references/writing.md
|
||||
===================================================================== -->
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 1 ═══════════ -->
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="name serif">{{姓名}}<span class="alias">{{别名/英文名}}</span></div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<span style="color: var(--brand); font-size: 10.5pt; font-weight: 500;">{{岗位定位,如"AI / Agent 工程"}}</span> · <a href="{{GITHUB_URL}}">GitHub @{{GITHUB_ID}}</a> · <a href="{{X_URL}}">X @{{X_ID}}</a> · <a href="tel:{{PHONE}}">{{PHONE}}</a> · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a> · {{年龄}} 岁在{{城市}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4 个最强的数字标签 -->
|
||||
<div class="metrics">
|
||||
<div class="metric"><span class="metric-value serif">{{数字}}<span class="unit">{{单位}}</span></span><span class="metric-label">{{标签}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{数字}}<span class="unit">{{单位}}</span></span><span class="metric-label">{{标签}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{数字}}<span class="unit">{{单位}}</span></span><span class="metric-label">{{标签}}</span></div>
|
||||
<div class="metric"><span class="metric-value serif">{{数字}}<span class="unit">{{单位}}</span></span><span class="metric-label">{{标签}}</span></div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="section-title">个人简介</div>
|
||||
<div class="summary">
|
||||
{{80 字以内。建议结构:现任职位 + 级别 + 时长。团队构成(人数、梯队、协作方)。长期演进方向。核心沉淀领域(4-6 个方向)。}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">工作经历<span class="sub">{{起始时间}} — 至今({{关键里程碑}})</span></div>
|
||||
|
||||
<!-- Timeline 3 步:长期演进信号 -->
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{年份}}</div><div class="tl-head">{{阶段标题}}</div></div>
|
||||
<div class="tl-body">{{一句解释这一步的意义}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{年份}}</div><div class="tl-head">{{阶段标题}}</div></div>
|
||||
<div class="tl-body">{{一句解释这一步的意义}}</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">{{年份}}</div><div class="tl-head">{{阶段标题}}</div></div>
|
||||
<div class="tl-body">{{一句解释这一步的意义}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目模板 - 重复此块 3-5 次 -->
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{项目名}}</span>
|
||||
<span class="proj-kind">· {{项目类型}}</span>
|
||||
<span class="proj-role">{{角色定位,如"方向主导"}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">角色</div>
|
||||
<div class="proj-text">{{~60 字:项目是什么 + 为什么做 + 你的位置}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">动作</div>
|
||||
<div class="proj-text">{{~80 字:技术方案 / 关键决策 / 执行路径}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">结果</div>
|
||||
<div class="proj-text">{{~100 字:数据为王。<span class="hl">关键数字</span> 高亮 1-2 处。}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{项目名}}</span>
|
||||
<span class="proj-kind">· {{项目类型}}</span>
|
||||
<span class="proj-role">{{角色定位}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">角色</div>
|
||||
<div class="proj-text">{{~60 字}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">动作</div>
|
||||
<div class="proj-text">{{~80 字}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">结果</div>
|
||||
<div class="proj-text">{{~100 字,含 <span class="hl">关键数字</span>}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">{{项目名}}</span>
|
||||
<span class="proj-kind">· {{项目类型}}</span>
|
||||
<span class="proj-role">{{角色}}</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">角色</div>
|
||||
<div class="proj-text">{{项目背景和定位}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">动作</div>
|
||||
<div class="proj-text">{{执行路径}}</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">结果</div>
|
||||
<div class="proj-text">{{可量化结果,含 <span class="hl">关键数字</span>}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 2 ═══════════ -->
|
||||
|
||||
<section class="page-break">
|
||||
<div class="section-title">开源项目 & 独立开发者<span class="sub">{{时间跨度}} · {{一句副标题}}</span></div>
|
||||
|
||||
<div class="os-intro">
|
||||
<span class="hl">{{一句自我定位}}</span>,{{简述开发者身份:设计审美 / 独立完成流程 / 跨语言实战 / 用户反馈}}。GitHub 累计 <span class="strong">{{STARS_TOTAL}} stars · {{FORKS_TOTAL}} forks · {{FOLLOWERS_TOTAL}} followers</span>。
|
||||
</div>
|
||||
|
||||
<div class="os-grid">
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{语言 + 核心定位 + 平台}}</span>
|
||||
<span class="os-star big">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{描述}}</span>
|
||||
<span class="os-star big">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{描述}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{描述}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{描述}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="{{URL}}">{{项目名}}</a></span>
|
||||
<span class="os-desc">{{描述}}</span>
|
||||
<span class="os-star">★ {{STARS}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="os-highlight">
|
||||
<span class="tag">{{亮点 TAG}}</span>{{这个项目的独特故事:开源时机 / 传播范围 / 知名人物推荐等}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">AI 判断与行动</div>
|
||||
<div class="ai-conviction">
|
||||
<div class="ai-card">
|
||||
<div class="ai-card-head"><span class="year">{{时间}}</span>{{事件标题}}</div>
|
||||
<div class="ai-card-body">
|
||||
{{具体做了什么判断或行动,为什么证明判断力}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-card">
|
||||
<div class="ai-card-head"><span class="year">{{时间}}</span>{{事件标题}}</div>
|
||||
<div class="ai-card-body">
|
||||
{{具体做了什么判断或行动}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-card">
|
||||
<div class="ai-card-head"><span class="year">{{时间}}</span>{{事件标题}}</div>
|
||||
<div class="ai-card-body">
|
||||
{{具体做了什么判断或行动}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">对外影响力</div>
|
||||
|
||||
<div class="twitter-strip">
|
||||
<span class="handle serif">{{平台}} · <a href="{{URL}}" style="color: var(--brand); border-bottom: none;">@{{HANDLE}}</a></span>
|
||||
<span class="follower">{{粉丝数}} 粉丝</span>
|
||||
<span class="desc">{{博客 / 周刊 / 其他内容产品简介}}</span>
|
||||
</div>
|
||||
|
||||
<div class="influence-grid">
|
||||
<div>
|
||||
<div class="inf-block-title">代表性技术长文<span class="small">{{副标题}}</span></div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="{{URL}}" style="text-decoration: none;">《{{文章标题}}》</a>
|
||||
<span class="art-date">{{日期}}</span>
|
||||
</div>
|
||||
<div class="art-stats">{{浏览量 / 赞数 / 影响力指标}}</div>
|
||||
</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="{{URL}}" style="text-decoration: none;">《{{文章标题}}》</a>
|
||||
<span class="art-date">{{日期}}</span>
|
||||
</div>
|
||||
<div class="art-stats">{{浏览量 / 赞数}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="inf-block-title">受邀演讲<span class="small">{{副标题}}</span></div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">《{{演讲标题}}》</div>
|
||||
<div class="talk-venue">{{主办方 / 地点}}</div>
|
||||
</div>
|
||||
<div class="talk-date">{{日期}}</div>
|
||||
</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">《{{演讲标题}}》</div>
|
||||
<div class="talk-venue">{{主办方 / 地点}}</div>
|
||||
</div>
|
||||
<div class="talk-date">{{日期}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">核心能力</div>
|
||||
<!-- 5 项固定结构,每项至少 1 处橙色高亮 -->
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{能力 1<br>标签}}</div>
|
||||
<div class="skill-body">{{描述。至少 <span class="em-brand">1 处强调</span>}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{能力 2<br>标签}}</div>
|
||||
<div class="skill-body">{{描述。至少 <span class="em-brand">1 处强调</span>}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{能力 3<br>标签}}</div>
|
||||
<div class="skill-body">{{描述。至少 <span class="em-brand">1 处强调</span>}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{能力 4<br>标签}}</div>
|
||||
<div class="skill-body">{{描述。至少 <span class="em-brand">1 处强调</span>}}</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">{{能力 5<br>标签}}</div>
|
||||
<div class="skill-body">{{描述。至少 <span class="em-brand">1 处强调</span>}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="break-inside: avoid;">
|
||||
<div class="section-title">教育背景</div>
|
||||
<div class="edu-row">
|
||||
<div>
|
||||
<span class="school serif">{{学校}}</span>
|
||||
<span class="major"> · {{学院}} · {{专业}} · {{一句判断性描述,如"放弃保研直接就业"}}</span>
|
||||
</div>
|
||||
<div class="date">{{起止时间}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
slides-en.py — parchment design system, English slide deck generator.
|
||||
|
||||
Usage:
|
||||
pip install python-pptx --break-system-packages
|
||||
python3 slides-en.py
|
||||
|
||||
Output:
|
||||
output.pptx (16:9, parchment aesthetic, Inter + Newsreader fonts)
|
||||
|
||||
This is a template. Fill in your content and run it directly.
|
||||
"""
|
||||
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Design system constants
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
IVORY = RGBColor(0xfa, 0xf9, 0xf5)
|
||||
BRAND = RGBColor(0x1B, 0x36, 0x5D)
|
||||
BRAND_DEEP = RGBColor(0x1B, 0x36, 0x5D)
|
||||
NEAR_BLACK = RGBColor(0x14, 0x14, 0x13)
|
||||
DARK_WARM = RGBColor(0x3d, 0x3d, 0x3a)
|
||||
CHARCOAL = RGBColor(0x4d, 0x4c, 0x48)
|
||||
OLIVE = RGBColor(0x5e, 0x5d, 0x59)
|
||||
STONE = RGBColor(0x87, 0x86, 0x7f)
|
||||
BORDER = RGBColor(0xe8, 0xe6, 0xdc)
|
||||
WHITE = RGBColor(0xff, 0xff, 0xff)
|
||||
|
||||
# English Silicon Valley stack. PowerPoint falls back silently if the
|
||||
# primary face is not installed on the viewing machine.
|
||||
SERIF = "Newsreader" # fallback: Charter -> Georgia
|
||||
SANS = "Inter" # fallback: Helvetica Neue -> Arial
|
||||
|
||||
SLIDE_W = Inches(13.33)
|
||||
SLIDE_H = Inches(7.5)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Helpers
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def blank_slide(prs, bg_color=PARCHMENT):
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
|
||||
0, 0, prs.slide_width, prs.slide_height)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = bg_color
|
||||
bg.line.fill.background()
|
||||
bg.shadow.inherit = False
|
||||
return slide
|
||||
|
||||
|
||||
def add_text(slide, text, left, top, width, height,
|
||||
font=SANS, size=18, bold=False, italic=False,
|
||||
color=NEAR_BLACK, align=PP_ALIGN.LEFT,
|
||||
vanchor=MSO_ANCHOR.TOP):
|
||||
tb = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.margin_left = tf.margin_right = 0
|
||||
tf.margin_top = tf.margin_bottom = 0
|
||||
tf.vertical_anchor = vanchor
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = align
|
||||
run = p.add_run()
|
||||
run.text = text
|
||||
run.font.name = font
|
||||
run.font.size = Pt(size)
|
||||
run.font.bold = bold
|
||||
run.font.italic = italic
|
||||
run.font.color.rgb = color
|
||||
return tb
|
||||
|
||||
|
||||
def add_line(slide, left, top, width, color=BRAND, weight_pt=1):
|
||||
line = slide.shapes.add_connector(1, left, top, left + width, top)
|
||||
line.line.color.rgb = color
|
||||
line.line.width = Pt(weight_pt)
|
||||
return line
|
||||
|
||||
|
||||
def add_card(slide, left, top, width, height,
|
||||
fill=IVORY, border=BORDER, border_weight=0.5):
|
||||
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
|
||||
left, top, width, height)
|
||||
card.fill.solid()
|
||||
card.fill.fore_color.rgb = fill
|
||||
card.line.color.rgb = border
|
||||
card.line.width = Pt(border_weight)
|
||||
card.shadow.inherit = False
|
||||
return card
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Slide templates
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def cover_slide(prs, title, subtitle, author, date):
|
||||
s = blank_slide(prs)
|
||||
add_text(s, title,
|
||||
Inches(1), Inches(2.5), Inches(11.33), Inches(1.5),
|
||||
font=SERIF, size=48, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_line(s, Inches(6.17), Inches(4.3), Inches(1), weight_pt=1.5)
|
||||
add_text(s, subtitle,
|
||||
Inches(1), Inches(4.6), Inches(11.33), Inches(0.8),
|
||||
font=SANS, size=18, italic=True, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_text(s, f"{author} · {date}",
|
||||
Inches(1), Inches(6.5), Inches(11.33), Inches(0.4),
|
||||
font=SANS, size=13, color=STONE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def toc_slide(prs, items):
|
||||
s = blank_slide(prs)
|
||||
add_text(s, "Contents",
|
||||
Inches(1.2), Inches(0.8), Inches(10), Inches(0.8),
|
||||
font=SERIF, size=34, color=NEAR_BLACK)
|
||||
add_line(s, Inches(1.2), Inches(1.8), Inches(11), weight_pt=1)
|
||||
|
||||
for i, item in enumerate(items):
|
||||
y = Inches(2.4 + i * 0.9)
|
||||
add_text(s, f"0{i+1}",
|
||||
Inches(1.2), y, Inches(1), Inches(0.6),
|
||||
font=SERIF, size=28, color=BRAND)
|
||||
add_text(s, item,
|
||||
Inches(2.4), y, Inches(9), Inches(0.6),
|
||||
font=SERIF, size=22, color=NEAR_BLACK,
|
||||
vanchor=MSO_ANCHOR.MIDDLE)
|
||||
return s
|
||||
|
||||
|
||||
def chapter_slide(prs, number, title):
|
||||
s = blank_slide(prs, bg_color=BRAND)
|
||||
add_text(s, f"0{number}",
|
||||
Inches(0.8), Inches(0.5), Inches(2), Inches(0.8),
|
||||
font=SERIF, size=28, color=WHITE)
|
||||
add_text(s, title,
|
||||
Inches(1), Inches(3), Inches(11.33), Inches(1.5),
|
||||
font=SERIF, size=60, color=WHITE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def content_slide(prs, eyebrow, title, body, page_num=None):
|
||||
s = blank_slide(prs)
|
||||
add_text(s, eyebrow.upper(),
|
||||
Inches(1.2), Inches(0.6), Inches(10), Inches(0.4),
|
||||
font=SANS, size=11, color=STONE)
|
||||
add_text(s, title,
|
||||
Inches(1.2), Inches(1.2), Inches(11.33), Inches(1.2),
|
||||
font=SERIF, size=34, color=NEAR_BLACK)
|
||||
add_line(s, Inches(1.2), Inches(2.55), Inches(1), weight_pt=1.5)
|
||||
add_text(s, body,
|
||||
Inches(1.2), Inches(3), Inches(11), Inches(3.5),
|
||||
font=SANS, size=18, color=DARK_WARM)
|
||||
if page_num is not None:
|
||||
add_text(s, f"— {page_num:02d}",
|
||||
Inches(11.5), Inches(6.9), Inches(1.5), Inches(0.3),
|
||||
font=SANS, size=11, color=STONE,
|
||||
align=PP_ALIGN.RIGHT)
|
||||
return s
|
||||
|
||||
|
||||
def metrics_slide(prs, title, metrics):
|
||||
"""metrics: [(value, label), ...]"""
|
||||
s = blank_slide(prs)
|
||||
add_text(s, title,
|
||||
Inches(1.2), Inches(0.8), Inches(11), Inches(1),
|
||||
font=SERIF, size=30, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_line(s, Inches(6.17), Inches(2), Inches(1))
|
||||
|
||||
n = len(metrics)
|
||||
card_w = Inches(2.8)
|
||||
gap = Inches(0.3)
|
||||
total_w = card_w * n + gap * (n - 1)
|
||||
start = (SLIDE_W - total_w) / 2
|
||||
|
||||
for i, (value, label) in enumerate(metrics):
|
||||
x = start + (card_w + gap) * i
|
||||
add_text(s, value,
|
||||
x, Inches(3), card_w, Inches(1.5),
|
||||
font=SERIF, size=56, color=BRAND,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_text(s, label,
|
||||
x, Inches(4.8), card_w, Inches(0.6),
|
||||
font=SANS, size=14, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def quote_slide(prs, quote, source):
|
||||
s = blank_slide(prs)
|
||||
add_text(s, f"\u201c{quote}\u201d",
|
||||
Inches(1.5), Inches(2.8), Inches(10.33), Inches(2.5),
|
||||
font=SERIF, size=30, italic=True, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER,
|
||||
vanchor=MSO_ANCHOR.MIDDLE)
|
||||
add_text(s, f"— {source}",
|
||||
Inches(1.5), Inches(5.2), Inches(10.33), Inches(0.4),
|
||||
font=SANS, size=14, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def ending_slide(prs, message, contact):
|
||||
s = blank_slide(prs)
|
||||
add_text(s, message,
|
||||
Inches(1), Inches(3), Inches(11.33), Inches(1.2),
|
||||
font=SERIF, size=44, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_line(s, Inches(6.17), Inches(4.5), Inches(1), weight_pt=1.5)
|
||||
add_text(s, contact,
|
||||
Inches(1), Inches(4.8), Inches(11.33), Inches(0.6),
|
||||
font=SANS, size=16, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Main — example deck, replace with your content
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def main():
|
||||
prs = Presentation()
|
||||
prs.slide_width = SLIDE_W
|
||||
prs.slide_height = SLIDE_H
|
||||
|
||||
cover_slide(prs,
|
||||
title="{{DOCUMENT_TITLE}}",
|
||||
subtitle="{{One-line description.}}",
|
||||
author="{{AUTHOR}}",
|
||||
date="2026.04")
|
||||
|
||||
toc_slide(prs, items=[
|
||||
"{{Chapter 1}}",
|
||||
"{{Chapter 2}}",
|
||||
"{{Chapter 3}}",
|
||||
"Q&A",
|
||||
])
|
||||
|
||||
chapter_slide(prs, 1, "{{Chapter Title}}")
|
||||
|
||||
content_slide(prs,
|
||||
eyebrow="{{Chapter · This page}}",
|
||||
title="{{Core claim as a sentence}}",
|
||||
body=("{{A short body paragraph, 18pt sans. Keep it under three lines. "
|
||||
"One slide, one core idea. The reader's attention is the scarce resource.}}"),
|
||||
page_num=4)
|
||||
|
||||
metrics_slide(prs,
|
||||
title="Key Results",
|
||||
metrics=[
|
||||
("+42%", "Conversion lift"),
|
||||
("3.8M", "Monthly actives"),
|
||||
("99.9%", "Availability SLA"),
|
||||
("5,000+", "QPS peak"),
|
||||
])
|
||||
|
||||
quote_slide(prs,
|
||||
quote="Good design is as little design as possible.",
|
||||
source="Dieter Rams")
|
||||
|
||||
ending_slide(prs,
|
||||
message="Thank you",
|
||||
contact="{{EMAIL}} · {{WEBSITE}}")
|
||||
|
||||
prs.save('output.pptx')
|
||||
print("✓ Saved output.pptx")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
gen_slides.py - parchment design system slide deck generator
|
||||
|
||||
用法:
|
||||
pip install python-pptx --break-system-packages
|
||||
python3 gen_slides.py
|
||||
|
||||
输出:
|
||||
output.pptx (16:9 宽屏, parchment 风格)
|
||||
|
||||
这是一个模板脚本 - 填充自己的内容后直接运行。
|
||||
"""
|
||||
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Design System Constants
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
# 色板
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
IVORY = RGBColor(0xfa, 0xf9, 0xf5)
|
||||
BRAND = RGBColor(0x1B, 0x36, 0x5D)
|
||||
BRAND_DEEP = RGBColor(0x1B, 0x36, 0x5D)
|
||||
NEAR_BLACK = RGBColor(0x14, 0x14, 0x13)
|
||||
DARK_WARM = RGBColor(0x3d, 0x3d, 0x3a)
|
||||
CHARCOAL = RGBColor(0x4d, 0x4c, 0x48)
|
||||
OLIVE = RGBColor(0x5e, 0x5d, 0x59)
|
||||
STONE = RGBColor(0x87, 0x86, 0x7f)
|
||||
BORDER = RGBColor(0xe8, 0xe6, 0xdc)
|
||||
WHITE = RGBColor(0xff, 0xff, 0xff)
|
||||
|
||||
# 字体(PPT 会按序 fallback)
|
||||
SERIF = "Source Han Serif SC" # 中文 serif
|
||||
SANS = "Source Han Sans SC" # 中文 sans
|
||||
|
||||
# 16:9 宽屏
|
||||
SLIDE_W = Inches(13.33)
|
||||
SLIDE_H = Inches(7.5)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Helpers
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def blank_slide(prs, bg_color=PARCHMENT):
|
||||
"""创建空白幻灯片,指定背景色"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6]) # 6 = Blank
|
||||
bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
|
||||
0, 0, prs.slide_width, prs.slide_height)
|
||||
bg.fill.solid()
|
||||
bg.fill.fore_color.rgb = bg_color
|
||||
bg.line.fill.background()
|
||||
bg.shadow.inherit = False
|
||||
return slide
|
||||
|
||||
|
||||
def add_text(slide, text, left, top, width, height,
|
||||
font=SANS, size=18, bold=False, italic=False,
|
||||
color=NEAR_BLACK, align=PP_ALIGN.LEFT,
|
||||
vanchor=MSO_ANCHOR.TOP):
|
||||
"""加一段文字"""
|
||||
tb = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = tb.text_frame
|
||||
tf.word_wrap = True
|
||||
tf.margin_left = tf.margin_right = 0
|
||||
tf.margin_top = tf.margin_bottom = 0
|
||||
tf.vertical_anchor = vanchor
|
||||
p = tf.paragraphs[0]
|
||||
p.alignment = align
|
||||
run = p.add_run()
|
||||
run.text = text
|
||||
run.font.name = font
|
||||
run.font.size = Pt(size)
|
||||
run.font.bold = bold
|
||||
run.font.italic = italic
|
||||
run.font.color.rgb = color
|
||||
return tb
|
||||
|
||||
|
||||
def add_line(slide, left, top, width, color=BRAND, weight_pt=1):
|
||||
"""加水平线"""
|
||||
line = slide.shapes.add_connector(1, left, top, left + width, top)
|
||||
line.line.color.rgb = color
|
||||
line.line.width = Pt(weight_pt)
|
||||
return line
|
||||
|
||||
|
||||
def add_card(slide, left, top, width, height,
|
||||
fill=IVORY, border=BORDER, border_weight=0.5):
|
||||
"""加卡片背景"""
|
||||
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
|
||||
left, top, width, height)
|
||||
card.fill.solid()
|
||||
card.fill.fore_color.rgb = fill
|
||||
card.line.color.rgb = border
|
||||
card.line.width = Pt(border_weight)
|
||||
card.shadow.inherit = False
|
||||
return card
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Slide Templates
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def cover_slide(prs, title, subtitle, author, date):
|
||||
"""封面:大标题 + 副标题 + 作者/日期"""
|
||||
s = blank_slide(prs)
|
||||
# 大标题(serif 44pt 居中)
|
||||
add_text(s, title,
|
||||
Inches(1), Inches(2.5), Inches(11.33), Inches(1.5),
|
||||
font=SERIF, size=44, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
# 品牌色短线
|
||||
add_line(s, Inches(6.17), Inches(4.2), Inches(1), weight_pt=1.5)
|
||||
# 副标题
|
||||
add_text(s, subtitle,
|
||||
Inches(1), Inches(4.5), Inches(11.33), Inches(0.8),
|
||||
font=SANS, size=18, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
# 作者 + 日期
|
||||
add_text(s, f"{author} · {date}",
|
||||
Inches(1), Inches(6.5), Inches(11.33), Inches(0.4),
|
||||
font=SANS, size=13, color=STONE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def toc_slide(prs, items):
|
||||
"""目录页:01 章节名 列表"""
|
||||
s = blank_slide(prs)
|
||||
add_text(s, "目录",
|
||||
Inches(1.2), Inches(0.8), Inches(10), Inches(0.8),
|
||||
font=SERIF, size=32, color=NEAR_BLACK)
|
||||
add_line(s, Inches(1.2), Inches(1.8), Inches(11), weight_pt=1)
|
||||
|
||||
for i, item in enumerate(items):
|
||||
y = Inches(2.4 + i * 0.9)
|
||||
add_text(s, f"0{i+1}",
|
||||
Inches(1.2), y, Inches(1), Inches(0.6),
|
||||
font=SERIF, size=28, color=BRAND)
|
||||
add_text(s, item,
|
||||
Inches(2.4), y, Inches(9), Inches(0.6),
|
||||
font=SERIF, size=22, color=NEAR_BLACK,
|
||||
vanchor=MSO_ANCHOR.MIDDLE)
|
||||
return s
|
||||
|
||||
|
||||
def chapter_slide(prs, number, title):
|
||||
"""章节首页:油墨蓝色背景 + 居中大标题"""
|
||||
s = blank_slide(prs, bg_color=BRAND)
|
||||
add_text(s, f"0{number}",
|
||||
Inches(0.8), Inches(0.5), Inches(2), Inches(0.8),
|
||||
font=SERIF, size=26, color=WHITE)
|
||||
add_text(s, title,
|
||||
Inches(1), Inches(3), Inches(11.33), Inches(1.5),
|
||||
font=SERIF, size=56, color=WHITE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def content_slide(prs, eyebrow, title, body, page_num=None):
|
||||
"""内容页:小标题 + 大标题 + 短线 + 正文"""
|
||||
s = blank_slide(prs)
|
||||
# eyebrow
|
||||
add_text(s, eyebrow,
|
||||
Inches(1.2), Inches(0.6), Inches(10), Inches(0.4),
|
||||
font=SANS, size=12, color=STONE)
|
||||
# title
|
||||
add_text(s, title,
|
||||
Inches(1.2), Inches(1.2), Inches(11.33), Inches(1.2),
|
||||
font=SERIF, size=32, color=NEAR_BLACK)
|
||||
# brand line
|
||||
add_line(s, Inches(1.2), Inches(2.5), Inches(1), weight_pt=1.5)
|
||||
# body
|
||||
add_text(s, body,
|
||||
Inches(1.2), Inches(3), Inches(11), Inches(3.5),
|
||||
font=SANS, size=18, color=DARK_WARM)
|
||||
# page number
|
||||
if page_num is not None:
|
||||
add_text(s, f"— {page_num:02d}",
|
||||
Inches(11.5), Inches(6.9), Inches(1.5), Inches(0.3),
|
||||
font=SANS, size=11, color=STONE,
|
||||
align=PP_ALIGN.RIGHT)
|
||||
return s
|
||||
|
||||
|
||||
def metrics_slide(prs, title, metrics):
|
||||
"""数据页:标题 + N 张数据卡并排
|
||||
metrics: [(value, label), ...]
|
||||
"""
|
||||
s = blank_slide(prs)
|
||||
# 标题
|
||||
add_text(s, title,
|
||||
Inches(1.2), Inches(0.8), Inches(11), Inches(1),
|
||||
font=SERIF, size=28, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_line(s, Inches(6.17), Inches(2), Inches(1))
|
||||
|
||||
# 数据卡
|
||||
n = len(metrics)
|
||||
card_w = Inches(2.8)
|
||||
gap = Inches(0.3)
|
||||
total_w = card_w * n + gap * (n - 1)
|
||||
start = (SLIDE_W - total_w) / 2
|
||||
|
||||
for i, (value, label) in enumerate(metrics):
|
||||
x = start + (card_w + gap) * i
|
||||
# 大数字
|
||||
add_text(s, value,
|
||||
x, Inches(3), card_w, Inches(1.5),
|
||||
font=SERIF, size=52, color=BRAND,
|
||||
align=PP_ALIGN.CENTER)
|
||||
# 标签
|
||||
add_text(s, label,
|
||||
x, Inches(4.8), card_w, Inches(0.6),
|
||||
font=SANS, size=14, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def quote_slide(prs, quote, source):
|
||||
"""引用页:极简,居中斜体引文"""
|
||||
s = blank_slide(prs)
|
||||
add_text(s, f"\u201c{quote}\u201d",
|
||||
Inches(1.5), Inches(2.8), Inches(10.33), Inches(2.5),
|
||||
font=SERIF, size=28, italic=True, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER,
|
||||
vanchor=MSO_ANCHOR.MIDDLE)
|
||||
add_text(s, f"— {source}",
|
||||
Inches(1.5), Inches(5.2), Inches(10.33), Inches(0.4),
|
||||
font=SANS, size=14, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
def ending_slide(prs, message, contact):
|
||||
"""结束页"""
|
||||
s = blank_slide(prs)
|
||||
add_text(s, message,
|
||||
Inches(1), Inches(3), Inches(11.33), Inches(1.2),
|
||||
font=SERIF, size=40, color=NEAR_BLACK,
|
||||
align=PP_ALIGN.CENTER)
|
||||
add_line(s, Inches(6.17), Inches(4.5), Inches(1), weight_pt=1.5)
|
||||
add_text(s, contact,
|
||||
Inches(1), Inches(4.8), Inches(11.33), Inches(0.6),
|
||||
font=SANS, size=16, color=OLIVE,
|
||||
align=PP_ALIGN.CENTER)
|
||||
return s
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# Main: 示例 deck,按实际需求改
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def main():
|
||||
prs = Presentation()
|
||||
prs.slide_width = SLIDE_W
|
||||
prs.slide_height = SLIDE_H
|
||||
|
||||
# 1. 封面
|
||||
cover_slide(prs,
|
||||
title="{{文档标题}}",
|
||||
subtitle="{{一句话描述}}",
|
||||
author="{{作者}}",
|
||||
date="2026.04")
|
||||
|
||||
# 2. 目录
|
||||
toc_slide(prs, items=[
|
||||
"{{章节 1}}",
|
||||
"{{章节 2}}",
|
||||
"{{章节 3}}",
|
||||
"{{Q&A}}",
|
||||
])
|
||||
|
||||
# 3. 章节首页
|
||||
chapter_slide(prs, 1, "{{章节标题}}")
|
||||
|
||||
# 4. 内容页
|
||||
content_slide(prs,
|
||||
eyebrow="{{章节 · 本页}}",
|
||||
title="{{核心论点标题}}",
|
||||
body="{{一段正文,18pt sans 字体。控制在 3 行内,一屏一个核心信息。}}",
|
||||
page_num=4)
|
||||
|
||||
# 5. 数据页
|
||||
metrics_slide(prs,
|
||||
title="关键结果",
|
||||
metrics=[
|
||||
("+42%", "转化率提升"),
|
||||
("3.8M", "月活用户"),
|
||||
("99.9%", "可用性 SLA"),
|
||||
("5,000+", "QPS 峰值"),
|
||||
])
|
||||
|
||||
# 6. 引用
|
||||
quote_slide(prs,
|
||||
quote="好的设计是尽可能少的设计。",
|
||||
source="Dieter Rams")
|
||||
|
||||
# 7. 结束
|
||||
ending_slide(prs,
|
||||
message="Thank you",
|
||||
contact="{{邮箱}} · {{网站}}")
|
||||
|
||||
prs.save('output.pptx')
|
||||
print("✓ Saved output.pptx")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,435 @@
|
||||
# Design System
|
||||
|
||||
## Manifesto
|
||||
|
||||
kami's aesthetic compresses into one sentence: **warm parchment canvas, ink-blue accent, serif carries authority, refuse cool grays and hard shadows**.
|
||||
|
||||
This is not a UI framework. It is a set of aesthetic constraints for print. It assumes a high-quality document should read like literature, not a dashboard. Every rule's trade-off is the same one: rather than add another option, remove another temptation.
|
||||
|
||||
**The eight invariants** (think hard before violating any):
|
||||
|
||||
1. Page background parchment `#f5f4ed`, never pure white
|
||||
2. Single accent: ink-blue `#1B365D`, no second chromatic color
|
||||
3. All grays warm-toned (yellow-brown undertone), no cool blue-grays
|
||||
4. English: serif for everything (headlines and body). Chinese: serif headlines, sans body. Sans only for UI elements (labels, eyebrows, meta) in both
|
||||
5. Serif weight locked at 500, no bold
|
||||
6. Line-heights: tight headlines 1.1-1.3, dense body 1.4-1.45, reading body 1.5-1.55. **Never 1.6+**
|
||||
7. Tag backgrounds must be solid hex, never rgba (WeasyPrint renders a double rectangle)
|
||||
8. Depth via ring shadow or whisper shadow, never hard drop shadows
|
||||
9. **No italic anywhere**. No `font-style: italic` in any template or demo. No italic @font-face declarations needed
|
||||
|
||||
This system is a fusion of Anthropic's visual language and real Chinese / English resume iteration. Details below.
|
||||
|
||||
---
|
||||
|
||||
## 1. Color
|
||||
|
||||
**Single accent, warm neutrals only, zero cool tones** — this is the core.
|
||||
|
||||
### Brand
|
||||
|
||||
```css
|
||||
--brand: #1B365D; /* Ink Blue — the only chromatic color. CTAs, accents, section-title underlines. */
|
||||
--brand-light: #2D5A8A; /* Coral — brighter variant, for links on dark surfaces. */
|
||||
```
|
||||
|
||||
**Rule**: ink-blue covers ≤ **5% of document surface area**. More than that is ornament, not restraint.
|
||||
|
||||
### Surface
|
||||
|
||||
```css
|
||||
--parchment: #f5f4ed; /* Page background — warm cream, the emotional foundation */
|
||||
--ivory: #faf9f5; /* Card / lifted container — brighter than parchment */
|
||||
--warm-sand: #e8e6dc; /* Button default / interactive surface */
|
||||
--dark-surface: #30302e; /* Dark-theme container — warm charcoal */
|
||||
--deep-dark: #141413; /* Dark-theme page background — not pure black, slight olive undertone */
|
||||
```
|
||||
|
||||
**Never**: `#ffffff` pure white as page background. `#f8f9fa` / `#f3f4f6` or any cool-gray surface.
|
||||
|
||||
### Text
|
||||
|
||||
```css
|
||||
--near-black: #141413; /* Primary text — deepest but not pure black, warm olive undertone */
|
||||
--dark-warm: #3d3d3a; /* Secondary dark / dark link color */
|
||||
--charcoal: #4d4c48; /* Button text / dense body */
|
||||
--olive: #5e5d59; /* Subtext — descriptions, captions */
|
||||
--stone: #87867f; /* Tertiary — dates, metadata */
|
||||
--warm-silver: #b0aea5; /* Light text on dark surfaces */
|
||||
```
|
||||
|
||||
**Mnemonic**: every gray has a **yellow-brown undertone**. In `rgb()`, warm gray is R ≈ G > B (or R > G > B with small gaps). Cool gray is R < G < B or R = G = B (neutral).
|
||||
|
||||
### Border
|
||||
|
||||
```css
|
||||
--border-cream: #f0eee6; /* Softest border — default cards */
|
||||
--border-warm: #e8e6dc; /* Prominent border — section dividers */
|
||||
--border-soft: #efede4; /* Dotted divider — between list items */
|
||||
--border-dark: #30302e; /* Border on dark surfaces */
|
||||
```
|
||||
|
||||
### Ring shadow (not traditional box-shadow)
|
||||
|
||||
```css
|
||||
--ring-warm: #d1cfc5; /* Button hover / focus */
|
||||
--ring-deep: #c2c0b6; /* Pressed state */
|
||||
```
|
||||
|
||||
### Functional (use sparingly)
|
||||
|
||||
```css
|
||||
--error: #b53333; /* Deep warm red — serious without alarming */
|
||||
--focus: #3898ec; /* Focus ring blue — the only cool color, strictly for accessibility */
|
||||
```
|
||||
|
||||
### Translucent -> Solid conversion (TAGS MUST BE SOLID)
|
||||
|
||||
**Why**: WeasyPrint's alpha compositing for padding vs glyph areas produces a visible double rectangle on zoom. See `production.md` Part 4 Pitfall #1.
|
||||
|
||||
Ink Blue `#1B365D` over parchment `#f5f4ed`:
|
||||
|
||||
| rgba alpha | Solid hex |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← default tag |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Typography
|
||||
|
||||
### Stacks
|
||||
|
||||
```css
|
||||
/* Serif for headlines — Silicon Valley editorial */
|
||||
font-family: "Newsreader", /* Google Fonts — preferred */
|
||||
"Source Serif 4", "Source Serif Pro",
|
||||
"Charter", /* macOS system font */
|
||||
Georgia, "Times New Roman", serif;
|
||||
|
||||
/* Sans for body / UI — Silicon Valley default */
|
||||
font-family: "Inter", "TsangerJinKai02",
|
||||
-apple-system, BlinkMacSystemFont,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
|
||||
/* Mono for code */
|
||||
font-family: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
```
|
||||
|
||||
For Chinese documents, see `design.md` for the TsangerJinKai02 + Source Han stack.
|
||||
|
||||
### Size scale (pt for print A4, px for screen)
|
||||
|
||||
**Print:**
|
||||
|
||||
| Role | Size | Weight | Line-height | Use |
|
||||
|---|---|---|---|---|
|
||||
| Display | 36-48pt | 500 | 1.10 | Cover title, one-pager hero |
|
||||
| H1 Section | 18-22pt | 500 | 1.20 | Chapter titles |
|
||||
| H2 | 14-16pt | 500 | 1.25 | Subsection |
|
||||
| H3 | 12-13pt | 500 | 1.30 | Item titles |
|
||||
| Body Lead | 11pt | 400 | 1.55 | Intro paragraphs |
|
||||
| Body | 9.5-10pt | 400 | 1.55 | Reading body |
|
||||
| Body Dense | 9-9.2pt | 400 | 1.42 | Dense body (resume, one-pager) |
|
||||
| Caption | 8.5-9pt | 400 | 1.45 | Notes, figure captions |
|
||||
| Label | 7.5-8pt | 600 | 1.35 | Small labels, corner tags |
|
||||
| Tiny | 7pt | 400 | 1.40 | Footer, minor metadata |
|
||||
|
||||
**Screen (px)** ≈ pt × 1.33 (9pt ≈ 12px, 18pt ≈ 24px).
|
||||
|
||||
### Weight
|
||||
|
||||
- **Serif**: locked at 500. Not 400, not 700. Single-weight consistency is part of the design language.
|
||||
- **Sans body**: 400 default
|
||||
- **Sans labels / small titles**: 500 or 600
|
||||
- **Forbidden**: 900 black, 100 thin
|
||||
|
||||
### Line-height
|
||||
|
||||
Print documents are **tighter** than English web body. English web typically runs 1.6-1.75; in print at pt sizes that feels loose and floats.
|
||||
|
||||
| Tier | Value | Use |
|
||||
|---|---|---|
|
||||
| Tight headline | 1.10-1.30 | Display, H1, H2 |
|
||||
| Dense body | 1.40-1.45 | Resume, one-pager, dense information |
|
||||
| Reading body | 1.50-1.55 | Long-doc chapters, letters |
|
||||
| Label / caption | 1.30-1.40 | Small labels, multi-line metadata |
|
||||
|
||||
**Forbidden**:
|
||||
- 1.60+ — loose feel, web rhythm, not print
|
||||
- 1.00-1.05 — lines collide except at extreme display sizes
|
||||
|
||||
### Letter-spacing
|
||||
|
||||
- Body text: **0** (or +0.05pt imperceptible)
|
||||
- English headings above 20pt: -0.3 to -0.5pt (tighten, English serifs handle it)
|
||||
- Small labels (< 10pt): +0.2 to +0.5pt for readability
|
||||
- All-caps overlines: +0.5 to +1pt mandatory
|
||||
|
||||
---
|
||||
|
||||
## 3. Spacing
|
||||
|
||||
### Base unit: 4pt (4px on screen)
|
||||
|
||||
| Tier | Value | Use |
|
||||
|---|---|---|
|
||||
| xs | 2-3pt | Inline adjacent elements |
|
||||
| sm | 4-5pt | Tag padding, dense layout |
|
||||
| md | 8-10pt | Component interior |
|
||||
| lg | 16-20pt | Between components / card padding |
|
||||
| xl | 24-32pt | Section-title margins |
|
||||
| 2xl | 40-60pt | Between major sections |
|
||||
| 3xl | 80-120pt | Between chapters (long docs) |
|
||||
|
||||
### Page margins (A4)
|
||||
|
||||
| Document | Top | Right | Bottom | Left |
|
||||
|---|---|---|---|---|
|
||||
| Resume (dense) | 11mm | 13mm | 11mm | 13mm |
|
||||
| One-Pager | 15mm | 18mm | 15mm | 18mm |
|
||||
| Long Doc | 20mm | 22mm | 22mm | 22mm |
|
||||
| Letter | 25mm | 25mm | 25mm | 25mm |
|
||||
| Portfolio | 12mm | 15mm | 12mm | 15mm |
|
||||
|
||||
**Rule**: denser = smaller margins, more formal (letter) = larger margins.
|
||||
|
||||
---
|
||||
|
||||
## 4. Components
|
||||
|
||||
### Cards / Containers
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 8pt;
|
||||
padding: 16pt 20pt;
|
||||
}
|
||||
|
||||
.card-featured {
|
||||
border-radius: 16pt;
|
||||
box-shadow: 0 4pt 24pt rgba(0,0,0,0.05); /* whisper shadow */
|
||||
}
|
||||
```
|
||||
|
||||
Radius scale: 4pt -> 6pt -> 8pt (default) -> 12pt -> 16pt -> 24pt -> 32pt (hero containers).
|
||||
|
||||
### Buttons
|
||||
|
||||
```css
|
||||
/* Primary — brand-colored */
|
||||
.btn-primary {
|
||||
background: var(--brand);
|
||||
color: var(--ivory);
|
||||
padding: 8pt 16pt;
|
||||
border-radius: 8pt;
|
||||
box-shadow: 0 0 0 1pt var(--brand); /* ring shadow */
|
||||
}
|
||||
|
||||
/* Secondary — warm-sand */
|
||||
.btn-secondary {
|
||||
background: var(--warm-sand);
|
||||
color: var(--charcoal);
|
||||
padding: 8pt 16pt;
|
||||
border-radius: 8pt;
|
||||
box-shadow: 0 0 0 1pt var(--ring-warm);
|
||||
}
|
||||
```
|
||||
|
||||
### Tags
|
||||
|
||||
Three tiers from weak to strong visual weight:
|
||||
|
||||
**Lightest solid** (default, most restrained):
|
||||
```css
|
||||
.tag {
|
||||
background: #EEF2F7; /* 0.08 solid equivalent */
|
||||
color: var(--brand);
|
||||
font-size: 8pt;
|
||||
font-weight: 600;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
```
|
||||
|
||||
**Standard solid** (when more contrast needed):
|
||||
```css
|
||||
.tag {
|
||||
background: #E4ECF5; /* 0.18 solid equivalent */
|
||||
color: var(--brand);
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 4pt;
|
||||
}
|
||||
```
|
||||
|
||||
**Gradient brush** (only when "hand-painted" feel is required — use sparingly):
|
||||
```css
|
||||
.tag {
|
||||
background: linear-gradient(to right, #D6E1EE, #E4ECF5 70%, #EEF2F7);
|
||||
color: var(--brand);
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
```
|
||||
|
||||
**Philosophy**: tint depth should be one step lighter than what decoration wants. Prefer pale over saturated. In iteration, "gradient brush" often steals focus — lightest solid wins most of the time.
|
||||
|
||||
**Never**: `background: rgba(201, 100, 66, 0.18)` — WeasyPrint double-rectangle bug.
|
||||
|
||||
### Lists
|
||||
|
||||
```css
|
||||
ul, ol {
|
||||
padding-left: 16pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
```
|
||||
|
||||
Editorial bookish variant — **em-dash instead of bullet**:
|
||||
|
||||
```css
|
||||
ul.dash { list-style: none; padding-left: 0; }
|
||||
ul.dash li::before {
|
||||
content: "- ";
|
||||
color: var(--brand);
|
||||
}
|
||||
```
|
||||
|
||||
### Quote
|
||||
|
||||
```css
|
||||
.quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
}
|
||||
```
|
||||
|
||||
### Code
|
||||
|
||||
```css
|
||||
.code-block {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 6pt;
|
||||
padding: 10pt 14pt;
|
||||
font-family: var(--mono);
|
||||
font-size: 8.5pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
```
|
||||
|
||||
### Section Title
|
||||
|
||||
```css
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 24pt 0 10pt 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Metric
|
||||
|
||||
Key numbers side-by-side (one-pager header, resume top, portfolio cover):
|
||||
|
||||
```css
|
||||
.metrics { display: flex; gap: 24pt; }
|
||||
.metric { flex: 1; display: flex; align-items: baseline; gap: 6pt; }
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums; /* align digits in columns */
|
||||
}
|
||||
.metric-label { font-size: 9pt; color: var(--olive); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Depth & Shadow
|
||||
|
||||
**Core rule**: do not use traditional hard shadows. Depth comes from three sources:
|
||||
|
||||
### 1. Ring shadow (border-like)
|
||||
|
||||
```css
|
||||
box-shadow: 0 0 0 1pt var(--ring-warm);
|
||||
box-shadow: 0 0 0 1pt var(--ring-deep); /* hover / active */
|
||||
```
|
||||
|
||||
### 2. Whisper shadow (barely visible lift)
|
||||
|
||||
```css
|
||||
box-shadow: 0 4pt 24pt rgba(0, 0, 0, 0.05);
|
||||
```
|
||||
|
||||
### 3. Section-level light/dark alternation
|
||||
|
||||
Long docs alternate parchment `#f5f4ed` and `#141413` dark sections. The entire section's ambient light shifts — more dramatic than any shadow.
|
||||
|
||||
**Forbidden**: `box-shadow: 0 2px 8px rgba(0,0,0,0.3)` and relatives.
|
||||
|
||||
---
|
||||
|
||||
## 6. Print & Pagination
|
||||
|
||||
### break-inside protection
|
||||
|
||||
```css
|
||||
.card, .metric, .project-item, .quote, .code-block, figure, .callout {
|
||||
break-inside: avoid;
|
||||
}
|
||||
```
|
||||
|
||||
### Force break
|
||||
|
||||
```css
|
||||
.page-break { break-before: page; }
|
||||
```
|
||||
|
||||
### Page background extending past margins
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm;
|
||||
background: #f5f4ed; /* extends past margin area, prevents printed white edges */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Quick decisions
|
||||
|
||||
When you're not sure "what should I use":
|
||||
|
||||
| Need | Use |
|
||||
|---|---|
|
||||
| Big headline | serif 500, size by level, line-height 1.10-1.30 |
|
||||
| Reading body (EN) | serif 400, 9.5-10pt, line-height 1.55 |
|
||||
| Reading body (CN) | sans 400, 9.5-10pt, line-height 1.55 |
|
||||
| Emphasize a number | `color: var(--brand)`, no bold |
|
||||
| Divide two sections | 1pt brand border-bottom, or 0.5pt warm-gray dotted |
|
||||
| Quote someone | 2pt brand left border + olive color |
|
||||
| Show code | ivory background + 0.5pt border + 6pt radius + mono |
|
||||
| Primary vs secondary button | Primary = brand fill + ivory text; Secondary = warm-sand + charcoal |
|
||||
| Highlight one card in a list | `border: 0.5pt solid var(--brand)` or `border-left: 3pt solid var(--brand)` |
|
||||
| Start a chapter | serif heading + 1pt brand line below |
|
||||
| Cover page | Display-size heading + right-aligned author/date + heavy whitespace |
|
||||
| Data card | ivory background + 8pt radius + serif big number + sans small label |
|
||||
|
||||
Not on this table -> return to first principles: **serif carries authority, sans carries utility, warm gray carries rhythm, ink-blue carries focus**.
|
||||
@@ -0,0 +1,465 @@
|
||||
# Design System
|
||||
|
||||
## 设计宣言
|
||||
|
||||
kami 的审美可以浓缩成一句话:**暖米纸底,油墨蓝点缀,serif 承担权威,拒绝冷蓝灰与硬阴影**。
|
||||
|
||||
这不是一套 UI 框架,是一套印刷品的审美约束。它相信:高质量的文档读起来像文学,不像仪表盘。每条铁律的 trade-off 都是"与其多一个选择,不如少一个诱惑"。
|
||||
|
||||
**八条铁律**(违反之前先想清楚是否真的要违反):
|
||||
|
||||
1. 页面背景 parchment `#f5f4ed`,不用纯白
|
||||
2. 强调色只有油墨蓝 `#1B365D`,不引入第二种彩色
|
||||
3. 所有灰色暖调(yellow-brown undertone),禁止冷蓝灰
|
||||
4. 英文模板: serif 通吃标题和正文。中文模板: 标题 serif,正文 sans。UI 元素 (label, eyebrow, meta) 都用 sans
|
||||
5. Serif 字重固定 500,不用 bold
|
||||
6. 行距三档:紧凑标题 1.1-1.3 / 密排正文 1.4-1.45 / 阅读型 1.5-1.55。禁用 1.6+
|
||||
7. Tag 背景必须实色 hex,禁止 rgba 半透明(WeasyPrint 会渲染出双层矩形)
|
||||
8. 阴影用 ring 或 whisper shadow,不用硬 drop shadow
|
||||
9. **禁止 italic**。所有模板和 demo 中不使用 `font-style: italic`,不需要 italic 字体文件
|
||||
|
||||
所有文档类型(One-Pager / Long Doc / Letter / Portfolio / Resume / Slides)都依据这份规范。这套系统来自 Anthropic 视觉语言 + 中文简历设计迭代,是两个来源的融合。
|
||||
|
||||
---
|
||||
|
||||
## 1. 色彩系统
|
||||
|
||||
**唯一强调色 + 纯暖调中性灰 + 零冷色**是这套设计的核心。
|
||||
|
||||
### 主调 · 品牌色
|
||||
|
||||
```css
|
||||
--brand: #1B365D; /* Ink Blue Brand - 唯一的彩色,用于 CTA、强调、section-title 下划线 */
|
||||
--brand-light: #2D5A8A; /* Coral Accent - 更亮的变体,偶尔用于深色底上的链接 */
|
||||
```
|
||||
|
||||
**使用规则**:油墨蓝 `#1B365D` 全文档不超过 **5% 的面积**。超过就是堆砌,不是克制。
|
||||
|
||||
### 画布 · 背景色
|
||||
|
||||
```css
|
||||
--parchment: #f5f4ed; /* 主页面背景 - 温暖米色,整个设计的情感基础 */
|
||||
--ivory: #faf9f5; /* 卡片/浮起容器 - 比 parchment 更亮的暖白 */
|
||||
--warm-sand: #e8e6dc; /* 按钮默认背景 / 明显的交互表面 */
|
||||
--dark-surface: #30302e; /* 深色主题容器 - 暖炭灰 */
|
||||
--deep-dark: #141413; /* 深色主题页面底色 - 不是纯黑,有橄榄绿底色 */
|
||||
```
|
||||
|
||||
**绝对禁止**:`#ffffff` 纯白作页面底 · 任何 `#f8f9fa` / `#f3f4f6` 这类冷灰底。
|
||||
|
||||
### 中性文字色
|
||||
|
||||
```css
|
||||
--near-black: #141413; /* 主文本 - 最深但不是纯黑,有暖橄榄底色 */
|
||||
--dark-warm: #3d3d3a; /* 次级深色文字 / 深色链接 */
|
||||
--charcoal: #4d4c48; /* 按钮文字 / 高密度正文 */
|
||||
--olive: #5e5d59; /* 副文本 - 描述、caption 等 */
|
||||
--stone: #87867f; /* 三级文字 - 日期、元信息 */
|
||||
--warm-silver: #b0aea5; /* 深色底上的浅色文字 */
|
||||
```
|
||||
|
||||
**记忆法**:每个灰都有 **yellow-brown undertone**。如果你在 `rgb()` 里看到 R ≈ G > B(或 R > G > B 且差距很小),基本就是暖灰。冷灰是 R < G < B(偏蓝)或 R = G = B(中性)。
|
||||
|
||||
### 边框与分隔
|
||||
|
||||
```css
|
||||
--border-cream: #f0eee6; /* 最柔的边框 - 卡片默认 */
|
||||
--border-warm: #e8e6dc; /* 明显的边框 - section 分隔 */
|
||||
--border-soft: #efede4; /* 更淡的虚线分隔 - 列表项之间 */
|
||||
--border-dark: #30302e; /* 深色主题下的边框 */
|
||||
```
|
||||
|
||||
### Ring 阴影(不用传统 box-shadow)
|
||||
|
||||
```css
|
||||
--ring-warm: #d1cfc5; /* 按钮 hover/focus 环 */
|
||||
--ring-deep: #c2c0b6; /* 按下状态 */
|
||||
```
|
||||
|
||||
### 功能色(尽量少用)
|
||||
|
||||
```css
|
||||
--error: #b53333; /* 错误 - 深暖红,不刺眼 */
|
||||
--focus: #3898ec; /* 聚焦蓝 - 唯一的冷色,只用于 input focus ring,无障碍必要 */
|
||||
```
|
||||
|
||||
### 半透明对应实色对照表(TAG / 标签必须实色)
|
||||
|
||||
**原因**:WeasyPrint 渲染 rgba 半透明时 padding 区域和字形区域透明度叠加不一致,放大后产生双层矩形。详见 `production.md Part 4`。
|
||||
|
||||
油墨蓝 `#1B365D` 叠加在 parchment `#f5f4ed` 上的等效实色:
|
||||
|
||||
| 想要的 rgba 透明度 | 等效实色 hex |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← tag 推荐默认 |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 字体系统
|
||||
|
||||
### 字体栈(按优先级 fallback)
|
||||
|
||||
```css
|
||||
/* Serif 标题(中英文) */
|
||||
font-family: "TsangerJinKai02", /* 仓耳今楷,需自备 .ttf */
|
||||
"Source Han Serif SC", /* 思源宋体(免费,Adobe/Google 联合出品)*/
|
||||
"Noto Serif CJK SC", /* 思源宋体的 Google 命名 */
|
||||
"Songti SC", /* macOS 系统宋体 */
|
||||
"STSong", /* Windows 中文宋体 */
|
||||
Georgia, serif;
|
||||
|
||||
/* Sans 正文/UI(中英文) */
|
||||
font-family: "Inter", "TsangerJinKai02",
|
||||
-apple-system, BlinkMacSystemFont,
|
||||
"Source Han Sans SC", "Noto Sans CJK SC",
|
||||
"PingFang SC", "Microsoft YaHei",
|
||||
Arial, sans-serif;
|
||||
|
||||
/* Mono 代码 */
|
||||
font-family: "JetBrains Mono", "Fira Code",
|
||||
"SF Mono", Consolas, Monaco,
|
||||
"Source Han Mono", monospace;
|
||||
```
|
||||
|
||||
### 字号层级(pt 用于 PDF,px 用于屏幕)
|
||||
|
||||
**印刷品(A4 PDF)用 pt**:
|
||||
|
||||
| 角色 | 字号 | 字重 | line-height | 用途 |
|
||||
|---|---|---|---|---|
|
||||
| Display | 36-48 pt | 500 | 1.10 | 封面大标题、one-pager 主标题 |
|
||||
| H1 Section | 18-22 pt | 500 | 1.20 | 章节大标题 |
|
||||
| H2 | 14-16 pt | 500 | 1.25 | 子章节 |
|
||||
| H3 | 12-13 pt | 500 | 1.30 | 条目标题 |
|
||||
| Body Lead | 11 pt | 400 | 1.55 | 导语、intro |
|
||||
| Body | 9.5-10 pt | 400 | 1.55 | 正文 |
|
||||
| Body Dense | 9-9.2 pt | 400 | 1.40 | 密集排版(简历、one-pager 等) |
|
||||
| Caption | 8.5-9 pt | 400 | 1.45 | 说明文字、图注 |
|
||||
| Label | 7.5-8 pt | 600 | 1.35 | 小标签、角标 |
|
||||
| Tiny | 7 pt | 400 | 1.40 | 页脚、minor metadata |
|
||||
|
||||
**屏幕(网页/PPT)用 px**:乘以约 1.33 得到等效 px(9 pt ≈ 12 px,18 pt ≈ 24 px)。
|
||||
|
||||
### 字重规则
|
||||
|
||||
- **Serif**:固定 500(不用 400、不用 700)。**单一字重是设计语言的一部分**。
|
||||
- **Sans 正文**:400 默认
|
||||
- **Sans 标签/标题**:500 或 600
|
||||
- **禁止 900 black 或 100 thin**
|
||||
|
||||
### 行距(line-height)三档
|
||||
|
||||
中文印刷品比英文网页**更紧凑**。英文网页常见的 1.6-1.75 是针对英文字母和非 fixed-width 的 body 优化的,放在中文 pt 字号的印刷品里会显得松散。
|
||||
|
||||
| 档位 | 值 | 用于 |
|
||||
|---|---|---|
|
||||
| 紧凑标题 | 1.10-1.30 | 大标题、Display、H1、H2 |
|
||||
| 密排正文 | 1.40-1.45 | Body Dense(简历、one-pager、名片、索引卡) |
|
||||
| 阅读型正文 | 1.50-1.55 | Body(long-doc 章节正文、letter 正文) |
|
||||
| 标签 / caption | 1.30-1.40 | 小字标签、多行 metadata |
|
||||
|
||||
**禁用**:
|
||||
- 1.60+ — 英文网页的节奏,中文印刷会显得松散
|
||||
- 1.00-1.05 — 除非极致紧凑标题,否则上下文字会粘连
|
||||
|
||||
### 字距(letter-spacing)
|
||||
|
||||
- body 默认 **0** 或极轻 +0.1 pt
|
||||
- 中文标题超过 20 pt 的,加 0.5-1 pt 字距
|
||||
- 小字 label (<10 pt) 可以加 0.15-0.3 pt 提高可读性
|
||||
- 全大写 overline 加 0.5 px(ALL CAPS 必须加字距)
|
||||
|
||||
---
|
||||
|
||||
## 3. 间距系统
|
||||
|
||||
### 基础单位:4 pt(或 4 px 屏幕)
|
||||
|
||||
| 尺度 | 值 | 用途 |
|
||||
|---|---|---|
|
||||
| xs | 2-3 pt | 同行内元素间距 |
|
||||
| sm | 4-5 pt | tag padding、紧凑布局 |
|
||||
| md | 8-10 pt | 组件内部 |
|
||||
| lg | 16-20 pt | 组件之间、卡片 padding |
|
||||
| xl | 24-32 pt | section 标题 margin |
|
||||
| 2xl | 40-60 pt | 大 section 之间 |
|
||||
| 3xl | 80-120 pt | 章节之间(长文档)|
|
||||
|
||||
### 页面 margin(A4)
|
||||
|
||||
| 文档类型 | 上 | 右 | 下 | 左 |
|
||||
|---|---|---|---|---|
|
||||
| Resume(紧凑)| 9 mm | 13 mm | 9 mm | 13 mm |
|
||||
| One-Pager | 15 mm | 18 mm | 15 mm | 18 mm |
|
||||
| Long Doc | 20 mm | 22 mm | 22 mm | 22 mm |
|
||||
| Letter | 25 mm | 25 mm | 25 mm | 25 mm |
|
||||
| Portfolio | 12 mm | 15 mm | 12 mm | 15 mm |
|
||||
|
||||
**规律**:密度越高 margin 越小,越正式(letter)margin 越大。
|
||||
|
||||
---
|
||||
|
||||
## 4. 组件样式
|
||||
|
||||
### Cards / Containers
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--ivory); /* 比 parchment 略浮起 */
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 8pt; /* 舒适圆角 */
|
||||
padding: 16pt 20pt;
|
||||
}
|
||||
|
||||
/* 特色卡片 */
|
||||
.card-featured {
|
||||
border-radius: 16pt; /* 更大圆角 */
|
||||
box-shadow: 0 4pt 24pt rgba(0,0,0,0.05); /* whisper shadow */
|
||||
}
|
||||
```
|
||||
|
||||
圆角尺度:4 pt -> 6 pt -> 8 pt(默认)-> 12 pt -> 16 pt -> 24 pt -> 32 pt(hero 容器)。
|
||||
|
||||
### Buttons (在 PPT / 作品集 / 图表里用)
|
||||
|
||||
```css
|
||||
/* Primary(品牌色) */
|
||||
.btn-primary {
|
||||
background: var(--brand);
|
||||
color: var(--ivory);
|
||||
padding: 8pt 16pt;
|
||||
border-radius: 8pt;
|
||||
box-shadow: 0 0 0 1pt var(--brand); /* ring shadow,不是外阴影 */
|
||||
}
|
||||
|
||||
/* Secondary(warm sand) */
|
||||
.btn-secondary {
|
||||
background: var(--warm-sand);
|
||||
color: var(--charcoal);
|
||||
padding: 8pt 16pt;
|
||||
border-radius: 8pt;
|
||||
box-shadow: 0 0 0 1pt var(--ring-warm);
|
||||
}
|
||||
```
|
||||
|
||||
### Tags / Badges
|
||||
|
||||
三个档位的 tag 样式,按视觉冲击力从弱到强选:
|
||||
|
||||
**极淡实色**(最克制、最灵动、推荐默认):
|
||||
```css
|
||||
.tag {
|
||||
background: #EEF2F7; /* rgba(201,100,66, 0.08) 等效色 */
|
||||
color: var(--brand);
|
||||
font-size: 8pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
letter-spacing: 0.05pt;
|
||||
}
|
||||
```
|
||||
|
||||
**标准实色**(需要稍强区分度,如多种 tag 混排时):
|
||||
```css
|
||||
.tag {
|
||||
background: #E4ECF5; /* rgba(201,100,66, 0.18) 等效色 */
|
||||
color: var(--brand);
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 4pt;
|
||||
}
|
||||
```
|
||||
|
||||
**笔刷渐变**(仅在需要强化"手感"时用,慎用):
|
||||
```css
|
||||
.tag {
|
||||
background: linear-gradient(to right, #D6E1EE, #E4ECF5 70%, #EEF2F7);
|
||||
color: var(--brand);
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
```
|
||||
|
||||
**设计哲学**:tint 浓度要比装饰性需求**低一档**。宁可清淡不可浓艳。实战发现"笔刷渐变"虽然技术上酷,但会抢焦点(见 production.md Part 4 #1 的实战经验)。
|
||||
|
||||
**绝对不用**:`background: rgba(201, 100, 66, 0.18)` —— 会触发 WeasyPrint 双层 bug。用等效实色替代。
|
||||
|
||||
### 列表
|
||||
|
||||
```css
|
||||
ul, ol {
|
||||
padding-left: 16pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker {
|
||||
color: var(--brand); /* bullet 点用品牌色 */
|
||||
}
|
||||
```
|
||||
|
||||
或者更有书卷气的**短横线代替圆点**:
|
||||
|
||||
```css
|
||||
ul.dash { list-style: none; padding-left: 0; }
|
||||
ul.dash li::before {
|
||||
content: "- ";
|
||||
color: var(--brand);
|
||||
}
|
||||
```
|
||||
|
||||
### 引用块
|
||||
|
||||
```css
|
||||
.quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
}
|
||||
```
|
||||
|
||||
### 代码块
|
||||
|
||||
```css
|
||||
.code-block {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 6pt;
|
||||
padding: 10pt 14pt;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 8.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--near-black);
|
||||
}
|
||||
```
|
||||
|
||||
### Section Title
|
||||
|
||||
```css
|
||||
.section-title {
|
||||
font-family: serif; /* 用 serif 承担所有标题 */
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 24pt 0 10pt 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Metric Card(数据卡)
|
||||
|
||||
关键指标并排显示(用于 one-pager 顶部、简历顶部、portfolio 封面):
|
||||
|
||||
```css
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 24pt;
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6pt;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: serif;
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums; /* 数字等宽对齐 */
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 阴影与深度
|
||||
|
||||
**核心原则**:Claude 设计系统**不用传统硬阴影**。深度通过三种方式创造:
|
||||
|
||||
### 1. Ring Shadow(边框式阴影)
|
||||
|
||||
```css
|
||||
/* 1pt 环,像 border 但更柔 */
|
||||
box-shadow: 0 0 0 1pt var(--ring-warm);
|
||||
|
||||
/* hover 时加深 */
|
||||
box-shadow: 0 0 0 1pt var(--ring-deep);
|
||||
```
|
||||
|
||||
### 2. Whisper Shadow(极软投影)
|
||||
|
||||
```css
|
||||
/* 几乎看不见的轻微浮起 */
|
||||
box-shadow: 0 4pt 24pt rgba(0, 0, 0, 0.05);
|
||||
```
|
||||
|
||||
### 3. 明暗交替(section 级别)
|
||||
|
||||
长文档里 parchment `#f5f4ed` 底 section 和 `#141413` 深色 section 交替,比任何阴影都戏剧化。
|
||||
|
||||
**禁止**:`box-shadow: 0 2px 8px rgba(0,0,0,0.3)` 这类传统硬阴影。
|
||||
|
||||
---
|
||||
|
||||
## 6. 打印与分页
|
||||
|
||||
### break-inside 保护
|
||||
|
||||
以下元素不允许跨页断开:
|
||||
|
||||
```css
|
||||
.card,
|
||||
.metric,
|
||||
.project-item,
|
||||
.quote,
|
||||
.code-block,
|
||||
figure,
|
||||
.callout {
|
||||
break-inside: avoid;
|
||||
}
|
||||
```
|
||||
|
||||
### 强制分页
|
||||
|
||||
```css
|
||||
.page-break { break-before: page; }
|
||||
```
|
||||
|
||||
用于封面与正文之间、章节之间。
|
||||
|
||||
### 页边背景
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm;
|
||||
background: #f5f4ed; /* 背景延伸到 margin 外,避免打印时留白边 */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 决策速查
|
||||
|
||||
遇到 "该用什么" 的时候查这张表:
|
||||
|
||||
| 要做什么 | 怎么做 |
|
||||
|---|---|
|
||||
| 大标题 | serif 500,字号根据层级,line-height 1.10-1.30 |
|
||||
| 正文阅读 | sans 400,9.5-10 pt,line-height 1.55 |
|
||||
| 强调一个数字 | `color: var(--brand)`,不要粗体 |
|
||||
| 分隔两段内容 | 1pt 品牌色 border-bottom,或 0.5pt 暖灰虚线 |
|
||||
| 引用某人的话 | 左 2pt 品牌色实线 + olive 色 |
|
||||
| 展示代码 | ivory 底 + 0.5pt border + 6pt 圆角 + mono 字体 |
|
||||
| 区分主次按钮 | Primary 用品牌色填充 + 白字,Secondary 用 warm-sand + charcoal |
|
||||
| 在卡片列表里区分某张特殊的 | `border: 0.5pt solid var(--brand)` 或 `border-left: 3pt solid var(--brand)` |
|
||||
| 章节开始 | serif 标题 + 下面一条 1pt 品牌色实线 |
|
||||
| 文档封面 | 单页 Display 字号标题 + 作者/日期 right align,中间大量留白 |
|
||||
| 一张数据卡 | ivory 底 + 8 pt 圆角 + serif 大数字 + sans 小标签 |
|
||||
|
||||
不在这张表里的情况 -> 回到原则:**serif 承担权威,sans 承担功能,暖灰承担节奏,油墨蓝承担焦点**。
|
||||
@@ -0,0 +1,173 @@
|
||||
# Diagrams
|
||||
|
||||
kami's drawing capability. **We don't reimplement 13 diagram types.** We ship three that are actually useful inside long-doc, portfolio, and slides: architecture, flowchart, quadrant. All of them wear kami's skin (parchment + ink-blue + warm grays). No second design system.
|
||||
|
||||
Every diagram is a **self-contained HTML + inline SVG**. No Mermaid, no JS, no build step. Browse them as standalone pages, or copy the `<svg>...</svg>` block into a long-doc `<figure>` to embed.
|
||||
|
||||
---
|
||||
|
||||
## 1. Selection
|
||||
|
||||
| Showing… | Use | Template |
|
||||
|---|---|---|
|
||||
| System components + connections | **Architecture** | `assets/diagrams/architecture.html` |
|
||||
| Decision branches, "if A then B else C" | **Flowchart** | `assets/diagrams/flowchart.html` |
|
||||
| Two-axis positioning / prioritization | **Quadrant** | `assets/diagrams/quadrant.html` |
|
||||
|
||||
Not on the list:
|
||||
- **Time / stages**: use the built-in timeline component in `resume.html` or `one-pager.html`. Don't reinvent.
|
||||
- **Compare two things**: use a table. A three-column table beats any diagram of a binary contrast.
|
||||
- **Hierarchy / nested lists**: a nested markdown `ul` is enough.
|
||||
- **One box with a label**: delete the box, write the sentence.
|
||||
|
||||
### The question before drawing
|
||||
|
||||
> Would a well-written paragraph teach the reader less than this diagram?
|
||||
|
||||
If "no", don't draw. Diagrams add signal to hierarchy, direction, and magnitude. They don't decorate prose.
|
||||
|
||||
---
|
||||
|
||||
## 2. Complexity budget
|
||||
|
||||
**Target density: 4/10**. Enough to be technically complete, not so dense the reader needs a guide.
|
||||
|
||||
- Nodes > 9 -> this is two diagrams, not one
|
||||
- Two nodes that always travel together -> they're one node
|
||||
- A line whose meaning is obvious from layout -> remove the line
|
||||
- 5 nodes in ink-blue -> you haven't decided what's focal
|
||||
|
||||
**Focal rule**: 1-2 focal elements per diagram (`#1B365D` stroke + `#EEF2F7` fill). Everything else goes neutral. Focal signal comes from contrast, not count.
|
||||
|
||||
---
|
||||
|
||||
## 3. Embedding in long-doc / portfolio
|
||||
|
||||
### Standalone preview
|
||||
|
||||
Open `assets/diagrams/architecture.html` (or `flowchart.html`, `quadrant.html`) directly. Each file is a complete HTML page with title, SVG, and caption.
|
||||
|
||||
### Embed in a kami document
|
||||
|
||||
Extract **only the `<svg>...</svg>` block** from the template (leave the frame / h1 / eyebrow behind). Drop it into a long-doc `<figure>`:
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- svg content copied from architecture.html -->
|
||||
</svg>
|
||||
<figcaption>Figure 1. {{Short editorial caption in serif.}}</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
`long-doc.html` already styles `figure` and `figcaption`. No extra CSS required.
|
||||
|
||||
### Editing nodes / text
|
||||
|
||||
Edit the `<text>` and `<rect>` values directly. Rules:
|
||||
|
||||
- **All coordinates, widths, and gaps must be divisible by 4.** This is the anti-AI-slop floor. Break it once and the diagram starts looking "close enough".
|
||||
- Node widths: 128 / 144 / 160 (three tiers, don't add more). Small diagrams (viewBox width < 360) may compress to 2 tiers, but still keep it 2 — don't tailor each node.
|
||||
- Node heights: 32 (pill) / 64 (standard)
|
||||
- Font sizes: 7 (small mono label) / 9 (sublabel mono) / 12 (name sans)
|
||||
- **Arrow endpoints land exactly on node edges**: start `(box.x + box.w, box.y + box.h/2)`, end `(box.x, box.y + box.h/2)`, not "close enough". A 10px gap is visible to the eye.
|
||||
- **SVG top padding**: the `y` in `<text y="…">` is the baseline. `y` must be ≥ font-size × 1.2, otherwise the tops of capital letters extend above the viewBox and get clipped (classic symptom: "TOOLS" renders as "TOULS"). Either pad the viewBox at the top or move `y` into the safe zone.
|
||||
- **Loop arc control points**: for a four-cardinal-node ring, each arc is a Q-curve whose control point sits at the **outer intersection of the two adjacent tangent axes**, not at a node corner. Example for PLAN (top) → ACT (right): start = PLAN's right-edge midpoint, end = ACT's top-edge midpoint, control = `(ACT.x + ACT.w/2, PLAN.y + PLAN.h/2)`. This gives a pure horizontal tangent at departure and pure vertical at arrival, reading as a clean quarter-circle. Control at the node corner produces a squashed arc.
|
||||
- **Closed loops need a dashed framing ring**: four directed arcs alone force the reader to mentally connect them into a loop. A dashed circle centered on the visual center (radius slightly larger than center-to-inner-edge distance) makes the loop immediately readable. Draw the ring below the nodes; solid node fills mask where the ring crosses each node; the ring shows only between nodes.
|
||||
- **Chevron arrows, not filled triangles**: use `<path d="M2 1 L8 5 L2 9" fill="none" stroke=... stroke-width="1.5" stroke-linecap="round"/>`. A filled triangle reads as technical UI; an open two-stroke chevron reads as editorial schematic. kami defaults to chevron. **WeasyPrint does not support `<marker orient="auto">`**: all markers render at 0° (pointing right). The fix is to skip `<marker>` and draw each arrowhead as a manual chevron `<path>` with hardcoded direction (see production.en.md #15).
|
||||
|
||||
### Color token map
|
||||
|
||||
Shared tokens across the three diagrams, mapping directly to kami's design system:
|
||||
|
||||
| SVG role | kami token | Value |
|
||||
|---|---|---|
|
||||
| Canvas | `--parchment` | `#f5f4ed` |
|
||||
| Standard node fill | (white) | `#ffffff` |
|
||||
| Standard node stroke | `--near-black` | `#141413` |
|
||||
| Store node fill | near-black 5% | `rgba(20,20,19,0.05)` |
|
||||
| Store node stroke | `--olive` | `#5e5d59` |
|
||||
| Cloud node fill | near-black 3% | `rgba(20,20,19,0.03)` |
|
||||
| Cloud node stroke | near-black 30% | `rgba(20,20,19,0.30)` |
|
||||
| External node fill | olive 8% | `rgba(94,93,89,0.08)` |
|
||||
| External node stroke | `--stone` | `#87867f` |
|
||||
| **Focal fill** | `--brand-tint` | `#EEF2F7` |
|
||||
| **Focal stroke** | `--brand` | `#1B365D` |
|
||||
| Standard arrow | `--olive` | `#5e5d59` |
|
||||
| Focal arrow | `--brand` | `#1B365D` |
|
||||
| Primary text | `--near-black` | `#141413` |
|
||||
| Secondary text | `--olive` | `#5e5d59` |
|
||||
| Tertiary text / small mono label | `--stone` | `#87867f` |
|
||||
|
||||
Don't add a fourth state ("warning amber", "success green"). kami has one accent.
|
||||
|
||||
---
|
||||
|
||||
## 4. AI-slop anti-patterns
|
||||
|
||||
Scan for these when drawing or reviewing:
|
||||
|
||||
| Anti-pattern | Why it fails |
|
||||
|---|---|
|
||||
| Dark mode + cyan / purple glow | Cheap "technical" signifier with no design decision |
|
||||
| All nodes identical size | Destroys hierarchy |
|
||||
| JetBrains Mono as the universal "dev" font | Mono is for technical content (ports, URLs, fields). Names go in sans. |
|
||||
| Legend floating inside the diagram area | Collides with nodes |
|
||||
| Arrow labels without a masking rect | Line bleeds through the text |
|
||||
| Vertical `writing-mode` text on arrows | Unreadable |
|
||||
| Three equal-width summary cards as a default | Template feel. Vary widths. |
|
||||
| `box-shadow` on anything | kami only permits ring / whisper |
|
||||
| `rounded-2xl` / border-radius above 10px | Max 6-10px. Beyond, it starts to look like App Store chrome. |
|
||||
| Ink Blue on every "important" node | Focal rule is 1-2, not a signaling system |
|
||||
| Emoji icons 🚀 📊 💡 | Disaster |
|
||||
| Gradient backgrounds | kami forbids them |
|
||||
| Focal color contradicts the caption's claim | Caption says "Simple **core**", but the ACT node is painted ink-blue — two focals competing. Focal color must match the word emphasized (`<span class="hl">`) in the caption |
|
||||
| Cycle diagram with a dashed ring AND four directed arcs | Same loop drawn twice; reader thinks there are two flows |
|
||||
| SVG text clipped at the viewBox top | `text` y is the baseline; cap letters extend above y=0. Pad the top by font-size × 1.2 or adjust the viewBox |
|
||||
| 5-10px gap between arrow endpoint and node edge | Reads as "arrow floating in space". Anchor endpoints to exact `box.x / box.x+w / box.y / box.y+h` |
|
||||
| Per-node custom widths within one diagram | Four steps at widths 60 / 76 / 80 / 100 feel hand-patched. Small diagram: 2 tiers. Large: 3 tiers. That's the full budget |
|
||||
| Porting an external diagram with one accent color per node type (purple/amber/green/red) | kami has one accent. When adapting external diagrams, migrate the focal to whichever element the caption's `<span class="hl">` emphasizes; concentrate color there, keep all other nodes neutral |
|
||||
| Ring diagram: every node is a single word, center is empty | Four labeled boxes looping with no anchor. Either add a subtitle to each node or place one line of text at the center (exit condition, LOC count, etc.). Pick one. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Common pairings
|
||||
|
||||
### Technical white paper
|
||||
- Architecture (system overview) + built-in timeline (from long-doc)
|
||||
- One architecture diagram per chapter, maximum. If you want two, the chapter is covering two topics and should split.
|
||||
|
||||
### Portfolio project page
|
||||
- Quadrant (competitive positioning) or architecture (the layer you owned)
|
||||
- **Not every project needs a diagram.** Only when the diagram says something prose can't.
|
||||
|
||||
### One-pager
|
||||
- Quadrant (priority) or flowchart (decision path)
|
||||
- One diagram only. If you're tempted to add a second, kill the weaker one.
|
||||
|
||||
### Resume
|
||||
- **No diagrams.** Resume real-estate costs more than diagrams. Rare exception: a URL to a portfolio diagram when showing system-level capability.
|
||||
|
||||
### Slides
|
||||
- One diagram per slide, max. The diagram is the body. Text is caption, not a sidebar.
|
||||
|
||||
---
|
||||
|
||||
## 6. Build / preview
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py diagram-architecture
|
||||
python3 scripts/build.py diagram-flowchart
|
||||
python3 scripts/build.py diagram-quadrant
|
||||
|
||||
# or all
|
||||
python3 scripts/build.py
|
||||
```
|
||||
|
||||
Or just open `assets/diagrams/*.html` in a browser.
|
||||
|
||||
---
|
||||
|
||||
## 7. Credit
|
||||
|
||||
This capability is inspired by Cathryn Lavery's [diagram-design](https://github.com/cathrynlavery/diagram-design) (a Claude Code skill with 13 editorial diagram types). kami borrowed the **approach** (inline SVG, semantic tokens, complexity budget, anti-slop table). Not the full catalog. Thirteen types would bloat kami; three are enough for every document type kami serves.
|
||||
@@ -0,0 +1,173 @@
|
||||
# Diagrams
|
||||
|
||||
kami 的绘图能力。**不重新造 13 种图**,只做 3 种最通用的、能嵌进 long-doc / portfolio / slides 里增强论述的图:架构、流程、象限。皮肤 100% 沿用 kami 设计语言(parchment + 油墨蓝 + 暖灰),不引入第二种设计系统。
|
||||
|
||||
所有图都是**自包含的 HTML + inline SVG**。没有 Mermaid、没有 JS、没有构建步骤。要么作为独立页面浏览,要么把 `<svg>...</svg>` 块整段复制进 long-doc 的 `<figure>` 里。
|
||||
|
||||
---
|
||||
|
||||
## 1. 选择表
|
||||
|
||||
| 你要表达的是… | 选这个 | 模板 |
|
||||
|---|---|---|
|
||||
| 系统组件 + 它们之间的连接 | **架构图** | `assets/diagrams/architecture.html` |
|
||||
| 决策分支 · "如果 A 则 B,否则 C" | **流程图** | `assets/diagrams/flowchart.html` |
|
||||
| 两个维度上的定位 / 优先级 | **象限图** | `assets/diagrams/quadrant.html` |
|
||||
|
||||
不在这三种里的情况:
|
||||
- **时序 / 阶段**:用 long-doc 自带的 timeline 组件(resume.html / one-pager.html 里都有)。不新造图。
|
||||
- **对比两件事**:用表格。三列就能说清的事不要画图。
|
||||
- **hierarchy / 缩进列表**:markdown 嵌套 ul 就够。
|
||||
- **一个方框一个标签**:删掉方框,直接写一句话。
|
||||
|
||||
### 绘图前的质问
|
||||
|
||||
> 如果把这张图换成一段好写的话,读者学到的会少吗?
|
||||
|
||||
答"不会"就别画。图是给"层次、方向、量级"加信号的,不是给文字加装饰的。
|
||||
|
||||
---
|
||||
|
||||
## 2. 克制密度
|
||||
|
||||
**目标密度 4/10**。够表达完整系统,但读者不需要图例指南就能看懂。
|
||||
|
||||
- 节点数 > 9 -> 这不是一张图,是两张
|
||||
- 两个永远一起出现的节点 -> 合并成一个
|
||||
- 有线但层级从位置就能读出来 -> 去掉这条线
|
||||
- 5 个节点都是油墨蓝 -> 你没想清楚什么是焦点
|
||||
|
||||
**焦点规则**:一张图 1-2 个焦点(`#1B365D` + `#EEF2F7` 填充)。其他全走中性色。焦点的意义来自对比,不是数量。
|
||||
|
||||
---
|
||||
|
||||
## 3. 嵌入 long-doc / portfolio 的姿势
|
||||
|
||||
### 独立浏览
|
||||
|
||||
直接打开 `assets/diagrams/architecture.html`(或 flowchart / quadrant)。每份都是完整 HTML,包含标题 + SVG + caption。
|
||||
|
||||
### 嵌入到 kami 文档里
|
||||
|
||||
从模板 HTML 里**只抠 `<svg>...</svg>` 块**(不要带 frame / h1 / eyebrow),丢进 long-doc 的 `<figure>` 里:
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- 从 architecture.html 里复制的 svg 内容 -->
|
||||
</svg>
|
||||
<figcaption>图 1 · {{简短描述,编辑风}}</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
long-doc.html 的 `<style>` 已经定义好 figure / figcaption 样式,不需要额外 CSS。
|
||||
|
||||
### 改动节点 / 文字
|
||||
|
||||
直接改 SVG 里的 `<text>` 和 `<rect>` 坐标。规矩:
|
||||
|
||||
- **所有坐标、宽度、间距都能被 4 整除**。这一条是防 AI-slop 的底线,违反之后图就会开始像"差不多就行"。
|
||||
- 节点宽度:128 / 144 / 160(三档够用,别再加)。小图(viewBox 宽 < 360)允许缩到 2 档,但仍限定 2 档,不要每个节点量体裁衣。
|
||||
- 节点高度:32(pill)/ 64(标准)
|
||||
- 字号:7(小标签 mono)/ 9(sublabel mono)/ 12(name sans)
|
||||
- **箭头端点落在节点边缘**:起点 `(box.x + box.w, box.y + box.h/2)`、终点 `(box.x, box.y + box.h/2)`,不能"大概靠近"。悬空 10px 人眼就看得出来。
|
||||
- **SVG 顶部文字留白**:SVG 的 `<text y="…">` 是 baseline 位置。y 必须 ≥ font-size × 1.2,否则字母顶部伸出 viewBox 被裁(典型是 "TOOLS" 变 "TOULS")。要么放宽 viewBox 顶部、要么把 y 加到安全区。
|
||||
- **环形图的弧线控制点**:四节点闭环,每条弧用 Q-curve,控制点落在**两节点切线轴的外侧交点**,不是节点的角。例如 PLAN(上)→ ACT(右)这段弧:起点是 PLAN 右边缘中点、终点是 ACT 顶边缘中点,控制点是 `(ACT.x + ACT.w/2, PLAN.y + PLAN.h/2)`。这样出发切线是纯水平、到达切线是纯垂直,弧形读起来像标准四分之一圆。控制点落在节点角上会把弧挤扁。
|
||||
- **闭环图加一层 dashed 圆框**:四条有向弧单独存在时,读者要在脑里自己拼成环。加一个 dashed 圆圈(圆心在视觉中心,半径略大于中心到节点内缘的距离),瞬间说清"这是一个闭环"。圆画在节点下面,节点实色填充遮住圆穿过节点那一段,只在节点之间可见。
|
||||
- **chevron 箭头而非实心三角**:`<path d="M2 1 L8 5 L2 9" fill="none" stroke=... stroke-width="1.5" stroke-linecap="round"/>`。实心三角读起来像技术 UI,两笔 open chevron 是编辑感。kami 默认用 chevron。**注意 WeasyPrint 不支持 `<marker orient="auto">`**,marker 方向全部固定为 0°(朝右)。解法是不用 marker,每个箭头端点手动画一条 chevron `<path>`,方向写死(详见 production.md #15)。
|
||||
|
||||
### 颜色 token 映射
|
||||
|
||||
三种图共用的 token 角色,直接对应 kami 设计系统:
|
||||
|
||||
| SVG 角色 | kami token | 值 |
|
||||
|---|---|---|
|
||||
| 画布 | `--parchment` | `#f5f4ed` |
|
||||
| 标准节点填充 | (白)| `#ffffff` |
|
||||
| 标准节点描边 | `--near-black` | `#141413` |
|
||||
| Store 节点填充 | near-black 5% | `rgba(20,20,19,0.05)` |
|
||||
| Store 节点描边 | `--olive` | `#5e5d59` |
|
||||
| Cloud 节点填充 | near-black 3% | `rgba(20,20,19,0.03)` |
|
||||
| Cloud 节点描边 | near-black 30% | `rgba(20,20,19,0.30)` |
|
||||
| External 节点填充 | olive 8% | `rgba(94,93,89,0.08)` |
|
||||
| External 节点描边 | `--stone` | `#87867f` |
|
||||
| **焦点填充** | `--brand-tint` | `#EEF2F7` |
|
||||
| **焦点描边** | `--brand` | `#1B365D` |
|
||||
| 标准箭头 | `--olive` | `#5e5d59` |
|
||||
| 焦点箭头 | `--brand` | `#1B365D` |
|
||||
| 文本主色 | `--near-black` | `#141413` |
|
||||
| 文本辅助色 | `--olive` | `#5e5d59` |
|
||||
| 文本三级色 / 小 mono 标签 | `--stone` | `#87867f` |
|
||||
|
||||
别加第四种状态(比如 "warning 橙" 或 "success 绿")。kami 只有一种强调色。
|
||||
|
||||
---
|
||||
|
||||
## 4. AI slop 反模式(看到这些就知道是 AI 默认输出)
|
||||
|
||||
写图 / review 图的时候扫一遍:
|
||||
|
||||
| 反模式 | 为什么失败 |
|
||||
|---|---|
|
||||
| 深色底 + 青紫色辉光 | "科技感"的廉价符号,没有设计决策 |
|
||||
| 所有节点一样大小 | 消除层级 |
|
||||
| JetBrains Mono 当通用"开发"字体 | Mono 只给技术内容(端口、URL、字段类型)。名字用 sans |
|
||||
| 图例浮在图内部 | 和节点冲撞 |
|
||||
| 箭头文字没有遮罩矩形 | 文字被线穿过去 |
|
||||
| 箭头上竖排文字 | 读不懂 |
|
||||
| 默认 3 个等宽总结卡片 | 模板感,宽度需要有变化 |
|
||||
| 任何元素加 shadow | kami 只用 ring / whisper shadow |
|
||||
| `rounded-2xl` / `border-radius: 16px+` 给节点 | 最大 6-10 px 圆角,否则开始像 App Store UI |
|
||||
| 油墨蓝撒满每个"重要"节点 | 焦点规则是 1-2 个,不是信号系统 |
|
||||
| 表情符号 🚀 📊 💡 做图标 | 灾难 |
|
||||
| 渐变背景 | kami 禁用 |
|
||||
| 焦点与标题论点不一致 | 标题说 "Simple **core**",图里却把 ACT 染成油墨蓝,两个 focal 打架。focal 的颜色必须和 caption 里用 `<span class="hl">` 强调的那个词对应 |
|
||||
| 循环图里既画虚线环、又画四段有向弧 | 同一条回路被画两遍,读者以为有两套流 |
|
||||
| SVG 文字顶部被 viewBox 裁切 | text 的 y 是 baseline,字母顶部伸到 y 负值区域。给顶部留 font-size × 1.2 的 padding,或改 viewBox |
|
||||
| 箭头起点/终点与节点边缘有 5-10px 缝隙 | 看起来像"箭头悬在半空"。endpoints 要锚到 `box.x / box.x+w / box.y / box.y+h` 的精确值 |
|
||||
| 同一张图里节点大小各自量体裁衣 | 四个步骤的宽度 60/76/80/100 给人"手工凑出来"的感觉。小图 2 档、大图 3 档,够了 |
|
||||
| 参考外部图表时把每类节点着一个 accent 色(紫/琥珀/绿/红) | kami 只有一种强调色。移植外部图时,把 focal 迁移到 caption 里 `<span class="hl">` 强调的那个词上,把颜色集中在那一个元素,不要铺给每个节点 |
|
||||
| 环形图所有节点都是单词、中心什么都没有 | 四个框轮流转,读者没有锚。要么节点带副标题,要么环心放一句话("20 LOC"、exit condition 等),二选一 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见组合
|
||||
|
||||
### 技术白皮书
|
||||
- 架构图(系统全景)+ 时序组件(long-doc 自带的 timeline)
|
||||
- 每章最多 1 张架构图。超过 1 张说明这一章讲了两个东西,应该拆章
|
||||
|
||||
### 作品集项目页
|
||||
- 象限图(竞争定位)或架构图(你负责的那一层)
|
||||
- **不要**每个项目都画图。只在这张图能说清而文字说不清的时候才画
|
||||
|
||||
### 一页纸方案
|
||||
- 象限图(优先级) 或 流程图(决策路径)
|
||||
- 只能有 1 张。如果想放 2 张,砍掉文字凑图的冲动,选最关键的那张
|
||||
|
||||
### 简历
|
||||
- **不画图**。简历空间比图值钱。极少数例外:展示系统架构能力时,附一个 URL 指到 portfolio
|
||||
|
||||
### Slides
|
||||
- 每页最多 1 张图。图占屏幕主体,文字是 caption,不是另外一栏
|
||||
|
||||
---
|
||||
|
||||
## 6. 构建 / 预览
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py diagram-architecture
|
||||
python3 scripts/build.py diagram-flowchart
|
||||
python3 scripts/build.py diagram-quadrant
|
||||
|
||||
# 或全部
|
||||
python3 scripts/build.py
|
||||
```
|
||||
|
||||
浏览器里打开 `assets/diagrams/*.html` 也能直接看。
|
||||
|
||||
---
|
||||
|
||||
## 7. 参考
|
||||
|
||||
这套能力的灵感来自 Cathryn Lavery 的 [diagram-design](https://github.com/cathrynlavery/diagram-design)(Claude Code skill,13 种编辑风 diagram)。kami 只吸收了它的**做法**(inline SVG、语义 token、复杂度预算、反 AI-slop 表),没有整体移植——13 种会把 kami 撑开,3 种够用于所有 kami 文档场景。
|
||||
@@ -0,0 +1,484 @@
|
||||
# Production (Build · Verify · Troubleshoot)
|
||||
|
||||
The engineering runbook for kami: from HTML / Python templates to PDF / PPTX deliverables. Four parts: **HTML -> PDF** · **Python -> PPTX** · **Verify & Debug** · **15 known pitfalls**.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 · HTML -> PDF (WeasyPrint)
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
pip install weasyprint pypdf --break-system-packages --quiet
|
||||
```
|
||||
|
||||
Linux first-time:
|
||||
```bash
|
||||
apt install -y libpango-1.0-0 libpangoft2-1.0-0 fonts-noto-cjk
|
||||
```
|
||||
|
||||
### Generate
|
||||
|
||||
```python
|
||||
from weasyprint import HTML
|
||||
HTML('doc.html').write_pdf('output.pdf')
|
||||
```
|
||||
|
||||
**CWD matters**: `@font-face { src: url("xxx.ttf") }` uses relative paths, so run from the directory containing the font file.
|
||||
|
||||
```bash
|
||||
cd /path/to/html-and-font
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
```
|
||||
|
||||
### Fonts
|
||||
|
||||
**Most stable setup**: font file alongside HTML, `@font-face` with relative path.
|
||||
|
||||
```html
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("Newsreader-VariableFont.ttf");
|
||||
}
|
||||
body { font-family: "Newsreader", serif; }
|
||||
</style>
|
||||
```
|
||||
|
||||
**No commercial font available**: fallback chains are embedded in every template.
|
||||
|
||||
```css
|
||||
/* English */
|
||||
font-family: "Newsreader", "Source Serif 4", "Charter",
|
||||
Georgia, serif;
|
||||
|
||||
/* Chinese */
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
```
|
||||
|
||||
**Font fallback affects page count**. Any font swap requires re-running the page-count check. If it overflows: lower `font-size` first, then tighten margins, then cut content.
|
||||
|
||||
### Page spec
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4; /* or 210mm 297mm / A4 landscape / 13in 10in */
|
||||
margin: 20mm 22mm;
|
||||
background: #f5f4ed; /* extend past margins to avoid white printed edge */
|
||||
}
|
||||
```
|
||||
|
||||
### Headers & footers
|
||||
|
||||
```css
|
||||
@page {
|
||||
@top-right {
|
||||
content: counter(page);
|
||||
font-family: serif; font-size: 9pt; color: #87867f;
|
||||
}
|
||||
@bottom-center {
|
||||
content: "{{DOC_NAME}} · {{AUTHOR}}";
|
||||
font-size: 8.5pt; color: #87867f;
|
||||
}
|
||||
}
|
||||
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
```
|
||||
|
||||
### WeasyPrint support matrix
|
||||
|
||||
| Solid | Partial | Unsupported |
|
||||
|---|---|---|
|
||||
| CSS Grid / Flexbox | CSS filter / transform (partial) | JavaScript |
|
||||
| `@page` rules | inline SVG (some attrs) | `position: sticky` |
|
||||
| `@font-face` | gradients (slow, use sparingly) | CSS animations / transitions |
|
||||
| `break-before` / `break-inside: avoid` | | |
|
||||
| CSS variables `var(--name)` | | |
|
||||
| `::before` / `::after` | | |
|
||||
|
||||
---
|
||||
|
||||
## Part 2 · Python -> PPTX (python-pptx)
|
||||
|
||||
PPT shares the same design language but the medium (screen, 16:9, one-idea-per-slide) changes the details: fonts larger, layouts more rigid.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
pip install python-pptx --break-system-packages --quiet
|
||||
```
|
||||
|
||||
### Dimensions
|
||||
|
||||
- **16:9 widescreen** (preferred): 13.33 × 7.5 inch
|
||||
- **4:3 traditional**: 10 × 7.5 inch
|
||||
- **Safe zone**: 0.5 inch margin on all sides (projector crop), plus 0.3 inch at bottom for page number
|
||||
|
||||
### Palette (1:1 with design.en.md)
|
||||
|
||||
```python
|
||||
from pptx.dml.color import RGBColor
|
||||
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
IVORY = RGBColor(0xfa, 0xf9, 0xf5)
|
||||
BRAND = RGBColor(0xc9, 0x64, 0x42)
|
||||
NEAR_BLACK = RGBColor(0x14, 0x14, 0x13)
|
||||
DARK_WARM = RGBColor(0x3d, 0x3d, 0x3a)
|
||||
OLIVE = RGBColor(0x5e, 0x5d, 0x59)
|
||||
STONE = RGBColor(0x87, 0x86, 0x7f)
|
||||
BORDER_WARM = RGBColor(0xe8, 0xe6, 0xdc)
|
||||
TAG_BG = RGBColor(0xed, 0xd9, 0xce)
|
||||
```
|
||||
|
||||
### Type (bigger than print, optimized for projection)
|
||||
|
||||
| Role | Size | Font |
|
||||
|---|---|---|
|
||||
| Title | 48pt | Serif 500 |
|
||||
| Subtitle | 24pt | Sans 400 |
|
||||
| H2 chapter | 32pt | Serif 500 |
|
||||
| H3 subtitle | 20pt | Serif 500 |
|
||||
| Body | 18pt | Sans 400 |
|
||||
| Caption | 14pt | Sans 400 |
|
||||
| Footer | 12pt | Sans 400 |
|
||||
|
||||
English stack on PowerPoint:
|
||||
- Serif: `Newsreader` -> `Charter` -> `Georgia`
|
||||
- Sans: `Inter` -> `Helvetica Neue` -> `Arial`
|
||||
|
||||
### Eight standard layouts
|
||||
|
||||
1. **Cover**: parchment background, centered display title + brand-colored short line + subtitle / author / date
|
||||
2. **Contents**: parchment, left-aligned `01 Chapter title` (number serif brand-colored)
|
||||
3. **Chapter divider**: full brand ink-blue background, centered white title — the **only** fully chromatic slide in the deck
|
||||
4. **Content slide**: eyebrow (sans stone) + core claim (serif near-black) + brand line + body (sans dark-warm)
|
||||
5. **Data slide**: top takeaway + 2-4 metric cards (big number serif brand + small label sans olive)
|
||||
6. **Comparison**: two columns with a 0.5pt warm-gray divider
|
||||
7. **Quote**: parchment, minimal, centered serif quote + `— Source`
|
||||
8. **Closing**: parchment, centered "Thank you / Q&A / Contact"
|
||||
|
||||
### Script skeleton
|
||||
|
||||
Full working example in `assets/templates/slides-en.py`. Key bits:
|
||||
|
||||
```python
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import PP_ALIGN
|
||||
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
BRAND = RGBColor(0xc9, 0x64, 0x42)
|
||||
SERIF = "Newsreader"
|
||||
SANS = "Inter"
|
||||
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(13.33)
|
||||
prs.slide_height = Inches(7.5)
|
||||
|
||||
def blank_slide():
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
|
||||
0, 0, prs.slide_width, prs.slide_height)
|
||||
bg.fill.solid(); bg.fill.fore_color.rgb = PARCHMENT
|
||||
bg.line.fill.background(); bg.shadow.inherit = False
|
||||
return slide
|
||||
```
|
||||
|
||||
### PPT notes
|
||||
|
||||
1. **One idea per slide** — if it runs over three lines, split it
|
||||
2. **No default PowerPoint template** — it's cool-blue-gray, clashes with parchment
|
||||
3. **Animations**: don't. Parchment is a print aesthetic, not a SaaS demo. At most `fade`
|
||||
4. **Export to PDF** for sharing — cross-machine consistency is better than .pptx
|
||||
- macOS: Keynote -> Export to PDF
|
||||
- Linux: `libreoffice --headless --convert-to pdf output.pptx`
|
||||
|
||||
---
|
||||
|
||||
## Part 3 · Verify & Debug
|
||||
|
||||
### The three-step loop (mandatory after every change)
|
||||
|
||||
```bash
|
||||
# 1. Generate
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
|
||||
# 2. Page count
|
||||
python3 -c "from pypdf import PdfReader; print(len(PdfReader('out.pdf').pages))"
|
||||
|
||||
# 3. Visual inspect (when in doubt)
|
||||
pdftoppm -png -r 300 out.pdf inspect
|
||||
```
|
||||
|
||||
**Not verified = not done.**
|
||||
|
||||
### Did the font actually load?
|
||||
|
||||
```bash
|
||||
pdffonts output.pdf
|
||||
```
|
||||
|
||||
If the output shows `DejaVuSerif` / `Bitstream Vera` — your specified font didn't load, fell through to system ultimate fallback. Expected: `Newsreader`, `Charter`, or `TsangerJinKai02`.
|
||||
|
||||
### One-step build + validate
|
||||
|
||||
Project script `scripts/build.py` is the productized version of the three-step loop:
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py # all 12 examples
|
||||
python3 scripts/build.py resume-en # one target + page count + fonts
|
||||
python3 scripts/build.py --check # scan for CSS rule violations
|
||||
```
|
||||
|
||||
### Hi-res visual inspection
|
||||
|
||||
```bash
|
||||
pdftoppm -png -r 160 output.pdf preview # standard
|
||||
pdftoppm -png -r 300 output.pdf preview # detail bugs
|
||||
pdftoppm -png -r 400 output.pdf preview # extreme detail (tag double-rect check)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 4 · 15 known pitfalls
|
||||
|
||||
Every entry below came from a real failure. Check here first when something looks wrong.
|
||||
|
||||
### 1. Tag / Badge double-rectangle bug (the worst)
|
||||
|
||||
**Symptom**: PDFs show two concentric rectangles on tag backgrounds at zoom — an outer softer one and an inner tighter one. Especially visible on mobile PDF viewers.
|
||||
|
||||
**Root cause**: WeasyPrint renders `rgba(..., 0.xx)` by compositing the **padding area** and the **glyph pixel area** independently. Glyph anti-aliasing stacks alpha differently, creating the second visible edge.
|
||||
|
||||
**Fix**: Tag backgrounds must be solid hex. No rgba.
|
||||
|
||||
```css
|
||||
/* ❌ */ .tag { background: rgba(201, 100, 66, 0.18); }
|
||||
/* ✅ */ .tag { background: #E4ECF5; }
|
||||
```
|
||||
|
||||
**rgba -> solid conversion** (parchment `#f5f4ed` base + ink-blue `#1B365D`):
|
||||
|
||||
| rgba alpha | Solid hex |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← default |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
Formula: `solid_channel = base + (foreground - base) × alpha`. Different base colors (e.g. ivory) need re-computing.
|
||||
|
||||
**Want "breathing" texture?** Use `linear-gradient` — the whole tag rasterizes as one bitmap, no alpha compositing:
|
||||
|
||||
```css
|
||||
.tag { background: linear-gradient(to right, #D6E1EE, #E4ECF5 70%, #EEF2F7); }
|
||||
```
|
||||
|
||||
**Aesthetic warning**: gradients work engineering-wise but usually oversell the tag. Priority order: lightest solid (`#EEF2F7`) > standard solid (`#E4ECF5`) > gradient (rarely). If the reader's eye lands on the tag background shape before the text inside — you went too far.
|
||||
|
||||
### 2. Thin border + radius = double circle
|
||||
|
||||
**Symptom**: `border: 0.4pt solid ...` + `border-radius: 2pt` shows two parallel arcs on zoom.
|
||||
|
||||
**Root cause**: WeasyPrint strokes border inner and outer paths separately when `< 1pt` + rounded corners — at thin widths they can't overlap.
|
||||
|
||||
**Fix (pick one)**:
|
||||
1. Use background fill instead (preferred, design-consistent)
|
||||
2. Border ≥ 1pt
|
||||
3. Drop `border-radius`
|
||||
|
||||
### 3. 2-page hard-limit overflow
|
||||
|
||||
For resume, one-pager, and other length-capped docs.
|
||||
|
||||
**Common causes**: font fallback, content added, font-size bumped by accident, line-height pushed from 1.4 to 1.6.
|
||||
|
||||
**Diagnose**: `pdffonts output.pdf` to verify what actually loaded.
|
||||
|
||||
**Fix (priority)**:
|
||||
1. Cut redundant qualifiers ("deeply researched" -> "researched")
|
||||
2. Merge related data points in the same section
|
||||
3. Drop non-essential items whole (not piecemeal)
|
||||
4. Reduce section spacing (use sparingly — affects global rhythm)
|
||||
5. Last resort: shrink font by 0.1-0.2pt
|
||||
|
||||
**Don't**: cut cover / education / timeline structural blocks; cut emphasis (resume becomes flat).
|
||||
|
||||
### 4. Font fallback causes page count inconsistency
|
||||
|
||||
**Symptom**: 2 pages locally, 4 pages in CI / on server.
|
||||
|
||||
**Root cause**: font file neither alongside HTML nor system-installed.
|
||||
|
||||
**Fix**:
|
||||
|
||||
```bash
|
||||
# Put .ttf alongside the HTML
|
||||
cp Newsreader.ttf workspace/
|
||||
|
||||
# Or system install (Linux)
|
||||
apt install fonts-noto-cjk
|
||||
mkdir -p ~/.fonts && cp *.ttf ~/.fonts/ && fc-cache -f
|
||||
```
|
||||
|
||||
### 5. CJK and Latin crowding (Chinese mode only)
|
||||
|
||||
**Symptom**: "125.4k GitHub Stars" — k and G feel glued.
|
||||
|
||||
**Wrong fixes**: hand-added ` ` / `margin-left: 2mm` (misaligns adjacent elements).
|
||||
|
||||
**Right fix**: separate spans with flex gap:
|
||||
|
||||
```html
|
||||
<div class="metric">
|
||||
<span class="metric-value">125.4k</span>
|
||||
<span class="metric-label">GitHub Stars</span>
|
||||
</div>
|
||||
```
|
||||
```css
|
||||
.metric { display: flex; align-items: baseline; gap: 6pt; }
|
||||
```
|
||||
|
||||
### 6. Full-width vs half-width spaces (Chinese mode)
|
||||
|
||||
- **Between Chinese characters**: U+3000 full-width space + `·` + space
|
||||
- **Between Latin words**: half-width space + `·` + space
|
||||
- **Mixed**: prefer flex gap, don't hand-type spaces
|
||||
|
||||
### 7. Thousands / percent / arrows — be consistent
|
||||
|
||||
| ✅ | ❌ |
|
||||
|---|---|
|
||||
| `5,000+` | `5000+` |
|
||||
| `90%` | `90 %` (pre-space) |
|
||||
| `->` | `->` / `->` |
|
||||
|
||||
Self-check:
|
||||
```bash
|
||||
grep -oE '->|->|⟶|⇒' doc.html | sort | uniq -c
|
||||
grep -oE '[0-9]{4,}' doc.html | sort -u
|
||||
```
|
||||
|
||||
### 8. Too much / too little emphasis
|
||||
|
||||
- Four or five ink-blue runs in one line -> visual fatigue, no focal point
|
||||
- Entire section with none -> flat, no scan handles
|
||||
|
||||
**Rule**: ≤ 2 emphases per line, ≥ 1 per section, only **quantifiable numbers or distinctive phrases** get highlighted — never adjectives.
|
||||
|
||||
Healthy ratio: one emphasis per 80-150 words.
|
||||
|
||||
### 9. `height: 100vh` doesn't work
|
||||
|
||||
**Symptom**: full-bleed cover using `height: 100vh` renders empty.
|
||||
|
||||
**Root cause**: viewport units are undefined in WeasyPrint's `@page` context.
|
||||
|
||||
**Fix**:
|
||||
|
||||
```css
|
||||
.cover {
|
||||
min-height: 257mm; /* A4 height 297 - 40mm margins */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
```
|
||||
|
||||
### 10. `break-inside` fails inside flex
|
||||
|
||||
**Symptom**: `.card { break-inside: avoid }` still splits across pages.
|
||||
|
||||
**Root cause**: WeasyPrint's flex/grid `break-inside` support on direct children is incomplete.
|
||||
|
||||
**Fix**: wrap the flex item in an extra block:
|
||||
|
||||
```html
|
||||
<div class="row">
|
||||
<div class="card-wrapper"><div class="card">...</div></div>
|
||||
</div>
|
||||
```
|
||||
```css
|
||||
.row { display: flex; }
|
||||
.card-wrapper { break-inside: avoid; }
|
||||
```
|
||||
|
||||
### 11. Hide page number on the first page
|
||||
|
||||
```css
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
}
|
||||
```
|
||||
|
||||
### 12. Printed white margin around the page
|
||||
|
||||
**Symptom**: printing produces a white border even though `background` is set.
|
||||
|
||||
**Root cause**: default `@page background` only covers the content area, not the margin.
|
||||
|
||||
**Fix**:
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4; margin: 20mm;
|
||||
background: #f5f4ed; /* extends past margins */
|
||||
}
|
||||
```
|
||||
|
||||
### 13. Blurry images
|
||||
|
||||
**Symptom**: images in PDF look soft.
|
||||
|
||||
**Root cause**: WeasyPrint renders at source pixel density. A4 @ 300 dpi = 2480 × 3508 pixels.
|
||||
|
||||
**Fix**: source images at 2x or 3x.
|
||||
|
||||
### 14. Verification loop (catch-all)
|
||||
|
||||
```bash
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
python3 -c "from pypdf import PdfReader; print(len(PdfReader('out.pdf').pages))"
|
||||
pdftoppm -png -r 300 out.pdf inspect # when in doubt
|
||||
```
|
||||
|
||||
**Not verified = not done.**
|
||||
|
||||
### 15. SVG marker `orient="auto"` ignored
|
||||
|
||||
**Symptom**: SVG arrows using `<marker orient="auto">` or `orient="auto-start-reverse"` all point right (the marker's default drawing direction), regardless of the path's tangent angle.
|
||||
|
||||
**Root cause**: WeasyPrint's SVG renderer does not support the `orient="auto"` attribute on markers. The marker is always drawn at 0°.
|
||||
|
||||
**Fix**: skip `<marker>` entirely. Draw each arrowhead as a manual chevron `<path>` at the endpoint, with the direction hardcoded.
|
||||
|
||||
```xml
|
||||
<!-- Bad: marker arrow, WeasyPrint renders all pointing right -->
|
||||
<defs>
|
||||
<marker id="a" orient="auto" ...>
|
||||
<path d="M2 1L8 5L2 9" .../>
|
||||
</marker>
|
||||
</defs>
|
||||
<path d="M 440 52 Q 568 52 568 244" marker-end="url(#a)"/>
|
||||
|
||||
<!-- Good: manual chevron, direction per endpoint -->
|
||||
<path d="M 440 52 Q 568 52 568 244" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<path d="M 560 236 L 568 244 L 576 236" fill="none" stroke="#5e5d59" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
```
|
||||
|
||||
Chevron templates (tip at endpoint, 8px arm length):
|
||||
|
||||
| Direction | chevron path |
|
||||
|---|---|
|
||||
| down | `M (x-8) (y-8) L x y L (x+8) (y-8)` |
|
||||
| left | `M (x+8) (y-8) L x y L (x+8) (y+8)` |
|
||||
| up | `M (x-8) (y+8) L x y L (x+8) (y+8)` |
|
||||
| right | `M (x-8) (y-8) L x y L (x-8) (y+8)` |
|
||||
@@ -0,0 +1,531 @@
|
||||
# Production(生成 · 验证 · 排错)
|
||||
|
||||
这份文档覆盖 kami 的工程执行:从 HTML/Python 模板到 PDF/PPTX 成品的完整流程。分四部分:**HTML -> PDF** · **Python -> PPTX** · **验证与调试** · **15 条踩坑**。
|
||||
|
||||
---
|
||||
|
||||
## Part 1 · HTML -> PDF(WeasyPrint)
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
pip install weasyprint pypdf --break-system-packages --quiet
|
||||
```
|
||||
|
||||
Linux 初次使用可能需要:
|
||||
```bash
|
||||
apt install -y libpango-1.0-0 libpangoft2-1.0-0 fonts-noto-cjk
|
||||
```
|
||||
|
||||
### 生成
|
||||
|
||||
```python
|
||||
from weasyprint import HTML
|
||||
HTML('doc.html').write_pdf('output.pdf')
|
||||
```
|
||||
|
||||
**注意 CWD**:HTML 里 `@font-face { src: url("xxx.ttf") }` 是相对路径,必须在**包含字体文件的目录**执行。
|
||||
|
||||
```bash
|
||||
cd /path/to/html-and-font
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
```
|
||||
|
||||
### 字体处理
|
||||
|
||||
**最稳的做法**:字体文件和 HTML 同目录,`@font-face` 用相对路径。
|
||||
|
||||
```html
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("TsangerJinKai02-W04.ttf");
|
||||
}
|
||||
body { font-family: "TsangerJinKai02", serif; }
|
||||
</style>
|
||||
```
|
||||
|
||||
**商业字体不可得时**,fallback 链已内嵌在所有模板里:
|
||||
```css
|
||||
font-family: "TsangerJinKai02",
|
||||
"Source Han Serif SC", "Noto Serif CJK SC",
|
||||
"Songti SC", Georgia, serif;
|
||||
```
|
||||
|
||||
**字体 fallback 影响页数**:换字体必须重新跑页数验证。溢出时优先调 `font-size`,再调 margin,最后砍内容。
|
||||
|
||||
### 页面规格
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4; /* 或 210mm 297mm / A4 landscape / 13in 10in */
|
||||
margin: 20mm 22mm;
|
||||
background: #f5f4ed; /* 背景延伸到 margin 外,避免打印白边 */
|
||||
}
|
||||
```
|
||||
|
||||
### 页眉页脚
|
||||
|
||||
```css
|
||||
@page {
|
||||
@top-right {
|
||||
content: counter(page);
|
||||
font-family: serif; font-size: 9pt; color: #87867f;
|
||||
}
|
||||
@bottom-center {
|
||||
content: "{{文档名}} · {{作者}}";
|
||||
font-size: 8.5pt; color: #87867f;
|
||||
}
|
||||
}
|
||||
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
```
|
||||
|
||||
### WeasyPrint 支持矩阵
|
||||
|
||||
| 支持良好 | 支持有限 | 不支持 |
|
||||
|---|---|---|
|
||||
| CSS Grid / Flexbox | CSS filter / transform(部分) | JavaScript |
|
||||
| `@page` 规则 | inline SVG(部分属性) | `position: sticky` |
|
||||
| `@font-face` | gradient(性能差,少用) | CSS 动画 / transition |
|
||||
| `break-before` / `break-inside: avoid` | | |
|
||||
| CSS 变量 `var(--name)` | | |
|
||||
| 伪元素 `::before` `::after` | | |
|
||||
|
||||
---
|
||||
|
||||
## Part 2 · Python -> PPTX(python-pptx)
|
||||
|
||||
PPT 审美和 PDF **共享同一套设计语言**,但因为载体是屏幕、16:9、一屏一信息,字号加大、版式固化。
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
pip install python-pptx --break-system-packages --quiet
|
||||
```
|
||||
|
||||
### 尺寸
|
||||
|
||||
- **16:9 宽屏**(推荐):13.33 × 7.5 inch
|
||||
- **4:3 传统**:10 × 7.5 inch
|
||||
- **安全区**:四周 0.5 inch 不放内容,底部额外 0.3 inch 给页码
|
||||
|
||||
### 色板(1:1 对应 design.md)
|
||||
|
||||
```python
|
||||
from pptx.dml.color import RGBColor
|
||||
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
IVORY = RGBColor(0xfa, 0xf9, 0xf5)
|
||||
BRAND = RGBColor(0xc9, 0x64, 0x42)
|
||||
NEAR_BLACK = RGBColor(0x14, 0x14, 0x13)
|
||||
DARK_WARM = RGBColor(0x3d, 0x3d, 0x3a)
|
||||
OLIVE = RGBColor(0x5e, 0x5d, 0x59)
|
||||
STONE = RGBColor(0x87, 0x86, 0x7f)
|
||||
BORDER_WARM = RGBColor(0xe8, 0xe6, 0xdc)
|
||||
TAG_BG = RGBColor(0xed, 0xd9, 0xce)
|
||||
```
|
||||
|
||||
### 字号(屏幕投影优先易读性,比 PDF 大)
|
||||
|
||||
| 角色 | 字号 | 字体 |
|
||||
|---|---|---|
|
||||
| Title | 44pt | Serif 500 |
|
||||
| Subtitle | 24pt | Sans 400 |
|
||||
| H2 章节 | 32pt | Serif 500 |
|
||||
| H3 小标题 | 20pt | Serif 500 |
|
||||
| Body | 18pt | Sans 400 |
|
||||
| Caption | 14pt | Sans 400 |
|
||||
| Footer | 12pt | Sans 400 |
|
||||
|
||||
中文字体栈:
|
||||
- Serif:`TsangerJinKai02` -> `Source Han Serif SC` -> `宋体`
|
||||
- Sans:`Source Han Sans SC` -> `PingFang SC` -> `微软雅黑`
|
||||
|
||||
### 8 种标准版式
|
||||
|
||||
1. **封面页**:Parchment 底,正中大标题 + 品牌色短线 + 副标题/作者/日期
|
||||
2. **目录页**:Parchment 底,左对齐 `01 章节标题`(数字 serif 品牌色)
|
||||
3. **章节首页**:油墨蓝 `#1B365D` 满屏,居中白色大字——deck 里唯一的彩色满屏
|
||||
4. **内容页**:小标题(sans stone)+ 核心论点(serif near-black)+ 品牌色短线 + 正文(sans dark-warm)
|
||||
5. **数据页**:顶部 takeaway + 下方 2-4 张 metric 卡(大数字 serif 品牌色 + 小标签 sans olive)
|
||||
6. **对比页**:左右两栏 + 中间 0.5pt 暖灰竖线
|
||||
7. **引用页**:Parchment 底极简,居中大号斜体引文 + `— 来源`
|
||||
8. **结束页**:Parchment 底,居中"谢谢 / Q&A / 联系方式"
|
||||
|
||||
### 脚本骨架
|
||||
|
||||
```python
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches, Pt
|
||||
from pptx.dml.color import RGBColor
|
||||
from pptx.enum.shapes import MSO_SHAPE
|
||||
from pptx.enum.text import PP_ALIGN
|
||||
|
||||
PARCHMENT = RGBColor(0xf5, 0xf4, 0xed)
|
||||
BRAND = RGBColor(0xc9, 0x64, 0x42)
|
||||
# ... 其他色见上表
|
||||
SERIF = "Source Han Serif SC"
|
||||
SANS = "Source Han Sans SC"
|
||||
|
||||
prs = Presentation()
|
||||
prs.slide_width = Inches(13.33)
|
||||
prs.slide_height = Inches(7.5)
|
||||
|
||||
def blank_slide():
|
||||
"""空白页,parchment 底"""
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
bg = slide.shapes.add_shape(
|
||||
MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height)
|
||||
bg.fill.solid(); bg.fill.fore_color.rgb = PARCHMENT
|
||||
bg.line.fill.background(); bg.shadow.inherit = False
|
||||
return slide
|
||||
|
||||
def add_text(slide, text, left, top, width, height,
|
||||
font=SANS, size=18, bold=False, italic=False,
|
||||
color=RGBColor(0x14,0x14,0x13), align=PP_ALIGN.LEFT):
|
||||
tb = slide.shapes.add_textbox(left, top, width, height)
|
||||
tf = tb.text_frame; tf.word_wrap = True
|
||||
tf.margin_left = tf.margin_right = 0
|
||||
tf.margin_top = tf.margin_bottom = 0
|
||||
p = tf.paragraphs[0]; p.alignment = align
|
||||
run = p.add_run(); run.text = text
|
||||
run.font.name = font; run.font.size = Pt(size)
|
||||
run.font.bold = bold; run.font.italic = italic
|
||||
run.font.color.rgb = color
|
||||
return tb
|
||||
|
||||
def add_line(slide, left, top, width, color=BRAND, weight_pt=1):
|
||||
line = slide.shapes.add_connector(1, left, top, left + width, top)
|
||||
line.line.color.rgb = color; line.line.width = Pt(weight_pt)
|
||||
return line
|
||||
|
||||
# 封面
|
||||
s = blank_slide()
|
||||
add_text(s, "文档标题", Inches(1.5), Inches(3), Inches(10.33), Inches(1),
|
||||
font=SERIF, size=44)
|
||||
add_line(s, Inches(1.5), Inches(4), Inches(2))
|
||||
add_text(s, "副标题 · 副说明", Inches(1.5), Inches(4.2), Inches(10.33), Inches(0.6),
|
||||
font=SANS, size=18, color=RGBColor(0x5e,0x5d,0x59))
|
||||
|
||||
prs.save('output.pptx')
|
||||
```
|
||||
|
||||
完整版见 `assets/templates/slides.py`。
|
||||
|
||||
### PPT 注意事项
|
||||
|
||||
1. **一页一个核心信息**:超过 3 段文字就拆分
|
||||
2. **不用自带 Template**:PowerPoint default 是冷蓝灰,和 parchment 冲突
|
||||
3. **动画**:不加。Parchment 风格是印刷品,不是 SaaS 演示。最多允许 fade
|
||||
4. **导出 PDF**:分享时推荐导出 PDF,跨机器一致性比 .pptx 高
|
||||
- macOS:Keynote 打开 -> Export to PDF
|
||||
- Linux:`libreoffice --headless --convert-to pdf output.pptx`
|
||||
|
||||
---
|
||||
|
||||
## Part 3 · 验证与调试
|
||||
|
||||
### 必跑三步(每次改动)
|
||||
|
||||
```bash
|
||||
# 1. 生成
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
|
||||
# 2. 页数
|
||||
python3 -c "from pypdf import PdfReader; print(len(PdfReader('out.pdf').pages))"
|
||||
|
||||
# 3. 视觉检查(怀疑视觉问题时)
|
||||
pdftoppm -png -r 300 out.pdf inspect
|
||||
```
|
||||
|
||||
**不验证不算改完**。
|
||||
|
||||
### 字体是否加载成功
|
||||
|
||||
```bash
|
||||
pdffonts output.pdf
|
||||
```
|
||||
|
||||
输出里如果看到 `DejaVuSerif` / `Bitstream Vera`,说明指定字体没生效,走到了系统兜底。正确应该看到 `TsangerJinKai02` 或 `Source Han Serif SC`。
|
||||
|
||||
### 一键生成 + 验证脚本
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""生成并验证 PDF"""
|
||||
import sys
|
||||
from weasyprint import HTML
|
||||
from pypdf import PdfReader
|
||||
|
||||
html_file = sys.argv[1] if len(sys.argv) > 1 else 'doc.html'
|
||||
pdf_file = sys.argv[2] if len(sys.argv) > 2 else 'output.pdf'
|
||||
max_pages = int(sys.argv[3]) if len(sys.argv) > 3 else 0
|
||||
|
||||
HTML(html_file).write_pdf(pdf_file)
|
||||
n = len(PdfReader(pdf_file).pages)
|
||||
print(f'✓ {pdf_file}, {n} pages')
|
||||
|
||||
if max_pages and n > max_pages:
|
||||
print(f'✗ Exceeded limit ({n} > {max_pages})')
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
项目脚本 `scripts/build.py` 是这段的产品化版本。
|
||||
|
||||
### 高分辨率视觉检查
|
||||
|
||||
```bash
|
||||
pdftoppm -png -r 160 output.pdf preview # 标准
|
||||
pdftoppm -png -r 300 output.pdf preview # 排查细节 bug
|
||||
pdftoppm -png -r 400 output.pdf preview # 极致细节(tag 双层等)
|
||||
```
|
||||
|
||||
### 生成多版本
|
||||
|
||||
```python
|
||||
for variant, vars_css in [
|
||||
('light', '--bg: #f5f4ed;'),
|
||||
('dark', '--bg: #141413;'),
|
||||
]:
|
||||
custom = base.replace('{{VARS}}', f':root {{ {vars_css} }}')
|
||||
HTML(string=custom).write_pdf(f'out-{variant}.pdf')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 4 · 15 条踩坑
|
||||
|
||||
每一条都是真实踩出来的。遇到视觉异常立刻来这里查。
|
||||
|
||||
### 1. Tag / Badge 双层矩形 bug(最坑)
|
||||
|
||||
**症状**:PDF 放大看背景色 tag,出现内外两层矩形。手机预览器尤其明显。
|
||||
|
||||
**根因**:WeasyPrint 渲染 `rgba(..., 0.xx)` 时,**padding 区域**和**字形像素区域**分别做透明度计算,字形 anti-alias 让周围透明度叠加更深,形成视觉第二层。
|
||||
|
||||
**解法**:Tag 背景必须用实色 hex,禁用 rgba。
|
||||
|
||||
```css
|
||||
/* ❌ */ .tag { background: rgba(201, 100, 66, 0.18); }
|
||||
/* ✅ */ .tag { background: #E4ECF5; }
|
||||
```
|
||||
|
||||
**rgba -> 实色对照表**(底 parchment `#f5f4ed` + 前景油墨蓝 `#1B365D`):
|
||||
|
||||
| rgba 透明度 | 等效实色 hex |
|
||||
|---|---|
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← 默认 |
|
||||
| 0.22 | `#ead3c7` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
公式:`实色通道 = 底 + (前景 - 底) × 透明度`。其他底色要重算。
|
||||
|
||||
**想要"呼吸感"**:用 CSS linear-gradient(整张 tag 栅格化为位图,不走逐像素合成):
|
||||
|
||||
```css
|
||||
.tag {
|
||||
background: linear-gradient(to right, #D6E1EE, #E4ECF5 70%, #EEF2F7);
|
||||
}
|
||||
```
|
||||
|
||||
**美学教训**:工程上 gradient 能做出笔刷感,审美上往往**用力过猛**。实战优先级:极淡实色 `#EEF2F7` > 稍浓实色 `#E4ECF5` > 慎用 gradient。如果读者第一眼看到的是背景形状而不是字,就过度了。
|
||||
|
||||
### 2. 薄边框 + 圆角 = 双圈 bug
|
||||
|
||||
**症状**:`border: 0.4pt solid ...` + `border-radius: 2pt` 放大后两条平行线。
|
||||
|
||||
**根因**:WeasyPrint 对 < 1pt border + 圆角,分别 stroke 内外 path,薄宽度没法重合。
|
||||
|
||||
**解法**三选一:
|
||||
1. 改用背景填充(首选,设计语言一致)
|
||||
2. border ≥ 1pt
|
||||
3. 去掉 border-radius
|
||||
|
||||
### 3. 2 页硬约束溢出
|
||||
|
||||
适用于 resume、one-pager 等页数受限的文档。
|
||||
|
||||
**常见诱因**:字体 fallback、新增内容、字号意外增大、line-height 从 1.4 -> 1.6。
|
||||
|
||||
**诊断**:`pdffonts output.pdf` 看实际加载字体。
|
||||
|
||||
**解法(按优先级)**:
|
||||
1. 删冗余副词("深入研究" -> "研究")
|
||||
2. 合并同义数据
|
||||
3. 砍次要项
|
||||
4. 减小 section 间距(慎用)
|
||||
5. 最后手段:字号降 0.1-0.2pt
|
||||
|
||||
**不要做**:去掉封面/教育/Timeline 这类结构性内容,砍高亮(简历会变死气)。
|
||||
|
||||
### 4. 字体 fallback 导致页数不一致
|
||||
|
||||
**症状**:本机 2 页,CI/服务器 4 页。
|
||||
|
||||
**根因**:字体文件没和 HTML 同目录/未系统安装。
|
||||
|
||||
**解法**:
|
||||
|
||||
```bash
|
||||
# 把 .ttf 放 HTML 同目录
|
||||
cp TsangerJinKai02-W04.ttf workspace/
|
||||
|
||||
# 或系统安装(Linux)
|
||||
apt install fonts-noto-cjk
|
||||
mkdir -p ~/.fonts && cp *.ttf ~/.fonts/ && fc-cache -f
|
||||
```
|
||||
|
||||
### 5. 中英文紧贴
|
||||
|
||||
**症状**:`125.4k GitHub Stars` 看起来 k 和 G 太贴。
|
||||
|
||||
**错误解法**:手动加 ` ` / `margin-left: 2mm`(影响对齐)。
|
||||
|
||||
**正确解法**:独立 span + flex gap:
|
||||
|
||||
```html
|
||||
<div class="metric">
|
||||
<span class="metric-value">125.4k</span>
|
||||
<span class="metric-label">GitHub Stars</span>
|
||||
</div>
|
||||
```
|
||||
```css
|
||||
.metric { display: flex; align-items: baseline; gap: 6pt; }
|
||||
```
|
||||
|
||||
### 6. 全角 vs 半角空格
|
||||
|
||||
- **中文之间**:全角空格 ` `(U+3000)+ `·` + 空格
|
||||
- **英文之间**:半角空格 + `·` + 空格
|
||||
- **中英混排**:flex gap 优先,不加空格
|
||||
|
||||
### 7. 千分位 · 百分号 · 箭头
|
||||
|
||||
| ✅ | ❌ |
|
||||
|---|---|
|
||||
| `5,000+` | `5000+` / `5,000+`(全角逗号) |
|
||||
| `90%` | `90 %`(前有空格) |
|
||||
| `->` | `->` / `->` |
|
||||
|
||||
自查:
|
||||
```bash
|
||||
grep -oE '->|->|⟶|⇒' doc.html | sort | uniq -c
|
||||
grep -oE '[0-9]{4,}' doc.html | sort -u
|
||||
```
|
||||
|
||||
### 8. 高亮过多 / 过少
|
||||
|
||||
- 一行 4-5 个橙字 -> 视觉疲劳
|
||||
- 整节没高亮 -> 扁平
|
||||
|
||||
**规则**:每行 ≤ 2 处,每节至少 1 处,只高亮**可量化的数字或独特表达**。
|
||||
|
||||
合理区间:文档总字数 ÷ 高亮数 ≈ 80-150 字/高亮。
|
||||
|
||||
### 9. `height: 100vh` 不工作
|
||||
|
||||
**症状**:想做满屏封面,`height: 100vh` 没效果。
|
||||
|
||||
**根因**:WeasyPrint 的 @page 语境下 viewport 单位不准。
|
||||
|
||||
**解法**:
|
||||
|
||||
```css
|
||||
.cover {
|
||||
min-height: 257mm; /* A4 高 297 - 上下 margin 40 */
|
||||
display: flex; flex-direction: column; justify-content: center;
|
||||
}
|
||||
```
|
||||
|
||||
### 10. break-inside 在 flex 容器里失效
|
||||
|
||||
**解法**:给 flex item 额外包一层 block 容器:
|
||||
|
||||
```html
|
||||
<div class="row">
|
||||
<div class="card-wrapper"><div class="card">...</div></div>
|
||||
</div>
|
||||
```
|
||||
```css
|
||||
.row { display: flex; }
|
||||
.card-wrapper { break-inside: avoid; }
|
||||
```
|
||||
|
||||
### 11. 首页不要页码
|
||||
|
||||
```css
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
}
|
||||
```
|
||||
|
||||
### 12. 打印留白边
|
||||
|
||||
**症状**:打印四周有白边(即使 background 设置了)。
|
||||
|
||||
**根因**:默认 WeasyPrint 的 `@page background` 只延伸到 page 区域。
|
||||
|
||||
**解法**:
|
||||
|
||||
```css
|
||||
@page {
|
||||
size: A4; margin: 20mm;
|
||||
background: #f5f4ed; /* 让背景延伸到 margin 外 */
|
||||
}
|
||||
```
|
||||
|
||||
### 13. 图片模糊
|
||||
|
||||
**症状**:PDF 里图片发虚。
|
||||
|
||||
**根因**:按原始像素渲染。A4 @ 300 dpi 需要 2480 × 3508 像素。
|
||||
|
||||
**解法**:嵌入图要用 2-3x 源。
|
||||
|
||||
### 14. 验证闭环(兜底)
|
||||
|
||||
```bash
|
||||
python3 -c "from weasyprint import HTML; HTML('doc.html').write_pdf('out.pdf')"
|
||||
python3 -c "from pypdf import PdfReader; print(len(PdfReader('out.pdf').pages))"
|
||||
pdftoppm -png -r 300 out.pdf inspect # 视觉怀疑时
|
||||
```
|
||||
|
||||
**不验证不算改完**。
|
||||
|
||||
### 15. SVG marker `orient="auto"` 不生效
|
||||
|
||||
**症状**:SVG 里用 `<marker orient="auto">` 或 `orient="auto-start-reverse"` 的箭头,所有方向都指向右(marker 的默认绘制方向),不随路径切线旋转。
|
||||
|
||||
**根因**:WeasyPrint 的 SVG 渲染不支持 marker 的 `orient="auto"` 属性。marker 永远按 0° 绘制。
|
||||
|
||||
**解法**:不用 `<marker>`,手动在每个箭头端点画 chevron `<path>`,方向写死。
|
||||
|
||||
```xml
|
||||
<!-- ❌ marker 箭头:WeasyPrint 全部朝右 -->
|
||||
<defs>
|
||||
<marker id="a" orient="auto" ...>
|
||||
<path d="M2 1L8 5L2 9" .../>
|
||||
</marker>
|
||||
</defs>
|
||||
<path d="M 440 52 Q 568 52 568 244" marker-end="url(#a)"/>
|
||||
|
||||
<!-- ✅ 手绘 chevron:每个方向单独写 -->
|
||||
<path d="M 440 52 Q 568 52 568 244" fill="none" stroke="#5e5d59" stroke-width="1.5"/>
|
||||
<path d="M 560 236 L 568 244 L 576 236" fill="none" stroke="#5e5d59" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
```
|
||||
|
||||
四个方向的 chevron 模板(tip 在端点,arm 长度 8px):
|
||||
|
||||
| 方向 | chevron path |
|
||||
|---|---|
|
||||
| ↓ | `M (x-8) (y-8) L x y L (x+8) (y-8)` |
|
||||
| ← | `M (x+8) (y-8) L x y L (x+8) (y+8)` |
|
||||
| ↑ | `M (x-8) (y+8) L x y L (x+8) (y+8)` |
|
||||
| → | `M (x-8) (y-8) L x y L (x-8) (y+8)` |
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"--parchment": "#f5f4ed",
|
||||
"--ivory": "#faf9f5",
|
||||
"--brand": "#1B365D",
|
||||
"--near-black": "#141413",
|
||||
"--dark-warm": "#3d3d3a",
|
||||
"--charcoal": "#4d4c48",
|
||||
"--olive": "#5e5d59",
|
||||
"--stone": "#87867f"
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
# Content Strategy
|
||||
|
||||
How to write, not how to lay out. Good typography with bad content is just "polished mediocrity". This document covers the writing principles.
|
||||
|
||||
---
|
||||
|
||||
## Core principles (all documents)
|
||||
|
||||
### 1. Data over adjectives
|
||||
|
||||
- ❌ "Delivered significant business growth"
|
||||
- ✅ Write the specific numbers and deltas
|
||||
|
||||
Every sentence should survive the follow-up question "how much, specifically?". If you can't answer, don't write it.
|
||||
|
||||
### 2. Judgment over execution
|
||||
|
||||
Junior writes "what they did". Mid writes "how they did it". **Senior writes "why they made that call, and what they predicted correctly"**.
|
||||
|
||||
- ❌ "Led the platform build-out"
|
||||
- ✅ Write what judgment you made and how it was proven right
|
||||
|
||||
### 3. Distinctive phrasing over industry clichés
|
||||
|
||||
- ❌ "Embrace the AI era, pioneer digital transformation paradigms"
|
||||
- ✅ Say it in your own words, skip the industry vocabulary
|
||||
|
||||
**Distinctive phrasing is memorable**. A line you invented beats a line borrowed from an earnings call. It sounds like a person thinking, not a deck regurgitating.
|
||||
|
||||
### 4. Honest boundaries
|
||||
|
||||
- If you didn't do it, don't claim it
|
||||
- If you don't know the exact number, don't invent one. Write a vague but honest magnitude
|
||||
- Attribute collaborators
|
||||
|
||||
---
|
||||
|
||||
## Per-document strategies
|
||||
|
||||
### One-Pager
|
||||
|
||||
**Single purpose**: the reader grasps the point in 30 seconds.
|
||||
|
||||
**Structure**:
|
||||
1. **Headline** (serif display) + one-line subtitle (sans body)
|
||||
2. **Metrics** — 3-4 cards, numbers first
|
||||
3. **Core argument** (1-2 paragraphs)
|
||||
4. **Key evidence / roadmap** (3-5 short bullets)
|
||||
5. **Next step / contact** (footer)
|
||||
|
||||
**Rules**:
|
||||
- Total: 200-350 words
|
||||
- All section headlines should work as a standalone outline — reading just the headlines should deliver the gist
|
||||
- Data must fill 30%+ of the body
|
||||
- No opening ceremony ("In recent years, as technology has rapidly evolved...")
|
||||
|
||||
### Long Document
|
||||
|
||||
**Structure**:
|
||||
1. **Cover** — big title + subtitle + author + date
|
||||
2. **Contents** (auto-generated or hand-written TOC)
|
||||
3. **Executive Summary** (≤ 1 page + 3-5 takeaways)
|
||||
4. **Body** — chapters that each stand alone as an essay
|
||||
5. **Appendix / references** (if applicable)
|
||||
|
||||
**Rules**:
|
||||
- Every chapter opens with a "claim paragraph" (2-3 sentences summarizing the argument)
|
||||
- After long paragraphs (>5 lines), intersperse callouts / quotes / figures to relieve eye fatigue
|
||||
- Highlight key data / conclusions with `<span class="hl">`
|
||||
- Use "chapter breaks" (blank page + chapter number) between major sections
|
||||
|
||||
### Letter
|
||||
|
||||
**Structure**:
|
||||
1. Letterhead (sender info, top right or centered)
|
||||
2. Date (right-aligned)
|
||||
3. Recipient salutation (left-aligned)
|
||||
4. Body (3-5 paragraphs)
|
||||
5. Sign-off ("Sincerely," / "Best regards,")
|
||||
6. Signature (serif 500)
|
||||
7. Enclosures (if any)
|
||||
|
||||
**Rules**:
|
||||
- Minimal — no decorative elements
|
||||
- Body prefers serif (editorial feel)
|
||||
- Slightly larger type (11-12pt body) — this will be read, not scanned
|
||||
- Paragraph spacing ≥ 10pt
|
||||
|
||||
**Common use cases**:
|
||||
- Resignation / notice
|
||||
- Recommendation letter
|
||||
- Formal collaboration proposal
|
||||
- Personal statement
|
||||
|
||||
### Portfolio
|
||||
|
||||
**Structure**:
|
||||
1. **Cover** (name + one-line positioning + contact)
|
||||
2. **About** (half-page introduction)
|
||||
3. **Per-project 1-2 pages**:
|
||||
- Project title + type tag + date range
|
||||
- One-line description
|
||||
- 2-3 hero images (if applicable)
|
||||
- Role + challenge + outcome
|
||||
4. **Selected works list** (additional projects as a short list)
|
||||
5. **Contact** (return to contact details)
|
||||
|
||||
**Rules**:
|
||||
- Visuals first, text supports
|
||||
- Every project's outcome must be quantifiable
|
||||
- Photos > design mockups > code screenshots (the more abstract, the better)
|
||||
- Don't list every tech stack — a mono tag row is enough
|
||||
|
||||
### Resume
|
||||
|
||||
The most constrained document type in kami.
|
||||
|
||||
**Hard constraints**:
|
||||
- Strictly 2 A4 pages
|
||||
- Every project follows three-part: Role / Actions / Impact
|
||||
- 5 core skills, each with at least one brand-color emphasis
|
||||
- Team size, tech stack, narrative voice must stay consistent throughout
|
||||
|
||||
**Key sections**:
|
||||
- Header + 4 metric cards
|
||||
- Summary (~50 words)
|
||||
- Timeline (3 steps — long-range evolution signal)
|
||||
- 3-5 core projects
|
||||
- Public work / impact (optional)
|
||||
- 5 core skills
|
||||
- Education
|
||||
|
||||
**Metric card selection rule**:
|
||||
- 1 card on **time** (years, consistency)
|
||||
- 1 card on **scale** (team, users, projects, or other quantifiable scope)
|
||||
- 2 cards on **results** (quantifiable external proof)
|
||||
|
||||
---
|
||||
|
||||
## Coupling rules (layout × content)
|
||||
|
||||
### Emphasis rhythm
|
||||
|
||||
Across any document:
|
||||
- ≤ 2 emphasized items per line
|
||||
- Emphasis must be a **quantifiable number** or a **distinctive phrase**
|
||||
- Do not emphasize adjectives
|
||||
|
||||
### Number formatting
|
||||
|
||||
| ✅ | ❌ |
|
||||
|---|---|
|
||||
| 5,000+ | 5000+ (missing thousands separator) |
|
||||
| 90% | 90 % (space before percent) |
|
||||
| ~$10M | $9,876,543 (false precision reads fake) |
|
||||
| 2026.04 | April 2026 (when horizontal space is tight) |
|
||||
| -> | → |
|
||||
|
||||
### Emphasis is not bold
|
||||
|
||||
Use `color: var(--brand)` alone — don't also add `font-weight: bold`. Bold breaks the single-weight design language.
|
||||
|
||||
---
|
||||
|
||||
## Pre-ship checklist
|
||||
|
||||
Run through before every draft:
|
||||
|
||||
- [ ] Any jargon like "leverage / unlock / embrace / pioneer"? Cut.
|
||||
- [ ] Does every paragraph's first sentence stand alone? If not, that paragraph has no claim.
|
||||
- [ ] Are all numbers verifiable? If asked "where did this come from", can you answer?
|
||||
- [ ] At least one **distinctive phrase** (not industry boilerplate)?
|
||||
- [ ] Every emphasized (brand-colored) span is either a number or a distinctive phrase? If not, remove the emphasis.
|
||||
- [ ] Paragraph lengths even? No paragraph over 5 lines?
|
||||
- [ ] Number format consistent (commas, percent signs, arrows)?
|
||||
- [ ] Page count within the document's constraint (resume 2, one-pager 1, letter 1)?
|
||||
|
||||
---
|
||||
|
||||
## Writing references
|
||||
|
||||
- **Paul Graham's essays** — short, direct, judgmental. The gold standard for essayistic writing.
|
||||
- **Stripe Press books** — print-grade typography paired with deep content. Where to learn the craft of the single sentence.
|
||||
- **Minto's Pyramid Principle** — conclusion first, evidence below. The shape of every one-pager and exec summary.
|
||||
- **Ben Horowitz's blog** — how to write technical and business judgment in prose ordinary people can read. The template for long-doc voice.
|
||||
|
||||
None are required, but reading any one of them will move the dial on both your writing and your judgment.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Content Strategy
|
||||
|
||||
怎么写,不是怎么排。好排版配坏内容只是"精致的平庸",这份文档讲**写作原则**。
|
||||
|
||||
---
|
||||
|
||||
## 核心原则(所有文档共享)
|
||||
|
||||
### 1. 数据优于形容词
|
||||
|
||||
- ❌ "带来了显著的业务增长"
|
||||
- ✅ 写具体数字和变化幅度
|
||||
|
||||
每一句都要能被追问"具体多少"。答不上来就别写。
|
||||
|
||||
### 2. 判断优于执行
|
||||
|
||||
初级写"做了什么",中级写"怎么做的",**高级写"为什么这么做以及预判对了什么"**。
|
||||
|
||||
- ❌ "主导了 XX 建设"
|
||||
- ✅ 写你做了什么判断,以及这个判断后来被证明是对的
|
||||
|
||||
### 3. 独特表达优于行业黑话
|
||||
|
||||
- ❌ "拥抱 AI 时代、打造数字化转型新范式"
|
||||
- ✅ 用自己的话说,不用行业通用词
|
||||
|
||||
**独特用词是记忆点**。自己想出来的短语比行业词汇更像一个有想法的人在说话。
|
||||
|
||||
### 4. 诚实的边界
|
||||
|
||||
- 不是你做的别往自己身上揽
|
||||
- 数据不知道精确值别编,写模糊但真实的量级
|
||||
- 协作方要注明
|
||||
|
||||
---
|
||||
|
||||
## 按文档类型的策略差异
|
||||
|
||||
### One-Pager(一页纸方案/报告)
|
||||
|
||||
**唯一目的**:30 秒内让读者抓到核心。
|
||||
|
||||
**结构模板**:
|
||||
1. **标题**(serif 大字)+ 一行副标题(sans 中字)
|
||||
2. **Metrics 3-4 卡**(数字优先)
|
||||
3. **核心论点**(1-2 段)
|
||||
4. **关键证据 / 路线图**(3-5 个短 bullet)
|
||||
5. **下一步行动 / 联系方式**(底部)
|
||||
|
||||
**写作规则**:
|
||||
- 总字数控制在 400-600 字
|
||||
- 所有 section 的 headline 都要能独立成"摘要"(读 headline 连起来就能 get 大意)
|
||||
- 数据占正文的 30%+
|
||||
- 不要放废话铺垫("近年来,随着技术飞速发展...")
|
||||
|
||||
### Long Document(白皮书/技术报告/年度总结)
|
||||
|
||||
**结构模板**:
|
||||
1. **封面**:大标题 + 副标题 + 作者 + 日期
|
||||
2. **目录**(自动生成或手写 TOC)
|
||||
3. **Executive Summary**(摘要,1 页以内 + 3-5 个 takeaway)
|
||||
4. **正文**:章节结构清晰,每章可独立成文
|
||||
5. **附录 / 参考资料**(如有)
|
||||
|
||||
**写作规则**:
|
||||
- 每章首段是"立论段",2-3 句话概括本章论点
|
||||
- 长段落(>5 行)后穿插 callout / 引用 / 图表缓解阅读疲劳
|
||||
- 关键数据 / 结论用 `<span class="hl">` 高亮
|
||||
- 章节之间可用"分隔页"(空白 + 章节号大字)
|
||||
|
||||
### Letter(正式信件/备忘录)
|
||||
|
||||
**结构模板**:
|
||||
1. 信头(寄件人信息,右上或顶部居中)
|
||||
2. 日期(右对齐)
|
||||
3. 收件人称呼(左对齐)
|
||||
4. 正文(3-5 段)
|
||||
5. 敬语(左对齐,"此致 敬礼" 或 "Best regards")
|
||||
6. 署名(serif 500)
|
||||
7. 附件说明(如有)
|
||||
|
||||
**写作规则**:
|
||||
- 极简:不要加任何装饰元素
|
||||
- 正文优先 serif(editorial 感)
|
||||
- 字号偏大(11-12pt 正文),因为是要被认真读的
|
||||
- 段落之间空行 >= 10pt
|
||||
|
||||
**常见场景**:
|
||||
- 辞职信 / 离职通知
|
||||
- 推荐信
|
||||
- 正式合作邀请
|
||||
- 个人声明
|
||||
|
||||
### Portfolio(作品集)
|
||||
|
||||
**结构模板**:
|
||||
1. **封面**(姓名 + 一句定位 + 联系方式)
|
||||
2. **About**(半页个人介绍)
|
||||
3. **每个项目 1-2 页**:
|
||||
- 项目标题 + 类型 tag + 时间
|
||||
- 一句话描述 "做了什么"
|
||||
- 2-3 张大图(如适用)
|
||||
- 角色 + 挑战 + 结果
|
||||
4. **Selected works list**(额外作品简列表)
|
||||
5. **Contact**(回到联系方式)
|
||||
|
||||
**写作规则**:
|
||||
- 视觉第一,文字辅助
|
||||
- 每个项目的结果必须可量化
|
||||
- 图片 > 设计稿截图 > 代码截图(越抽象越好)
|
||||
- 不写技术栈长清单(mono tag 排列即可)
|
||||
|
||||
### Resume(简历)
|
||||
|
||||
简历是本 skill 最特殊的文档类型,约束最严格。
|
||||
|
||||
**硬约束**:
|
||||
- 严格 2 页 A4
|
||||
- 项目必须三段式:角色 / 动作 / 结果
|
||||
- 核心能力 5 项每项至少 1 处高亮
|
||||
- 团队规模、技术栈等叙事口径需一致
|
||||
|
||||
**关键 section**:
|
||||
- Header + Metrics 4 卡
|
||||
- 个人简介(~80 字)
|
||||
- Timeline 3 步(长期演进信号)
|
||||
- 3-5 个核心项目
|
||||
- 公开成果/影响力(如有)
|
||||
- 核心能力 5 项
|
||||
- 教育背景
|
||||
|
||||
**Metrics 4 卡选择原则**:
|
||||
- 1 卡用**时间**(年限、持续性)
|
||||
- 1 卡用**规模**(团队、用户、项目等可量化的范围)
|
||||
- 2 卡用**成果**(可量化的外部证明)
|
||||
|
||||
---
|
||||
|
||||
## 排版与内容的耦合规则
|
||||
|
||||
### 高亮节律
|
||||
|
||||
任何文档里:
|
||||
- 每行强调 ≤ 2 处
|
||||
- 强调必须是**可量化的数字**或**独特表达**
|
||||
- 形容词不高亮
|
||||
|
||||
### 数字格式
|
||||
|
||||
| ✅ 要 | ❌ 不要 |
|
||||
|---|---|
|
||||
| 5,000+ | 5000+(不带千分位) |
|
||||
| 5,000+ | 5,000+(全角逗号) |
|
||||
| 90% | 90 %(中间有空格) |
|
||||
| 近千万量级 | 9,876,543(过于精确显得虚假) |
|
||||
| 2026.04 | 2026年4月(展示场合空间小时) |
|
||||
| -> | → |
|
||||
|
||||
### 引号
|
||||
|
||||
中文优先 `「」` 而非 `""`,体现印刷品气质。
|
||||
|
||||
### 强调不是加粗
|
||||
|
||||
用 `color: var(--brand)` 加品牌色就够了,**不要**再加 `font-weight: bold`。粗体破坏单字重设计语言。
|
||||
|
||||
---
|
||||
|
||||
## 写作口径 checklist(交付前自查)
|
||||
|
||||
复制一份,每写完一版过一遍:
|
||||
|
||||
- [ ] 有没有"拥抱 / 打造 / 赋能 / 重构"这类烂大街的黑话?删掉换人话
|
||||
- [ ] 每段第一句能不能单独成句?如果不能,说明段落没有论点
|
||||
- [ ] 所有数字是否可验证?如果被问"具体哪里来的"你能答吗?
|
||||
- [ ] 是否有至少 1 处"独特表达"(非行业通用语)?
|
||||
- [ ] 被强调(高亮)的内容是不是都是数字或独特表达?不是的话去掉强调
|
||||
- [ ] 段落长度是否均匀?没有超过 5 行的长段?
|
||||
- [ ] 数字格式是否统一(千分位、百分号、箭头)?
|
||||
- [ ] 页数是否符合文档类型约束(简历 2 页、one-pager 1 页、letter 1 页)?
|
||||
|
||||
---
|
||||
|
||||
## 几个写作资源(可选参考)
|
||||
|
||||
- **Paul Graham 的文章**:短、直接、判断性 —— 散文式写作的黄金标准
|
||||
- **Stripe Press 的书**:印刷品级排版搭配深度内容 —— 标题句的写法
|
||||
- **麦肯锡 pyramid principle**:金字塔结构,"先结论再论据" —— one-pager 和 executive summary 的写法
|
||||
- **Ben Horowitz 的 blog**:如何把技术和生意的判断写得人人能读 —— long doc 的范例
|
||||
|
||||
这些都不是必须读,但读一本会让判断力和写作感上一个档。
|
||||
@@ -0,0 +1,482 @@
|
||||
#!/usr/bin/env python3
|
||||
"""kami build & check
|
||||
|
||||
Usage:
|
||||
python3 scripts/build.py # build all 15 examples (10 HTML + 3 diagrams + 2 PPTX)
|
||||
python3 scripts/build.py resume # build one template, print pages + fonts
|
||||
python3 scripts/build.py --check # scan templates for CSS rule violations
|
||||
python3 scripts/build.py --check -v # verbose (show each scanned file)
|
||||
python3 scripts/build.py --sync # check CSS token drift across templates
|
||||
python3 scripts/build.py --verify # build all + page count + font + placeholder checks
|
||||
python3 scripts/build.py --verify resume-en # single target full verification
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
TEMPLATES = ROOT / "assets" / "templates"
|
||||
DIAGRAMS = ROOT / "assets" / "diagrams"
|
||||
EXAMPLES = ROOT / "assets" / "examples"
|
||||
|
||||
# name -> (source, max_pages). max_pages=0 means no hard check.
|
||||
HTML_TARGETS: dict[str, tuple[str, int]] = {
|
||||
# Chinese
|
||||
"one-pager": ("one-pager.html", 1),
|
||||
"letter": ("letter.html", 1),
|
||||
"long-doc": ("long-doc.html", 0),
|
||||
"portfolio": ("portfolio.html", 0),
|
||||
"resume": ("resume.html", 2),
|
||||
# English
|
||||
"one-pager-en": ("one-pager-en.html", 1),
|
||||
"letter-en": ("letter-en.html", 1),
|
||||
"long-doc-en": ("long-doc-en.html", 0),
|
||||
"portfolio-en": ("portfolio-en.html", 0),
|
||||
"resume-en": ("resume-en.html", 2),
|
||||
}
|
||||
PPTX_TARGETS: dict[str, str] = {
|
||||
"slides": "slides.py",
|
||||
"slides-en": "slides-en.py",
|
||||
}
|
||||
|
||||
# Diagram HTMLs live in a separate directory and have no page-count contract.
|
||||
DIAGRAM_TARGETS: dict[str, str] = {
|
||||
"diagram-architecture": "architecture.html",
|
||||
"diagram-flowchart": "flowchart.html",
|
||||
"diagram-quadrant": "quadrant.html",
|
||||
}
|
||||
|
||||
|
||||
# ------------------------- build -------------------------
|
||||
|
||||
def build_html(name: str, source: str, max_pages: int,
|
||||
src_dir: Path = TEMPLATES) -> bool:
|
||||
try:
|
||||
from weasyprint import HTML
|
||||
from pypdf import PdfReader
|
||||
except ImportError:
|
||||
print("✗ missing deps: pip install weasyprint pypdf --break-system-packages")
|
||||
return False
|
||||
|
||||
src = src_dir / source
|
||||
if not src.exists():
|
||||
print(f"✗ {name}: source not found ({src})")
|
||||
return False
|
||||
|
||||
EXAMPLES.mkdir(parents=True, exist_ok=True)
|
||||
out = EXAMPLES / f"{name}.pdf"
|
||||
|
||||
# weasyprint resolves @font-face relative to CWD. Run from the source dir
|
||||
# so fonts placed next to the HTML are found.
|
||||
HTML(str(src), base_url=str(src.parent)).write_pdf(str(out))
|
||||
n = len(PdfReader(str(out)).pages)
|
||||
msg = f"✓ {name}: {n} pages"
|
||||
if max_pages and n > max_pages:
|
||||
msg = f"✗ {name}: {n} pages (limit {max_pages})"
|
||||
print(msg)
|
||||
return False
|
||||
print(msg)
|
||||
return True
|
||||
|
||||
|
||||
def build_slides(name: str = "slides") -> bool:
|
||||
source = PPTX_TARGETS.get(name)
|
||||
if source is None:
|
||||
print(f"✗ {name}: unknown slides target")
|
||||
return False
|
||||
src = TEMPLATES / source
|
||||
if not src.exists():
|
||||
print(f"✗ {name}: source not found ({src})")
|
||||
return False
|
||||
|
||||
EXAMPLES.mkdir(parents=True, exist_ok=True)
|
||||
out = EXAMPLES / f"{name}.pptx"
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(src)],
|
||||
cwd=str(src.parent),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"✗ {name}: {result.stderr.strip() or 'script failed'}")
|
||||
return False
|
||||
# The script writes output.pptx in cwd; move to examples/ under our name.
|
||||
generated = src.parent / "output.pptx"
|
||||
if generated.exists():
|
||||
generated.replace(out)
|
||||
print(f"✓ {name}: generated {out.name}")
|
||||
return True
|
||||
print(f"✗ {name}: output.pptx not produced")
|
||||
return False
|
||||
|
||||
|
||||
def build_all() -> int:
|
||||
failures = 0
|
||||
for name, (source, max_pages) in HTML_TARGETS.items():
|
||||
if not build_html(name, source, max_pages):
|
||||
failures += 1
|
||||
for name, source in DIAGRAM_TARGETS.items():
|
||||
if not build_html(name, source, 0, src_dir=DIAGRAMS):
|
||||
failures += 1
|
||||
for name in PPTX_TARGETS:
|
||||
if not build_slides(name):
|
||||
failures += 1
|
||||
return failures
|
||||
|
||||
|
||||
def build_single(name: str) -> int:
|
||||
if name in HTML_TARGETS:
|
||||
source, max_pages = HTML_TARGETS[name]
|
||||
ok = build_html(name, source, max_pages)
|
||||
if ok:
|
||||
show_fonts(EXAMPLES / f"{name}.pdf")
|
||||
return 0 if ok else 1
|
||||
if name in DIAGRAM_TARGETS:
|
||||
source = DIAGRAM_TARGETS[name]
|
||||
ok = build_html(name, source, 0, src_dir=DIAGRAMS)
|
||||
return 0 if ok else 1
|
||||
if name in PPTX_TARGETS:
|
||||
return 0 if build_slides(name) else 1
|
||||
known = list(HTML_TARGETS) + list(DIAGRAM_TARGETS) + list(PPTX_TARGETS)
|
||||
print(f"✗ unknown target: {name}. Known: {', '.join(known)}")
|
||||
return 2
|
||||
|
||||
|
||||
def show_fonts(pdf: Path) -> None:
|
||||
if not pdf.exists():
|
||||
return
|
||||
try:
|
||||
out = subprocess.run(["pdffonts", str(pdf)], capture_output=True, text=True, check=False)
|
||||
if out.returncode == 0:
|
||||
print("--- pdffonts ---")
|
||||
print(out.stdout.rstrip())
|
||||
except FileNotFoundError:
|
||||
pass # pdffonts not installed; silent
|
||||
|
||||
|
||||
# ------------------------- sync -------------------------
|
||||
|
||||
ROOT_BLOCK = re.compile(r":root\s*\{([^}]*)\}", re.DOTALL)
|
||||
CSS_VAR = re.compile(r"--([\w-]+)\s*:\s*([^;]+);")
|
||||
TOKENS_FILE = ROOT / "references" / "tokens.json"
|
||||
|
||||
|
||||
def sync_check(verbose: bool = False) -> int:
|
||||
if not TOKENS_FILE.exists():
|
||||
print(f"✗ tokens.json not found at {TOKENS_FILE.relative_to(ROOT)}")
|
||||
return 1
|
||||
|
||||
canonical: dict[str, str] = json.loads(TOKENS_FILE.read_text())
|
||||
|
||||
targets: list[Path] = list(TEMPLATES.glob("*.html"))
|
||||
if DIAGRAMS.exists():
|
||||
targets.extend(DIAGRAMS.glob("*.html"))
|
||||
|
||||
drift: list[tuple[str, str, str, str]] = [] # (file, token, expected, actual)
|
||||
|
||||
for path in sorted(targets):
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
block_match = ROOT_BLOCK.search(text)
|
||||
if not block_match:
|
||||
if verbose:
|
||||
print(f" (skip {path.name}: no :root block)")
|
||||
continue
|
||||
root_block = block_match.group(1)
|
||||
found: dict[str, str] = {
|
||||
m.group(1): m.group(2).strip()
|
||||
for m in CSS_VAR.finditer(root_block)
|
||||
}
|
||||
rel = path.relative_to(ROOT)
|
||||
for token, expected in canonical.items():
|
||||
name = token.lstrip("-")
|
||||
actual = found.get(name)
|
||||
# Only flag if the template defines the token but with a wrong value.
|
||||
# Templates that don't use a token don't need to define it.
|
||||
if actual is not None and actual.lower() != expected.lower():
|
||||
drift.append((str(rel), token, expected, actual))
|
||||
|
||||
if not drift:
|
||||
print(f"✓ tokens in sync across {len(targets)} template(s)")
|
||||
return 0
|
||||
|
||||
print(f"\n[token-drift] {len(drift)}")
|
||||
for file, token, expected, actual in drift:
|
||||
print(f" {file}: {token} expected {expected}, got {actual}")
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
# ------------------------- verify -------------------------
|
||||
|
||||
PLACEHOLDER = re.compile(r"\{\{[^}]+\}\}")
|
||||
|
||||
# Primary fonts expected in embedded PDF font names
|
||||
CN_PRIMARY_FONTS = {"TsangerJinKai02"}
|
||||
EN_PRIMARY_FONTS = {"Newsreader", "Inter"}
|
||||
|
||||
|
||||
def _pdf_font_names(pdf_path: Path) -> set[str]:
|
||||
try:
|
||||
from pypdf import PdfReader
|
||||
reader = PdfReader(str(pdf_path))
|
||||
fonts: set[str] = set()
|
||||
for page in reader.pages:
|
||||
resources = page.get("/Resources")
|
||||
if resources is None:
|
||||
continue
|
||||
font_dict = resources.get("/Font")
|
||||
if not isinstance(font_dict, dict):
|
||||
continue
|
||||
for obj in font_dict.values():
|
||||
try:
|
||||
resolved = obj.get_object() if hasattr(obj, "get_object") else obj
|
||||
except Exception:
|
||||
resolved = obj
|
||||
if isinstance(resolved, dict):
|
||||
base = resolved.get("/BaseFont")
|
||||
if base:
|
||||
fonts.add(str(base).lstrip("/"))
|
||||
return fonts
|
||||
except Exception:
|
||||
return set()
|
||||
|
||||
|
||||
def verify_target(name: str, source: str, max_pages: int, src_dir: Path) -> list[str]:
|
||||
issues: list[str] = []
|
||||
src = src_dir / source
|
||||
if not src.exists():
|
||||
issues.append(f"source not found: {src}")
|
||||
return issues
|
||||
|
||||
# placeholder check (before build)
|
||||
html = src.read_text(encoding="utf-8", errors="replace")
|
||||
hits = PLACEHOLDER.findall(html)
|
||||
if hits:
|
||||
issues.append(f"unfilled placeholder(s): {', '.join(dict.fromkeys(hits))}")
|
||||
|
||||
# build
|
||||
try:
|
||||
from weasyprint import HTML
|
||||
from pypdf import PdfReader
|
||||
except ImportError:
|
||||
issues.append("missing deps: pip install weasyprint pypdf --break-system-packages")
|
||||
return issues
|
||||
|
||||
EXAMPLES.mkdir(parents=True, exist_ok=True)
|
||||
out = EXAMPLES / f"{name}.pdf"
|
||||
HTML(str(src), base_url=str(src_dir)).write_pdf(str(out))
|
||||
|
||||
# page count check
|
||||
n = len(PdfReader(str(out)).pages)
|
||||
if max_pages and n > max_pages:
|
||||
issues.append(f"page overflow: {n} pages (limit {max_pages})")
|
||||
|
||||
# font check
|
||||
embedded = _pdf_font_names(out)
|
||||
is_en = name.endswith("-en")
|
||||
expected = EN_PRIMARY_FONTS if is_en else CN_PRIMARY_FONTS
|
||||
if not any(exp in font_name for exp in expected for font_name in embedded):
|
||||
primary = next(iter(expected))
|
||||
fallback_present = any(
|
||||
kw in font for font in embedded
|
||||
for kw in ("Newsreader", "Inter", "TsangerJinKai", "SourceHan", "Noto", "Georgia", "Charter", "Songti")
|
||||
)
|
||||
if not fallback_present:
|
||||
issues.append(f"no recognizable font embedded in {out.name}")
|
||||
else:
|
||||
issues.append(f"primary font ({primary}) not embedded; using fallback")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def verify_all(target: str | None = None) -> int:
|
||||
targets_to_run: dict[str, tuple[str, int, Path]] = {}
|
||||
if target:
|
||||
if target in HTML_TARGETS:
|
||||
src, mp = HTML_TARGETS[target]
|
||||
targets_to_run[target] = (src, mp, TEMPLATES)
|
||||
elif target in DIAGRAM_TARGETS:
|
||||
targets_to_run[target] = (DIAGRAM_TARGETS[target], 0, DIAGRAMS)
|
||||
else:
|
||||
print(f"✗ unknown target: {target}")
|
||||
return 2
|
||||
else:
|
||||
for name, (src, mp) in HTML_TARGETS.items():
|
||||
targets_to_run[name] = (src, mp, TEMPLATES)
|
||||
for name, src in DIAGRAM_TARGETS.items():
|
||||
targets_to_run[name] = (src, 0, DIAGRAMS)
|
||||
|
||||
failures = 0
|
||||
rows: list[tuple[str, str]] = []
|
||||
for name, (source, max_pages, src_dir) in targets_to_run.items():
|
||||
issues = verify_target(name, source, max_pages, src_dir)
|
||||
if issues:
|
||||
rows.append((f"✗ {name}", "; ".join(issues)))
|
||||
failures += 1
|
||||
else:
|
||||
rows.append((f"✓ {name}", "ok"))
|
||||
|
||||
for status, detail in rows:
|
||||
print(f"{status}: {detail}")
|
||||
|
||||
return 0 if failures == 0 else 1
|
||||
|
||||
|
||||
# ------------------------- check -------------------------
|
||||
|
||||
# Cool / neutral gray hex values that violate the "warm undertone only" rule.
|
||||
COOL_GRAY_BLOCKLIST = {
|
||||
"#888", "#888888", "#666", "#666666", "#999", "#999999",
|
||||
"#ccc", "#cccccc", "#ddd", "#dddddd", "#eee", "#eeeeee",
|
||||
"#111", "#111111", "#222", "#222222", "#333", "#333333",
|
||||
"#444", "#444444", "#555", "#555555", "#777", "#777777",
|
||||
"#aaa", "#aaaaaa", "#bbb", "#bbbbbb",
|
||||
# Tailwind cool grays
|
||||
"#6b7280", "#9ca3af", "#d1d5db", "#e5e7eb", "#f3f4f6",
|
||||
"#4b5563", "#374151", "#1f2937", "#111827",
|
||||
# Bootstrap-like neutrals
|
||||
"#f8f9fa", "#e9ecef", "#dee2e6", "#ced4da", "#adb5bd",
|
||||
"#6c757d", "#495057", "#343a40", "#212529",
|
||||
}
|
||||
|
||||
RGBA_BG_DIRECT = re.compile(r"background(?:-color)?\s*:\s*[^;]*rgba\s*\(", re.IGNORECASE)
|
||||
RGBA_VAR_DEF = re.compile(r"--([\w-]+)\s*:\s*[^;]*rgba\s*\(", re.IGNORECASE)
|
||||
BG_VAR_USE = re.compile(r"background(?:-color)?\s*:\s*[^;]*var\s*\(\s*--([\w-]+)", re.IGNORECASE)
|
||||
RGBA_BORDER_DIRECT = re.compile(r"border(?:-\w+)?\s*:\s*[^;]*rgba\s*\(", re.IGNORECASE)
|
||||
BORDER_VAR_USE = re.compile(r"border(?:-\w+)?\s*:\s*[^;]*var\s*\(\s*--([\w-]+)", re.IGNORECASE)
|
||||
LINE_HEIGHT_LOOSE = re.compile(r"line-height\s*:\s*1\.[6-9]\d*", re.IGNORECASE)
|
||||
UNICODE_ARROW = re.compile(r"→") # U+2192; should not appear in EN template body
|
||||
HEX_ANY = re.compile(r"#[0-9a-fA-F]{3,6}\b")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
file: Path
|
||||
line: int
|
||||
rule: str
|
||||
excerpt: str
|
||||
|
||||
|
||||
def scan_file(path: Path) -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
lines = text.splitlines()
|
||||
|
||||
# Pass 1: collect variable names that hold rgba(...) so the tag-background
|
||||
# bug can be detected through one level of indirection.
|
||||
rgba_vars: set[str] = set()
|
||||
for raw in lines:
|
||||
m = RGBA_VAR_DEF.search(raw)
|
||||
if m:
|
||||
rgba_vars.add(m.group(1))
|
||||
|
||||
is_en = path.name.endswith("-en.html")
|
||||
|
||||
# Pass 2: per-line rule checks
|
||||
for i, raw in enumerate(lines, start=1):
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("//") or line.startswith("#"):
|
||||
continue
|
||||
|
||||
if RGBA_BG_DIRECT.search(raw):
|
||||
findings.append(Finding(path, i, "rgba-background",
|
||||
"rgba() used directly on background (tag double-rectangle bug)"))
|
||||
|
||||
bg_var = BG_VAR_USE.search(raw)
|
||||
if bg_var and bg_var.group(1) in rgba_vars:
|
||||
findings.append(Finding(path, i, "rgba-background",
|
||||
f"background: var(--{bg_var.group(1)}) resolves to rgba() (tag double-rectangle bug)"))
|
||||
|
||||
if RGBA_BORDER_DIRECT.search(raw):
|
||||
findings.append(Finding(path, i, "rgba-border",
|
||||
"rgba() used on border (violates solid-color invariant)"))
|
||||
|
||||
border_var = BORDER_VAR_USE.search(raw)
|
||||
if border_var and border_var.group(1) in rgba_vars:
|
||||
findings.append(Finding(path, i, "rgba-border",
|
||||
f"border: var(--{border_var.group(1)}) resolves to rgba() (solid-color invariant)"))
|
||||
|
||||
if is_en and UNICODE_ARROW.search(raw):
|
||||
# skip CSS comment lines (/* ... */) and the arrow-in-CSS-content patterns
|
||||
stripped = raw.lstrip()
|
||||
if not stripped.startswith("/*") and not stripped.startswith("*") and "content:" not in raw:
|
||||
findings.append(Finding(path, i, "arrow-unicode-in-en",
|
||||
"→ (U+2192) in English template; use 'to' or '->' per patterns §2"))
|
||||
|
||||
m = LINE_HEIGHT_LOOSE.search(raw)
|
||||
if m:
|
||||
findings.append(Finding(path, i, "line-height-too-loose",
|
||||
f"{m.group(0)} exceeds 1.55 ceiling"))
|
||||
|
||||
for hex_match in HEX_ANY.finditer(raw):
|
||||
h = hex_match.group(0).lower()
|
||||
if h in COOL_GRAY_BLOCKLIST:
|
||||
findings.append(Finding(path, i, "cool-gray",
|
||||
f"{h} is a cool / neutral gray, use warm undertone"))
|
||||
return findings
|
||||
|
||||
|
||||
def check_all(verbose: bool) -> int:
|
||||
targets: list[Path] = []
|
||||
for p in TEMPLATES.glob("*.html"):
|
||||
targets.append(p)
|
||||
for p in TEMPLATES.glob("*.py"):
|
||||
targets.append(p)
|
||||
if DIAGRAMS.exists():
|
||||
for p in DIAGRAMS.glob("*.html"):
|
||||
targets.append(p)
|
||||
|
||||
findings: list[Finding] = []
|
||||
for p in sorted(targets):
|
||||
file_findings = scan_file(p)
|
||||
findings.extend(file_findings)
|
||||
if verbose:
|
||||
print(f"scanned {p.relative_to(ROOT)}: {len(file_findings)} finding(s)")
|
||||
|
||||
if not findings:
|
||||
print(f"✓ no violations across {len(targets)} templates")
|
||||
return 0
|
||||
|
||||
by_rule: dict[str, list[Finding]] = {}
|
||||
for f in findings:
|
||||
by_rule.setdefault(f.rule, []).append(f)
|
||||
|
||||
print(f"✗ {len(findings)} violation(s) across {len({f.file for f in findings})} file(s)")
|
||||
for rule, items in by_rule.items():
|
||||
print(f"\n[{rule}] {len(items)}")
|
||||
for f in items:
|
||||
rel = f.file.relative_to(ROOT)
|
||||
print(f" {rel}:{f.line} {f.excerpt}")
|
||||
return 1
|
||||
|
||||
|
||||
# ------------------------- entry -------------------------
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
return build_all()
|
||||
if args[0] in ("-h", "--help"):
|
||||
print(__doc__)
|
||||
return 0
|
||||
if args[0] == "--check":
|
||||
verbose = "-v" in args[1:] or "--verbose" in args[1:]
|
||||
css_result = check_all(verbose)
|
||||
sync_result = sync_check(verbose)
|
||||
return max(css_result, sync_result)
|
||||
if args[0] == "--sync":
|
||||
verbose = "-v" in args[1:] or "--verbose" in args[1:]
|
||||
return sync_check(verbose)
|
||||
if args[0] == "--verify":
|
||||
target = args[1] if len(args) > 1 and not args[1].startswith("-") else None
|
||||
return verify_all(target)
|
||||
return build_single(args[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,56 @@
|
||||
# Kami Design System · Web Page
|
||||
|
||||
一页可编辑的设计系统参考页。部署到 GitHub Pages 或直接本地打开浏览。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
site/
|
||||
├── index.html # 页面主体(可直接编辑 HTML 内容)
|
||||
├── styles.css # 所有样式(可直接调色 / 改字号)
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
字体文件复用仓库自带的:`../assets/fonts/TsangerJinKai02-W04.ttf`(相对路径已在 `styles.css` 配好)。
|
||||
|
||||
## 本地预览
|
||||
|
||||
直接用浏览器打开 `site/index.html` 即可。
|
||||
|
||||
或者用任意静态服务器:
|
||||
```bash
|
||||
cd site && python3 -m http.server 8000
|
||||
# 访问 http://localhost:8000
|
||||
```
|
||||
|
||||
## 部署到 GitHub Pages
|
||||
|
||||
1. 把 `site/` 整个目录提交到仓库
|
||||
2. 仓库 **Settings → Pages**
|
||||
3. Source 选 **Deploy from a branch**
|
||||
4. Branch 选 `main` · Folder 选 `/site`
|
||||
5. Save,几分钟后访问 `https://<you>.github.io/kami/`
|
||||
|
||||
> ⚠️ 因为 `styles.css` 里引用了 `../assets/fonts/TsangerJinKai02-W04.ttf`,`site/` 必须和 `assets/` 位于同一层级。你仓库本身的结构刚好满足。
|
||||
|
||||
## 导出 PDF
|
||||
|
||||
浏览器里按 **Cmd/Ctrl + P**:
|
||||
- 纸张:A4
|
||||
- 边距:默认
|
||||
- ✅ 勾选"背景图形"(否则米色底会丢失)
|
||||
- 保存为 PDF
|
||||
|
||||
`@page` 规则已经把页边背景延伸为 parchment,分页保护也已内置(卡片、引用、指标不会跨页断开)。
|
||||
|
||||
## 二次编辑指南
|
||||
|
||||
**改色** → 打开 `styles.css` 顶部 `:root { ... }`,所有 token 在这里集中定义。
|
||||
|
||||
**改字号** → 搜 `.type-sample.display` / `.hero h1` 等具名样式。
|
||||
|
||||
**改内容** → 改 `index.html`,样式完全由 class 驱动,改文案不影响布局。
|
||||
|
||||
**加章节** → 复制一个 `<section>` 块粘到下方,按 `.section-head` → `.section-num` / `.section-title` / `.section-lede` 的结构填。
|
||||
|
||||
**改字体** → 若要换 serif 主字,改 `styles.css` 里 `--serif` 变量的回退链顺序即可。
|
||||
@@ -0,0 +1,412 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Kami · Design System</title>
|
||||
<meta name="viewport" content="width=1280">
|
||||
<meta name="description" content="Kami - warm parchment design system for professional documents. One accent color, serif-led hierarchy, editorial whitespace.">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="lang-switch"><a href="./index.html">中</a><a href="./index-en.html" class="active">EN</a></nav>
|
||||
<main class="page">
|
||||
|
||||
<!-- ============ HERO ============ -->
|
||||
<header class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Design System · v1.0 · April 2026</p>
|
||||
<h1>Kami<span class="cn">紙</span></h1>
|
||||
<p class="tagline">The paper your deliverables land on. One design language across six document types: warm parchment, a single ink-blue accent, serif-led hierarchy, editorial whitespace.</p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div><b>Canvas</b> #f5f4ed</div>
|
||||
<div><b>Accent</b> #1B365D</div>
|
||||
<div><b>Serif</b> Newsreader / TsangerJinKai</div>
|
||||
<div><b>Sans</b> Inter / Source Han Sans</div>
|
||||
<div style="margin-top:10px; color: var(--olive);">Six templates · One language</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============ 00 MANIFESTO ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">00 · Manifesto</p>
|
||||
<h2 class="section-title">Design Manifesto</h2>
|
||||
</div>
|
||||
<p class="manifesto">Warm parchment canvas, <em>ink blue</em> as the sole accent, serif carries authority, no hard shadows or flashy palettes. This is not a UI framework; it is an aesthetic constraint system for printed matter. It believes: quality documents read like literature, not dashboards.</p>
|
||||
<ol class="rules">
|
||||
<li><span class="n">1</span><p>Page background is parchment <code>#f5f4ed</code>, never pure white</p></li>
|
||||
<li><span class="n">2</span><p>Accent color is ink blue <code>#1B365D</code> only; no second chromatic hue</p></li>
|
||||
<li><span class="n">3</span><p>All grays are warm (yellow-brown undertone); no cool blue-grays</p></li>
|
||||
<li><span class="n">4</span><p>Headlines in serif, body and UI in sans</p></li>
|
||||
<li><span class="n">5</span><p>Serif weight fixed at 500, never bold. Single weight is part of the design language</p></li>
|
||||
<li><span class="n">6</span><p>Three line-height bands: tight titles 1.1-1.3 / dense 1.4-1.45 / reading 1.5-1.55. Never 1.6+</p></li>
|
||||
<li><span class="n">7</span><p>Tag backgrounds must be solid hex; no rgba (WeasyPrint double-rectangle bug)</p></li>
|
||||
<li><span class="n">8</span><p>Shadows: ring or whisper only, no hard drop shadows</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============ 01 COLOR ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">01 · Color</p>
|
||||
<h2 class="section-title">Color</h2>
|
||||
<p class="section-lede">One accent + warm neutrals + zero cool colors. Ink blue covers no more than <b>5%</b> of any page. Beyond that is clutter, not restraint.</p>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Canvas</h3>
|
||||
<div class="swatches">
|
||||
<div class="swatch"><div class="chip" style="background:#f5f4ed"></div><div class="info"><p class="name">Parchment</p><p class="role">Page background, the emotional foundation</p><span class="hex">#F5F4ED</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#faf9f5"></div><div class="info"><p class="name">Ivory</p><p class="role">Cards / elevated surfaces</p><span class="hex">#FAF9F5</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#e8e6dc"></div><div class="info"><p class="name">Warm Sand</p><p class="role">Button default / interactive surfaces</p><span class="hex">#E8E6DC</span></div></div>
|
||||
<div class="swatch dark"><div class="chip" style="background:#141413"></div><div class="info"><p class="name" style="color:var(--deep-dark)">Deep Dark</p><p class="role">Dark theme page base, not pure black</p><span class="hex">#141413</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Brand</h3>
|
||||
<div class="swatches">
|
||||
<div class="swatch brand"><div class="chip" style="background:#1B365D"></div><div class="info"><p class="name">Ink Blue</p><p class="role">Primary color · CTA · section underlines</p><span class="hex">#1B365D</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#2D5A8A"></div><div class="info"><p class="name">Ink Light</p><p class="role">Links on dark surfaces · lighter variant</p><span class="hex">#2D5A8A</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#30302e"></div><div class="info"><p class="name" style="color:var(--deep-dark)">Dark Surface</p><p class="role">Dark theme container · warm charcoal</p><span class="hex">#30302E</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#b53333"></div><div class="info"><p class="name">Error</p><p class="role">Error state, deep warm red</p><span class="hex">#B53333</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Warm Neutrals</h3>
|
||||
<div class="swatches" style="grid-template-columns: repeat(6, 1fr);">
|
||||
<div class="swatch"><div class="chip" style="background:#141413"></div><div class="info"><p class="name">Near Black</p><span class="hex">#141413</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#3d3d3a"></div><div class="info"><p class="name">Dark Warm</p><span class="hex">#3D3D3A</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#4d4c48"></div><div class="info"><p class="name">Charcoal</p><span class="hex">#4D4C48</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#5e5d59"></div><div class="info"><p class="name">Olive</p><span class="hex">#5E5D59</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#87867f"></div><div class="info"><p class="name">Stone</p><span class="hex">#87867F</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#b0aea5"></div><div class="info"><p class="name">Warm Silver</p><span class="hex">#B0AEA5</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Tag Tints: rgba to solid hex</h3>
|
||||
<p style="font-size:12.5px; color: var(--olive); max-width: 720px; margin: 0 0 16px; line-height: 1.55;">Ink Blue <code style="font-family:var(--mono);font-size:11px;background:#EEF2F7;color:var(--brand);padding:1px 5px;border-radius:2px">#1B365D</code> layered on parchment as equivalent solid hex. <b style="color:var(--brand);font-weight:500">Never</b> use <code style="font-family:var(--mono);font-size:11px;background:#EEF2F7;color:var(--brand);padding:1px 5px;border-radius:2px">rgba()</code>: WeasyPrint double-rectangle bug.</p>
|
||||
<div class="tint-strip">
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#EEF2F7">Tag sample</span><p class="opacity">0.08</p><span class="hex">#EEF2F7</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#E4ECF5">Tag sample</span><p class="opacity">0.14</p><span class="hex">#E4ECF5</span></div>
|
||||
<div class="tint default"><span class="flag">Default</span><span class="sample" style="background:#E4ECF5">Tag sample</span><p class="opacity">0.18</p><span class="hex">#E4ECF5</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#D6E1EE">Tag sample</span><p class="opacity">0.22</p><span class="hex">#D6E1EE</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#D6E1EE">Tag sample</span><p class="opacity">0.30</p><span class="hex">#D6E1EE</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 02 TYPE ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">02 · Typography</p>
|
||||
<h2 class="section-title">Typography</h2>
|
||||
<p class="section-lede">Serif carries authority, sans carries function. Serif weight fixed at 500: single weight is the system's signature.</p>
|
||||
</div>
|
||||
|
||||
<div class="family-grid">
|
||||
<div class="family serif">
|
||||
<p class="glyph">Aa</p>
|
||||
<p class="role">Serif · Headlines</p>
|
||||
<p class="name">Newsreader / TsangerJinKai02</p>
|
||||
<p class="use">All headlines, pull quotes, and numeric emphasis. Fixed weight 500, never bold. English uses Newsreader; Chinese uses TsangerJinKai02.</p>
|
||||
</div>
|
||||
<div class="family sans">
|
||||
<p class="glyph">Aa</p>
|
||||
<p class="role">Sans · Body / UI</p>
|
||||
<p class="name">Inter / Source Han Sans</p>
|
||||
<p class="use">Body text, labels, buttons, metadata. Default 400, labels and titles use 500-600. Silicon Valley editorial stack.</p>
|
||||
</div>
|
||||
<div class="family mono">
|
||||
<p class="glyph"></></p>
|
||||
<p class="role">Mono · Code</p>
|
||||
<p class="name">JetBrains Mono</p>
|
||||
<p class="use">Code blocks, version numbers, hex values, tabular figures. One size smaller when paired with serif or sans.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Scale (print pt; screen px = pt x 1.33)</h3>
|
||||
<div class="type-grid">
|
||||
|
||||
<div class="type-row">
|
||||
<div class="type-label">Display</div>
|
||||
<div class="type-sample display">Quality documents read<br>like literature</div>
|
||||
<div class="type-specs"><b>36-48 pt</b><br>weight 500<br>line 1.10</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H1 Section</div>
|
||||
<div class="type-sample h1">Color System · Palette</div>
|
||||
<div class="type-specs"><b>18-22 pt</b><br>weight 500<br>line 1.20</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H2 Subsection</div>
|
||||
<div class="type-sample h2">Canvas · Warm Neutrals</div>
|
||||
<div class="type-specs"><b>14-16 pt</b><br>weight 500<br>line 1.25</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H3 Item</div>
|
||||
<div class="type-sample h3">Tag Tints · Solid Hex</div>
|
||||
<div class="type-specs"><b>12-13 pt</b><br>weight 500<br>line 1.30</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body Lead</div>
|
||||
<div class="type-sample lede">This is not a UI framework; it is an aesthetic constraint system for printed matter. Every rule trades one more option for one fewer temptation.</div>
|
||||
<div class="type-specs"><b>11 pt</b><br>weight 400<br>line 1.55</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body</div>
|
||||
<div class="type-sample body">Ink blue covers no more than 5% of any page. Beyond that is clutter, not restraint. All grays have a yellow-brown undertone: R ≈ G > B is the rule of thumb.</div>
|
||||
<div class="type-specs"><b>9.5-10 pt</b><br>weight 400<br>line 1.55</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body Dense</div>
|
||||
<div class="type-sample dense">Dense body is for resumes, one-pagers, business cards, index cards. At print pt sizes, line-height 1.4-1.45 feels natural; the web-standard 1.6 looks loose in this context.</div>
|
||||
<div class="type-specs"><b>9-9.2 pt</b><br>weight 400<br>line 1.40</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Caption</div>
|
||||
<div class="type-sample caption">Figure notes, footnotes, annotation text. Color drops to olive or stone.</div>
|
||||
<div class="type-specs"><b>8.5-9 pt</b><br>weight 400<br>line 1.45</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Label</div>
|
||||
<div class="type-sample label">Design System · v1.0</div>
|
||||
<div class="type-specs"><b>7.5-8 pt</b><br>weight 600<br>line 1.35</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 03 SPACING ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">03 · Spacing & Shape</p>
|
||||
<h2 class="section-title">Spacing & Shape</h2>
|
||||
<p class="section-lede">Base unit: 4pt. Denser layouts get smaller margins; more formal documents get larger ones.</p>
|
||||
</div>
|
||||
|
||||
<table class="space-table">
|
||||
<tr>
|
||||
<th>Scale</th><th>Value</th><th>Use</th><th></th>
|
||||
</tr>
|
||||
<tr><td class="name">xs</td><td class="val">2-3 pt</td><td class="use">Inline elements</td><td class="bar"><span style="width:6px"></span></td></tr>
|
||||
<tr><td class="name">sm</td><td class="val">4-5 pt</td><td class="use">Tag padding · tight layout</td><td class="bar"><span style="width:12px"></span></td></tr>
|
||||
<tr><td class="name">md</td><td class="val">8-10 pt</td><td class="use">Component internals</td><td class="bar"><span style="width:24px"></span></td></tr>
|
||||
<tr><td class="name">lg</td><td class="val">16-20 pt</td><td class="use">Between components · card padding</td><td class="bar"><span style="width:48px"></span></td></tr>
|
||||
<tr><td class="name">xl</td><td class="val">24-32 pt</td><td class="use">Section title margins</td><td class="bar"><span style="width:72px"></span></td></tr>
|
||||
<tr><td class="name">2xl</td><td class="val">40-60 pt</td><td class="use">Between major sections</td><td class="bar"><span style="width:140px"></span></td></tr>
|
||||
<tr><td class="name">3xl</td><td class="val">80-120 pt</td><td class="use">Between long-doc chapters</td><td class="bar"><span style="width:240px"></span></td></tr>
|
||||
</table>
|
||||
|
||||
<h3 class="subhead">Radii</h3>
|
||||
<div class="radii">
|
||||
<div class="radius"><div class="box" style="border-radius:4px"></div><div class="r"><b>4</b> pt</div><div class="label">Tight</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:6px"></div><div class="r"><b>6</b> pt</div><div class="label">Code block</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:8px"></div><div class="r"><b>8</b> pt</div><div class="label">Default card</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:12px"></div><div class="r"><b>12</b> pt</div><div class="label">Container</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:16px"></div><div class="r"><b>16</b> pt</div><div class="label">Feature card</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:24px"></div><div class="r"><b>24</b> pt</div><div class="label">Large container</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:32px"></div><div class="r"><b>32</b> pt</div><div class="label">Hero</div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Depth: Three Shadow Methods</h3>
|
||||
<p style="font-size:12.5px; color: var(--olive); max-width: 720px; margin: 0 0 12px; line-height: 1.55;">Kami avoids traditional hard shadows. Depth is created through ring shadow / whisper shadow / light-dark alternation.</p>
|
||||
<div class="shadow-row">
|
||||
<div class="shadow-demo sd-ring">
|
||||
<p class="t">Ring Shadow</p>
|
||||
<p class="s">box-shadow: 0 0 0 1pt var(--ring-warm)<br> <br>Buttons · card hover</p>
|
||||
</div>
|
||||
<div class="shadow-demo sd-whisper">
|
||||
<p class="t">Whisper Shadow</p>
|
||||
<p class="s">0 4pt 24pt rgba(0,0,0,0.05)<br> <br>Gentle elevation · feature cards</p>
|
||||
</div>
|
||||
<div class="shadow-demo sd-alt">
|
||||
<p class="t">Light / Dark</p>
|
||||
<p class="s">parchment / deep-dark<br> <br>Section-level · most dramatic</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 04 COMPONENTS ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">04 · Components</p>
|
||||
<h2 class="section-title">Components</h2>
|
||||
<p class="section-lede">A small, fixed set. Each answers a specific question.</p>
|
||||
</div>
|
||||
|
||||
<div class="comp-grid">
|
||||
|
||||
<div class="comp">
|
||||
<h4>Buttons</h4>
|
||||
<p class="hint">ring shadow instead of hard drop · 8pt radius</p>
|
||||
<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span class="btn btn-primary">Primary CTA</span>
|
||||
<span class="btn btn-secondary">Secondary</span>
|
||||
<span class="btn btn-ghost">Ghost</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Tags (three levels)</h4>
|
||||
<p class="hint">solid hex · pick from weak to strong</p>
|
||||
<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
||||
<span class="tag calm">Light 0.08</span>
|
||||
<span class="tag standard">Standard 0.18</span>
|
||||
<span class="tag brush">Brush gradient</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Quote</h4>
|
||||
<p class="hint">left 2pt brand solid + olive text</p>
|
||||
<blockquote class="quote">A thousand no's for every yes. Prefer clarity over decoration.</blockquote>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Metric</h4>
|
||||
<p class="hint">serif number + sans label · tabular-nums</p>
|
||||
<div class="metrics demo">
|
||||
<div class="metric"><span class="metric-value">6</span><span class="metric-label">Doc types</span></div>
|
||||
<div class="metric"><span class="metric-value">1</span><span class="metric-label">Accent</span></div>
|
||||
<div class="metric"><span class="metric-value">8</span><span class="metric-label">Rules</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Section Title</h4>
|
||||
<p class="hint">serif 500 + 1pt brand underline (signature style)</p>
|
||||
<div class="demo">
|
||||
<span class="sample-section-title">Selected Works · Projects</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Dash List</h4>
|
||||
<p class="hint">dash instead of bullet · more literary</p>
|
||||
<ul class="dash demo">
|
||||
<li>Warm parchment, never pure white</li>
|
||||
<li>Ink blue accent, no second color</li>
|
||||
<li>Serif carries authority</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Code Block</h4>
|
||||
<p class="hint">ivory bg + 0.5pt border + 6pt radius</p>
|
||||
<pre class="code"><span class="c">/* section title */</span>
|
||||
.section-title {
|
||||
font-family: <span class="k">serif</span>;
|
||||
font-weight: <span class="k">500</span>;
|
||||
border-bottom: <span class="k">1pt</span> solid
|
||||
var(--brand);
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Featured Card</h4>
|
||||
<p class="hint">whisper shadow + 16pt radius</p>
|
||||
<div class="demo" style="background:var(--ivory); border-radius:16px; padding:18px 20px; box-shadow: 0 4px 24px rgba(0,0,0,0.05); border: 1px solid var(--border-cream);">
|
||||
<p style="font-family:var(--serif); font-weight:500; font-size:15px; margin:0 0 6px; color:var(--near-black);">Tesla Company Profile · One-Pager</p>
|
||||
<p style="font-size:12px; color:var(--olive); margin:0 0 10px; line-height:1.5;">Single A4 page · 4 metric cards + 3 body sections + timeline</p>
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<span class="tag calm">English</span>
|
||||
<span class="tag calm">A4 x 1</span>
|
||||
<span class="tag calm">Ink Blue</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 05 DECISION TABLE ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">05 · Decision Lookup</p>
|
||||
<h2 class="section-title">Decision Lookup</h2>
|
||||
<p class="section-lede">When in doubt about what to use, consult this table. If it's not here, go back to first principles.</p>
|
||||
</div>
|
||||
<table class="decision">
|
||||
<tr><th>Task</th><th>How</th></tr>
|
||||
<tr><td class="task">Large headline</td><td class="how">serif <code>500</code>, size by hierarchy, line-height <code>1.10-1.30</code></td></tr>
|
||||
<tr><td class="task">Reading body</td><td class="how">sans <code>400</code>, <code>9.5-10 pt</code>, line-height <code>1.55</code></td></tr>
|
||||
<tr><td class="task">Emphasize a number</td><td class="how"><code>color: var(--brand)</code>, not bold</td></tr>
|
||||
<tr><td class="task">Separate two sections</td><td class="how"><code>1pt</code> brand border-bottom, or <code>0.5pt</code> warm-gray dashed</td></tr>
|
||||
<tr><td class="task">Quote someone</td><td class="how">Left <code>2pt</code> brand solid + olive text</td></tr>
|
||||
<tr><td class="task">Show code</td><td class="how">Ivory bg + <code>0.5pt</code> border + <code>6pt</code> radius + mono font</td></tr>
|
||||
<tr><td class="task">Primary vs secondary</td><td class="how">Primary: brand fill + ivory text; Secondary: warm-sand + charcoal</td></tr>
|
||||
<tr><td class="task">Mark a special card</td><td class="how"><code>border: 0.5pt solid var(--brand)</code> or <code>border-left: 3pt</code></td></tr>
|
||||
<tr><td class="task">Begin a section</td><td class="how">Serif title + <code>1pt</code> brand underline below</td></tr>
|
||||
<tr><td class="task">Document cover</td><td class="how">Single page, Display size + right-aligned author/date + generous whitespace</td></tr>
|
||||
<tr><td class="task">Data card</td><td class="how">Ivory bg + <code>8pt</code> radius + serif large number (ink blue) + sans small label</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ============ 06 ANTI ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">06 · Anti-Patterns</p>
|
||||
<h2 class="section-title">Anti-Patterns</h2>
|
||||
<p class="section-lede">Before breaking a rule, make sure you truly intend to.</p>
|
||||
</div>
|
||||
|
||||
<div class="anti">
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>Use <code>#ffffff</code> pure white or <code>#f3f4f6</code> cool gray as page background. Kami's emotional foundation is warm parchment.</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Always use <code>#f5f4ed</code> parchment. Set <code>@page background</code> to the same color to avoid white edges when printing.</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>Use <code>rgba(27,54,93,0.18)</code> for tags. WeasyPrint renders padding and glyph areas at different opacity, creating double rectangles.</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Use the tint lookup table for equivalent solid hex <code>#E4ECF5</code>. Bug gone, color consistent.</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>Set headlines to <code>font-weight: 700</code> bold. Serif 500 is the system's signature; bold makes the layout feel restless.</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Keep serif at 500. For stronger presence, increase size or add a 1pt brand underline. Never go bold.</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>Use hard drop shadow <code>0 2px 8px rgba(0,0,0,0.3)</code>. Cold, SaaS-like, prints as dark blobs.</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Ring shadow (<code>0 0 0 1pt</code>) or whisper (<code>rgba 0.05</code>), or simply alternate light and dark sections.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ FOOTER ============ -->
|
||||
<footer class="footer">
|
||||
<div class="mark">
|
||||
<svg width="56" height="56" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-label="Kami mark">
|
||||
<rect x="6" y="6" width="68" height="68" rx="10" fill="#faf9f5" stroke="#1B365D" stroke-width="1.25"/>
|
||||
<line x1="16" y1="26" x2="64" y2="26" stroke="#1B365D" stroke-width="1.25"/>
|
||||
<text x="40" y="56" font-family="TsangerJinKai02, Georgia, serif" font-size="34" font-weight="500" fill="#141413" text-anchor="middle">紙</text>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="wm-name">Kami · 紙</p>
|
||||
<p class="wm-sub">The paper your deliverables land on.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colophon">
|
||||
<div><b>Distilled from</b> references/design.md · CHEATSHEET.md · tokens.json</div>
|
||||
<div><b>Document types</b> One-Pager · Long Doc · Letter · Portfolio · Resume · Slides</div>
|
||||
<div style="margin-top:8px; color: var(--olive); font-family: var(--serif);">
|
||||
Serif for authority, sans for function, warm grays for rhythm, ink blue for focus.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,412 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Kami · Design System</title>
|
||||
<meta name="viewport" content="width=1280">
|
||||
<meta name="description" content="Kami 紙: 温暖纸质感设计系统,用于专业文档排版。单一强调色,serif 主导层级,编辑级留白。">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="lang-switch"><a href="./index.html" class="active">中</a><a href="./index-en.html">EN</a></nav>
|
||||
<main class="page">
|
||||
|
||||
<!-- ============ HERO ============ -->
|
||||
<header class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Design System · v1.0 · April 2026</p>
|
||||
<h1>Kami<span class="cn">紙</span></h1>
|
||||
<p class="tagline">你的交付物落在什么纸上。六种文档,一套设计语言: 暖米纸底,油墨蓝点缀,serif 主导层级,编辑级留白。</p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div><b>Canvas</b> #f5f4ed</div>
|
||||
<div><b>Accent</b> #1B365D</div>
|
||||
<div><b>Serif</b> TsangerJinKai / Newsreader</div>
|
||||
<div><b>Sans</b> Source Han Sans / Inter</div>
|
||||
<div style="margin-top:10px; color: var(--olive);">六种文档 · 一套语言</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============ 00 MANIFESTO ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">00 · Manifesto</p>
|
||||
<h2 class="section-title">设计宣言</h2>
|
||||
</div>
|
||||
<p class="manifesto">暖米纸底,<em>油墨蓝</em>点缀,serif 承担权威,拒绝硬阴影与花哨配色。这不是一套 UI 框架,是一套印刷品的审美约束。它相信:高质量的文档读起来像文学,不像仪表盘。</p>
|
||||
<ol class="rules">
|
||||
<li><span class="n">1</span><p>页面背景 parchment <code>#f5f4ed</code>,不用纯白</p></li>
|
||||
<li><span class="n">2</span><p>强调色只有油墨蓝 <code>#1B365D</code>,不引入第二种彩色</p></li>
|
||||
<li><span class="n">3</span><p>所有灰色暖调 (yellow-brown undertone),禁止冷蓝灰</p></li>
|
||||
<li><span class="n">4</span><p>标题 serif,正文 / UI sans</p></li>
|
||||
<li><span class="n">5</span><p>Serif 字重固定 500,不用 bold。单一字重是设计语言的一部分</p></li>
|
||||
<li><span class="n">6</span><p>行距三档:紧凑标题 1.1–1.3 / 密排 1.4–1.45 / 阅读 1.5–1.55。禁 1.6+</p></li>
|
||||
<li><span class="n">7</span><p>Tag 背景必须实色 hex,禁止 rgba(WeasyPrint 双层矩形 bug)</p></li>
|
||||
<li><span class="n">8</span><p>阴影只用 ring 或 whisper shadow,不用硬 drop shadow</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============ 01 COLOR ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">01 · Color</p>
|
||||
<h2 class="section-title">色彩</h2>
|
||||
<p class="section-lede">唯一强调色 + 纯暖调中性灰 + 零冷色。油墨蓝全文档不超过 <b>5%</b> 面积。超过就是堆砌,不是克制。</p>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">画布 · Canvas</h3>
|
||||
<div class="swatches">
|
||||
<div class="swatch"><div class="chip" style="background:#f5f4ed"></div><div class="info"><p class="name">Parchment</p><p class="role">页面底色,整个设计的情感基础</p><span class="hex">#F5F4ED</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#faf9f5"></div><div class="info"><p class="name">Ivory</p><p class="role">卡片 / 浮起容器</p><span class="hex">#FAF9F5</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#e8e6dc"></div><div class="info"><p class="name">Warm Sand</p><p class="role">按钮默认背景 / 交互面</p><span class="hex">#E8E6DC</span></div></div>
|
||||
<div class="swatch dark"><div class="chip" style="background:#141413"></div><div class="info"><p class="name" style="color:var(--deep-dark)">Deep Dark</p><p class="role">深色主题页面底,非纯黑,有橄榄底色</p><span class="hex">#141413</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">主调 · Brand</h3>
|
||||
<div class="swatches">
|
||||
<div class="swatch brand"><div class="chip" style="background:#1B365D"></div><div class="info"><p class="name">Ink Blue</p><p class="role">主色 · CTA · section 下划线</p><span class="hex">#1B365D</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#2D5A8A"></div><div class="info"><p class="name">Ink Light</p><p class="role">深色底上的链接 · 亮变体</p><span class="hex">#2D5A8A</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#30302e"></div><div class="info"><p class="name" style="color:var(--deep-dark)">Dark Surface</p><p class="role">深色主题容器 · 暖炭灰</p><span class="hex">#30302E</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#b53333"></div><div class="info"><p class="name">Error</p><p class="role">错误,深暖红,不刺眼</p><span class="hex">#B53333</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">中性 · Warm Neutrals</h3>
|
||||
<div class="swatches" style="grid-template-columns: repeat(6, 1fr);">
|
||||
<div class="swatch"><div class="chip" style="background:#141413"></div><div class="info"><p class="name">Near Black</p><span class="hex">#141413</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#3d3d3a"></div><div class="info"><p class="name">Dark Warm</p><span class="hex">#3D3D3A</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#4d4c48"></div><div class="info"><p class="name">Charcoal</p><span class="hex">#4D4C48</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#5e5d59"></div><div class="info"><p class="name">Olive</p><span class="hex">#5E5D59</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#87867f"></div><div class="info"><p class="name">Stone</p><span class="hex">#87867F</span></div></div>
|
||||
<div class="swatch"><div class="chip" style="background:#b0aea5"></div><div class="info"><p class="name">Warm Silver</p><span class="hex">#B0AEA5</span></div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">Tag Tints · rgba → 实色对照</h3>
|
||||
<p style="font-size:12.5px; color: var(--olive); max-width: 720px; margin: 0 0 16px; line-height: 1.55;">油墨蓝 <code style="font-family:var(--mono);font-size:11px;background:#EEF2F7;color:var(--brand);padding:1px 5px;border-radius:2px">#1B365D</code> 叠加在 parchment 上的等效实色。<b style="color:var(--brand);font-weight:500">禁止</b>使用 <code style="font-family:var(--mono);font-size:11px;background:#EEF2F7;color:var(--brand);padding:1px 5px;border-radius:2px">rgba()</code>,WeasyPrint 双层矩形 bug。</p>
|
||||
<div class="tint-strip">
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#EEF2F7">Tag sample</span><p class="opacity">0.08</p><span class="hex">#EEF2F7</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#E4ECF5">Tag sample</span><p class="opacity">0.14</p><span class="hex">#E4ECF5</span></div>
|
||||
<div class="tint default"><span class="flag">Default</span><span class="sample" style="background:#E4ECF5">Tag sample</span><p class="opacity">0.18</p><span class="hex">#E4ECF5</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#D6E1EE">Tag sample</span><p class="opacity">0.22</p><span class="hex">#D6E1EE</span></div>
|
||||
<div class="tint"><span class="flag"> </span><span class="sample" style="background:#D6E1EE">Tag sample</span><p class="opacity">0.30</p><span class="hex">#D6E1EE</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 02 TYPE ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">02 · Typography</p>
|
||||
<h2 class="section-title">字体</h2>
|
||||
<p class="section-lede">Serif 承担权威,sans 承担功能。Serif 字重固定 500,单一字重是这套系统的签名式样。</p>
|
||||
</div>
|
||||
|
||||
<div class="family-grid">
|
||||
<div class="family serif">
|
||||
<p class="glyph">紙 Aa</p>
|
||||
<p class="role">Serif · 标题</p>
|
||||
<p class="name">TsangerJinKai02 / Newsreader</p>
|
||||
<p class="use">承担所有标题、引语与数字强调。固定字重 500,不用 bold。中文首选仓耳今楷,英文首选 Newsreader。</p>
|
||||
</div>
|
||||
<div class="family sans">
|
||||
<p class="glyph">文 Aa</p>
|
||||
<p class="role">Sans · 正文 / UI</p>
|
||||
<p class="name">Source Han Sans / Inter</p>
|
||||
<p class="use">正文、标签、按钮、metadata。默认 400,标签 / 标题用 500–600。Silicon Valley editorial stack。</p>
|
||||
</div>
|
||||
<div class="family mono">
|
||||
<p class="glyph"></></p>
|
||||
<p class="role">Mono · 代码</p>
|
||||
<p class="name">JetBrains Mono</p>
|
||||
<p class="use">代码块、版本号、hex 值、等宽数字。与 serif / sans 搭配时字号小一档。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">层级(印刷品 pt · 屏幕 px ≈ pt × 1.33)</h3>
|
||||
<div class="type-grid">
|
||||
|
||||
<div class="type-row">
|
||||
<div class="type-label">Display</div>
|
||||
<div class="type-sample display">高质量文档读起来<br>像文学,不像仪表盘</div>
|
||||
<div class="type-specs"><b>36–48 pt</b><br>weight 500<br>line 1.10</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H1 Section</div>
|
||||
<div class="type-sample h1">色彩系统 · Color</div>
|
||||
<div class="type-specs"><b>18–22 pt</b><br>weight 500<br>line 1.20</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H2 Subsection</div>
|
||||
<div class="type-sample h2">画布 · Canvas</div>
|
||||
<div class="type-specs"><b>14–16 pt</b><br>weight 500<br>line 1.25</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">H3 Item</div>
|
||||
<div class="type-sample h3">Tag Tints · rgba 对照</div>
|
||||
<div class="type-specs"><b>12–13 pt</b><br>weight 500<br>line 1.30</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body Lead</div>
|
||||
<div class="type-sample lede">这不是一套 UI 框架,是一套印刷品的审美约束。每条铁律的 trade-off 都是"与其多一个选择,不如少一个诱惑"。</div>
|
||||
<div class="type-specs"><b>11 pt</b><br>weight 400<br>line 1.55</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body</div>
|
||||
<div class="type-sample body">油墨蓝全文档不超过 5% 面积。超过就是堆砌,不是克制。所有灰色都有暖黄底色,R ≈ G > B 是基本规律。</div>
|
||||
<div class="type-specs"><b>9.5–10 pt</b><br>weight 400<br>line 1.55</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Body Dense</div>
|
||||
<div class="type-sample dense">密排正文用于简历、一页纸、名片、索引卡。中文 pt 字号下,1.4–1.45 的行距最舒服;英文网页的 1.6 放进来会显得松散。</div>
|
||||
<div class="type-specs"><b>9–9.2 pt</b><br>weight 400<br>line 1.40</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Caption</div>
|
||||
<div class="type-sample caption">图注、脚注、说明文字。颜色用 olive 或 stone 降一级。</div>
|
||||
<div class="type-specs"><b>8.5–9 pt</b><br>weight 400<br>line 1.45</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">Label</div>
|
||||
<div class="type-sample label">Design System · v1.0</div>
|
||||
<div class="type-specs"><b>7.5–8 pt</b><br>weight 600<br>line 1.35</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 03 SPACING ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">03 · Spacing & Shape</p>
|
||||
<h2 class="section-title">间距 · 形状</h2>
|
||||
<p class="section-lede">基础单位 4pt。密度越高 margin 越小,越正式 margin 越大。</p>
|
||||
</div>
|
||||
|
||||
<table class="space-table">
|
||||
<tr>
|
||||
<th>Scale</th><th>Value</th><th>Use</th><th></th>
|
||||
</tr>
|
||||
<tr><td class="name">xs</td><td class="val">2–3 pt</td><td class="use">同行内元素</td><td class="bar"><span style="width:6px"></span></td></tr>
|
||||
<tr><td class="name">sm</td><td class="val">4–5 pt</td><td class="use">tag padding · 紧凑布局</td><td class="bar"><span style="width:12px"></span></td></tr>
|
||||
<tr><td class="name">md</td><td class="val">8–10 pt</td><td class="use">组件内部</td><td class="bar"><span style="width:24px"></span></td></tr>
|
||||
<tr><td class="name">lg</td><td class="val">16–20 pt</td><td class="use">组件之间 · 卡片 padding</td><td class="bar"><span style="width:48px"></span></td></tr>
|
||||
<tr><td class="name">xl</td><td class="val">24–32 pt</td><td class="use">section 标题 margin</td><td class="bar"><span style="width:72px"></span></td></tr>
|
||||
<tr><td class="name">2xl</td><td class="val">40–60 pt</td><td class="use">大 section 之间</td><td class="bar"><span style="width:140px"></span></td></tr>
|
||||
<tr><td class="name">3xl</td><td class="val">80–120 pt</td><td class="use">长文档章节之间</td><td class="bar"><span style="width:240px"></span></td></tr>
|
||||
</table>
|
||||
|
||||
<h3 class="subhead">圆角尺度 · Radii</h3>
|
||||
<div class="radii">
|
||||
<div class="radius"><div class="box" style="border-radius:4px"></div><div class="r"><b>4</b> pt</div><div class="label">极紧</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:6px"></div><div class="r"><b>6</b> pt</div><div class="label">代码块</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:8px"></div><div class="r"><b>8</b> pt</div><div class="label">默认卡片</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:12px"></div><div class="r"><b>12</b> pt</div><div class="label">容器</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:16px"></div><div class="r"><b>16</b> pt</div><div class="label">特色卡片</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:24px"></div><div class="r"><b>24</b> pt</div><div class="label">大容器</div></div>
|
||||
<div class="radius"><div class="box" style="border-radius:32px"></div><div class="r"><b>32</b> pt</div><div class="label">Hero</div></div>
|
||||
</div>
|
||||
|
||||
<h3 class="subhead">深度 · 阴影三法</h3>
|
||||
<p style="font-size:12.5px; color: var(--olive); max-width: 720px; margin: 0 0 12px; line-height: 1.55;">Kami 不用传统硬阴影。深度通过 ring shadow / whisper shadow / 明暗交替 三种方式创造。</p>
|
||||
<div class="shadow-row">
|
||||
<div class="shadow-demo sd-ring">
|
||||
<p class="t">Ring Shadow</p>
|
||||
<p class="s">box-shadow: 0 0 0 1pt var(--ring-warm)<br> <br>按钮 · 卡片 hover</p>
|
||||
</div>
|
||||
<div class="shadow-demo sd-whisper">
|
||||
<p class="t">Whisper Shadow</p>
|
||||
<p class="s">0 4pt 24pt rgba(0,0,0,0.05)<br> <br>极轻浮起 · 特色卡</p>
|
||||
</div>
|
||||
<div class="shadow-demo sd-alt">
|
||||
<p class="t">Light ⇌ Dark</p>
|
||||
<p class="s">parchment ⇌ deep-dark<br> <br>section 级别 · 最戏剧化</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 04 COMPONENTS ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">04 · Components</p>
|
||||
<h2 class="section-title">组件</h2>
|
||||
<p class="section-lede">小而定型的一组。每个都回答一个具体问题。</p>
|
||||
</div>
|
||||
|
||||
<div class="comp-grid">
|
||||
|
||||
<div class="comp">
|
||||
<h4>Buttons</h4>
|
||||
<p class="hint">ring shadow 替代硬投影 · 8pt 圆角</p>
|
||||
<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span class="btn btn-primary">Primary CTA</span>
|
||||
<span class="btn btn-secondary">Secondary</span>
|
||||
<span class="btn btn-ghost">Ghost</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Tags · 三档</h4>
|
||||
<p class="hint">实色 hex · 从弱到强选</p>
|
||||
<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
|
||||
<span class="tag calm">极淡 0.08</span>
|
||||
<span class="tag standard">标准 0.18</span>
|
||||
<span class="tag brush">笔刷渐变</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Quote</h4>
|
||||
<p class="hint">左 2pt 品牌实线 + olive 色</p>
|
||||
<blockquote class="quote">一千个 no 换一个 yes。宁可清淡不可浓艳。</blockquote>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Metric</h4>
|
||||
<p class="hint">serif 数字 + sans 标签 · tabular-nums</p>
|
||||
<div class="metrics demo">
|
||||
<div class="metric"><span class="metric-value">6</span><span class="metric-label">文档类型</span></div>
|
||||
<div class="metric"><span class="metric-value">1</span><span class="metric-label">强调色</span></div>
|
||||
<div class="metric"><span class="metric-value">8</span><span class="metric-label">条铁律</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Section Title</h4>
|
||||
<p class="hint">serif 500 + 1pt 品牌色下划线(签名式样)</p>
|
||||
<div class="demo">
|
||||
<span class="sample-section-title">核心项目 · Selected Works</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Dash List</h4>
|
||||
<p class="hint">短横线代替圆点 · 更书卷气</p>
|
||||
<ul class="dash demo">
|
||||
<li>暖米纸底,不用纯白</li>
|
||||
<li>油墨蓝点缀,不引第二色</li>
|
||||
<li>Serif 承担权威</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Code Block</h4>
|
||||
<p class="hint">ivory 底 + 0.5pt border + 6pt 圆角</p>
|
||||
<pre class="code"><span class="c">/* section title */</span>
|
||||
.section-title {
|
||||
font-family: <span class="k">serif</span>;
|
||||
font-weight: <span class="k">500</span>;
|
||||
border-bottom: <span class="k">1pt</span> solid
|
||||
var(--brand);
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="comp">
|
||||
<h4>Featured Card</h4>
|
||||
<p class="hint">whisper shadow + 16pt 圆角</p>
|
||||
<div class="demo" style="background:var(--ivory); border-radius:16px; padding:18px 20px; box-shadow: 0 4px 24px rgba(0,0,0,0.05); border: 1px solid var(--border-cream);">
|
||||
<p style="font-family:var(--serif); font-weight:500; font-size:15px; margin:0 0 6px; color:var(--near-black);">Tesla 公司介绍 · One-Pager</p>
|
||||
<p style="font-size:12px; color:var(--olive); margin:0 0 10px; line-height:1.5;">单页 A4 · 中文版 · 4 张指标卡 + 3 段正文 + 时间线</p>
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
||||
<span class="tag calm">中文</span>
|
||||
<span class="tag calm">A4 × 1</span>
|
||||
<span class="tag calm">Ink Blue</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ 05 DECISION TABLE ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">05 · Decision Lookup</p>
|
||||
<h2 class="section-title">决策速查</h2>
|
||||
<p class="section-lede">遇到"该用什么"的时候查这张表。不在表里 → 回到原则。</p>
|
||||
</div>
|
||||
<table class="decision">
|
||||
<tr><th>要做什么</th><th>怎么做</th></tr>
|
||||
<tr><td class="task">大标题</td><td class="how">serif <code>500</code>,字号按层级,line-height <code>1.10–1.30</code></td></tr>
|
||||
<tr><td class="task">正文阅读</td><td class="how">sans <code>400</code>,<code>9.5–10 pt</code>,line-height <code>1.55</code></td></tr>
|
||||
<tr><td class="task">强调一个数字</td><td class="how"><code>color: var(--brand)</code>,不要粗体</td></tr>
|
||||
<tr><td class="task">分隔两段内容</td><td class="how"><code>1pt</code> 品牌色 border-bottom,或 <code>0.5pt</code> 暖灰虚线</td></tr>
|
||||
<tr><td class="task">引用某人的话</td><td class="how">左 <code>2pt</code> 品牌色实线 + olive 色</td></tr>
|
||||
<tr><td class="task">展示代码</td><td class="how">ivory 底 + <code>0.5pt</code> border + <code>6pt</code> 圆角 + mono 字体</td></tr>
|
||||
<tr><td class="task">区分主次按钮</td><td class="how">Primary 品牌色填充 + ivory 字;Secondary warm-sand + charcoal</td></tr>
|
||||
<tr><td class="task">标注某张特殊卡片</td><td class="how"><code>border: 0.5pt solid var(--brand)</code> 或 <code>border-left: 3pt</code></td></tr>
|
||||
<tr><td class="task">章节开始</td><td class="how">serif 标题 + 下方 <code>1pt</code> 品牌色实线</td></tr>
|
||||
<tr><td class="task">文档封面</td><td class="how">单页 Display 字号 + 右对齐作者/日期 + 大量留白</td></tr>
|
||||
<tr><td class="task">数据卡</td><td class="how">ivory 底 + <code>8pt</code> 圆角 + serif 大数字(油墨蓝) + sans 小标签</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ============ 06 ANTI ============ -->
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<p class="section-num">06 · Anti-Patterns</p>
|
||||
<h2 class="section-title">反面示例</h2>
|
||||
<p class="section-lede">违反铁律前,先想清楚是否真的要违反。</p>
|
||||
</div>
|
||||
|
||||
<div class="anti">
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>页面背景用 <code>#ffffff</code> 纯白或 <code>#f3f4f6</code> 冷灰。Kami 的情感基础是暖米纸。</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>始终 <code>#f5f4ed</code> parchment。打印时把 <code>@page background</code> 也设为同色,避免白边。</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>Tag 用 <code>rgba(201,100,66,0.18)</code>。WeasyPrint 渲染时 padding 区与字形区透明度叠加,出双层矩形。</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>查 tint 对照表用等效实色 <code>#E4ECF5</code>。Bug 消失,颜色一致。</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>标题用 <code>font-weight: 700</code> bold。Serif 500 是这套系统的签名,bold 会让版面浮躁。</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Serif 固定 500。需要更强存在感用字号或 1pt 品牌色下划线,不加粗。</p>
|
||||
</div>
|
||||
|
||||
<div class="no">
|
||||
<h5>Don't</h5>
|
||||
<p>box-shadow 硬投影 <code>0 2px 8px rgba(0,0,0,0.3)</code>。冷硬、SaaS 感、印刷会发黑。</p>
|
||||
</div>
|
||||
<div class="yes">
|
||||
<h5>Do</h5>
|
||||
<p>Ring shadow (<code>0 0 0 1pt</code>) 或 whisper (<code>rgba 0.05</code>),或直接明暗交替。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ FOOTER ============ -->
|
||||
<footer class="footer">
|
||||
<div class="mark">
|
||||
<svg width="56" height="56" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-label="Kami mark">
|
||||
<rect x="6" y="6" width="68" height="68" rx="10" fill="#faf9f5" stroke="#1B365D" stroke-width="1.25"/>
|
||||
<line x1="16" y1="26" x2="64" y2="26" stroke="#1B365D" stroke-width="1.25"/>
|
||||
<text x="40" y="56" font-family="TsangerJinKai02, Georgia, serif" font-size="34" font-weight="500" fill="#141413" text-anchor="middle">紙</text>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="wm-name">Kami · 紙</p>
|
||||
<p class="wm-sub">The paper your deliverables land on.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colophon">
|
||||
<div><b>Distilled from</b> references/design.md · CHEATSHEET.md · tokens.json</div>
|
||||
<div><b>Document types</b> One-Pager · Long Doc · Letter · Portfolio · Resume · Slides</div>
|
||||
<div style="margin-top:8px; color: var(--olive); font-family: var(--serif);">
|
||||
Serif 承担权威,sans 承担功能,暖灰承担节奏,油墨蓝承担焦点。
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,873 @@
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../assets/fonts/Newsreader.woff2") format("woff2");
|
||||
font-weight: 400 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Newsreader";
|
||||
src: url("../assets/fonts/Newsreader-italic.woff") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter-500.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter-600.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../assets/fonts/JetBrainsMono.woff2") format("woff2");
|
||||
font-weight: 400 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--warm-sand: #e8e6dc;
|
||||
--dark-surface: #30302e;
|
||||
--deep-dark: #141413;
|
||||
|
||||
--brand: #1B365D;
|
||||
--brand-light: #2D5A8A;
|
||||
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #5e5d59;
|
||||
--stone: #87867f;
|
||||
--warm-silver: #b0aea5;
|
||||
|
||||
--border-cream: #f0eee6;
|
||||
--border-warm: #e8e6dc;
|
||||
--border-soft: #efede4;
|
||||
--border-dark: #30302e;
|
||||
|
||||
--ring-warm: #d1cfc5;
|
||||
--ring-deep: #c2c0b6;
|
||||
|
||||
--serif: "TsangerJinKai02", "Newsreader", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
--sans: "Inter", "TsangerJinKai02", -apple-system, BlinkMacSystemFont, "Source Han Sans SC", "Noto Sans CJK SC", "PingFang SC", "Microsoft YaHei", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
/* Language switcher */
|
||||
.lang-switch {
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
right: 32px;
|
||||
display: flex;
|
||||
gap: 0;
|
||||
z-index: 100;
|
||||
font-family: var(--sans);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.lang-switch a {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
color: var(--olive);
|
||||
border: 1px solid var(--border-warm);
|
||||
background: var(--ivory);
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.lang-switch a:first-child { border-radius: 4px 0 0 4px; }
|
||||
.lang-switch a:last-child { border-radius: 0 4px 4px 0; border-left: none; }
|
||||
.lang-switch a.active {
|
||||
background: var(--brand);
|
||||
color: var(--ivory);
|
||||
border-color: var(--brand);
|
||||
}
|
||||
.lang-switch a:hover:not(.active) {
|
||||
background: var(--warm-sand);
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
/* Page transition */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
|
||||
/* Print rules: keep warm background, avoid harsh page breaks */
|
||||
@page { size: A4; margin: 14mm 16mm; background: #f5f4ed; }
|
||||
@media print {
|
||||
body { background: #f5f4ed; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
.page { padding: 0 !important; max-width: 100% !important; }
|
||||
section { page-break-inside: auto; margin-bottom: 36px; }
|
||||
.hero, .section-head, .family, .comp, .shadow-demo, .tint, .swatch, .radius, blockquote, pre, tr, .anti > div { break-inside: avoid; page-break-inside: avoid; }
|
||||
.hero h1 { font-size: 68px; }
|
||||
h2.section-title { font-size: 26px; }
|
||||
.comp-grid, .swatches, .tint-strip, .family-grid, .shadow-row, .anti { break-inside: auto; }
|
||||
}
|
||||
body {
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
font-family: var(--sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
padding: 88px 64px 120px;
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: end;
|
||||
gap: 40px;
|
||||
padding-bottom: 40px;
|
||||
border-bottom: 1px solid var(--brand);
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
.eyebrow {
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2px;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 92px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -1px;
|
||||
margin: 0 0 14px;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.hero h1 .cn {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
color: var(--brand);
|
||||
}
|
||||
.hero .tagline {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 1.35;
|
||||
color: var(--olive);
|
||||
margin: 0;
|
||||
max-width: 560px;
|
||||
}
|
||||
.hero .meta {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.hero .meta b { color: var(--charcoal); font-weight: 500; }
|
||||
|
||||
/* ---------- Section ---------- */
|
||||
section { margin-bottom: 88px; }
|
||||
.section-head { margin-bottom: 28px; }
|
||||
.section-num {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5px;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 32px;
|
||||
line-height: 1.2;
|
||||
color: var(--near-black);
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.section-lede {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
max-width: 720px;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
/* ---------- Manifesto ---------- */
|
||||
.manifesto {
|
||||
font-family: var(--serif);
|
||||
font-size: 24px;
|
||||
line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
max-width: 820px;
|
||||
margin: 0 0 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.manifesto em {
|
||||
color: var(--brand);
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.rules {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0;
|
||||
border-top: 1px solid var(--border-warm);
|
||||
}
|
||||
.rules li {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr;
|
||||
gap: 14px;
|
||||
padding: 18px 24px 18px 0;
|
||||
border-bottom: 1px solid var(--border-warm);
|
||||
align-items: baseline;
|
||||
}
|
||||
.rules li:nth-child(odd) { padding-right: 32px; border-right: 1px solid var(--border-warm); padding-left: 0; }
|
||||
.rules li:nth-child(even) { padding-left: 32px; }
|
||||
.rules .n {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
}
|
||||
.rules p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
.rules code {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--brand);
|
||||
background: #EEF2F7;
|
||||
padding: 1px 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ---------- Color ---------- */
|
||||
.swatches {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.swatch {
|
||||
border: 1px solid var(--border-cream);
|
||||
border-radius: 8px;
|
||||
background: var(--ivory);
|
||||
overflow: hidden;
|
||||
}
|
||||
.swatch .chip {
|
||||
height: 96px;
|
||||
border-bottom: 1px solid var(--border-cream);
|
||||
}
|
||||
.swatch .info {
|
||||
padding: 12px 14px 14px;
|
||||
}
|
||||
.swatch .name {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
color: var(--near-black);
|
||||
margin: 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.swatch .role {
|
||||
font-size: 11px;
|
||||
color: var(--olive);
|
||||
margin: 3px 0 6px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.swatch .hex {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.swatch.brand .name { color: var(--brand); }
|
||||
.swatch.dark .chip { border-bottom-color: var(--deep-dark); }
|
||||
|
||||
.subhead {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
color: var(--dark-warm);
|
||||
margin: 40px 0 14px;
|
||||
}
|
||||
|
||||
/* tag tint table */
|
||||
.tint-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
background: var(--parchment);
|
||||
}
|
||||
.tint {
|
||||
border: 1px solid var(--border-cream);
|
||||
border-radius: 8px;
|
||||
background: var(--ivory);
|
||||
padding: 18px 16px 14px;
|
||||
}
|
||||
.tint .sample {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
letter-spacing: 0.3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tint .opacity {
|
||||
font-family: var(--serif);
|
||||
font-size: 14px;
|
||||
color: var(--olive);
|
||||
margin: 0 0 2px;
|
||||
}
|
||||
.tint .hex {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--stone);
|
||||
}
|
||||
.tint.default { outline: 1.5px solid var(--brand); outline-offset: -1.5px; }
|
||||
.tint.default .opacity { color: var(--brand); }
|
||||
.tint .flag {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.8px;
|
||||
color: var(--brand);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
/* ---------- Type ---------- */
|
||||
.type-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr 160px;
|
||||
column-gap: 40px;
|
||||
row-gap: 0;
|
||||
border-top: 1px solid var(--border-warm);
|
||||
}
|
||||
.type-row {
|
||||
display: contents;
|
||||
}
|
||||
.type-row > * {
|
||||
padding: 22px 0;
|
||||
border-bottom: 1px solid var(--border-warm);
|
||||
align-self: baseline;
|
||||
}
|
||||
.type-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
padding-top: 30px !important;
|
||||
}
|
||||
.type-specs {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--olive);
|
||||
line-height: 1.6;
|
||||
text-align: right;
|
||||
padding-top: 30px !important;
|
||||
}
|
||||
.type-specs b { color: var(--charcoal); font-weight: 500; }
|
||||
.type-sample.display {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 60px; line-height: 1.1; color: var(--near-black);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.type-sample.h1 {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 30px; line-height: 1.2; color: var(--near-black);
|
||||
}
|
||||
.type-sample.h2 {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 22px; line-height: 1.25; color: var(--near-black);
|
||||
}
|
||||
.type-sample.h3 {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 17px; line-height: 1.3; color: var(--near-black);
|
||||
}
|
||||
.type-sample.lede {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 15px; line-height: 1.55; color: var(--near-black);
|
||||
max-width: 540px;
|
||||
}
|
||||
.type-sample.body {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 13.5px; line-height: 1.55; color: var(--charcoal);
|
||||
max-width: 540px;
|
||||
}
|
||||
.type-sample.dense {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 12.5px; line-height: 1.4; color: var(--charcoal);
|
||||
max-width: 540px;
|
||||
}
|
||||
.type-sample.caption {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 12px; line-height: 1.45; color: var(--olive);
|
||||
}
|
||||
.type-sample.label {
|
||||
font-family: var(--serif); font-weight: 500;
|
||||
font-size: 10.5px; line-height: 1.35; color: var(--brand);
|
||||
letter-spacing: 0.4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* family cards */
|
||||
.family-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.family {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
border-radius: 12px;
|
||||
padding: 28px 24px 22px;
|
||||
}
|
||||
.family .glyph {
|
||||
font-weight: 500;
|
||||
font-size: 80px;
|
||||
line-height: 1;
|
||||
color: var(--near-black);
|
||||
margin: 0 0 18px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.family.serif .glyph { font-family: var(--serif); }
|
||||
.family.sans .glyph { font-family: var(--sans); }
|
||||
.family.mono .glyph { font-family: var(--mono); font-weight: 500; font-size: 64px; }
|
||||
.family .role {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
color: var(--near-black);
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.family .name {
|
||||
font-size: 12px;
|
||||
color: var(--olive);
|
||||
font-family: var(--mono);
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.family .use {
|
||||
font-size: 12px;
|
||||
color: var(--charcoal);
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ---------- Spacing ---------- */
|
||||
.space-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.space-table th, .space-table td {
|
||||
text-align: left;
|
||||
padding: 14px 16px 14px 0;
|
||||
border-bottom: 1px solid var(--border-warm);
|
||||
font-size: 13px;
|
||||
color: var(--charcoal);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.space-table th {
|
||||
font-family: var(--sans);
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
padding-top: 0;
|
||||
}
|
||||
.space-table td.name {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
color: var(--near-black);
|
||||
width: 120px;
|
||||
}
|
||||
.space-table td.val { font-family: var(--mono); font-size: 12px; color: var(--olive); width: 130px; }
|
||||
.space-table td.bar {
|
||||
width: 280px;
|
||||
}
|
||||
.bar span {
|
||||
display: block;
|
||||
height: 10px;
|
||||
background: var(--brand);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.space-table td.use { color: var(--olive); font-size: 12px; }
|
||||
|
||||
/* radius row */
|
||||
.radii {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.radius {
|
||||
text-align: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.radius .box {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.radius .r { font-family: var(--mono); font-size: 11px; color: var(--charcoal); }
|
||||
.radius .r b { color: var(--brand); font-weight: 500; }
|
||||
.radius .label { font-size: 10.5px; color: var(--olive); margin-top: 2px; }
|
||||
|
||||
/* ---------- Components ---------- */
|
||||
.comp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
.comp {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
border-radius: 12px;
|
||||
padding: 28px 28px 24px;
|
||||
}
|
||||
.comp h4 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: var(--near-black);
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.comp .hint {
|
||||
font-size: 11px;
|
||||
color: var(--stone);
|
||||
font-family: var(--mono);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
.comp .demo { margin-bottom: 4px; }
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-family: var(--sans);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--brand);
|
||||
color: var(--ivory);
|
||||
box-shadow: 0 0 0 1px var(--brand);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: var(--warm-sand);
|
||||
color: var(--charcoal);
|
||||
box-shadow: 0 0 0 1px var(--ring-warm);
|
||||
}
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--brand);
|
||||
box-shadow: 0 0 0 1px var(--brand);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
font-family: var(--sans);
|
||||
font-size: 10.5px;
|
||||
font-weight: 500;
|
||||
padding: 2px 7px;
|
||||
border-radius: 2px;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.tag.calm { background: #EEF2F7; }
|
||||
.tag.standard { background: #E4ECF5; border-radius: 4px; padding: 2px 8px; }
|
||||
.tag.brush {
|
||||
background: linear-gradient(to right, #D6E1EE, #E4ECF5 70%, #EEF2F7);
|
||||
}
|
||||
|
||||
/* Quote */
|
||||
.quote {
|
||||
border-left: 2px solid var(--brand);
|
||||
padding: 4px 0 4px 14px;
|
||||
color: var(--olive);
|
||||
font-family: var(--serif);
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Metrics */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
}
|
||||
.metric {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 11px;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* Section title inline sample */
|
||||
.sample-section-title {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
color: var(--near-black);
|
||||
border-bottom: 1px solid var(--brand);
|
||||
padding-bottom: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Code block */
|
||||
.code {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.55;
|
||||
color: var(--near-black);
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.code .k { color: var(--brand); }
|
||||
.code .c { color: var(--stone); }
|
||||
|
||||
/* List */
|
||||
ul.dash {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
ul.dash li { position: relative; padding-left: 18px; }
|
||||
ul.dash li::before {
|
||||
content: "-";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* Shadows demo */
|
||||
.shadow-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.shadow-demo {
|
||||
background: var(--ivory);
|
||||
border-radius: 12px;
|
||||
padding: 24px 20px;
|
||||
min-height: 110px;
|
||||
}
|
||||
.shadow-demo .t {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
color: var(--near-black);
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.shadow-demo .s {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.sd-ring { box-shadow: 0 0 0 1px var(--ring-warm); }
|
||||
.sd-whisper { box-shadow: 0 4px 24px rgba(0,0,0,0.05); }
|
||||
.sd-alt { background: var(--deep-dark); color: var(--warm-silver); }
|
||||
.sd-alt .t { color: var(--ivory); font-family: var(--serif); }
|
||||
.sd-alt .s { color: var(--warm-silver); }
|
||||
|
||||
/* Decision table */
|
||||
.decision {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.decision th, .decision td {
|
||||
padding: 12px 16px 12px 0;
|
||||
border-bottom: 1px solid var(--border-warm);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.decision th {
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
font-weight: 600;
|
||||
padding-top: 0;
|
||||
}
|
||||
.decision td.task {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--near-black);
|
||||
width: 38%;
|
||||
}
|
||||
.decision td.how { color: var(--charcoal); }
|
||||
.decision td.how code {
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
background: #EEF2F7;
|
||||
color: var(--brand);
|
||||
padding: 1px 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Brand footer */
|
||||
.footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 40px;
|
||||
border-top: 1px solid var(--border-warm);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 40px;
|
||||
}
|
||||
.footer .mark {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
.footer .mark img { width: 56px; height: 56px; }
|
||||
.footer .mark .wm-name {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
color: var(--near-black);
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.footer .mark .wm-sub {
|
||||
font-family: var(--serif);
|
||||
font-size: 14px;
|
||||
color: var(--olive);
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
.footer .colophon {
|
||||
font-size: 11px;
|
||||
color: var(--stone);
|
||||
line-height: 1.6;
|
||||
max-width: 420px;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.footer .colophon b { color: var(--charcoal); font-weight: 500; }
|
||||
|
||||
/* Anti-patterns */
|
||||
.anti {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.anti > div {
|
||||
padding: 18px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.anti .no {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
}
|
||||
.anti .yes {
|
||||
background: var(--ivory);
|
||||
border: 1px solid var(--border-cream);
|
||||
border-left: 3px solid var(--brand);
|
||||
}
|
||||
.anti h5 {
|
||||
font-family: var(--sans);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2px;
|
||||
text-transform: uppercase;
|
||||
margin: 0 0 8px;
|
||||
color: var(--stone);
|
||||
}
|
||||
.anti .yes h5 { color: var(--brand); }
|
||||
.anti p {
|
||||
margin: 0;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
.anti code {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--brand);
|
||||
background: #EEF2F7;
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||