@deckspec/cli 0.1.1 → 0.1.3

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 (50) hide show
  1. package/dist/cli.js +13 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/dev.d.ts +3 -1
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +4 -2
  6. package/dist/commands/dev.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +22 -17
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/render.d.ts.map +1 -1
  11. package/dist/commands/render.js +13 -4
  12. package/dist/commands/render.js.map +1 -1
  13. package/package.json +6 -5
  14. package/src/cli.ts +12 -2
  15. package/src/commands/dev.ts +3 -2
  16. package/src/commands/init.ts +22 -17
  17. package/src/commands/render.ts +13 -4
  18. package/templates/noir-display/components/card/index.tsx +31 -0
  19. package/templates/noir-display/components/index.ts +2 -0
  20. package/templates/noir-display/components/slide-header/index.tsx +47 -0
  21. package/templates/noir-display/design.md +289 -0
  22. package/templates/noir-display/globals.css +263 -0
  23. package/templates/noir-display/package.json +39 -0
  24. package/templates/noir-display/patterns/_lib/chart-colors.ts +12 -0
  25. package/templates/noir-display/patterns/_lib/icon.tsx +50 -0
  26. package/templates/noir-display/patterns/big-number/index.tsx +87 -0
  27. package/templates/noir-display/patterns/body-message/index.tsx +295 -0
  28. package/templates/noir-display/patterns/bullet-list/index.tsx +132 -0
  29. package/templates/noir-display/patterns/challenge-cards/index.tsx +112 -0
  30. package/templates/noir-display/patterns/chart-bar/index.tsx +107 -0
  31. package/templates/noir-display/patterns/comparison-columns/index.tsx +115 -0
  32. package/templates/noir-display/patterns/feature-metrics/index.tsx +94 -0
  33. package/templates/noir-display/patterns/flow-diagram/index.tsx +151 -0
  34. package/templates/noir-display/patterns/hero-statement/index.tsx +72 -0
  35. package/templates/noir-display/patterns/icon-grid/index.tsx +126 -0
  36. package/templates/noir-display/patterns/index.ts +17 -0
  37. package/templates/noir-display/patterns/phased-roadmap/index.tsx +179 -0
  38. package/templates/noir-display/patterns/photo-split/index.tsx +110 -0
  39. package/templates/noir-display/patterns/pricing-tiers/index.tsx +127 -0
  40. package/templates/noir-display/patterns/showcase-grid/index.tsx +99 -0
  41. package/templates/noir-display/patterns/thank-you/index.tsx +86 -0
  42. package/templates/noir-display/patterns/three-pillars/index.tsx +112 -0
  43. package/templates/noir-display/patterns/title-center/index.tsx +46 -0
  44. package/templates/noir-display/tokens.json +30 -0
  45. package/templates/noir-display/tsconfig.json +13 -0
  46. package/templates/skills/deckspec-add-pattern/SKILL.md +78 -0
  47. package/templates/skills/deckspec-make-slides/SKILL.md +70 -0
  48. package/templates/skills/deckspec-promote-pattern/SKILL.md +133 -0
  49. package/templates/skills/deckspec-screenshot/SKILL.md +41 -0
  50. package/templates/skills/deckspec-to-pptx/SKILL.md +36 -0
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+
3
+ const pillarSchema = z.object({
4
+ title: z.string().min(1).describe("Pillar title"),
5
+ description: z.string().min(1).describe("Pillar description"),
6
+ value: z.string().optional().describe("Optional large display value"),
7
+ });
8
+
9
+ export const schema = z.object({
10
+ label: z.string().min(1).max(30).optional().describe("Accent eyebrow"),
11
+ heading: z.string().min(1).max(60).describe("Section heading"),
12
+ pillars: z.array(pillarSchema).min(2).max(3).describe("Pillars (2-3)"),
13
+ });
14
+
15
+ type Props = z.infer<typeof schema>;
16
+
17
+ export default function ThreePillars({ label, heading, pillars }: Props) {
18
+ return (
19
+ <div className="slide-stack" style={{ justifyContent: "center", gap: 48 }}>
20
+ {/* Header */}
21
+ <div style={{ textAlign: "center" }}>
22
+ {label && (
23
+ <span
24
+ style={{
25
+ fontSize: 17,
26
+ fontWeight: 600,
27
+ letterSpacing: "-0.022em",
28
+ color: "var(--color-primary)",
29
+ display: "block",
30
+ marginBottom: 8,
31
+ }}
32
+ >
33
+ {label}
34
+ </span>
35
+ )}
36
+ <h2
37
+ style={{
38
+ fontSize: 48,
39
+ fontWeight: 600,
40
+ lineHeight: 1.08,
41
+ letterSpacing: "-0.003em",
42
+ }}
43
+ >
44
+ {heading}
45
+ </h2>
46
+ </div>
47
+
48
+ {/* Pillars as white cards on gray canvas */}
49
+ <div
50
+ style={{
51
+ display: "grid",
52
+ gridTemplateColumns: `repeat(${pillars.length}, 1fr)`,
53
+ gap: 20,
54
+ }}
55
+ >
56
+ {pillars.map((p, i) => (
57
+ <div
58
+ key={i}
59
+ style={{
60
+ backgroundColor: "#ffffff",
61
+ borderRadius: 18,
62
+ padding: "36px 32px",
63
+ display: "flex",
64
+ flexDirection: "column",
65
+ gap: 12,
66
+ boxShadow: "4px 4px 12px rgba(0,0,0,0.06)",
67
+ }}
68
+ >
69
+ {p.value && (
70
+ <span
71
+ style={{
72
+ fontSize: 40,
73
+ fontWeight: 600,
74
+ fontFamily: "var(--font-heading)",
75
+ lineHeight: 1,
76
+ letterSpacing: "-0.009em",
77
+ color: "var(--color-primary)",
78
+ }}
79
+ >
80
+ {p.value}
81
+ </span>
82
+ )}
83
+ <span
84
+ style={{
85
+ fontSize: 21,
86
+ fontWeight: 600,
87
+ fontFamily: "var(--font-heading)",
88
+ letterSpacing: "0.011em",
89
+ color: "var(--color-foreground)",
90
+ lineHeight: 1.24,
91
+ }}
92
+ >
93
+ {p.title}
94
+ </span>
95
+ <span
96
+ style={{
97
+ fontSize: 17,
98
+ fontWeight: 400,
99
+ fontFamily: "var(--font-body)",
100
+ letterSpacing: "-0.022em",
101
+ color: "var(--color-muted-foreground)",
102
+ lineHeight: 1.47,
103
+ }}
104
+ >
105
+ {p.description}
106
+ </span>
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+
3
+ export const schema = z.object({
4
+ title: z.string().min(1).max(40).describe("Display headline — large, dramatic"),
5
+ subtitle: z.string().min(1).max(80).describe("Muted subtitle beneath headline"),
6
+ });
7
+
8
+ type Props = z.infer<typeof schema>;
9
+
10
+ export default function TitleCenter({ title, subtitle }: Props) {
11
+ return (
12
+ <div
13
+ className="slide"
14
+ style={{
15
+ display: "flex",
16
+ alignItems: "center",
17
+ justifyContent: "center",
18
+ background: "#ffffff",
19
+ }}
20
+ >
21
+ <div className="stack-center" style={{ gap: 16, maxWidth: 900, marginBottom: 32 }}>
22
+ <h1
23
+ style={{
24
+ fontSize: 72,
25
+ fontWeight: 600,
26
+ lineHeight: 1.05,
27
+ letterSpacing: "-0.015em",
28
+ }}
29
+ >
30
+ {title}
31
+ </h1>
32
+ <p
33
+ style={{
34
+ fontSize: 24,
35
+ fontWeight: 400,
36
+ lineHeight: 1.17,
37
+ letterSpacing: "0.009em",
38
+ color: "var(--color-muted-foreground)",
39
+ }}
40
+ >
41
+ {subtitle}
42
+ </p>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "noir-display",
3
+ "displayName": "Pristine Display",
4
+ "description": "Apple.com inspired light theme — canvas gray background, display typography, dramatic whitespace, and cinematic clarity.",
5
+ "colors": {
6
+ "background": "#f5f5f7",
7
+ "foreground": "#1d1d1f",
8
+ "primary": "#0071e3",
9
+ "muted": "#e8e8ed",
10
+ "muted-foreground": "#6e6e73",
11
+ "border": "rgba(0,0,0,0.08)",
12
+ "accent": "#0066cc",
13
+ "card-background": "#ffffff"
14
+ },
15
+ "fonts": {
16
+ "heading": "'SF Pro JP', 'SF Pro Display', 'Hiragino Sans', 'Helvetica Neue', 'Noto Sans JP', sans-serif",
17
+ "body": "'SF Pro JP', 'SF Pro Text', 'Hiragino Sans', 'Helvetica Neue', 'Noto Sans JP', sans-serif"
18
+ },
19
+ "spacing": {
20
+ "slide-padding": "60px",
21
+ "slide-padding-x": "80px",
22
+ "slide-gap": "32px"
23
+ },
24
+ "borderRadius": "12px",
25
+ "slide": {
26
+ "width": 1200,
27
+ "height": 675,
28
+ "aspectRatio": "16/9"
29
+ }
30
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": ".",
6
+ "composite": true,
7
+ "jsx": "react-jsx"
8
+ },
9
+ "include": [
10
+ "patterns/",
11
+ "components/"
12
+ ]
13
+ }
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: deckspec-add-pattern
3
+ description: "Use this skill when the user asks to create a new pattern, add a layout, or make a reusable slide template. Triggers on: 'add pattern', 'create pattern', 'new layout', 'パターン作って', 'レイアウト追加'."
4
+ ---
5
+
6
+ # Add Pattern Skill
7
+
8
+ Create a new reusable pattern in the theme.
9
+
10
+ ## Arguments
11
+
12
+ The user should specify:
13
+ - Pattern name (kebab-case)
14
+ - What the pattern displays (layout description)
15
+ - Which theme to add it to
16
+
17
+ ## Workflow
18
+
19
+ ### 1. Read the theme's design system
20
+ - Read `themes/<theme>/design.md` for design philosophy and rules
21
+ - Read `themes/<theme>/tokens.json` for colors, fonts, spacing
22
+ - Read `themes/<theme>/globals.css` for available semantic classes
23
+ - Look at existing patterns for conventions
24
+
25
+ ### 2. Create the pattern
26
+
27
+ Create `themes/<theme>/patterns/<name>/index.tsx`:
28
+
29
+ ```tsx
30
+ import { z } from "zod";
31
+ import React from "react";
32
+
33
+ export const schema = z.object({
34
+ // Define vars with .describe() for clarity
35
+ heading: z.string().describe("Main heading text"),
36
+ items: z.array(z.object({
37
+ label: z.string(),
38
+ value: z.string(),
39
+ })).describe("List of items to display"),
40
+ });
41
+
42
+ type Props = z.infer<typeof schema>;
43
+
44
+ export default function PatternName(props: Props) {
45
+ return (
46
+ <div className="slide-pad slide-stack">
47
+ <h1>{props.heading}</h1>
48
+ {/* Layout using semantic classes */}
49
+ </div>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ### 3. Create examples.yaml
55
+
56
+ Create `themes/<theme>/patterns/<name>/examples.yaml`:
57
+
58
+ ```yaml
59
+ - name: "Basic example"
60
+ vars:
61
+ heading: "Example Heading"
62
+ items:
63
+ - label: "Item 1"
64
+ value: "Value 1"
65
+ ```
66
+
67
+ ### 4. Validate and preview
68
+
69
+ Create a test deck referencing the new pattern, validate it, and preview with `npx deckspec dev`.
70
+
71
+ ## Design Rules
72
+ - Use semantic CSS classes (`.slide-pad`, `.card`, `.list`, etc.)
73
+ - Colors via CSS custom properties (`var(--color-primary)`) — never hardcode hex
74
+ - Minimum font size: 16px
75
+ - No serif fonts
76
+ - No Tailwind CSS
77
+ - Export both `schema` (Zod) and `default` (React component)
78
+ - Use `.describe()` on Zod fields for AI-friendly documentation
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: deckspec-make-slides
3
+ description: "Use this skill when the user asks to create a presentation, make slides, build a deck, or write deck.yaml. Triggers on: 'make slides', 'create presentation', 'build a deck', 'プレゼン作って', 'スライド作って', 'デッキ作って'."
4
+ ---
5
+
6
+ # Make Slides Skill
7
+
8
+ Create a DeckSpec presentation from a user request.
9
+
10
+ ## Workflow
11
+
12
+ ### 1. Understand the request
13
+ - What is the presentation about?
14
+ - How many slides?
15
+ - What tone? (business, technical, casual)
16
+
17
+ ### 2. Check available patterns
18
+ Run: `npx deckspec patterns --examples`
19
+ Read: `themes/*/design.md` for design philosophy and pattern catalog.
20
+
21
+ ### 3. Create deck.yaml
22
+
23
+ Create `decks/<deck-name>/deck.yaml`:
24
+
25
+ ```yaml
26
+ meta:
27
+ title: "Presentation Title"
28
+ theme: noir-display
29
+ slides:
30
+ - file: <pattern-name>
31
+ vars:
32
+ # ... content
33
+ ```
34
+
35
+ **Pattern selection guide:**
36
+ - Opening: `title-center` or `hero-statement`
37
+ - Data/metrics: `feature-metrics`, `big-number`, `chart-bar`
38
+ - Comparison: `comparison-columns`, `pricing-tiers`
39
+ - Process/flow: `flow-diagram`, `three-pillars`
40
+ - Content: `bullet-list`, `icon-grid`
41
+ - Closing: `thank-you`
42
+
43
+ ### 4. Validate
44
+ ```bash
45
+ npx deckspec validate decks/<deck-name>/deck.yaml
46
+ ```
47
+ Fix any schema errors.
48
+
49
+ ### 5. Preview
50
+ ```bash
51
+ npx deckspec dev
52
+ ```
53
+ Open http://localhost:3002 and navigate to the deck.
54
+
55
+ ### 6. Iterate
56
+ Ask the user for feedback and adjust slides accordingly.
57
+
58
+ ## If no existing pattern fits
59
+
60
+ Create a deck-local pattern at `decks/<deck-name>/patterns/<name>/index.tsx`:
61
+ - Export `schema` (Zod) and `default` (React component)
62
+ - Use semantic CSS classes from the theme's `globals.css`
63
+ - Do NOT hardcode colors — use CSS custom properties
64
+ - The pattern is compiled on-the-fly with esbuild, no build step needed
65
+
66
+ ## Rules
67
+ - Always validate before presenting to the user
68
+ - Use pattern `examples.yaml` as reference for vars structure
69
+ - Minimum font size: 16px, no serif fonts
70
+ - Colors via CSS custom properties only
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: deckspec-promote-pattern
3
+ description: "Use this skill when the user asks to promote, elevate, or graduate a deck-local pattern to a theme pattern. Triggers on: 'promote pattern', 'elevate to theme', 'make it reusable', 'add to theme', 'graduate pattern', 'パターン昇格', 'テーマに昇格', or any request to move a deck-local pattern into the theme."
4
+ ---
5
+
6
+ # Promote Pattern Skill
7
+
8
+ Promote a deck-local pattern to a theme pattern. Abstract hardcoded values into vars, making it a reusable, generic pattern.
9
+
10
+ ## Arguments
11
+
12
+ ```
13
+ /promote-pattern <deck-pattern-path> [--theme <theme-name>]
14
+ ```
15
+
16
+ | Argument | Default | Description |
17
+ |----------|---------|-------------|
18
+ | `deck-pattern-path` | (required) | Path to the deck-local pattern (e.g., `decks/my-project/patterns/product-comparison`) |
19
+ | `--theme` | `noir-display` | Target theme name |
20
+
21
+ ## Workflow
22
+
23
+ This skill proceeds **interactively**. Each step asks for user confirmation before moving on.
24
+
25
+ ### Phase 1: Analysis
26
+
27
+ 1. Read the specified deck-local pattern's `index.tsx`
28
+ 2. Analyze the source code to identify:
29
+ - **Hardcoded values**: text strings, numbers, currency symbols, proper nouns, industry terms
30
+ - **Domain-specific variable names**: names tied to a particular domain (e.g., `products` → `items`, `competitors` → `columns`)
31
+ - **Domain-specific logic**: fixed format functions (e.g., hardcoded currency locale)
32
+ - **Values already in vars**: can be kept as-is
33
+ 3. Present the analysis to the user
34
+
35
+ ### Phase 2: Interactive design decisions
36
+
37
+ **All design decisions use `AskUserQuestion` with `options`.** Let the user click choices rather than type free text.
38
+
39
+ #### 2a. Pattern name
40
+
41
+ `AskUserQuestion` with candidates:
42
+ - header: "Pattern name"
43
+ - options: suggested names (mark recommended with `(Recommended)`) + 2-3 alternatives
44
+ - e.g., `["product-comparison (Recommended)", "feature-grid", "data-table"]`
45
+
46
+ #### 2b. Hardcoded value → vars decisions
47
+
48
+ For each detected hardcoded value, use `AskUserQuestion`:
49
+ - header: "Hardcoded value"
50
+ - question: `"Total" (table footer label) — what should we do?`
51
+ - options:
52
+ - `Add to vars: footerLabel (string, optional)` — recommended
53
+ - `Keep hardcoded`
54
+ - `Remove`
55
+
56
+ For many values, use `multiSelect: true` for batch selection:
57
+ - question: `Which hardcoded values should become vars?`
58
+ - options: list each value
59
+
60
+ #### 2c. Variable renaming
61
+
62
+ `AskUserQuestion`:
63
+ - header: "Rename vars"
64
+ - options:
65
+ - `Apply suggested renames (Recommended)` — description lists the rename map
66
+ - `Keep current names`
67
+
68
+ #### 2d. Extra options
69
+
70
+ `AskUserQuestion` with `multiSelect: true`:
71
+ - header: "Extra options"
72
+ - question: `Select additional options to add`
73
+ - options: each candidate option with description
74
+
75
+ ### Phase 3: Implementation
76
+
77
+ Based on user answers:
78
+
79
+ 1. **Create pattern file**: `themes/{theme}/patterns/{new-name}/index.tsx`
80
+ - Replace hardcoded values with vars
81
+ - Rename variables as agreed
82
+ - Implement extra options
83
+ - Add `.describe()` to all Zod schema fields
84
+
85
+ 2. **Create examples.yaml**: `themes/{theme}/patterns/{new-name}/examples.yaml`
86
+ - First example: the original deck's vars (proof it works as-is)
87
+ - Second example: usage in a different domain (proof of generality)
88
+ - Third example (optional): demonstrate optional features
89
+
90
+ 3. **Update index.ts**: Add re-export to `themes/{theme}/patterns/index.ts`
91
+
92
+ 4. **Update design.md**: Add the pattern to the catalog section with vars spec
93
+
94
+ ### Phase 4: Verification
95
+
96
+ 1. Run `pnpm build` to confirm the theme builds
97
+ 2. Create a test deck with examples.yaml vars, run `npx deckspec validate` + `npx deckspec render`
98
+ 3. Take screenshots and present to user
99
+ 4. Fix issues based on user feedback
100
+
101
+ ### Phase 5: Old pattern disposition
102
+
103
+ `AskUserQuestion`:
104
+ - header: "Old pattern"
105
+ - question: `What should we do with the original deck-local pattern?`
106
+ - options:
107
+ - `Keep it (Recommended)` — description: maintains backward compatibility
108
+ - `Delete and update deck.yaml` — description: replace with new theme pattern name
109
+
110
+ ## Analysis hints
111
+
112
+ ### Detecting hardcoded values
113
+ - String literals in the target language (any natural language text)
114
+ - Currency/unit symbols (`"$"`, `"%"`, `"EUR"`, etc.)
115
+ - Fixed locale strings (e.g., `"en-US"` in `.toLocaleString()`)
116
+ - Hardcoded CSS colors (`"#xxx"` — anything not using `var()`)
117
+ - Fixed number formatting
118
+
119
+ ### Naming conventions
120
+ - Pattern names: kebab-case, replace domain terms with generic ones (e.g., `product-comparison` → `comparison-table`)
121
+ - Var names: camelCase, use `.describe()` in Zod schema
122
+ - Array names: plural (`rows`, `columns`, `items`)
123
+
124
+ ### examples.yaml best practices
125
+ - Example 1: original deck's real data (proof of migration)
126
+ - Example 2: different domain usage (proof of generality)
127
+ - Example 3: optional feature showcase (e.g., `showTotal: false`)
128
+
129
+ ## Important notes
130
+
131
+ - Read the target theme's `design.md` first to understand design rules
132
+ - Keep var naming consistent with existing patterns (`label`, `heading`, etc.)
133
+ - If the pattern uses theme utilities like `_lib/icon.tsx`, verify they exist in the target theme
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: deckspec-screenshot
3
+ description: "Use this skill when the user asks to screenshot, capture, or photograph slides from a DeckSpec deck. Triggers on: 'screenshot', 'capture slides', 'save slide images', 'スクリーンショット', 'スライド撮影'."
4
+ ---
5
+
6
+ # Screenshot Deck Skill
7
+
8
+ Capture all slides of a DeckSpec deck as individual PNG images (1200x675, 16:9).
9
+
10
+ ## Prerequisites
11
+
12
+ - Playwright installed: `npm install -D playwright && npx playwright install chromium`
13
+
14
+ ## Workflow
15
+
16
+ ### 1. Render the deck to HTML
17
+
18
+ ```bash
19
+ npx deckspec render decks/<deck-name>/deck.yaml -o output/<deck-name>
20
+ ```
21
+
22
+ ### 2. Run the screenshot script
23
+
24
+ ```bash
25
+ node scripts/screenshot-deck.mjs output/<deck-name>/index.html output/<deck-name>-slides
26
+ ```
27
+
28
+ This will:
29
+ - Open the HTML in headless Chromium at 1200x675 viewport
30
+ - Hide navigation controls
31
+ - Screenshot each slide as `slide-01.png`, `slide-02.png`, etc.
32
+
33
+ ### 3. Verify
34
+
35
+ Read the generated PNG files to visually confirm the output.
36
+
37
+ ## Output
38
+
39
+ - `output/<deck-name>-slides/slide-01.png`
40
+ - `output/<deck-name>-slides/slide-02.png`
41
+ - Each image is 1200x675px PNG (16:9)
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: deckspec-to-pptx
3
+ description: "Use this skill when the user asks to convert a DeckSpec deck to PowerPoint (.pptx). Triggers on: 'PowerPoint', 'pptx', 'convert to pptx', 'export to PowerPoint', 'パワポ', 'パワーポイント'."
4
+ ---
5
+
6
+ # Deck to PowerPoint Skill
7
+
8
+ Convert a DeckSpec deck.yaml to a PowerPoint (.pptx) file.
9
+
10
+ ## Prerequisites
11
+
12
+ - pptxgenjs installed: `npm install -D pptxgenjs`
13
+
14
+ ## Workflow
15
+
16
+ ### 1. Run the conversion
17
+
18
+ ```bash
19
+ node scripts/deck-to-pptx.mjs decks/<deck-name>/deck.yaml -o output/<deck-name>.pptx
20
+ ```
21
+
22
+ ### 2. Check for warnings
23
+
24
+ The script warns for unregistered patterns: `⚠ No pptx renderer for pattern "<name>" — blank slide`
25
+
26
+ To fix, add a renderer function in `scripts/deck-to-pptx.mjs`.
27
+
28
+ ### 3. Open and verify
29
+
30
+ Open the .pptx in PowerPoint or Google Slides to verify.
31
+
32
+ ## Notes
33
+
34
+ - Colors and fonts are loaded from `themes/<theme>/tokens.json`
35
+ - Local images are embedded as base64
36
+ - Not all patterns have pptx renderers yet — unregistered patterns produce blank slides