voxflow 1.15.6 → 1.16.0
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.
- package/README.md +2 -2
- package/dist/index.js +1 -1
- package/lib/commands/skills.js +3 -3
- package/lib/commands/slice-fork.js +4 -4
- package/lib/commands/slice.js +1 -1
- package/package.json +1 -1
- package/skills/.claude-plugin/marketplace.json +2 -2
- package/skills/.claude-plugin/plugin.json +3 -2
- package/skills/README.md +9 -7
- package/skills/card/SKILL.md +410 -0
- package/skills/card/references/design-languages/cinematic-still.md +57 -0
- package/skills/card/references/design-languages/data-poster.md +57 -0
- package/skills/card/references/design-languages/editorial-artifact.md +58 -0
- package/skills/card/references/design-languages/field-notes.md +58 -0
- package/skills/card/references/design-languages/image-led-magazine.md +57 -0
- package/skills/card/references/design-languages/newsroom-poster.md +58 -0
- package/skills/card/references/design-languages/product-catalog.md +57 -0
- package/skills/card/references/design-languages/swiss-poster.md +60 -0
- package/skills/card/references/design-languages.md +166 -0
- package/skills/card/references/layouts/card-layouts.md +268 -0
- package/skills/card/references/magazine-card-adaptations.md +154 -0
- package/skills/card/references/taste.md +121 -0
- package/skills/card/references/themes/presets.md +314 -0
- package/skills/card/scripts/render-cards.mjs +216 -0
- package/skills/voxflow-slice/SKILL.md +0 -415
- package/skills/voxflow-slice/examples/article.md +0 -13
- package/skills/voxflow-slice/examples/expected-deck.json +0 -39
- package/skills/voxflow-slice/examples/validate.mjs +0 -46
- /package/skills/{voxflow-slice → slice}/templates/data-finding/deck.json +0 -0
- /package/skills/{voxflow-slice → slice}/templates/founder-lesson/deck.json +0 -0
- /package/skills/{voxflow-slice → slice}/templates/incident-review/deck.json +0 -0
- /package/skills/{voxflow-slice → slice}/templates/manifest.json +0 -0
- /package/skills/{voxflow-slice → slice}/templates/product-launch/deck.json +0 -0
- /package/skills/{voxflow-slice → slice}/templates/quiet-essay/deck.json +0 -0
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: voxflow-slice
|
|
3
|
-
description: "Author + iterate on a VoxFlow Slice card video (1080×1920 vertical, 33 themes) locally in Claude Code / Cursor — the user's own Claude does the work, no VoxFlow API call to slice (0 quota). Full multi-turn workflow: (1) fork a curated template via `voxflow slice fork <id>` OR slice an article into a deck.json by hand; (2) edit deck.json fields in place with Edit/Write tools while `voxflow slice preview deck.json` is running (browser hot-reloads in ~50ms); (3) audition voice per card with ▶ in the browser (proxies /api/tts/synthesize, content-hash cached); (4) regenerate AI images per card with 🎨 (card.images: [{id, prompt, aspect, quality}], proxies hunyuan-image, content-hash cached); (5) render mp4 with `voxflow slice render deck.json` — audio + AI images baked in by default, --no-audio / --no-images opt out, --legacy silent fallback. Schema gains: per-card `voiceover` (voiceId/text/rate/enabled), per-card `images` registry, V2 LayoutTree `el: \"raw-html\"` escape hatch (4096 char cap). Loop rules: edit only the fields the user asked about (preserve diff highlight), never re-run `voxflow slice <article>` during iteration (overwrites edits), don't restart the preview server, don't call /api/audition or /api/imagine yourself (user-driven via ▶/🎨). Pick this skill for ANY iterative Slice work — even when the user has a token; the iteration loop is faster + cheaper than the one-shot cloud route. Triggers: slice / paper-slide / paperslide / card video / 卡片视频 / 切片视频 / 知识卡片 / 文章转视频 / 抖音知识号 / 小红书图文转视频 / 知乎长文转视频 / 公众号转视频 / iterate slice / multi-turn slice / 多轮编辑切片 / 迭代切片 / fork template / slice fork / 模板 / 模板市场 / template gallery / audition card / play voiceover / 试听口播 / 试听卡片 / regenerate image / 生图 / image-gen card / raw-html slice / custom panel / offline slice / local slice / 离线切片 / 本地切片 / 不联网 / no-cloud slice. For the one-shot cloud route (calls /api/slice/deck, 200 quota, no iteration), use the `slice` skill instead."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# voxflow-slice — Article → deck.json (offline)
|
|
7
|
-
|
|
8
|
-
This skill owns the **slicing workflow** for VoxFlow Slice in pure local mode: you read an article the user points at, then write a `deck.json` that picks ONE of the 33 curated Remotion themes and fills in card fields. The user's own Claude does the work — there is no VoxFlow API call, no JWT, no quota deduction.
|
|
9
|
-
|
|
10
|
-
Compare with the broader `voxflow:slice` skill (which routes between web app, `voxflow slice` cloud command, and `voxflow slice stage` preview). This skill is the local-only alternative: when the user has no token, wants to iterate freely, or asks for an offline slice — slice here, hand off to `voxflow slice preview deck.json` for visual review.
|
|
11
|
-
|
|
12
|
-
## Hard rules
|
|
13
|
-
|
|
14
|
-
1. **Write exactly one file**: `deck.json` in the directory the user points at (or the current working directory if they don't specify). Never write `.tsx`, `.css`, `.jsx`, or any React/Remotion source — themes are fixed; you only fill in card fields.
|
|
15
|
-
2. **Pick ONE theme from the registry** (see Theme catalog below). Do not invent themes. Typo'd theme ids are hard-rejected by the validator.
|
|
16
|
-
3. **5–8 cards inclusive.** First card must be `kind: "title"`. An optional `kind: "outro"` may appear *only as the last card* (server-side BrandKit usually appends this — leave it off unless the user explicitly asks for a CTA card).
|
|
17
|
-
4. **No images you didn't already have.** Don't `WebFetch` or generate stock-photo URLs. `imageUrl` is only valid when the user explicitly provides a public http(s) URL and the chosen theme supports it (`photo-feature` / `atmospheric`).
|
|
18
|
-
5. **`figureKeyword` must come from the controlled list** in the Card shapes section below. Invented keywords render as a default arrow.
|
|
19
|
-
6. **No `narration` ≥ 60 zh chars and no `caption` ≥ 16 zh chars** — these are hard validator caps; the renderer's text-box clips overflows silently.
|
|
20
|
-
7. **Captions must NOT end with punctuation** (no 。!?…)— they are subtitles, not prose.
|
|
21
|
-
8. **Do not call any VoxFlow API.** This skill is offline. The user pays nothing.
|
|
22
|
-
|
|
23
|
-
## When to use this skill vs `voxflow:slice`
|
|
24
|
-
|
|
25
|
-
| Situation | Pick |
|
|
26
|
-
|---|---|
|
|
27
|
-
| User wants a `deck.json` produced locally, no quota cost | **this skill** |
|
|
28
|
-
| User asks "切一下这篇文章" and is logged in via `voxflow login` | `voxflow:slice` → uses `voxflow slice <file>` (200 quota) |
|
|
29
|
-
| User wants the mp4 too | `voxflow:slice` → web app |
|
|
30
|
-
| User wants to iterate visually on an existing deck | hand off to `voxflow slice preview deck.json` (after this skill writes the file) |
|
|
31
|
-
|
|
32
|
-
## Step 1 — Locate the article
|
|
33
|
-
|
|
34
|
-
The user will give you one of:
|
|
35
|
-
- a path to a `.md` / `.txt` / `.mdx` file (read it with the Read tool)
|
|
36
|
-
- raw text pasted into the chat
|
|
37
|
-
- a URL — only fetch it if the user is on a system with network access *and* explicitly asked you to fetch; otherwise ask them to paste the text
|
|
38
|
-
|
|
39
|
-
Minimum content: **80 characters** (validator floor). If shorter, ask for more material before slicing.
|
|
40
|
-
|
|
41
|
-
## Step 2 — Pick a theme
|
|
42
|
-
|
|
43
|
-
Confirm with the user once. Default is `paper-slide` (good for 抖音 / 视频号 / 小红书 knowledge-card content). If they have a target platform in mind, suggest the matching theme from the catalog below. **Only pick from the 33 registered ids** — anything else is rejected by `backend/services/paper-slide/deck-validator.js:148`.
|
|
44
|
-
|
|
45
|
-
## Step 3 — Plan the structure
|
|
46
|
-
|
|
47
|
-
Sketch the deck as a list before writing JSON. Default arc:
|
|
48
|
-
|
|
49
|
-
| Slot | Kind | Purpose |
|
|
50
|
-
|---|---|---|
|
|
51
|
-
| 1 | `title` | Hook line (反差 / 悬念 / 数字), 2 short rows |
|
|
52
|
-
| 2 | `body` | The setup — first idea, one sentence |
|
|
53
|
-
| 3 | `body` | Pivot or evidence — second idea |
|
|
54
|
-
| 4 | `body` | Payoff — third idea |
|
|
55
|
-
| 5 | `body` | Closing point — last idea (or a `data` / `quote` / `list` swap-in) |
|
|
56
|
-
|
|
57
|
-
Rules of thumb:
|
|
58
|
-
- One idea per card. Don't cram two into one `body`.
|
|
59
|
-
- Vary adjacent `figureKeyword` — never two consecutive `thinking` / `evidence-board` etc.
|
|
60
|
-
- A `quote` card is only justified if the article literally contains a stand-out line (≤30 zh chars).
|
|
61
|
-
- A `data` card is only justified if the article cites a memorable number.
|
|
62
|
-
- A `list` card is only justified if the article has an explicit 2–4 point structure.
|
|
63
|
-
- Each rich kind (quote / data / list) appears **at most once per deck**.
|
|
64
|
-
|
|
65
|
-
## Step 4 — Write `deck.json`
|
|
66
|
-
|
|
67
|
-
Use the schemas in the next section. The validator lives at `backend/services/paper-slide/deck-validator.js` and is the source of truth — when in doubt, read it.
|
|
68
|
-
|
|
69
|
-
## Deck schema (V1 — the shape this skill produces)
|
|
70
|
-
|
|
71
|
-
```jsonc
|
|
72
|
-
{
|
|
73
|
-
"header": "顶部小字,≤22 zh chars", // required, non-empty string
|
|
74
|
-
"seriesTitle": "系列名,≤10 zh chars", // required, will be wrapped in 【】 by some themes
|
|
75
|
-
"seriesTagline": "底部斜体副标题,≤22 zh chars", // required
|
|
76
|
-
"theme": "paper-slide", // optional but recommended — must be in catalog
|
|
77
|
-
"coverHookCaption": "封面钩子,≤18 zh chars", // optional; falls back to first body's caption
|
|
78
|
-
"coverFigureKeyword": "growth-system", // optional; falls back to first body's figureKeyword
|
|
79
|
-
"cards": [ /* 2–9 entries; first must be title; outro only last */ ]
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Card shapes by `kind`
|
|
84
|
-
|
|
85
|
-
All cards require a non-empty `narration` string (TTS reads this; 30–60 zh chars, ends with 。).
|
|
86
|
-
|
|
87
|
-
```jsonc
|
|
88
|
-
// kind: "title" — first card only
|
|
89
|
-
{
|
|
90
|
-
"kind": "title",
|
|
91
|
-
"title": ["第一行 ≤14 zh chars", "第二行 ≤14 zh chars"], // non-empty array
|
|
92
|
-
"narration": "30–60 zh chars 开场白,自然口语。"
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// kind: "body" — default for ideas
|
|
96
|
-
{
|
|
97
|
-
"kind": "body",
|
|
98
|
-
"caption": "一句金句字幕,≤16 zh chars,结尾无标点", // required
|
|
99
|
-
"figureKeyword": "problem-framing", // pick from controlled list below
|
|
100
|
-
"narration": "30–60 zh chars 配音稿,自然口语。"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// kind: "quote" — quote card (max 1 per deck)
|
|
104
|
-
{
|
|
105
|
-
"kind": "quote",
|
|
106
|
-
"quote": {
|
|
107
|
-
"text": "≤60 chars 引文,结尾无标点",
|
|
108
|
-
"attribution": "出处或人名(可省略),≤30 chars"
|
|
109
|
-
},
|
|
110
|
-
"narration": "30–60 zh chars 配音稿。"
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// kind: "data" — number card (max 1 per deck)
|
|
114
|
-
{
|
|
115
|
-
"kind": "data",
|
|
116
|
-
"data": {
|
|
117
|
-
"value": "≤12 chars,如 80 / 3x / 240万+",
|
|
118
|
-
"unit": "≤8 chars(可省略),如 % / 倍 / 年",
|
|
119
|
-
"label": "≤30 chars 数字解释"
|
|
120
|
-
},
|
|
121
|
-
"narration": "30–60 zh chars 配音稿。"
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// kind: "list" — 2–4 items (max 1 per deck)
|
|
125
|
-
{
|
|
126
|
-
"kind": "list",
|
|
127
|
-
"list": {
|
|
128
|
-
"items": [
|
|
129
|
-
{ "text": "≤30 chars 短句", "glyph": "✦" }, // glyph is optional emoji/char
|
|
130
|
-
{ "text": "≤30 chars 短句" },
|
|
131
|
-
{ "text": "≤30 chars 短句" }
|
|
132
|
-
]
|
|
133
|
-
},
|
|
134
|
-
"narration": "30–60 zh chars 配音稿。"
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// kind: "outro" — only allowed as LAST card; usually server-appended
|
|
138
|
-
{
|
|
139
|
-
"kind": "outro",
|
|
140
|
-
"narration": "8–60 chars 关注语口播。",
|
|
141
|
-
"outro": {
|
|
142
|
-
"cta": "关注 + 系列名,看下集",
|
|
143
|
-
"handle": "@voxflow(可省略)"
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Optional per-card `images` registry (Phase B)
|
|
149
|
-
|
|
150
|
-
Any card kind may carry an `images: [{ id, prompt, aspect?, quality? }]`
|
|
151
|
-
array of AI-generation recipes. The stage UI's 🎨 button resolves each
|
|
152
|
-
entry through `/api/imagine` (proxies hunyuan-image, content-hash cached
|
|
153
|
-
at `~/.config/voxflow/stage-image-cache/`); the first entry's resolved
|
|
154
|
-
URL is used as the card's `slide.imageUrl` at render time, overriding any
|
|
155
|
-
external `card.imageUrl`.
|
|
156
|
-
|
|
157
|
-
```jsonc
|
|
158
|
-
{
|
|
159
|
-
"kind": "body",
|
|
160
|
-
"caption": "...",
|
|
161
|
-
"narration": "...",
|
|
162
|
-
"figureKeyword": "growth-system",
|
|
163
|
-
"images": [
|
|
164
|
-
{
|
|
165
|
-
"id": "hero", // stable id ([a-zA-Z0-9_-]+, ≤64 chars, unique on card)
|
|
166
|
-
"prompt": "晨雾中的山脉,水墨风", // ≤1000 chars
|
|
167
|
-
"aspect": "portrait", // portrait | landscape | square (default: portrait)
|
|
168
|
-
"quality": "fast" // fast (200 quota) | hd (500 quota)
|
|
169
|
-
}
|
|
170
|
-
]
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Validator caps: `prompt` ≤ 1000 chars, `id` ≤ 64 chars matching `[a-zA-Z0-9_-]+`,
|
|
175
|
-
at most 8 images per card, unique `id` within a card.
|
|
176
|
-
|
|
177
|
-
### Optional per-card `el: "raw-html"` element (Phase C)
|
|
178
|
-
|
|
179
|
-
V2 LayoutTree decks may carry a `{ el: "raw-html", html: "..." }` child to
|
|
180
|
-
escape into arbitrary markup. The validator accepts strings up to 4096
|
|
181
|
-
chars and `normalizeV2Children` maps the first occurrence to a `rawHtml`
|
|
182
|
-
field on the V1 normalized output. **PaperSlide composition rendering of
|
|
183
|
-
arbitrary HTML lands in a follow-up PR** — for now the composition
|
|
184
|
-
silently skips the element, so a deck with raw-html validates + saves +
|
|
185
|
-
edits cleanly but renders blank visually until the JSX side is updated.
|
|
186
|
-
|
|
187
|
-
```jsonc
|
|
188
|
-
{
|
|
189
|
-
"kind": "body",
|
|
190
|
-
"narration": "...",
|
|
191
|
-
"children": [
|
|
192
|
-
{ "el": "heading", "text": "Custom panel" },
|
|
193
|
-
{ "el": "raw-html", "html": "<div style='font-size:48px'>★</div>" }
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
On a body card, exactly one of `paper-figure` OR `raw-html` is required
|
|
199
|
-
(both is rejected). Other kinds (title / quote / data / list) allow
|
|
200
|
-
`raw-html` as a supplementary element.
|
|
201
|
-
|
|
202
|
-
### Optional per-card `voiceover` override
|
|
203
|
-
|
|
204
|
-
Any card kind may carry a nested `voiceover` object to tune its audio
|
|
205
|
-
track. All four sub-fields are optional inside an optional object —
|
|
206
|
-
absent ⇒ the renderer uses the job-level default voice with
|
|
207
|
-
`card.narration` at 1× speed (back-compat with Phase 0 silent decks).
|
|
208
|
-
|
|
209
|
-
```jsonc
|
|
210
|
-
{
|
|
211
|
-
"kind": "body",
|
|
212
|
-
"caption": "短字幕",
|
|
213
|
-
"narration": "默认是 TTS 朗读的文本",
|
|
214
|
-
"voiceover": {
|
|
215
|
-
"enabled": true, // false → this card is silent in the mp4
|
|
216
|
-
"voiceId": "v-female-R2s4N9qJ", // overrides the job-level default voice
|
|
217
|
-
"text": "口播稿可以跟字幕不一样", // overrides narration for TTS only (visible caption unaffected)
|
|
218
|
-
"rate": 1.1 // [0.5, 2.0], default 1.0
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Validator caps: `voiceover.text` ≤ 500 chars; `rate ∈ [0.5, 2.0]`.
|
|
224
|
-
Render-time resolution precedence:
|
|
225
|
-
`voiceover.voiceId → card.voiceId → job-level default`.
|
|
226
|
-
|
|
227
|
-
> **Source of truth**: `backend/services/paper-slide/deck-validator.js` — caps live at lines 39–46 (QUOTE_TEXT_MAX, DATA_VALUE_MAX, LIST_ITEM_MAX_LEN, etc.). Read it if anything below seems ambiguous.
|
|
228
|
-
|
|
229
|
-
### Controlled `figureKeyword` list
|
|
230
|
-
|
|
231
|
-
Pick from these (mirrors `backend/services/paper-slide/prompt.js:31–61`). Anything else renders as a default arrow.
|
|
232
|
-
|
|
233
|
-
**Narrative scenes** (best for paper-slide / daisy-pastel / showa-catalog / photo-feature / atmospheric / hand-lettered):
|
|
234
|
-
`problem-framing` · `evidence-board` · `customer-pain` · `timeline-review` · `owner-deadline` · `risk-guardrail` · `cashflow-ledger` · `team-alignment` · `before-after` · `learning-loop` · `decision-fork` · `growth-system`
|
|
235
|
-
|
|
236
|
-
**Pose / mood** (paper-slide family only):
|
|
237
|
-
`climbing` · `thinking` · `stuck` · `running` · `celebrating`
|
|
238
|
-
|
|
239
|
-
**Single-symbol Lucide glyphs** (use these for all other themes — see Theme catalog for which themes are Lucide-only):
|
|
240
|
-
`briefcase` · `users` · `target` · `trending-up` · `dollar-sign` · `clock` · `message-circle` · `flame` · `lightbulb` · `chart-bar` · `rocket` · `bell`
|
|
241
|
-
|
|
242
|
-
## Theme catalog (33 themes)
|
|
243
|
-
|
|
244
|
-
All ids are valid for `deck.theme`. The "figure mode" column tells you which `figureKeyword` vocabulary to draw from — themes marked **Lucide** ignore narrative scene keywords.
|
|
245
|
-
|
|
246
|
-
| Theme id | Vibe (1 line) | Platform fit | Figure mode |
|
|
247
|
-
|---|---|---|---|
|
|
248
|
-
| `paper-slide` | 泛黄纸纹 + 衬线红字 + 手绘印章 | 抖音 / 视频号 / 小红书 | narrative |
|
|
249
|
-
| `editorial-mag` | 米白页面 + 衬线斜体 + 杂志感留白 | 知乎 / 公众号 / LinkedIn | Lucide |
|
|
250
|
-
| `bold-poster` | 左侧 accent 条 + 黑体超粗大字 + 红方块数字 | X / Threads / LinkedIn | Lucide |
|
|
251
|
-
| `notion-card` | 纯白底 + 暖灰墨 + 蓝色 accent + 页面图标 | 公众号 / 飞书 / 知识星球 | Lucide |
|
|
252
|
-
| `brutalist` | 黑白纯色 + 粗边框 + NO.NN 标签 + raw 排版 | X / Mastodon / 播客 | Lucide |
|
|
253
|
-
| `glass-dark` | 深色渐变 + 紫色发光 + 玻璃拟态 + 短视频感 | 抖音 / 视频号 / TikTok | Lucide |
|
|
254
|
-
| `broadsheet` | FT 三文鱼底 + 重磅衬线 + 首字下沉 + DATELINE | LinkedIn / 知乎 / 雪球 | Lucide |
|
|
255
|
-
| `blueprint` | 青底 + 白色网格 + 橙色尺寸标 + 工程图纸感 | 少数派 / 知乎 / GitHub | Lucide |
|
|
256
|
-
| `daisy-pastel` | 奶油底 + 手绘雏菊 + 星星 + 粉嫩可爱 | 小红书 / 微博 / 即刻 | narrative |
|
|
257
|
-
| `showa-catalog` | 70s city-pop + 彩虹斜条 + 太阳印章 | 小红书 / B 站 / 播客 | narrative |
|
|
258
|
-
| `photo-feature` | 全屏摄影 + 渐变压底 + 重磅衬线(needs `imageUrl`) | 小红书 / 知乎 / 微博 | narrative |
|
|
259
|
-
| `atmospheric` | 黑底 + 一束暖光 + 衬线粉红 italic + 深夜散文 | 微博 / 即刻 / 播客 | narrative |
|
|
260
|
-
| `art-mag` | 颗粒感色块 + 大字衬线 + 几何球 + 艺术画廊感 | 小红书 / 播客 / 即刻 | Lucide |
|
|
261
|
-
| `tome-noir` | 纯黑画布 + humanist sans + 60/40 非对称 + 暖光晕 | X / LinkedIn / 即刻 | Lucide |
|
|
262
|
-
| `flomo-mute` | #fafafa Muji 纸面 + IBM Plex + 苹方 + 绿色细线 | 小红书 / 即刻 / 微博 | Lucide |
|
|
263
|
-
| `substack-drop` | 米色衬线 + 首字下沉 + 章节分隔 + 阅读时间 chip | 公众号 / 知乎 / 少数派 | Lucide |
|
|
264
|
-
| `ink-scroll` | 米黄宣纸 + 楷体竖排 + 印章红 + 水墨边缘 | 小红书 / 微博 / B 站国创 | Lucide |
|
|
265
|
-
| `podcast-clip` | 封面渐变 + 圆形封面 + 大字幕 + 波形底栏 | 小宇宙 / 即刻 / 抖音 | Lucide |
|
|
266
|
-
| `ink-wash` | 宣纸 + 思源宋体 + 横排单字 + 笔触 + 朱印 + 留白 | 知乎长文 / 小红书 / 公众号 | Lucide |
|
|
267
|
-
| `morandi-calm` | 低饱和粉褐 / 灰绿 / 灰蓝色块 + 苹方 Light | 小红书 / 公众号 / 即刻 | Lucide |
|
|
268
|
-
| `douyin-data` | 纯黑底 + 抖音品红 + 湖蓝 + RGB 色差 + 大数字 | 抖音 / 视频号 / 小红书 | Lucide |
|
|
269
|
-
| `highlighter-note` | 奶油格纸 + 印刷体黑字 + Caveat 手写 + 黄色荧光笔 | 小红书 / B 站 / 知乎 | Lucide |
|
|
270
|
-
| `memphis-design` | 奶油 + 原色块 + 波浪线 + 锯齿 + 实心圆 + 1986 后现代 | 即刻 / 小红书 / 播客 | Lucide |
|
|
271
|
-
| `bauhaus-grid` | 奶油纸 + 红黄蓝原色 + 圆方三角 + 1923 包豪斯几何 | LinkedIn / 设计博客 / 知乎 | Lucide |
|
|
272
|
-
| `riso-print` | 荧光粉 + 钴蓝错版 + 网点 + 噪点 + Risograph | 独立开发 / 设计 newsletter | Lucide |
|
|
273
|
-
| `chrome-y2k` | 镀铬全息 + 像素栅 + 双径向辉光 + Y2K Bratz/iPod | 小红书 girlie / 抖音 Z / TikTok | Lucide |
|
|
274
|
-
| `botanical-press` | 羊皮纸 + Cormorant 斜体 + 拉丁学名 + 18 世纪植物标本 | 茶道 / 园艺 / 慢生活 | Lucide |
|
|
275
|
-
| `art-nouveau` | Mucha 曲线 + 烫金 + 装饰边框 + 1900 巴黎沙龙海报 | 艺术评论 / 美术志 / 插画家 | Lucide |
|
|
276
|
-
| `tabloid-print` | 红色 BREAKING + Anton 巨字 + 半调 + 双栏 + 小报头条 | 行业八卦 / 争议事件 / 独立专栏 | Lucide |
|
|
277
|
-
| `arcade-pixel` | CRT 扫描线 + 8-bit 像素 + RGB 色差 + 80s 街机标题屏 | 独立游戏 / 像素艺术 / 90s 复古 | Lucide |
|
|
278
|
-
| `hand-lettered` | 黑板绿底 + 粉笔白字 + Caveat 手写 + 咖啡馆菜单板 | 咖啡馆菜单 / 教师笔记 / 生活感 | narrative |
|
|
279
|
-
| `stamp-collector` | 齿孔 + 邮戳 + 面额 + 复古集邮册标本页 | 旅行游记 / 收藏品 / 怀旧专栏 | Lucide |
|
|
280
|
-
| `tropical-postcard` | 落日渐变 + 椰影 + 浪潮线 + 60s 旅游海报明信片 | 旅行游记 / 夏日散文 / 小红书 vlog | Lucide |
|
|
281
|
-
|
|
282
|
-
### Per-theme caption voice (quick guide)
|
|
283
|
-
|
|
284
|
-
The slicing prompt at `backend/services/paper-slide/prompt.js:94–237` defines per-theme caption tone. Highlights:
|
|
285
|
-
|
|
286
|
-
- `paper-slide`: 短、实、有冲击;反差/悬念/痛点钩子
|
|
287
|
-
- `bold-poster`: 数字 + 硬观点,≤12 字最佳
|
|
288
|
-
- `glass-dark`: 未来 / tech 感,断句多
|
|
289
|
-
- `broadsheet`: 财经评论调,开篇即判断 + 数字
|
|
290
|
-
- `ink-scroll` / `ink-wash`: 文言短句 / 现代东方意味
|
|
291
|
-
- `douyin-data`: "你不知道的…" / "%/倍/前 N" 爆款句式
|
|
292
|
-
- `morandi-calm`: 安静、克制、像周报作者写给自己
|
|
293
|
-
- `hand-lettered`: 温暖、随手、像便签上一句话
|
|
294
|
-
|
|
295
|
-
When picking caption vocabulary, match the theme's register. The user's own LLM (you) is doing the slicing — read the relevant block in `prompt.js` for the chosen theme if precision matters.
|
|
296
|
-
|
|
297
|
-
## Top schema invariants you MUST enforce
|
|
298
|
-
|
|
299
|
-
These are the most common failure modes. The validator surfaces a clean error, but it's faster to get them right the first time.
|
|
300
|
-
|
|
301
|
-
1. **First card is `kind: "title"`** — `deck-validator.js:198–200`. Reordering would push a body to slot 0; the renderer assumes title at slot 0 for cover-card composition.
|
|
302
|
-
2. **Card count 2–9 inclusive** — `deck-validator.js:195–197`. Target 5 cards; never exceed 8 (BrandKit may append an outro server-side, room for 1 more).
|
|
303
|
-
3. **`caption` must not end with punctuation** for body cards — implicit in V2 at `deck-validator.js:323` and prompt rule line 425. Trailing 。!?…)leaks into subtitles.
|
|
304
|
-
4. **`figureKeyword`** has no validator check on the keyword string itself (only that it's a string if present) — but **unknown keywords render as a default arrow**. Always pick from the controlled list.
|
|
305
|
-
5. **`outro` card invariant** — at most one, must be last. `deck-validator.js:204–208`. Multiple outros = wiring bug, mid-deck outro = bug.
|
|
306
|
-
|
|
307
|
-
## Quick start with curated templates
|
|
308
|
-
|
|
309
|
-
When the user is new to Slice and wants something to copy from, point them
|
|
310
|
-
at the curated gallery. Five hand-picked decks ship with the CLI and
|
|
311
|
-
match the most common content shapes (product launch, founder lesson,
|
|
312
|
-
data finding, incident review, quiet essay).
|
|
313
|
-
|
|
314
|
-
```bash
|
|
315
|
-
voxflow slice fork --list # browse the gallery
|
|
316
|
-
voxflow slice fork product-launch # copy the deck.json to cwd
|
|
317
|
-
voxflow slice preview product-launch-deck.json # iterate
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
The same gallery is browsable at `voxflow.studio/apps/slice/templates`.
|
|
321
|
-
|
|
322
|
-
## Hand-off
|
|
323
|
-
|
|
324
|
-
After writing `deck.json`, tell the user:
|
|
325
|
-
|
|
326
|
-
```
|
|
327
|
-
Wrote deck.json (<N> cards, theme: <theme-id>). Next:
|
|
328
|
-
|
|
329
|
-
voxflow slice preview deck.json # browser preview + per-card audition + 🎨 + render
|
|
330
|
-
voxflow slice render deck.json --output out.mp4 # one-shot mp4 from the terminal
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Do not run either command yourself unless the user asks.
|
|
334
|
-
|
|
335
|
-
Both commands work fully offline for the visual side. **Audio (per-card
|
|
336
|
-
TTS audition + render audio track) requires `voxflow login`** — 100 quota
|
|
337
|
-
per unique `(voice, text)` clip, then cached at
|
|
338
|
-
`~/.config/voxflow/stage-tts-cache/`. With no login, both commands fall
|
|
339
|
-
back silently to a Phase-0-style silent video; pass `--no-audio` to
|
|
340
|
-
`render` to suppress the audio pass entirely.
|
|
341
|
-
|
|
342
|
-
## Multi-turn editing loop (Claude Code / Cursor / native `Edit`)
|
|
343
|
-
|
|
344
|
-
When the user is iterating — "shorten card 2", "swap order", "different
|
|
345
|
-
voice for card 3", "hear card 1 again" — they are **NOT** asking for a
|
|
346
|
-
regen. Stay in this loop:
|
|
347
|
-
|
|
348
|
-
```bash
|
|
349
|
-
# Run once at the start of the session; auto-opens http://127.0.0.1:5180.
|
|
350
|
-
# The page hot-reloads on every save of deck.json (~50 ms fs watcher).
|
|
351
|
-
voxflow slice preview deck.json &
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Then for every follow-up:
|
|
355
|
-
|
|
356
|
-
| User intent | Your move | Cost |
|
|
357
|
-
|---|---|---|
|
|
358
|
-
| "card N is too long" | `Edit` `cards[N-1].caption` / `.narration` — page hot-reloads | 0 |
|
|
359
|
-
| "swap card 2 and 3" | `Edit` the `cards` array order | 0 |
|
|
360
|
-
| "different voice for card 3" | `Edit` `cards[2].voiceover.voiceId` — or tell user to paste a voiceId in the toolbar voice picker | 0 (Edit) |
|
|
361
|
-
| "make card 4 silent" | `Edit` `cards[3].voiceover = { "enabled": false }` | 0 |
|
|
362
|
-
| "口播说点不一样的" | `Edit` `cards[i].voiceover.text` so TTS reads override while caption stays | 0 |
|
|
363
|
-
| "I want to hear card 3" | Tell user to click ▶ on card 3 in the browser; the toolbar shows cache hit / quota cost / error | 100 (first time per clip), 0 (cached) |
|
|
364
|
-
| "regenerate the image on card 2" | `Edit` `cards[1].images[0].prompt` — user clicks 🎨 in stage to see the new variant | 200 (first time per prompt), 0 (cached) |
|
|
365
|
-
| "give me a custom panel on card 4" | `Edit` `cards[3].children` to swap `paper-figure` for `raw-html` (V2 LayoutTree only) | 0 |
|
|
366
|
-
| "render mp4" | Tell user: click **Render mp4 (local)** in the browser, OR `voxflow slice render deck.json` | TTS + image pass (cached if seen) + render |
|
|
367
|
-
|
|
368
|
-
### Loop rules
|
|
369
|
-
|
|
370
|
-
1. **Edit only the fields the user asked about.** Other cards must stay
|
|
371
|
-
byte-identical — the stage UI's diff highlight is the user's "what
|
|
372
|
-
changed" indicator. Touching extra fields breaks that signal.
|
|
373
|
-
2. **Never re-run `voxflow slice <article>` during iteration** — that
|
|
374
|
-
costs 200 quota AND overwrites every user edit with a fresh LLM draft.
|
|
375
|
-
3. **Re-validate after every save** by re-reading the file. If the user
|
|
376
|
-
says "page shows old content" or "red banner appeared", the JSON has a
|
|
377
|
-
syntax error (trailing comma, unbalanced quote) — open, fix, save.
|
|
378
|
-
4. **Don't restart the preview server.** One process handles the whole
|
|
379
|
-
session; restarting wipes snapshot history.
|
|
380
|
-
5. **Don't call `/api/audition` yourself.** It's user-driven via the ▶
|
|
381
|
-
button. Editing `cards[i].voiceover.voiceId` is enough — the next ▶
|
|
382
|
-
click picks up the new voice.
|
|
383
|
-
|
|
384
|
-
## Self-review checklist
|
|
385
|
-
|
|
386
|
-
Before declaring the slice done:
|
|
387
|
-
|
|
388
|
-
- [ ] `deck.json` parses as JSON (no trailing commas, no comments)
|
|
389
|
-
- [ ] `theme` is one of the 33 registered ids
|
|
390
|
-
- [ ] 5–8 cards (target 5; max 8 without outro, 9 with)
|
|
391
|
-
- [ ] First card is `title`, with `title` as a non-empty array of 1–2 short strings
|
|
392
|
-
- [ ] Every card has a non-empty `narration`
|
|
393
|
-
- [ ] No `caption` ends with 。!?…
|
|
394
|
-
- [ ] Every `figureKeyword` is in the controlled list
|
|
395
|
-
- [ ] No `caption` exceeds 16 zh chars; no `narration` exceeds 60 zh chars
|
|
396
|
-
- [ ] At most one of each rich kind (quote / data / list)
|
|
397
|
-
- [ ] If the theme is `photo-feature` or `atmospheric` and the user provided per-card images, `imageUrl` starts with `https://`
|
|
398
|
-
- [ ] No outro card unless the user explicitly asked for one
|
|
399
|
-
- [ ] No React, TSX, or CSS files were created
|
|
400
|
-
- [ ] If any card has a `voiceover` object, every key inside it (`enabled` / `voiceId` / `text` / `rate`) matches the schema (boolean / non-empty string ≤128 / string ≤500 / number in [0.5, 2.0])
|
|
401
|
-
|
|
402
|
-
## Anti-patterns
|
|
403
|
-
|
|
404
|
-
- ❌ Inventing themes ("modern-clean", "tech-blue"). Only the 33 registered ids work.
|
|
405
|
-
- ❌ Writing TSX / React / CSS — the renderer is fixed; you only fill in card fields.
|
|
406
|
-
- ❌ `narration` longer than 60 zh chars — gets clipped silently by the TTS-text-box constraint.
|
|
407
|
-
- ❌ `caption` ending in 。!?— these are subtitles, not prose.
|
|
408
|
-
- ❌ Two consecutive `figureKeyword: "thinking"` — visually repetitive.
|
|
409
|
-
- ❌ Adding a `kind: "outro"` card on your own — the server's BrandKit usually appends it.
|
|
410
|
-
- ❌ Calling `voxflow slice <file>` to do the work — this skill is the **offline** path.
|
|
411
|
-
- ❌ Fetching arbitrary image URLs into `imageUrl` — only public http(s) URLs the user explicitly provided.
|
|
412
|
-
|
|
413
|
-
## Examples
|
|
414
|
-
|
|
415
|
-
See `examples/article.md` for a representative input and `examples/expected-deck.json` for the matching output that validates cleanly. Run `node examples/validate.mjs` to verify any deck against the real backend validator.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# 为什么远程办公提升了生产力
|
|
2
|
-
|
|
3
|
-
我做了五年远程办公,从最初被全公司怀疑"在家肯定摸鱼",到现在团队整体效率比坐班时期高出 30%。这五年我换过三家公司,从 8 人创业团队到 200 人 SaaS,结论高度一致:远程办公不是没人监督就摸鱼,而是把"坐在工位"这个伪指标,换成了"交付的具体东西"。
|
|
4
|
-
|
|
5
|
-
第一个变化是会议变少了。坐班时同事一抬眼就能拉你聊五分钟,五个五分钟一天就废了。远程之后大家默认异步沟通,能发文档就不开会,能 Slack 就不视频。我现在每周开会时间从 15 小时降到 4 小时,省下来的 11 小时全部用在写代码、写文档、做深度思考。
|
|
6
|
-
|
|
7
|
-
第二个变化是通勤消失了。我以前在上海每天通勤来回 3 小时,地铁上根本没法做任何有质量的工作。现在这 3 小时变成 1.5 小时锻炼 + 1.5 小时早晨 deep work。整个人精神状态完全不一样,下午也不会累瘫。
|
|
8
|
-
|
|
9
|
-
第三个变化是产出可被衡量。坐班时管理者看的是"在不在工位"——这是个伪指标。远程必须看 PR、看文档、看 demo,结果反而是真正的好员工更容易被看到。我团队里两个内向的工程师,坐班三年一直没存在感,远程半年成了团队顶梁柱,因为他们的 PR 质量本来就最高。
|
|
10
|
-
|
|
11
|
-
最后一个变化是招聘半径变大了。我们最近一年招的 8 个人里,有 4 个不在公司所在城市。这意味着我们能挑全国最合适的人,不再被"必须搬来上海"这个门槛卡住。同样的薪资能买到的人才质量直接提升一档。
|
|
12
|
-
|
|
13
|
-
所以远程办公提升生产力,不是因为人变勤奋了,而是因为把"看起来在工作"换成了"实际交付了什么"。把伪指标换成真指标,效率自然会上来。这个转变对管理者要求其实更高——但回报也大得多。
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"header": "远程办公为什么效率反而更高",
|
|
3
|
-
"seriesTitle": "远程办公真相",
|
|
4
|
-
"seriesTagline": "把伪指标换成真指标,效率自然来",
|
|
5
|
-
"theme": "paper-slide",
|
|
6
|
-
"coverHookCaption": "远程不是摸鱼,是把伪指标换掉",
|
|
7
|
-
"coverFigureKeyword": "before-after",
|
|
8
|
-
"cards": [
|
|
9
|
-
{
|
|
10
|
-
"kind": "title",
|
|
11
|
-
"title": ["远程办公不是摸鱼", "是换掉一个伪指标"],
|
|
12
|
-
"narration": "做了五年远程办公,效率比坐班时高出三成,原因不是人变勤奋。"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"kind": "body",
|
|
16
|
-
"caption": "会议从十五小时砍到四小时",
|
|
17
|
-
"figureKeyword": "timeline-review",
|
|
18
|
-
"narration": "异步沟通让会议从每周十五小时降到四小时,省下的全是深度工作时间。"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"kind": "body",
|
|
22
|
-
"caption": "三小时通勤换成深度工作",
|
|
23
|
-
"figureKeyword": "before-after",
|
|
24
|
-
"narration": "通勤消失,每天多出三小时,一半锻炼一半早晨深度工作,状态完全不同。"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"kind": "body",
|
|
28
|
-
"caption": "看产出不再看工位",
|
|
29
|
-
"figureKeyword": "evidence-board",
|
|
30
|
-
"narration": "管理者从看在不在工位,变成看代码和文档,好员工反而更容易被看到。"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"kind": "body",
|
|
34
|
-
"caption": "招聘半径放大整整一档",
|
|
35
|
-
"figureKeyword": "growth-system",
|
|
36
|
-
"narration": "招聘不再被城市限制,能挑全国最合适的人,人才质量直接提升一档。"
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* validate.mjs — run a deck.json through the real backend validator.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* node examples/validate.mjs # validates expected-deck.json next to this file
|
|
7
|
-
* node examples/validate.mjs path/to/deck.json # validates a specific file
|
|
8
|
-
*
|
|
9
|
-
* Exit code: 0 = pass, 1 = fail (with the validator's error message on stderr).
|
|
10
|
-
*
|
|
11
|
-
* Reaches into ../../../backend/services/paper-slide/deck-validator.js so the
|
|
12
|
-
* skill's example is checked against the same code the production /api/slice/deck
|
|
13
|
-
* route runs. If the validator ever moves, update the path below.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { resolve, dirname } from 'node:path';
|
|
19
|
-
import { createRequire } from 'node:module';
|
|
20
|
-
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
22
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
|
|
24
|
-
// Hop from cli/skills/voxflow-slice/examples/ → repo root → backend services.
|
|
25
|
-
const validatorPath = resolve(here, '../../../../backend/services/paper-slide/deck-validator.js');
|
|
26
|
-
const { validatePaperSlideDeck } = require(validatorPath);
|
|
27
|
-
|
|
28
|
-
const deckPath = resolve(here, process.argv[2] || 'expected-deck.json');
|
|
29
|
-
let deck;
|
|
30
|
-
try {
|
|
31
|
-
deck = JSON.parse(readFileSync(deckPath, 'utf8'));
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error(`Could not read ${deckPath}: ${err.message}`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
validatePaperSlideDeck(deck);
|
|
39
|
-
console.log(`PASS ${deckPath}`);
|
|
40
|
-
console.log(` cards: ${deck.cards.length}, theme: ${deck.theme || '(default paper-slide)'}`);
|
|
41
|
-
process.exit(0);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.error(`FAIL ${deckPath}`);
|
|
44
|
-
console.error(` ${err.message}`);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|