@taloswork/ose-shared 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +32 -17
  3. package/skills/document/art-directions/base.css +23 -0
  4. package/skills/document/art-directions/cinematic-depth.css +6 -0
  5. package/skills/document/art-directions/collage-paper.css +8 -0
  6. package/skills/document/art-directions/editorial-cut.css +8 -0
  7. package/skills/document/art-directions/geometric-pop.css +9 -0
  8. package/skills/document/art-directions/gradient-mesh.css +6 -0
  9. package/skills/document/art-directions/illustrated-doodle.css +8 -0
  10. package/skills/document/art-directions/luxury-ornament.css +6 -0
  11. package/skills/document/art-directions/maximalist-blocks.css +9 -0
  12. package/skills/document/art-directions/technical-grid.css +6 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/bin/deck-build.cjs +266 -10
  15. package/skills/document/bin/doc-convert.cjs +66 -28
  16. package/skills/document/bin/doc-gen.cjs +8 -9
  17. package/skills/document/bin/html2pptx.cjs +243 -24
  18. package/skills/document/bin/pptx-clone.cjs +41 -30
  19. package/skills/document/bin/pptx-embed-fonts.cjs +6 -2
  20. package/skills/document/bin/pptx-slide.cjs +46 -50
  21. package/skills/document/catalog.json +11 -0
  22. package/skills/document/components/base.css +45 -0
  23. package/skills/document/components/profiles/clean.css +2 -0
  24. package/skills/document/components/profiles/editorial.css +3 -0
  25. package/skills/document/components/profiles/glass.css +2 -0
  26. package/skills/document/components/profiles/luxury.css +3 -0
  27. package/skills/document/components/profiles/organic.css +2 -0
  28. package/skills/document/components/profiles/poster.css +3 -0
  29. package/skills/document/content-pipeline.md +110 -54
  30. package/skills/document/deck-design.md +24 -15
  31. package/skills/document/layouts/README.md +41 -12
  32. package/skills/document/layouts/blocks-2x2.tpl +19 -9
  33. package/skills/document/layouts/business-model.tpl +6 -0
  34. package/skills/document/layouts/cards-3.tpl +1 -1
  35. package/skills/document/layouts/customer-proof.tpl +6 -0
  36. package/skills/document/layouts/funding-ask.tpl +5 -0
  37. package/skills/document/layouts/market-rings.tpl +5 -0
  38. package/skills/document/layouts/org-tree.tpl +1 -1
  39. package/skills/document/layouts/pitch-problem.tpl +6 -0
  40. package/skills/document/layouts/pitch-solution.tpl +5 -0
  41. package/skills/document/layouts/pricing-tiers.tpl +5 -0
  42. package/skills/document/layouts/product-device.tpl +6 -0
  43. package/skills/document/layouts/profiles.json +24 -0
  44. package/skills/document/layouts/slots.json +1 -1
  45. package/skills/document/layouts/steps-vertical.tpl +1 -1
  46. package/skills/document/layouts/team-founders.tpl +5 -0
  47. package/skills/document/layouts/traction-milestones.tpl +6 -0
  48. package/skills/document/media-styles/cinematic.css +2 -0
  49. package/skills/document/media-styles/clean-product.css +2 -0
  50. package/skills/document/media-styles/cutout-collage.css +3 -0
  51. package/skills/document/media-styles/device-stage.css +3 -0
  52. package/skills/document/media-styles/editorial-photo.css +3 -0
  53. package/skills/document/media-styles/illustrated.css +2 -0
  54. package/skills/document/media-styles/spatial-3d.css +3 -0
  55. package/skills/document/motion/mckinsey-photo.json +22 -0
  56. package/skills/document/motion/prismatic-3d.json +31 -0
  57. package/skills/document/packs/README.md +7 -3
  58. package/skills/document/packs/mckinsey-photo/backdrop.html +6 -0
  59. package/skills/document/packs/mckinsey-photo/layouts/cover-image.tpl +5 -0
  60. package/skills/document/packs/mckinsey-photo/layouts/cover.tpl +5 -0
  61. package/skills/document/packs/mckinsey-photo/layouts/image-left.tpl +5 -0
  62. package/skills/document/packs/mckinsey-photo/layouts/numbered-brief.tpl +5 -0
  63. package/skills/document/packs/mckinsey-photo/pack.css +27 -0
  64. package/skills/document/packs/prismatic-3d/backdrop.html +4 -0
  65. package/skills/document/packs/prismatic-3d/layouts/big-stat.tpl +6 -0
  66. package/skills/document/packs/prismatic-3d/layouts/cover.tpl +9 -0
  67. package/skills/document/packs/prismatic-3d/layouts/section-image.tpl +6 -0
  68. package/skills/document/packs/prismatic-3d/pack.css +28 -0
  69. package/skills/document/recipes/agency-pitch.json +13 -0
  70. package/skills/document/recipes/product-launch.json +13 -0
  71. package/skills/document/recipes/startup-fundraising.json +19 -0
  72. package/skills/document/styles/README.md +13 -6
  73. package/skills/document/styles/_contract-defaults.css +1 -1
  74. package/skills/document/styles/_samples/_groups/biz-premium.webp +0 -0
  75. package/skills/document/styles/_samples/_groups/creative.webp +0 -0
  76. package/skills/document/styles/_samples/_groups/editorial.webp +0 -0
  77. package/skills/document/styles/_samples/_groups/minimal.webp +0 -0
  78. package/skills/document/styles/_samples/_groups/natural.webp +0 -0
  79. package/skills/document/styles/_samples/_groups/tech-future.webp +0 -0
  80. package/skills/document/styles/_samples/bistro-warm.webp +0 -0
  81. package/skills/document/styles/_samples/board-brief.webp +0 -0
  82. package/skills/document/styles/_samples/bold-clash.webp +0 -0
  83. package/skills/document/styles/_samples/campus-teal.webp +0 -0
  84. package/skills/document/styles/_samples/clinic-care.webp +0 -0
  85. package/skills/document/styles/_samples/dark-glass.webp +0 -0
  86. package/skills/document/styles/_samples/gov-brief.webp +0 -0
  87. package/skills/document/styles/_samples/grid-tech.webp +0 -0
  88. package/skills/document/styles/_samples/legal-slate.webp +0 -0
  89. package/skills/document/styles/_samples/mckinsey-photo.webp +0 -0
  90. package/skills/document/styles/_samples/memphis.webp +0 -0
  91. package/skills/document/styles/_samples/neon-cyber.webp +0 -0
  92. package/skills/document/styles/_samples/organic-earth.webp +0 -0
  93. package/skills/document/styles/_samples/pastel-saas.webp +0 -0
  94. package/skills/document/styles/_samples/portfolio-noir.webp +0 -0
  95. package/skills/document/styles/_samples/press-room.webp +0 -0
  96. package/skills/document/styles/_samples/prismatic-3d.webp +0 -0
  97. package/skills/document/styles/_samples/retro-press.webp +0 -0
  98. package/skills/document/styles/_samples/sport-arena.webp +0 -0
  99. package/skills/document/styles/_samples/wedding-blush.webp +0 -0
  100. package/skills/document/styles/dark-glass/backdrop.html +7 -3
  101. package/skills/document/styles/dark-glass/skin.css +1 -0
  102. package/skills/document/styles/portfolio-noir/skin.css +3 -0
  103. package/skills/document/theme-system.json +68 -0
  104. package/skills/document/theme-system.md +101 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taloswork/ose-shared",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Talos cross-OSE shared library — skills + MCP servers auto-loaded into every OSE (P93 2026-07-27).",
