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≥20px) 的非灰色 `color` | 大字号标题色权重 ×5,取最高频 | +| `secondary` | 同上 | 第二高频非灰色,无则从 primary 调亮 10% 派生 | +| `background` | `#js_content` 或顶层 `section` 的 `background` | 直接取,无则默认 `#ffffff` | +| 正文字号 | `

` 的 `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` | ``, `

` 的 `background` | 直接取,无则默认深色 `#1e293b` |
+| `code_color` | ``, `
` 的 `color` | 直接取,无则默认 `#e2e8f0` |
+| `border_radius` | 所有元素 `border-radius` | 众数,无则默认 `6px` |
+| 标题装饰 | `

`-`

` 的 border/padding | 直接映射到 CSS | +| 段落间距 | `

` 的 `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 --name +``` + +## 依赖 + +- `requests`(已有) +- `beautifulsoup4`(已有) +- `pyyaml`(已有) +- `colorsys`(标准库) + +无新依赖。 + +## 不做的事 + +- 不学习结构布局(卡片嵌套、多栏、SVG 分割线) +- 不支持本地 HTML 文件输入(仅 URL) +- 不自动处理微信登录/验证码(抓取失败直接报错) +- 不修改现有主题系统代码