diff --git a/docs/superpowers/specs/2026-04-01-learn-theme-design.md b/docs/superpowers/specs/2026-04-01-learn-theme-design.md new file mode 100644 index 0000000..9556161 --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-learn-theme-design.md @@ -0,0 +1,155 @@ +# Learn Theme — 从公众号文章 URL 提取排版主题 + +**日期**: 2026-04-01 +**状态**: 设计完成,待实现 + +## 概述 + +新增 `learn-theme` 功能:用户提供一个微信公众号文章 URL,脚本自动抓取 HTML、提取 inline style,生成与现有 16 个主题格式一致的 YAML 主题文件,立即可用于排版。 + +## 用户接口 + +```bash +python3 toolkit/cli.py learn-theme https://mp.weixin.qq.com/s/xxxx --name my-style +``` + +- `url`(必填):微信公众号文章链接 +- `--name`(必填):主题名称,用于文件名和后续引用 +- 输出:`toolkit/themes/{name}.yaml` + +成功后终端打印提取摘要(主色、字号、行高等)并提示: +``` +Theme saved to toolkit/themes/my-style.yaml +Use it: python3 toolkit/cli.py preview article.md --theme my-style +Or set in style.yaml: theme: my-style +``` + +## 核心流程 + +``` +URL → fetch HTML → parse #js_content → 按元素类型提取 inline style +→ 频率统计 + 语义角色推断 → 生成 theme YAML → 写入 toolkit/themes/ +``` + +### Step 1: Fetch + +- `requests.get(url)` + 浏览器 User-Agent header +- 强制 UTF-8 解码(微信 API 不声明 charset) +- 验证返回的 HTML 包含 `#js_content`,否则报错退出 + +### Step 2: Extract + +用 BeautifulSoup 解析 `#js_content` 内所有带 inline style 的元素,按标签类型分组收集 CSS 属性值。 + +目标元素:`p`, `section`, `span`, `strong`, `h1`-`h4`, `blockquote`, `code`, `pre`, `img` + +每个元素提取的属性:`color`, `font-size`, `line-height`, `letter-spacing`, `font-family`, `background`, `background-color`, `border-left`, `border-bottom`, `border-radius`, `margin`, `padding` + +### Step 3: Analyze — 语义角色推断 + +**层 1 — 配色 + 字号体系(占观感 ~70%):** + +| 目标属性 | 数据来源 | 推断逻辑 | +|---------|---------|---------| +| `text` | `
` 的 `color` | 最高频颜色 |
+| `text_light` | 所有元素的灰色系 `color` | 排除亮度 >0.85 和 <0.15 的值,取亮度最高的灰色 |
+| `primary` | ``, ` ` 的 `font-size` | 众数 |
+| 行高 | ` ` 的 `line-height` | 众数 |
+| 字间距 | ` ` 的 `letter-spacing` | 众数,无则不设 |
+| 字体 | `` 的 `font-family` | 最高频 |
+
+**层 2 — 装饰细节(占观感 ~20%):**
+
+| 目标属性 | 数据来源 | 推断逻辑 |
+|---------|---------|---------|
+| `quote_border` | 含 `border-left` 的 `blockquote`/`section` | 直接取 border-left-color,无则用 primary |
+| `quote_bg` | 同上的 `background` | 直接取,无则从 primary 派生浅底色 |
+| `code_bg` | ` ` 的 `margin` | 众数,映射到 body margin 和 p margin |
+
+### Step 4: Generate
+
+基于提取结果,以 `professional-clean.yaml` 为模板(确保所有 CSS selector 覆盖完整),替换颜色值和排版参数,生成完整的 theme YAML。
+
+结构:
+```yaml
+name: "{name}"
+description: "从 {article_title} 学习的排版主题"
+colors:
+ primary: "{extracted}"
+ secondary: "{extracted}"
+ text: "{extracted}"
+ text_light: "{extracted}"
+ background: "{extracted}"
+ code_bg: "{extracted}"
+ code_color: "{extracted}"
+ quote_border: "{extracted}"
+ quote_bg: "{extracted}"
+ border_radius: "{extracted}"
+darkmode:
+ # 从 light mode 颜色自动派生
+base_css: |
+ # 基于 professional-clean 模板,替换提取到的值
+```
+
+**Darkmode 派生规则:**
+- `background` → 取反亮度,钳位到 `#1a1a1a`-`#2a2a2a`
+- `text` → 亮度提升到 0.8,如 `#c8c8c8`
+- `primary` → 饱和度不变,亮度提升 15%
+- 其余属性同理微调
+
+### Step 5: Report
+
+终端输出提取摘要:
+```
+Learned theme from: 短剧行业的AI重构
+ text: #000000
+ primary: #2d71d6 (blue)
+ secondary: #5f9cef
+ font: Optima-Regular, PingFangTC-light
+ size: 16px / line-height 1.75 / spacing 1px
+
+Theme saved → toolkit/themes/my-style.yaml
+```
+
+## Fallback 策略
+
+每个属性提取失败时的默认值继承自 `professional-clean` 主题,确保输出始终是一个完整可用的主题文件。不会因为文章结构简单就生成残缺主题。
+
+## 文件结构
+
+```
+scripts/learn_theme.py # 核心逻辑:fetch + extract + analyze + generate
+toolkit/cli.py # 新增 learn-theme 子命令,调用 scripts/learn_theme.py
+```
+
+**不修改的文件:** `theme.py`, `converter.py`, 现有主题文件 — 零侵入。
+
+## SKILL.md 集成
+
+在辅助功能列表中新增触发词"学习排版"/"学排版",调用:
+```bash
+python3 scripts/learn_theme.py `, `` 的 `background` | 直接取,无则默认深色 `#1e293b` |
+| `code_color` | `
`, `` 的 `color` | 直接取,无则默认 `#e2e8f0` |
+| `border_radius` | 所有元素 `border-radius` | 众数,无则默认 `6px` |
+| 标题装饰 | `
`-`
` 的 border/padding | 直接映射到 CSS |
+| 段落间距 | `