5
5
  "type": "module",
6
6
  "files": [
@@ -10,6 +10,6 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "_talosPublishHash": "sha256-CLI4WbNNvJRRbZYx7qniS0d-cVs5XiJlcU904jHxDww",
14
- "_talosPublishedAt": "2026-09-06T18:53:54.545Z"
13
+ "_talosPublishHash": "sha256-Jwek2npsPD2O5y281xlaBct9hS3AkgkMGHd-LXNgHCw",
14
+ "_talosPublishedAt": "2026-09-16T17:21:18.072Z"
15
15
  }
@@ -61,7 +61,8 @@ script only when:
61
61
  tone. Show at most 8 recommended entries. A scenario-matching `kind === "pack"` ranks
62
62
  before matching skins; include at least one matching pack whenever one exists.
63
63
  - Every image card maps to exactly one final entry and uses
64
- `{ label: entry.kind === "pack" ? "PACK · " + entry.name_cn : entry.name_cn,
64
+ `{ label: entry.kind === "pack" ? "PACK · " + entry.name_cn :
65
+ (entry 属于 renderMode === "classic-v1" 的 collection ? "经典 · " + entry.name_cn : entry.name_cn),
65
66
  description: entry.oneliner, imagePath: <skill目录下entry.sample的绝对路径> }`.
66
67
  Copy `entry.oneliner` verbatim. Never use `group.sample` in this default picker.
67
68
  - Append `{ label: "查看全部风格", description: "一次查看全部模板" }`, then
@@ -81,8 +82,13 @@ script only when:
81
82
 
82
83
  Samples are pre-rendered WebP files shipped with the skill: pass their absolute paths
83
84
  directly and do not render anything during the question. The selected `entry.id` is the
84
- `--style` value: `html2pptx deck.html out.pptx --style <id>`. Skin and pack use the same
85
- render command; `catalog.json` resolves their different asset directories.
85
+ stable `style` id. Resolve the selected entry against `catalog.collections` before writing
86
+ the spec. Entries in a `renderMode:"classic-v1"` collection are frozen: omit
87
+ `themeVersion`, `mediaStyle`, `decorRhythm`, per-slide scene controls, and every layout in
88
+ `layouts/profiles.json.themeV2Layouts`; render them with `--style <id>` and never add
89
+ `--theme-v2`. All other newly authored decks use Theme V2 (`"themeVersion":2`), which
90
+ composes that family with its component profile, media treatment and art direction. Read
91
+ **theme-system.md** before authoring. The CLIs reject Theme V2 for frozen classic entries.
86
92
 
87
93
  **Keep both calls LEAN.** The content/scenario/count questions use bare label strings,
88
94
  no per-option descriptions; every question stem is one short sentence. Skip chat
@@ -95,7 +101,7 @@ script only when:
95
101
  `catalog.json` is the only roster; `styles/README.md` is explanatory only. Never render
96
102
  unstyled. After the user confirms, save the preference to memory.
97
103
 
98
- **Custom style (user picked 其他/自定义 on Q3)** — clarify which route, **prefer B
104
+ **Custom style (user picked 其他/自定义 in the style picker)** — clarify which route, **prefer B
99
105
  (derive a bespoke look)**:
100
106
  - **B — derive a bespoke look (PREFERRED).** From an **existing deck**: `pptx-tokenize`
101
107
  it to reuse its EXACT layouts/master/brand as a fillable template (rewrite.md B2),
@@ -103,7 +109,7 @@ script only when:
103
109
  **reference image / brand color**: build a new `styles/<name>/skin.css` (sample the
104
110
  palette → fill the class contract; see deck-design "build a skin from a reference
105
111
  image"). *(A deck given here is for its STYLE — distinct from a deck given as
106
- content, which is text-only; see content-pipeline Q1.)*
112
+ content, which is text-only; see content-pipeline's content-source choice.)*
107
113
  - **A — map to the nearest of catalog 里的族** (they only described a vibe): pick the
108
114
  closest, render its sample, confirm.
109
115
  - **C — tweak an existing skin** ("刚才那个族但换绿色"): clone it, swap accent/palette or
@@ -111,12 +117,14 @@ script only when:
111
117
  Then render a cover+content sample of the result and confirm (same image-card step as
112
118
  a normal pick) before authoring; save the resulting preference to memory.
113
119
 
114
- **THE deck method — html2pptx HYBRID + the structure/skin split, the ONE path.**
115
- Compose from **structure-only layouts** and paint with **one skin** at render:
120
+ **THE deck method — html2pptx HYBRID with catalog-governed V1/V2 composition.**
121
+ Modern entries compose **Theme + component profile + media style + art direction +
122
+ content-selected layouts**; frozen `classic-v1` entries keep family paint only:
116
123
  1. **Read deck-design.md** (⭐ PREFERRED workflow) + **`layouts/README.md`**
117
124
  (every layout tagged role · structure · density · has-image; that index is
118
125
  the source of truth for which layouts exist).
119
- 2. **Plan pages, pick layouts** by **P1/P2/P3** (structure → density → **≤2 dedup**;
126
+ 2. **Plan pages, pick layouts** from `layouts/profiles.json` by intent, then by
127
+ **P1/P2/P3** (structure → density → **≤2 dedup**;
120
128
  `html2pptx` warns on repeats). Paste each `layouts/<id>.tpl` into `deck.html`
121
129
  (they carry `data-layout`), fill REAL content — **never hardcode a brand hex**
122
130
  (leave color to the skin; `html2pptx` warns on hardcoded colors). 8 roles:
@@ -128,16 +136,23 @@ script only when:
128
136
  computes the geometry (bar spans, ring arcs, pin positions, the radar chart)
129
137
  from your numbers. Hand-pasting one leaves `{{#…}}` markers in the deck, or a
130
138
  drawing that contradicts its own labels.
131
- 3. **Render:** `bin/html2pptx.cjs deck.html out.pptx --style <skin> [--backdrop
132
- <variant>]` — DEFAULT hybrid (no `--editable`/`--rasterize`): pixel-perfect
139
+ For Theme V2, keep each slide's semantic `intent`: `deck-build` uses it to derive
140
+ the `hero/content/data/media/closing` art scene and balance low/medium/high decoration
141
+ across the deck. Do not hand-assign every scene; override `scene` or `decorDensity`
142
+ only for a deliberate exception. `decorRhythm:"steady"` disables auto balancing.
143
+ For `classic-v1`, choose only pre-V2 layouts and omit scene/decor controls.
144
+ 3. **Render:** modern entry → `bin/html2pptx.cjs deck.html out.pptx --style <id>
145
+ --theme-v2 [--media-style <id>] [--backdrop <variant>]`; frozen classic entry →
146
+ `bin/html2pptx.cjs deck.html out.pptx --style <id> [--backdrop <variant>]`.
147
+ DEFAULT hybrid (no `--editable`/`--rasterize`): pixel-perfect
133
148
  visuals baked into a backdrop + native EDITABLE text and native `data-chart`
134
- on top. `--style` injects the skin, fills each slide's `.slide-backdrop`, and
135
- colors the chart from the skin. Chromium gives the full CSS toolbox (Tailwind
149
+ on top. Theme V2 injects the compatible V1 family paint plus semantic component,
150
+ media and art-direction layers; it never chooses a layout. Chromium gives the full CSS toolbox (Tailwind
136
151
  + a `size="1".."7"` scale, gradients, shadows, per-element SVG, `<img>`
137
152
  auto-inline, speaker notes) → NATIVE editable pptx.
138
153
  **For a substantive deck, first read content-pipeline.md** (premium-deck method:
139
- clarify+brief gate → research → narrative structure → outline gate → author from
140
- `layouts/` → visual QA; premium density = rich VISUAL + sparse TEXT, details to
154
+ clarify + persist brief → research → narrative structure → one outline gate → author from
155
+ `layouts/` → fast HTML preview → final PPTX QA; premium density = rich VISUAL + sparse TEXT, details to
141
156
  speaker notes). **Do NOT build a deck with `doc-gen pptx` archetypes or `doc-run`
142
157
  PptxGenJS — those deck paths are OFF.** (html2pptx needs a browser: the app's
143
158
  Chromium service or bundled Playwright; if neither, say so and stop — never
@@ -204,7 +219,7 @@ Fidelity-decision self-correct, but pointed at your OWN output:
204
219
  - `bin/ocr.cjs <image.png|jpg> [lang]` — read text off a scanned image (tesseract.js). For a scanned PDF, `pdf-render` a page first, then OCR the PNG. Language data downloads on first use / set `$TALOS_TESSDATA` for offline. **Prefer vision** when a vision model is available; OCR is the text-only fallback.
205
220
  - `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]` — spreadsheet compute.
206
221
  - `bin/doc-gen.cjs <docx|xlsx|pptx|pdf> <out> <spec.json> [--theme <theme.json>] [--topic <subject> | --palette <id>] [--design native|html]` — generate documents from scratch. DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); unknown fields and empty bodies fail with `DOC_SPEC_INVALID` before any output is written (`allowCoverOnly:true` is the explicit title-only opt-in). Correct the reported JSON path and rerun. `--theme` applies a presentation theme (colors/fonts/cover/header/footer/pptx master — see rewrite.md "Themes"; built-ins in `themes/`). **`--topic <subject>` (pptx, when no `--theme`) picks a topic-adaptive palette** so the deck is designed-for-subject instead of template-flat, and turns on richness (motif + gradient wash + depth). `--palette <id>` forces a specific palette (`doc-gen --list-palettes` prints them). **`--accent <#hex>` / `--style <skin>` (docx/pdf)**: white-paper-black-text base with ONE brand color on H1/H2 + a faint table-header tint — the practical doc shape (a document is read/printed/edited, not projected, so body text stays near-black). `--accent` takes a literal hex; `--style <skin>` pulls just the `--accent` from `styles/<skin>/skin.css` (the deck skin's full palette is NOT carried onto paper). Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default. `--design html` (pptx only, in-app) renders eligible slides through designed HTML templates for a magazine look — richer but a flat collage (no master structure); auto-falls back to native when headless or a slide has no template. Default `native` (structured, editable, charts, headless-safe).
207
- - `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf>` — faithful docx/pptx/xlsx → PDF conversion. Engine precedence (best fidelity first, each downgrade tagged on stdout `[pdf (…)]` + warned on stderr): (1) **LibreOffice** if a system `soffice` is found — true layout render (page geometry, headers/footers, text-box positions, native charts), offline; (2) **LibreOffice sidecar** if `TALOS_OFFICE_URL` is set — same true-layout fidelity via a remote converter (e.g. Gotenberg), needs network; (3) **Chromium** (in-app) — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide); (4) **pdf-lib text-only** headless last resort — images/tables flatten. Surface the resulting `[pdf (engine)]` caveat to the user. NOTE: spreadsheets (xlsx/xls/ods) render ONLY via tiers 1–2 (real LibreOffice); tiers 3–4 do not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
222
+ - `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>]` — faithful docx/pptx/xlsx → PDF conversion. Engine precedence (best fidelity first, each downgrade tagged on stdout `[pdf (…)]` + warned on stderr): (1) **LibreOffice** if a system `soffice` is found — true layout render (page geometry, headers/footers, text-box positions, native charts), offline; (2) **LibreOffice sidecar** if `TALOS_OFFICE_URL` is set — same true-layout fidelity via a remote converter (e.g. Gotenberg), needs network; (3) **Chromium** (in-app) — reconstructed HTML, content/structure/images preserved but in-document layout is not (pptx = one page per slide); (4) **pdf-lib text-only** headless last resort — images/tables flatten. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses a content-hashed local LibreOffice result for the exact same immutable artifact; caching is opt-in so document bytes are not retained by default. Surface the resulting `[pdf (engine)]` caveat to the user. NOTE: spreadsheets (xlsx/xls/ods) render ONLY via tiers 1–2 (real LibreOffice); tiers 3–4 do not rebuild spreadsheets, so with no LibreOffice available the command errors (never a degraded text-only sheet).
208
223
  - `bin/doc-validate.cjs <file.docx|pptx|xlsx> [--strict] [--design-lint]` — structural OOXML validation gate (zip/parts/rels/Content_Types/XML well-formedness); run after every generation or edit; exit 1 = broken, fix before delivering. `--strict` adds full ECMA-376 schema (XSD) validation of every recognised part — REQUIRED after custom-code (doc-run/html2pptx) or raw-XML output; a strict failure means the file is non-standard even if Office opens it. `--design-lint` (pptx only) adds an ADVISORY design pass — flags `text-only-slide` (a wall of text with no visual anchor), `low-contrast-text`, `placeholder-text` (a layout pasted but never filled — 替换为…/占位/Lorem still in the slide), `font-mismatch` (CJK text set in a Latin-only font → 豆腐; expected language defaults to the system locale, override with `TALOS_DECK_LANG`), `off-canvas`, `tiny-font`, `shape-overlap`; findings are warnings (never change the exit code) but are real design bugs — fix them. See deck-design.md "Design principles".
209
224
  - `bin/deck-qa.cjs <file.pptx> [--json]` — MECHANICAL design QA (deterministic, token-free): reads slide XML and flags `off-canvas` (content clipped past the edge), `tiny-font` (<10pt), `shape-overlap` (colliding text), plus `text-only-slide` / `low-contrast-text` / `placeholder-text` / `font-mismatch` (CJK text in a Latin-only font → 豆腐). Advisory (exit 0 even with findings). `--json` → `{ok,findings:[{slide,code,message}]}` for the visual-QA loop. It is the CHEAP GATE before spending vision tokens — run it every QA round and fix all findings first; then do the vision critic + per-slide regeneration (deck-design.md "Verify before delivering").
210
225
  - `bin/doc-xml.cjs unpack <file> <dir>` / `pack <dir> <out>` — raw-XML escape hatch (pretty-printed unpack → edit → repack); always doc-validate afterwards. `replace <in.docx> <out.docx> <find> <replacement>` — cross-run character-offset replace (finds text even when Word split it over runs, preserves formatting; covers body + headers/footers) — try this FIRST for plain text edits before unpacking. `merge-runs <in.docx> <out.docx>` repairs run fragmentation when you do need manual XML edits.
@@ -220,8 +235,8 @@ Fidelity-decision self-correct, but pointed at your OWN output:
220
235
  - `bin/pdf-ops.cjs merge <out> <in…>` / `split <in> <from-to> <out>` / `rotate <in> <deg> <out> [pages]` / `watermark <in> <text> <out>` / `encrypt <in> <out> <user-pw> [owner-pw]` / `decrypt <in> <pw> <out>` — PDF page ops + AES-256 password protection.
221
236
  - `bin/xlsx-recalc.cjs <in.xlsx> <out.xlsx>` — re-evaluate all formulas (HyperFormula) and cache the results, keeping the formulas. Use when a spreadsheet's formula cells show stale/blank values.
222
237
  - `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry). The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS doc-validate the output.
223
- - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> <out.pptx> [--style <skin>] [--backdrop <variant>] [--theme <theme.json>] [--editable] [--rasterize]` — **THE deck path**: write slide HTML (`<section class="slide" data-layout="…">` on a 1280×720 canvas), Chromium lays it out → NATIVE editable pptx. **Compose from `layouts/*.tpl` (structure) + `--style <skin>` (paint)** — the preferred structure/skin split (`styles/README.md`); `--style` injects the skin's CSS, fills each `.slide-backdrop`, colors `data-chart` from the skin. `--backdrop <variant>` picks a signature backdrop. **DEFAULT = hybrid**: each slide's visuals bake into a full-fidelity raster backdrop with native EDITABLE text on top; auto-falls back to plain native if backdrop capture is unavailable. `--editable` = plain native only (effects flatten). `--rasterize` = full image slides (ZERO editability — tell the user). Advisory warnings on hardcoded brand colors + repeated layouts (P3). Runs in the app's Chromium OR bundled headless Chromium (Playwright) — works off-app (CI/cloud); layout JSON works anywhere. **Tailwind + `size="1".."7"` scale available** (vendored offline). See deck-design.md + layouts/README.md. `[--theme <theme.json>]` is the legacy path (superseded by `--style`). **Don't confuse the two `--style` flags:** html2pptx's `--style` = the whole deck **skin** (backgrounds, cards, the lot); doc-gen's `--style` on **docx/pdf** is a different, narrower flag that borrows only the skin's ONE accent color onto black-text paper (a document, not a deck).
224
- - `bin/deck-build.cjs <spec.json> <deck.html>` — **THE FAST deck path** for catalog slides: emit a COMPACT spec (per slide a `layout` id + its content `slots` `{…}`, plus `rows` for list/figure layouts and an optional `chart`) and this reads each `layouts/<id>.tpl`, fills it, and assembles `deck.html` — so you stream ~content only, not ~40KB of boilerplate HTML (much faster). Then `html2pptx deck.html out.pptx --style <spec.style>` as usual. **What each layout takes is in `layouts/slots.json`** (`slots` + `rows` per layout; `"chart":"derived"` means the layout BUILDS its chart from its rows — pass rows, never `chart`). Un-filled slots fall back to the template default, but a spec the catalog can't satisfy (unknown layout/slot/field, a `rows` block with no data, a figure row missing its numbers) **exits 1 and writes nothing** — fix the spec rather than shipping demo data. For a **bespoke** slide the catalog can't express, hand-author that ONE `<section>` and append it to deck.html (deck-build handles the catalog slides). spec: `{ "style":"<catalog entry id>", "slides":[ {"layout":"cover","slots":{"title":"…"}}, {"layout":"chart-kpi","slots":{…},"chart":{…}}, {"layout":"gantt","slots":{…},"rows":{"cols":[{"label":"Q1"}],"tasks":[{"name":"调研","from":1,"to":2}]}} ] }`.
238
+ - `bin/html2pptx.cjs <deck.html|snapshot.layout.json> [out.pptx] [--style <id>] [--theme-v2] [--media-style <id>] [--backdrop <variant>] [--editable] [--rasterize] [--preview-dir <dir>] [--preview-pages <all|N,N-M>]` — **THE deck path**: write slide HTML (`<section class="slide" data-layout="…">` on a 1280×720 canvas), Chromium lays it out → NATIVE editable pptx. `--preview-dir` is a preview-only fast path that writes slide PNGs directly from the styled HTML and skips PPTX/LibreOffice; use all pages once, then `--preview-pages` for changed pages. `--theme-v2` composes the selected family's existing paint with its semantic component profile, default media style and bound art direction; `--media-style` overrides only the media treatment. Entries in a `classic-v1` catalog collection reject both flags; omit them for the frozen V1 path. `--backdrop <variant>` picks a signature backdrop. **DEFAULT = hybrid**: each slide's visuals bake into a full-fidelity raster backdrop with native EDITABLE text on top; auto-falls back to plain native if backdrop capture is unavailable. `--editable` = plain native only (effects flatten). `--rasterize` = full image slides (ZERO editability — tell the user). Advisory warnings on hardcoded brand colors + repeated layouts (P3). Runs in the app's Chromium OR bundled headless Chromium (Playwright) — works off-app (CI/cloud); layout JSON works anywhere. **Tailwind + `size="1".."7"` scale available** (vendored offline). See theme-system.md + deck-design.md + layouts/README.md. `[--theme <theme.json>]` is a separate legacy document recolor path.
239
+ - `bin/deck-build.cjs <spec.json> <deck.html>` — **THE FAST deck path** for catalog slides: emit a COMPACT spec with `style`, independent `layoutPack`, and per-slide `intent` + `layout` + content. Modern entries add `themeVersion:2` plus optional `mediaStyle`/`decorRhythm`; frozen `classic-v1` entries must omit those fields and use only pre-V2 layouts. Use `layouts/profiles.json` to match content intent before selecting geometry; under V2 the same intent derives the art scene and deck-level decoration rhythm. The CLI warns on an intent/layout mismatch and errors on an unknown intent. `layoutPack:"none"` forces shared layouts; omitting `layoutPack` preserves the old behavior where a `kind:"pack"` style implicitly uses its same-name layouts. `slots`, `rows`, and `chart` are still validated against `layouts/slots.json`; any unknown layout/slot/field or missing figure input exits 1 and writes nothing. See theme-system.md for the full schema and compatibility contract.
225
240
 
226
241
  All scripts are Node and print a machine-readable result plus a human note. On a
227
242
  missing dependency or unreadable file they exit non-zero with a clear message —
@@ -0,0 +1,23 @@
1
+ /* Theme V2 art-direction canvas. The renderer injects this decoration-only layer
2
+ into .slide-backdrop; V1 never receives the markup or these rules. */
3
+ .theme-art-layer,.theme-art-layer>*{position:absolute;pointer-events:none}
4
+ .theme-art-layer{inset:0;overflow:hidden}
5
+ .theme-art-layer .art-shape{display:block}
6
+
7
+ /* Scene controls composition; density controls intensity. These selectors only
8
+ affect the decoration canvas, never content geometry. */
9
+ [data-decor-density="low"] .theme-art-layer{opacity:.38}
10
+ [data-decor-density="medium"] .theme-art-layer{opacity:.72}
11
+ [data-decor-density="high"] .theme-art-layer{opacity:1}
12
+ [data-decor-density="low"] .theme-art-layer :is(.art-c,.art-pattern){display:none}
13
+ [data-scene="content"] .theme-art-layer .art-c{display:none}
14
+ [data-scene="data"] .theme-art-layer .art-b{display:none}
15
+ [data-scene="media"] .theme-art-layer .art-pattern{display:none}
16
+ [data-scene="closing"] .theme-art-layer .art-pattern{display:none}
17
+
18
+ /* Occurrence variants shift which motif carries the visual weight, so repeated
19
+ pages in the same scene do not receive an identical background stamp. */
20
+ [data-scene-variant="b"] .theme-art-layer .art-a{opacity:.28}
21
+ [data-scene-variant="b"] .theme-art-layer .art-b{opacity:.82}
22
+ [data-scene-variant="c"] .theme-art-layer .art-a{opacity:.18}
23
+ [data-scene-variant="c"] .theme-art-layer .art-c{opacity:.86}
@@ -0,0 +1,6 @@
1
+ /* Stage lighting, deep beams and floating rings. */
2
+ .theme-art-layer .art-a{inset:-20%;background:radial-gradient(circle at 80% 18%,var(--c2),transparent 28%),radial-gradient(circle at 18% 86%,var(--c1),transparent 32%);filter:blur(28px);opacity:.25}
3
+ .theme-art-layer .art-b{left:18%;top:-230px;width:260px;height:1120px;background:linear-gradient(180deg,color-mix(in srgb,var(--c3) 36%,transparent),transparent 78%);transform:rotate(-28deg);filter:blur(18px);opacity:.52}
4
+ .theme-art-layer .art-c{right:84px;top:62px;width:210px;height:210px;border:1px solid var(--c1);border-radius:50%;box-shadow:0 0 0 28px color-mix(in srgb,var(--c1) 8%,transparent),0 0 70px color-mix(in srgb,var(--c2) 30%,transparent);opacity:.62}
5
+ .theme-art-layer .art-pattern{inset:0;background:linear-gradient(180deg,transparent 64%,color-mix(in srgb,var(--title) 7%,transparent));opacity:.8}
6
+
@@ -0,0 +1,8 @@
1
+ /* Layered paper scraps, tape and a small checker field. */
2
+ .theme-art-layer .art-a{right:-40px;top:-52px;width:390px;height:245px;background:var(--c2);transform:rotate(8deg);opacity:.56;box-shadow:-34px 30px 0 color-mix(in srgb,var(--c1) 72%,transparent)}
3
+ .theme-art-layer .art-b{left:-64px;bottom:-52px;width:310px;height:210px;background:var(--c3);transform:rotate(-7deg);opacity:.34;clip-path:polygon(0 10%,92% 0,100% 22%,90% 30%,100% 44%,91% 55%,100% 71%,88% 78%,100% 100%,0 100%)}
4
+ .theme-art-layer .art-c{left:53%;top:22px;width:150px;height:30px;background:color-mix(in srgb,var(--c4) 48%,transparent);transform:rotate(-4deg);opacity:.72}
5
+ .theme-art-layer .art-pattern{right:54px;bottom:44px;width:170px;height:120px;background:conic-gradient(from 90deg,color-mix(in srgb,var(--c1) 32%,transparent) 25%,transparent 0 50%,color-mix(in srgb,var(--c2) 28%,transparent) 0 75%,transparent 0);background-size:24px 24px;transform:rotate(3deg);opacity:.68}
6
+ .component-grid>.component-card:nth-child(odd),.component-grid>.team-card:nth-child(odd){transform:rotate(-.45deg)}
7
+ .component-grid>.component-card:nth-child(even),.component-grid>.team-card:nth-child(even){transform:rotate(.45deg)}
8
+
@@ -0,0 +1,8 @@
1
+ /* Magazine crop: asymmetric columns, rules and one decisive color interruption. */
2
+ .theme-art-layer .art-a{right:0;top:0;width:27%;height:100%;background:color-mix(in srgb,var(--c1) 18%,transparent)}
3
+ .theme-art-layer .art-b{right:0;top:0;width:27%;height:16px;background:linear-gradient(90deg,var(--c2),var(--c3),var(--c4))}
4
+ .theme-art-layer .art-c{left:42px;bottom:0;width:5px;height:33%;background:var(--c2)}
5
+ .theme-art-layer .art-pattern{right:44px;bottom:34px;width:220px;height:164px;border:1px solid var(--c1);background:repeating-linear-gradient(-45deg,transparent 0 11px,color-mix(in srgb,var(--c1) 14%,transparent) 11px 13px);opacity:.58}
6
+ [data-layout="cover"] .theme-art-layer .art-a,[data-layout="section"] .theme-art-layer .art-a{width:40%;background:var(--c1);opacity:.9}
7
+ .component-grid>.component-card:nth-child(even),.component-grid>.proof-card:nth-child(even){background:color-mix(in srgb,var(--c2) 9%,var(--cmp-surface))}
8
+
@@ -0,0 +1,9 @@
1
+ /* Large modular color fields + cropped geometry. */
2
+ .theme-art-layer .art-a{right:-80px;bottom:-110px;width:430px;height:360px;background:linear-gradient(135deg,var(--c2),var(--c1));clip-path:polygon(22% 0,100% 0,100% 78%,58% 100%,0 62%);opacity:.82}
3
+ .theme-art-layer .art-b{left:-70px;bottom:-92px;width:360px;height:250px;background:var(--c3);clip-path:polygon(0 20%,78% 0,100% 100%,0 100%);opacity:.34}
4
+ .theme-art-layer .art-c{right:260px;bottom:44px;width:118px;height:118px;border:22px solid var(--c4);transform:rotate(18deg);opacity:.38}
5
+ .theme-art-layer .art-pattern{left:0;top:0;width:42%;height:36%;background-image:radial-gradient(var(--c1) 1.4px,transparent 1.4px);background-size:18px 18px;opacity:.18}
6
+ [data-layout="cover"] .theme-art-layer .art-a,[data-layout="pitch-problem"] .theme-art-layer .art-a{right:-80px;top:-90px;bottom:auto;width:510px;height:470px;opacity:.94}
7
+ .component-grid>.component-card:nth-child(4n+1),.component-grid>.feature-card:nth-child(4n+1){border-top:7px solid var(--c1)}
8
+ .component-grid>.component-card:nth-child(4n+2),.component-grid>.feature-card:nth-child(4n+2){border-top:7px solid var(--c2)}
9
+ .component-grid>.component-card:nth-child(4n+3),.component-grid>.feature-card:nth-child(4n+3){border-top:7px solid var(--c3)}
@@ -0,0 +1,6 @@
1
+ /* Multi-hue atmospheric field; intentionally uses the full theme ramp. */
2
+ .theme-art-layer .art-a{inset:-22%;background:radial-gradient(circle at 18% 20%,var(--c2),transparent 32%),radial-gradient(circle at 82% 16%,var(--c1),transparent 34%),radial-gradient(circle at 68% 86%,var(--c3),transparent 36%),radial-gradient(circle at 20% 92%,var(--c4),transparent 34%);filter:blur(34px);opacity:.24;transform:rotate(-4deg)}
3
+ .theme-art-layer .art-b{right:-110px;top:90px;width:380px;height:520px;border-radius:52% 0 0 52%;background:linear-gradient(150deg,color-mix(in srgb,var(--c2) 78%,transparent),color-mix(in srgb,var(--c4) 58%,transparent));opacity:.18;transform:rotate(8deg)}
4
+ .theme-art-layer .art-c{left:58%;top:-160px;width:320px;height:320px;border:1px solid var(--c1);border-radius:50%;box-shadow:0 0 0 38px color-mix(in srgb,var(--c1) 8%,transparent),0 0 0 76px color-mix(in srgb,var(--c2) 6%,transparent);opacity:.45}
5
+ .theme-art-layer .art-pattern{inset:0;background:linear-gradient(115deg,transparent 48%,color-mix(in srgb,var(--c1) 12%,transparent) 49%,transparent 51%);opacity:.6}
6
+
@@ -0,0 +1,8 @@
1
+ /* Friendly blobs, outlined loops and dotted texture. */
2
+ .theme-art-layer .art-a{right:-95px;top:-76px;width:410px;height:330px;background:var(--c2);border-radius:58% 42% 66% 34% / 45% 62% 38% 55%;transform:rotate(18deg);opacity:.28}
3
+ .theme-art-layer .art-b{left:-90px;bottom:-90px;width:350px;height:270px;background:var(--c3);border-radius:38% 62% 42% 58% / 64% 38% 62% 36%;opacity:.22}
4
+ .theme-art-layer .art-c{right:210px;bottom:58px;width:150px;height:96px;border:4px solid var(--c1);border-radius:50%;transform:rotate(-18deg);opacity:.42}
5
+ .theme-art-layer .art-pattern{left:32px;top:28px;width:260px;height:180px;background-image:radial-gradient(var(--c4) 2px,transparent 2px);background-size:20px 20px;mask-image:linear-gradient(135deg,var(--title),transparent);opacity:.28}
6
+ .component-grid>.component-card:nth-child(3n+1),.component-grid>.feature-card:nth-child(3n+1){background:color-mix(in srgb,var(--c1) 9%,var(--cmp-surface))}
7
+ .component-grid>.component-card:nth-child(3n+2),.component-grid>.feature-card:nth-child(3n+2){background:color-mix(in srgb,var(--c2) 9%,var(--cmp-surface))}
8
+ .component-grid>.component-card:nth-child(3n),.component-grid>.feature-card:nth-child(3n){background:color-mix(in srgb,var(--c3) 9%,var(--cmp-surface))}
@@ -0,0 +1,6 @@
1
+ /* Framed composition with restrained radial ornament and corner rays. */
2
+ .theme-art-layer .art-a{inset:26px;border:1px solid var(--c1);box-shadow:inset 0 0 0 7px color-mix(in srgb,var(--c1) 4%,transparent);opacity:.5}
3
+ .theme-art-layer .art-b{right:-150px;top:-150px;width:420px;height:420px;border:1px solid var(--c2);border-radius:50%;box-shadow:0 0 0 34px color-mix(in srgb,var(--c2) 7%,transparent),0 0 0 68px color-mix(in srgb,var(--c3) 5%,transparent);opacity:.46}
4
+ .theme-art-layer .art-c{left:-120px;bottom:-120px;width:320px;height:320px;background:radial-gradient(circle,var(--c1) 0 2px,transparent 3px),conic-gradient(from 0deg,transparent 0 8deg,color-mix(in srgb,var(--c1) 18%,transparent) 8deg 10deg,transparent 10deg 20deg);border-radius:50%;opacity:.4}
5
+ .theme-art-layer .art-pattern{right:50px;bottom:50px;width:180px;height:1px;background:linear-gradient(90deg,transparent,var(--c1),transparent);box-shadow:0 -8px 0 color-mix(in srgb,var(--c1) 22%,transparent),0 8px 0 color-mix(in srgb,var(--c1) 22%,transparent)}
6
+
@@ -0,0 +1,9 @@
1
+ /* Loud, high-contrast blocks for anti-design / maximalist families. */
2
+ .theme-art-layer .art-a{right:-80px;top:-126px;width:520px;height:220px;background:var(--c2);transform:rotate(-4deg);opacity:.76}
3
+ .theme-art-layer .art-b{left:-80px;bottom:-78px;width:470px;height:250px;background:var(--c3);transform:rotate(6deg);opacity:.58}
4
+ .theme-art-layer .art-c{right:36px;bottom:48px;width:210px;height:160px;background:var(--c4);border:4px solid var(--title);box-shadow:12px 12px 0 var(--title);transform:rotate(2deg);opacity:.7}
5
+ .theme-art-layer .art-pattern{left:0;top:0;width:230px;height:150px;background:repeating-linear-gradient(135deg,var(--c1) 0 12px,transparent 12px 24px);opacity:.45}
6
+ .component-grid>.component-card:nth-child(4n+1),.component-grid>.price-card:nth-child(4n+1){background:var(--c1)}
7
+ .component-grid>.component-card:nth-child(4n+2),.component-grid>.price-card:nth-child(4n+2){background:var(--c2)}
8
+ .component-grid>.component-card:nth-child(4n+3),.component-grid>.price-card:nth-child(4n+3){background:var(--c3)}
9
+ .component-grid>.component-card:nth-child(4n+4),.component-grid>.price-card:nth-child(4n+4){background:var(--c4)}
@@ -0,0 +1,6 @@
1
+ /* Blueprint grid, modular zones and diagram nodes. */
2
+ .theme-art-layer .art-a{inset:0;background-image:linear-gradient(color-mix(in srgb,var(--c1) 10%,transparent) 1px,transparent 1px),linear-gradient(90deg,color-mix(in srgb,var(--c1) 10%,transparent) 1px,transparent 1px);background-size:36px 36px;opacity:.62}
3
+ .theme-art-layer .art-b{right:0;top:42%;width:29%;height:58%;background:linear-gradient(180deg,color-mix(in srgb,var(--c2) 18%,transparent),color-mix(in srgb,var(--c3) 8%,transparent));border-left:1px solid color-mix(in srgb,var(--c1) 36%,transparent);border-top:1px solid color-mix(in srgb,var(--c1) 36%,transparent)}
4
+ .theme-art-layer .art-c{right:76px;top:64px;width:122px;height:122px;border:1px solid var(--c1);box-shadow:26px 26px 0 color-mix(in srgb,var(--c2) 16%,transparent);opacity:.58}
5
+ .theme-art-layer .art-pattern{left:36px;bottom:32px;width:300px;height:26px;background:repeating-linear-gradient(90deg,var(--c1) 0 2px,transparent 2px 18px);opacity:.32}
6
+ .component-grid>.component-card,.component-grid>.metric-card{background:color-mix(in srgb,var(--slide-bg) 82%,transparent);backdrop-filter:blur(8px)}
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Single source of truth for the skill's bundled font set. Consumed by bin-src/html2pptx.mjs (base64 @font-face injection at render) and by scripts/deck-skin-samples.mjs + scripts/deck-fidelity.mjs (served from disk via page.route). Add a face HERE, drop the .woff2 beside it, and every renderer picks it up — never inline a base64 @font-face into a skin.css. `substitutesInvisibly: true` marks a neutral grotesque: it is NOT on any OS, so the native pptx text layer swaps it for Helvetica/Arial, but the swap differs by a hair and no reader would name it — such a face may lead a skin unbaked. A serif or display face is the opposite call: bake every rule that uses it, or the audience sees a different font (deck-layouts.test.ts enforces both directions, and gen-skin refuses a family that gets it wrong).",
2
+ "_comment": "Single source of truth for the skill's bundled font set. Consumed by bin-src/html2pptx.mjs (base64 @font-face injection at render) and by scripts/deck/deck-skin-samples.mjs + scripts/deck/deck-fidelity.mjs (served from disk via page.route). Add a face HERE, drop the .woff2 beside it, and every renderer picks it up — never inline a base64 @font-face into a skin.css. `substitutesInvisibly: true` marks a neutral grotesque: it is NOT on any OS, so the native pptx text layer swaps it for Helvetica/Arial, but the swap differs by a hair and no reader would name it — such a face may lead a skin unbaked. A serif or display face is the opposite call: bake every rule that uses it, or the audience sees a different font (deck-layouts.test.ts enforces both directions, and gen-skin refuses a family that gets it wrong).",
3
3
  "cjkUnicodeRange": "U+2E80-2EFF,U+3000-303F,U+3040-30FF,U+3200-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF",
4
4
  "cjk": [
5
5
  {
@@ -32,7 +32,7 @@
32
32
  "families": [
33
33
  "Anton"
34
34
  ],
35
- "note": "Ultra-condensed display face. cinereel + ivory-gold use it for --font-head and BAKE their titles, which is why it survives into the pptx. Also staged into the xhs skill by scripts/build-xhs-skills.mjs.",
35
+ "note": "Ultra-condensed display face. cinereel + ivory-gold use it for --font-head and BAKE their titles, which is why it survives into the pptx. Also staged into the xhs skill by scripts/build/build-xhs-skills.mjs.",
36
36
  "license": "SIL OFL 1.1 (Google Fonts)"
37
37
  },
38
38
  {