@lalalic/markcut 2.2.8 → 2.2.9
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/package.json +2 -1
- package/skills/markcut/SKILL.md +0 -2
- package/skills/markcut/docs/markdown-descriptive.md +1 -1
- package/src/config.mjs +60 -63
- package/src/descriptive/resolve.ts +3 -2
- package/src/player/pipeline.mjs +64 -21
- package/src/render/cli.mjs +42 -69
- package/src/vision/cli.mjs +21 -45
- package/skills/markcut/docs/template.md +0 -212
- package/skills/markcut/docs/templates/courseware/TEMPLATE.md +0 -526
- package/skills/markcut/docs/templates/courseware/agents/reviewer.md +0 -84
- package/skills/markcut/docs/templates/courseware/prompts/fix.md +0 -30
- package/skills/markcut/docs/templates/courseware/prompts/outline.md +0 -58
- package/skills/markcut/docs/templates/courseware/prompts/scene.md +0 -70
- package/skills/markcut/docs/templates/deep-dive/TEMPLATE.md +0 -354
- package/skills/markcut/docs/templates/deep-dive/agents/researcher.md +0 -69
- package/skills/markcut/docs/templates/deep-dive/agents/reviewer.md +0 -108
- package/skills/markcut/docs/templates/deep-dive/prompts/fix.md +0 -28
- package/skills/markcut/docs/templates/deep-dive/prompts/outline.md +0 -102
- package/skills/markcut/docs/templates/deep-dive/prompts/script.md +0 -64
- package/skills/markcut/docs/templates/illustrated-book/TEMPLATE.md +0 -356
- package/skills/markcut/docs/templates/illustrated-book/agents/reviewer.md +0 -115
- package/skills/markcut/docs/templates/illustrated-book/prompts/fix.md +0 -29
- package/skills/markcut/docs/templates/illustrated-book/prompts/illustration.md +0 -35
- package/skills/markcut/docs/templates/illustrated-book/prompts/story.md +0 -69
- package/skills/markcut/docs/templates/short-film/TEMPLATE.md +0 -385
- package/skills/markcut/docs/templates/short-film/agents/reviewer.md +0 -115
- package/skills/markcut/docs/templates/short-film/prompts/fix.md +0 -29
- package/skills/markcut/docs/templates/short-film/prompts/screenplay.md +0 -101
- package/skills/markcut/docs/templates/short-film/prompts/storyboard.md +0 -54
- package/skills/markcut/docs/templates/short-video/TEMPLATE.md +0 -301
- package/skills/markcut/docs/templates/short-video/agents/reviewer.md +0 -94
- package/skills/markcut/docs/templates/short-video/prompts/fix.md +0 -27
- package/skills/markcut/docs/templates/short-video/prompts/script.md +0 -65
- package/skills/markcut/docs/templates/vlog/TEMPLATE.md +0 -326
- package/skills/markcut/docs/templates/vlog/agents/reviewer.md +0 -89
- package/skills/markcut/docs/templates/vlog/agents/story-writer.md +0 -100
- package/skills/markcut/docs/templates/vlog/prompts/bgm-select.md +0 -38
- package/skills/markcut/docs/templates/vlog/prompts/group-clips.md +0 -93
- package/skills/markcut/docs/templates/vlog/prompts/local-context.md +0 -59
- package/skills/markcut/docs/templates/vlog/prompts/outline.md +0 -56
- package/skills/markcut/docs/templates/vlog/prompts/review-fix.md +0 -27
- package/skills/markcut/docs/templates/vlog/prompts/storyboard.md +0 -41
- package/src/render/cli.test.ts +0 -13
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: courseware
|
|
3
|
-
description: Turn a topic or teaching material into a professional slide-based course video with TTS narration, bullet-reveal slides, and a reviewer quality gate.
|
|
4
|
-
when-to-use: lessons, lectures, training, tutorials, explainers — 1-5 minute educational videos built from slides + narration
|
|
5
|
-
test:
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Courseware Template
|
|
9
|
-
|
|
10
|
-
Follow this file top to bottom. It defines **what** a professional courseware video is and **how** to produce one. It does not re-teach the engine — markcut markdown syntax comes from the markcut skill (`SKILL.md` → `docs/markdown-descriptive.md`). Read those first if you have not.
|
|
11
|
-
|
|
12
|
-
Layout of this template package:
|
|
13
|
-
|
|
14
|
-
| Path | Runs in | Purpose |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| `TEMPLATE.md` | your context | everything: inputs, structure, rules, workflow, quality gate |
|
|
17
|
-
| `prompts/*.md` | **your own (orchestrator) context** | fill-in prompt templates you execute yourself |
|
|
18
|
-
| `agents/*.md` | **separate agent session** | subagent definitions (system prompt + task template) |
|
|
19
|
-
|
|
20
|
-
## 0. Prerequisites
|
|
21
|
-
|
|
22
|
-
- markcut skill loaded (engine contract: `SKILL.md` + `docs/markdown-descriptive.md`)
|
|
23
|
-
- `npx @lalalic/markcut` runnable
|
|
24
|
-
- For the reviewer gate: `ffmpeg`/`ffprobe`, an STT CLI (e.g. whisper), and image-understanding capability
|
|
25
|
-
|
|
26
|
-
## 1. Inputs — collect before starting
|
|
27
|
-
|
|
28
|
-
| Input | Required | Default | Notes |
|
|
29
|
-
|---|---|---|---|
|
|
30
|
-
| Topic / source material | **yes** | — | plain topic, outline, doc, or article |
|
|
31
|
-
| Audience level | no | beginner | beginner / intermediate / expert |
|
|
32
|
-
| Language(s) | no | en | extra languages become variants (see §3) |
|
|
33
|
-
| Target duration | no | 2 min | 1–5 min |
|
|
34
|
-
| Accent color | no | `#61dafb` | theme knob, see §4 |
|
|
35
|
-
|
|
36
|
-
**Rule:** if the topic/material is missing or ambiguous, ask the user. Never invent course content beyond the given material plus well-established knowledge of the topic. Never invent facts, numbers, or citations.
|
|
37
|
-
|
|
38
|
-
## 2. Scene grammar — mandatory structure
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
# video ← root: width:1920 height:1080 fps:30 layout:series
|
|
42
|
-
│ subtitle:{fontSize:"20px"}
|
|
43
|
-
├── ## Hook ← 3–5s attention grabber, fixed duration
|
|
44
|
-
├── ## Slides ← layout:transitionSeries transition:fade(0.5)
|
|
45
|
-
│ ├── ### TitleSlide ← transitionSeries, bullet-reveal (1 beat)
|
|
46
|
-
│ ├── ### <Concept> × 3–6 ← transitionSeries, bullet-reveal (1 beat per bullet)
|
|
47
|
-
│ └── ### Summary ← transitionSeries, bullet-reveal (1 beat per takeaway)
|
|
48
|
-
└── ## Thanks ← fixed duration:6, no script
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Scene rules:
|
|
52
|
-
|
|
53
|
-
- **Hook**: `- image duration:3 prompt:"<vivid visual, cinematic>"` (or `video`). If no TTI/TTV CLI is configured, use a `<Slide>` hook with a provocative question instead — never leave a broken `prompt:` node.
|
|
54
|
-
- **Every scene inside Slides (TitleSlide, concepts, Summary)** uses the **bullet-reveal pattern** — this is mandatory, not optional:
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
### <SceneName>
|
|
58
|
-
layout:transitionSeries transition:fade(0.5)
|
|
59
|
-
- component id:<shortId> isBackground:true jsx:"<Slide current={current}>{source}</Slide>"
|
|
60
|
-
~~~md source
|
|
61
|
-
## Title
|
|
62
|
-
- 🎯 **Key point 1** — elaboration
|
|
63
|
-
- 🎯 **Key point 2** — elaboration
|
|
64
|
-
- 🎯 **Key point 3** — elaboration
|
|
65
|
-
~~~
|
|
66
|
-
- script "Paragraph expanding bullet 1..." on:(start, <shortId>.current=1)
|
|
67
|
-
- script "Paragraph expanding bullet 2..." on:(start, <shortId>.current=2)
|
|
68
|
-
- script "Paragraph expanding bullet 3..." on:(start, <shortId>.current=3)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
- `id` must be unique per scene (e.g. `t`, `c1`, `c2`, `s`). The `id` connects the component to the script events.
|
|
72
|
-
- `current={current}` on the Slide component reads the event-driven bullet index and highlights the matching `<li>`.
|
|
73
|
-
- Each `- script "..."` node is one beat in the `transitionSeries`, so it gets a fade transition. The TTS duration of that script determines the segment length.
|
|
74
|
-
- The component has `isBackground:true` so its `<Slide>` spans all beats of the scene.
|
|
75
|
-
- **Beat count = bullet count**: TitleSlide gets 1 beat (just the welcome line), concept scenes get 1 beat per bullet, Summary gets 1 beat per row in the comparison table or per major takeaway.
|
|
76
|
-
|
|
77
|
-
- **Thanks**: fixed-visual, `duration:6`, no script.
|
|
78
|
-
|
|
79
|
-
- **Mermaid diagrams inside slides**: Fenced mermaid code blocks (` ```mermaid `) inside `~~~md source` are rendered by the `<Slide>` component (see §4). Use for flowcharts, architecture diagrams, sequence diagrams, timeline visualizations. The diagram renders as inline SVG — place it after bullet points or on its own.
|
|
80
|
-
|
|
81
|
-
- **Duration is TTS-driven**: never set explicit `duration` on scenes that have a script. Only fixed-visual scenes (Hook, Thanks) get `duration:`.
|
|
82
|
-
|
|
83
|
-
### Production polish — optional enhancements
|
|
84
|
-
|
|
85
|
-
Once the basic structure works, add these to make the video more professional.
|
|
86
|
-
|
|
87
|
-
#### A. Bullet-level media (image/video/component per bullet)
|
|
88
|
-
|
|
89
|
-
Each bullet can have its own visual accompaniment that appears only during that beat. Use `#### Beat` sub-scenes inside the concept scene to group the media and script into one transition segment:
|
|
90
|
-
|
|
91
|
-
```markdown
|
|
92
|
-
### Commits
|
|
93
|
-
layout:transitionSeries transition:fade(0.5)
|
|
94
|
-
- component id:c isBackground:true jsx:"<Slide current={current}>{source}</Slide>"
|
|
95
|
-
~~~md source
|
|
96
|
-
## Commits
|
|
97
|
-
- 📸 **Snapshot** — Every commit records changes
|
|
98
|
-
- 🏷️ **Unique hash** — Each has an ID like a1b2c3d
|
|
99
|
-
- 🔗 **Real example** — GitHub commit history for a PR
|
|
100
|
-
~~~
|
|
101
|
-
|
|
102
|
-
#### B1-Snapshot
|
|
103
|
-
layout:parallel
|
|
104
|
-
- image src:snapshot.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
|
|
105
|
-
- script "A commit is like a photograph of your project at a moment in time..." start:0.5 on:(start, c.current=1)
|
|
106
|
-
|
|
107
|
-
#### B2-Hash
|
|
108
|
-
layout:parallel
|
|
109
|
-
- image src:unique-id.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
|
|
110
|
-
- script "Every commit gets a unique hash, like a1b2c3d, so you can always find it later..." start:0.5 on:(start, c.current=2)
|
|
111
|
-
|
|
112
|
-
#### B3-Example
|
|
113
|
-
layout:parallel
|
|
114
|
-
- image src:pr-history.png style:position:absolute;right:60px;top:50%;width:35%;border-radius:12px;transform:translateY(-50%)
|
|
115
|
-
- script "For example, when reviewing a pull request on GitHub, each commit tells a story..." start:0.5 on:(start, c.current=3)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Rules for bullet-level media:
|
|
119
|
-
- Position the media in the **right third** of the slide (`right:60px;width:35%`) so it doesn't overlap the left-aligned text.
|
|
120
|
-
- Use `border-radius:12px` for images, `border-radius:50%` for circular narrator avatars.
|
|
121
|
-
- The media node and script node are siblings in a `layout:parallel` sub-scene so they play together as one segment.
|
|
122
|
-
- Each beat node name should briefly describe the visual (e.g. `#### B1-Snapshot`).
|
|
123
|
-
- For **video** clips per bullet: use `- video src:...` instead of `- image src:...`. Same positioning.
|
|
124
|
-
- For **component** per bullet: use `- component jsx:"<MyBulletVisual />"` with its own styling.
|
|
125
|
-
|
|
126
|
-
#### B. Transition-audio timing
|
|
127
|
-
|
|
128
|
-
In a `transitionSeries`, adjacent segments overlap by the transition duration (default 0.5s). Without adjustment, the next paragraph's TTS audio starts playing during the fade — creating cacophony.
|
|
129
|
-
|
|
130
|
-
**Fix: add `start:<T>` to every script node in a transitionSeries, where T = your transition time.**
|
|
131
|
-
|
|
132
|
-
```markdown
|
|
133
|
-
- script "Paragraph..." start:0.5 on:(start, c.current=1)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
This delays both the TTS audio AND the bullet-highlight event until after the transition completes. The transition time value must match the `transition:fade(T)` parameter.
|
|
137
|
-
|
|
138
|
-
> **If using multiple transitionSeries (nested)**: segment overlap compounds. Each script still gets `start:T` relative to its own segment. The engine handles the compound timing.
|
|
139
|
-
|
|
140
|
-
#### C. Persistent logo overlay
|
|
141
|
-
|
|
142
|
-
Add a logo that appears throughout the entire video by placing an `- image isBackground:true` at the root level:
|
|
143
|
-
|
|
144
|
-
```markdown
|
|
145
|
-
# video
|
|
146
|
-
width:1920 height:1080 fps:30 layout:series
|
|
147
|
-
- image isBackground:true src:logo.png style:position:absolute;bottom:24px;right:24px;width:60px;opacity:0.7
|
|
148
|
-
|
|
149
|
-
## Hook ...
|
|
150
|
-
## Slides ...
|
|
151
|
-
## Thanks ...
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
The `isBackground:true` flag wraps the image in `<Loop>` so it plays for the full video duration. Position it in a corner so it doesn't cover slide content.
|
|
155
|
-
|
|
156
|
-
Variations:
|
|
157
|
-
- **Bottom-left**: `bottom:24px;left:24px`
|
|
158
|
-
- **Transparent watermark**: `opacity:0.3;width:160px`
|
|
159
|
-
- **Animated logo**: use `- video loop isBackground:true src:logo-animated.mp4`
|
|
160
|
-
- **Component logo**: use `- component isBackground:true jsx:"<Logo />"` and define `<Logo>` in the imports block for custom styling.
|
|
161
|
-
|
|
162
|
-
#### D. Narrator overlay (picture-in-picture)
|
|
163
|
-
|
|
164
|
-
Add a narrator's face or avatar as a persistent overlay in a corner. Two approaches:
|
|
165
|
-
|
|
166
|
-
**Static image (circular crop):**
|
|
167
|
-
```markdown
|
|
168
|
-
- image isBackground:true src:narrator.png style:position:absolute;bottom:40px;left:40px;width:100px;height:100px;border-radius:50px;border:3px solid #61dafb;object-fit:cover
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**Video loop (animated narrator):**
|
|
172
|
-
```markdown
|
|
173
|
-
- video loop isBackground:true src:narrator.mp4 style:position:absolute;bottom:40px;right:40px;width:140px;height:140px;border-radius:70px;object-fit:cover;border:3px solid rgba(255,255,255,.3)
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**With name label (using component):**
|
|
177
|
-
```markdown
|
|
178
|
-
- component isBackground:true jsx:"<NarratorBox src='narrator.png' name='Dr. Smith' />"
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
The `NarratorBox` component is available in the imports block (see §4) and adds a name label + glow effect.
|
|
182
|
-
|
|
183
|
-
**Placement rules:**
|
|
184
|
-
- Narrator overlay goes in the **opposite corner** from the logo. If logo is bottom-right, narrator goes bottom-left.
|
|
185
|
-
- The overlay should be small enough (≤140px diameter) to not distract from slide content.
|
|
186
|
-
- For video narrators, keep the B-roll simple and well-lit (talking head style).
|
|
187
|
-
|
|
188
|
-
#### E. Effects (entrance animations)
|
|
189
|
-
|
|
190
|
-
Apply CSS keyframe animations to images and components for entrance effects:
|
|
191
|
-
```markdown
|
|
192
|
-
- image src:chart.png effect:zoomIn duration:1
|
|
193
|
-
- component jsx:"<Counter />" effect:fadeIn duration:2
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
See markcut docs for available effect types: `fadeIn`, `zoomIn`, `bounceIn`, `slideInLeft`, `slideInRight`, `flipIn`, or custom keyframes.
|
|
197
|
-
|
|
198
|
-
#### F. TTS expression control
|
|
199
|
-
|
|
200
|
-
Guide TTS pronunciation for more natural speech using SSML-style cues in the script text:
|
|
201
|
-
- **Pause**: use `—` (em dash) or `...` for natural breath pauses
|
|
202
|
-
- **Rate**: set `tts.rate:+10%` on the root config line for faster pacing, or `tts:{"--rate +10%"}` per scene
|
|
203
|
-
- **Phonetic spelling**: for jargon ("Snyk" = "snik", "Grafana" = "gruh-fah-nuh"), add parenthetical pronunciation on first use: `Snyk (pronounced "snik")`
|
|
204
|
-
- **Language mix**: for bilingual content, switch tts voice per language using the `# <lang>` variant block
|
|
205
|
-
|
|
206
|
-
## 3. Authoring rules — the professional bar
|
|
207
|
-
|
|
208
|
-
Slide content:
|
|
209
|
-
|
|
210
|
-
- One idea per slide. ≤ 6 bullets. Bullet shape: `🔹 **bold key phrase** — short elaboration`.
|
|
211
|
-
- Emoji bullet prefixes for scannability (🤖 📊 🔄 🏷️ 💡 ⚡ …), consistent within the course.
|
|
212
|
-
- Use a markdown **table** whenever content is comparative (applications, methods, milestones).
|
|
213
|
-
- Use a `>` quote block for the one memorable takeaway line.
|
|
214
|
-
- Code blocks only when actually teaching code; keep ≤ 10 lines per slide.
|
|
215
|
-
|
|
216
|
-
Narration script:
|
|
217
|
-
|
|
218
|
-
- **One paragraph per bullet.** Never merge bullets into a single monolithic script. Each bullet gets its own `- script "..."` node with `on:(start, id.current=N)`.
|
|
219
|
-
- Paragraph length ≈ **15–30 words** per beat (≈6–12 seconds at 2.5 w/s). Total words = target minutes × 150.
|
|
220
|
-
- Each paragraph **expands** that bullet — never reads it verbatim. The paragraph plus all sibling paragraphs must touch every bullet on the slide.
|
|
221
|
-
- The concrete example gets its own bullet and its own script paragraph. Never bury the example in a generic paragraph.
|
|
222
|
-
- Bullet ordering: concepts first (what/why), example last (concrete reinforcement). The example is the final beat before advancing to the next scene.
|
|
223
|
-
- Summary: each row in the comparison table gets one beat. The final beat closes the course ("Thank you for watching…").
|
|
224
|
-
- **Transition-audio timing**: in a `transitionSeries`, add `start:<transitionTime>` to each script node to prevent TTS audio from playing during fade transitions (see §2.b).
|
|
225
|
-
|
|
226
|
-
Mermaid diagrams:
|
|
227
|
-
|
|
228
|
-
- Use ` ```mermaid ` fenced code blocks inside `~~~md source` for charts, flowcharts, architecture diagrams, sequence diagrams, and timelines.
|
|
229
|
-
- Place mermaid blocks after the bullet list, separated by a blank line.
|
|
230
|
-
- The Slide component (see §4) renders mermaid code blocks as inline SVG. Ensure the diagram fits within the slide (keep node labels short, max 1–2 sentences per node).
|
|
231
|
-
- For complex diagrams that take time to render, pre-render them as SVG files via `mmdc` (mermaid-cli) or your TTI CLI, and reference as `- image src:...` instead — this is more reliable for final render.
|
|
232
|
-
|
|
233
|
-
Multi-language (variants):
|
|
234
|
-
|
|
235
|
-
- For each extra language: a `<lang>:"..."` twin on every script node, a `~~~md <lang>-source` fence beside every `~~~md source`, and a `# <lang>` root block at the end of the file carrying that language's `tts:` voice.
|
|
236
|
-
- See the worked example in §7 for the exact shape (`zh` variant).
|
|
237
|
-
|
|
238
|
-
## 4. Components & styles — canonical, copy verbatim
|
|
239
|
-
|
|
240
|
-
Copy both blocks into the generated `course.md` unchanged (except the theme knobs below).
|
|
241
|
-
|
|
242
|
-
~~~~text
|
|
243
|
-
~~~js imports
|
|
244
|
-
import ReactMarkdown from 'react-markdown';
|
|
245
|
-
import remarkGfm from 'remark-gfm'
|
|
246
|
-
import mermaid from 'mermaid'
|
|
247
|
-
import React from 'react'
|
|
248
|
-
import { delayRender, continueRender } from 'remotion'
|
|
249
|
-
|
|
250
|
-
// Initialize mermaid once. Theme matches the slide deck's dark scheme.
|
|
251
|
-
mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose' })
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Renders a mermaid diagram as inline SVG.
|
|
255
|
-
* Uses Remotion's delayRender/continueRender so the diagram is guaranteed
|
|
256
|
-
* to be ready before the frame is captured.
|
|
257
|
-
*/
|
|
258
|
-
export function Mermaid({ source }) {
|
|
259
|
-
const ref = React.useRef(null)
|
|
260
|
-
|
|
261
|
-
React.useEffect(() => {
|
|
262
|
-
if (!source || !ref.current) return
|
|
263
|
-
const handle = delayRender('Mermaid rendering')
|
|
264
|
-
mermaid.render('mmd-' + Math.random().toString(36).slice(2), source)
|
|
265
|
-
.then((result) => {
|
|
266
|
-
if (ref.current) ref.current.innerHTML = result.svg
|
|
267
|
-
continueRender(handle)
|
|
268
|
-
})
|
|
269
|
-
.catch((err) => {
|
|
270
|
-
console.error('Mermaid error:', err)
|
|
271
|
-
continueRender(handle)
|
|
272
|
-
})
|
|
273
|
-
}, [source])
|
|
274
|
-
|
|
275
|
-
return <div ref={ref} style={{ width: '100%', maxWidth: 960, margin: '20px auto' }} />
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Courseware slide component.
|
|
280
|
-
* - Renders markdown via react-markdown
|
|
281
|
-
* - Highlights the Nth bullet when `current=N`
|
|
282
|
-
* - Renders mermaid code blocks ( ```mermaid ...``` ) as inline diagrams
|
|
283
|
-
*/
|
|
284
|
-
/**
|
|
285
|
-
* Picture-in-picture narrator overlay with name label.
|
|
286
|
-
* Place as isBackground:true at root level.
|
|
287
|
-
*/
|
|
288
|
-
export function NarratorBox({ src, name = '', size = 100 }) {
|
|
289
|
-
return (
|
|
290
|
-
<div style={{
|
|
291
|
-
position: 'absolute',
|
|
292
|
-
bottom: 40,
|
|
293
|
-
left: 40,
|
|
294
|
-
display: 'flex',
|
|
295
|
-
flexDirection: 'column',
|
|
296
|
-
alignItems: 'center',
|
|
297
|
-
gap: 6,
|
|
298
|
-
}}>
|
|
299
|
-
<div style={{
|
|
300
|
-
width: size,
|
|
301
|
-
height: size,
|
|
302
|
-
borderRadius: '50%',
|
|
303
|
-
overflow: 'hidden',
|
|
304
|
-
border: '3px solid #61dafb',
|
|
305
|
-
boxShadow: '0 0 20px rgba(97, 218, 251, 0.3)',
|
|
306
|
-
}}>
|
|
307
|
-
<img
|
|
308
|
-
src={src}
|
|
309
|
-
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
|
310
|
-
/>
|
|
311
|
-
</div>
|
|
312
|
-
{name && (
|
|
313
|
-
<span style={{
|
|
314
|
-
fontSize: 14,
|
|
315
|
-
color: '#a8dadc',
|
|
316
|
-
textShadow: '0 2px 8px rgba(0,0,0,.6)',
|
|
317
|
-
whiteSpace: 'nowrap',
|
|
318
|
-
}}>{name}</span>
|
|
319
|
-
)}
|
|
320
|
-
</div>
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Two-column layout: slide text on left, media on right.
|
|
326
|
-
* Use inside a parallel layout for per-bullet media (see §2.b).
|
|
327
|
-
*/
|
|
328
|
-
export function SplitSlide({ source, mediaSrc, current = 0, mediaPosition = 'right' }) {
|
|
329
|
-
const mediaContent = mediaSrc && (
|
|
330
|
-
<img src={mediaSrc} style={{
|
|
331
|
-
maxWidth: '100%', maxHeight: '100%', objectFit: 'contain',
|
|
332
|
-
borderRadius: 12, boxShadow: '0 4px 20px rgba(0,0,0,.4)',
|
|
333
|
-
}} />
|
|
334
|
-
)
|
|
335
|
-
let idx = 1
|
|
336
|
-
return (
|
|
337
|
-
<div style={{
|
|
338
|
-
display: 'flex', flexDirection: mediaPosition === 'right' ? 'row' : 'row-reverse',
|
|
339
|
-
width: '100%', height: '100%', padding: 40, boxSizing: 'border-box', gap: 32,
|
|
340
|
-
}}>
|
|
341
|
-
<div style={{
|
|
342
|
-
flex: 1, display: 'flex', flexDirection: 'column',
|
|
343
|
-
justifyContent: 'center', minWidth: 0,
|
|
344
|
-
}}>
|
|
345
|
-
<div className="slide" style={{ padding: 0, margin: 0, width: '100%' }}>
|
|
346
|
-
<ReactMarkdown remarkPlugins={[remarkGfm]}
|
|
347
|
-
components={{
|
|
348
|
-
li: ({ children }) => {
|
|
349
|
-
const highlight = idx === current; idx++
|
|
350
|
-
return <li className={highlight ? 'highlight' : ''}>{children}</li>
|
|
351
|
-
},
|
|
352
|
-
pre: ({ children }) => {
|
|
353
|
-
const code = React.Children.toArray(children)[0]
|
|
354
|
-
if (code?.props?.className === 'language-mermaid') {
|
|
355
|
-
return <Mermaid source={String(code.props.children)} />
|
|
356
|
-
}
|
|
357
|
-
return <pre>{children}</pre>
|
|
358
|
-
},
|
|
359
|
-
}}>{source}</ReactMarkdown>
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
<div style={{
|
|
363
|
-
width: '40%', flexShrink: 0,
|
|
364
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
365
|
-
}}>
|
|
366
|
-
{mediaContent}
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export function Slide({ current = 0, children }) {
|
|
373
|
-
let idx = 1
|
|
374
|
-
return (
|
|
375
|
-
<div className="slide">
|
|
376
|
-
<ReactMarkdown remarkPlugins={[remarkGfm]}
|
|
377
|
-
components={{
|
|
378
|
-
li: ({ children }) => {
|
|
379
|
-
const highlight = idx === current; idx++
|
|
380
|
-
return <li className={highlight ? 'highlight' : ''}>{children}</li>
|
|
381
|
-
},
|
|
382
|
-
// Fenced code blocks — detect mermaid, render as diagram
|
|
383
|
-
pre: ({ children }) => {
|
|
384
|
-
const code = React.Children.toArray(children)[0]
|
|
385
|
-
if (code?.props?.className === 'language-mermaid') {
|
|
386
|
-
return <Mermaid source={String(code.props.children)} />
|
|
387
|
-
}
|
|
388
|
-
return <pre>{children}</pre>
|
|
389
|
-
},
|
|
390
|
-
}}>{children}</ReactMarkdown>
|
|
391
|
-
</div>
|
|
392
|
-
)
|
|
393
|
-
}
|
|
394
|
-
~~~
|
|
395
|
-
~~~~
|
|
396
|
-
|
|
397
|
-
~~~~text
|
|
398
|
-
~~~css stylesheet
|
|
399
|
-
/* Container sizing for a perfect 16:9 widescreen presentation slide */
|
|
400
|
-
.slide {
|
|
401
|
-
color: #f5f5f7;
|
|
402
|
-
padding: 40px;
|
|
403
|
-
display: flex;
|
|
404
|
-
flex-direction: column;
|
|
405
|
-
justify-content: center;
|
|
406
|
-
align-items: flex-start;
|
|
407
|
-
font-family: 'Helvetica Neue', Arial, sans-serif;
|
|
408
|
-
font-size: 30px;
|
|
409
|
-
|
|
410
|
-
/* Typography rules optimized for visibility from a distance */
|
|
411
|
-
h1 {
|
|
412
|
-
font-size: 2.8em;
|
|
413
|
-
color: #61dafb;
|
|
414
|
-
margin-top: 0;
|
|
415
|
-
margin-bottom: 20px;
|
|
416
|
-
line-height: 1.2;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
h2 {
|
|
420
|
-
font-size: 2em;
|
|
421
|
-
color: #a8dadc;
|
|
422
|
-
margin-top: 0;
|
|
423
|
-
margin-bottom: 15px;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
p {
|
|
427
|
-
font-size: 1.3em;
|
|
428
|
-
line-height: 1.6;
|
|
429
|
-
color: #e0e0e6;
|
|
430
|
-
margin-bottom: 15px;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/* List styling specific to presentation bullet points */
|
|
434
|
-
ul, ol {
|
|
435
|
-
margin-left: 25px;
|
|
436
|
-
margin-bottom: 20px;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
li {
|
|
440
|
-
font-size: 1.3em;
|
|
441
|
-
line-height: 1.8;
|
|
442
|
-
margin-bottom: 10px;
|
|
443
|
-
color: #e0e0e6;
|
|
444
|
-
list-style-type: none; /* Remove default bullets for custom styling */
|
|
445
|
-
|
|
446
|
-
/* Highlighted list item for event-driven bullet reveal */
|
|
447
|
-
&.highlight {
|
|
448
|
-
color: red;
|
|
449
|
-
font-weight: 700;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/* Code block handling inside slides */
|
|
454
|
-
pre {
|
|
455
|
-
background-color: #2d2d34;
|
|
456
|
-
padding: 15px;
|
|
457
|
-
border-radius: 6px;
|
|
458
|
-
width: 100%;
|
|
459
|
-
box-sizing: border-box;
|
|
460
|
-
overflow-x: auto;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
code {
|
|
464
|
-
font-family: 'Courier New', Courier, monospace;
|
|
465
|
-
font-size: 1.1em;
|
|
466
|
-
color: #ffb703;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
table {
|
|
470
|
-
width: 100%;
|
|
471
|
-
border-collapse: collapse;
|
|
472
|
-
margin-bottom: 20px;
|
|
473
|
-
|
|
474
|
-
th, td {
|
|
475
|
-
border: 1px solid #444;
|
|
476
|
-
padding: 10px;
|
|
477
|
-
text-align: left;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
~~~
|
|
482
|
-
~~~~
|
|
483
|
-
|
|
484
|
-
Theme knobs — change **values only**, never the structure:
|
|
485
|
-
|
|
486
|
-
| Knob | Selector | Default |
|
|
487
|
-
|---|---|---|
|
|
488
|
-
| Accent (h1) | `.slide h1 { color }` | `#61dafb` |
|
|
489
|
-
| Secondary (h2) | `.slide h2 { color }` | `#a8dadc` |
|
|
490
|
-
| Reveal highlight | `.slide li.highlight { color }` | `red` |
|
|
491
|
-
| Deck background | `.slide { background }` | transparent |
|
|
492
|
-
| Font | `.slide { font-family }` | Helvetica Neue |
|
|
493
|
-
|
|
494
|
-
## 5. Workflow
|
|
495
|
-
|
|
496
|
-
Prompts in `prompts/` are fill-in templates: replace every `{placeholder}`, then execute the prompt **in your own context**. `agents/` definitions run in a **separate session** (e.g. a fresh-context subagent).
|
|
497
|
-
|
|
498
|
-
1. **Outline** — fill `prompts/outline.md` → course outline. **Present the outline to the user and get confirmation** before continuing.
|
|
499
|
-
2. **Scenes** — for each outline section, fill `prompts/scene.md` → slide source + narration script (all languages).
|
|
500
|
-
3. **Assemble** — write `course.md`: §2 grammar, §3 rules, §4 blocks verbatim, root config line with the user's width/height/fps/voice.
|
|
501
|
-
- **Basic**: standard bullet-reveal pattern (one scene, one Slide component, N script nodes per scene).
|
|
502
|
-
- **With per-bullet media**: use `#### Beat` sub-scenes in `transitionSeries` for each bullet (see §2). Each beat is `layout:parallel` containing the image + script. Add `start:0.5` to each script node.
|
|
503
|
-
- **Overlays**: add persistent logo/narrator as `- image isBackground:true` at the root level.
|
|
504
|
-
4. **Render** — `npx @lalalic/markcut render course.md`. On engine errors: fix and re-render, max 3 attempts per error, then ask the user.
|
|
505
|
-
5. **Review (quality gate)** — run `agents/reviewer.md` in a fresh separate session. Give it absolute paths to: `course.md`, the rendered MP4, this `TEMPLATE.md`, plus target duration and language(s). It returns `{verdict, findings[]}` and never edits anything.
|
|
506
|
-
6. **Fix loop** — on FAIL: fill `prompts/fix.md` with the findings, apply the edits yourself, go back to step 4. Max **3** review iterations, then escalate to the user with the open findings.
|
|
507
|
-
|
|
508
|
-
## 6. Quality gate — exit criteria
|
|
509
|
-
|
|
510
|
-
Done only when ALL hold:
|
|
511
|
-
|
|
512
|
-
- [ ] reviewer verdict = `PASS` (zero blocker/major findings)
|
|
513
|
-
- [ ] total duration within ±15% of target
|
|
514
|
-
- [ ] structure matches §2 (hook, title, 3–6 concepts, summary, thanks)
|
|
515
|
-
- [ ] no blank/black frames at scene boundaries; slides legible at target resolution
|
|
516
|
-
- [ ] STT transcript of rendered audio matches scripts (≥90% content match)
|
|
517
|
-
- [ ] each bullet has a narration beat — beat count = bullet count in every concept scene
|
|
518
|
-
- [ ] ≥80% of slides include a visual element beyond text-only bullets (chart, image, diagram, mermaid)
|
|
519
|
-
- [ ] overlays (logo, narrator) positioned without obstructing slide content
|
|
520
|
-
- [ ] no TTS audio plays during transitions (checked via `start:` offset on transitionSeries scripts)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
## 7. Reference — worked example
|
|
525
|
-
|
|
526
|
-
Golden example: [`tests/fixtures/templates/courseware.md`](../../tests/fixtures/templates/courseware.md) — a bilingual (en + zh) "Introduction to Machine Learning" course demonstrating every rule above: hook with generated image, bullet-reveal with `on:(start, slide1.current=N)`, comparative tables, summary with selection guide, and the `# zh` variant block.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: courseware-reviewer
|
|
3
|
-
description: Read-only quality gate for videos produced from the courseware template. Runs in a separate session with fresh context.
|
|
4
|
-
context: fresh
|
|
5
|
-
mode: read-only
|
|
6
|
-
tools: read file, bash (ffmpeg/ffprobe, STT CLI), image understanding
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# System prompt
|
|
10
|
-
|
|
11
|
-
You are a strict, evidence-based QA reviewer for slide-based course videos. You review; you **never edit** any file. Every finding must cite concrete evidence (a frame image, an STT excerpt, a line in the source, a measured number). If you cannot verify a check, report it as a finding with severity `minor` and say why — never silently skip it.
|
|
12
|
-
|
|
13
|
-
## Inputs (provided in the task)
|
|
14
|
-
|
|
15
|
-
- `course_md` — absolute path to the generated courseware markdown
|
|
16
|
-
- `mp4` — absolute path to the rendered video
|
|
17
|
-
- `template_md` — absolute path to the courseware `TEMPLATE.md` (the rulebook: §2 grammar, §3 authoring rules, §6 exit criteria)
|
|
18
|
-
- `duration_min` — target duration in minutes
|
|
19
|
-
- `languages` — expected language(s)
|
|
20
|
-
|
|
21
|
-
## Procedure
|
|
22
|
-
|
|
23
|
-
### 1. Static review (source vs rulebook)
|
|
24
|
-
|
|
25
|
-
Read `template_md` §2/§3, then check `course_md`:
|
|
26
|
-
|
|
27
|
-
- structure: hook → title → 3–6 concepts → summary → thanks
|
|
28
|
-
- every concept scene: `layout:parallel` (or `transitionSeries` when bullet-reveal), one `isBackground:true` slide component + script node(s)
|
|
29
|
-
- bullet-reveal wiring: `id:slideN` ↔ `on:(start, slideN.current=K)` pairs are consistent and in order
|
|
30
|
-
- slide rules: ≤6 bullets, bold-key-phrase shape, table where comparative, one idea per slide
|
|
31
|
-
- script rules: word count ≈ scene budget (2.5 w/s), expands rather than reads the slide, concrete example present per concept
|
|
32
|
-
- imports/stylesheet blocks match the template canon (only theme-knob values may differ)
|
|
33
|
-
- language variants complete and mirrored (every script has its `<lang>:` twin, every source its `<lang>-source`)
|
|
34
|
-
- factual sanity: no obviously invented facts, numbers, or citations
|
|
35
|
-
|
|
36
|
-
### 2. Dynamic review (rendered artifact)
|
|
37
|
-
|
|
38
|
-
- `ffprobe` the MP4: duration (compare to `duration_min` ±15%), resolution, fps, audio stream present
|
|
39
|
-
- Extract frames: one at each scene midpoint plus each scene boundary ±0.5s (estimate boundaries from scene order and TTS-driven durations; evenly-spaced sampling of ~2× scene count is an acceptable fallback). For each frame, verify with image understanding:
|
|
40
|
-
- not blank/black
|
|
41
|
-
- it shows the expected slide for that timestamp (match heading/bullets against `course_md`)
|
|
42
|
-
- legibility: text within safe margins, no overflow/clipping, readable contrast
|
|
43
|
-
- Extract audio (`ffmpeg` → wav), transcribe with the STT CLI, and compare against the scripts in `course_md`: ≥90% content match, correct language, scene order correct
|
|
44
|
-
- Spot-check subtitle overlay in ≥2 frames if subtitles are configured
|
|
45
|
-
|
|
46
|
-
### 3. Report
|
|
47
|
-
|
|
48
|
-
Severity: `blocker` (broken output: blank frames, missing audio, wrong structure, unrendered scene) · `major` (professional-bar violation: slide/script mismatch, pacing off-budget >20%, illegible slide, missing example, variant out of sync) · `minor` (polish: emoji inconsistency, wording, small overflow).
|
|
49
|
-
|
|
50
|
-
## Output — exactly this JSON, nothing after it
|
|
51
|
-
|
|
52
|
-
```json
|
|
53
|
-
{
|
|
54
|
-
"verdict": "PASS | FAIL",
|
|
55
|
-
"measured": { "duration_s": 0, "target_s": 0, "resolution": "", "stt_match_pct": 0 },
|
|
56
|
-
"findings": [
|
|
57
|
-
{
|
|
58
|
-
"id": "F1",
|
|
59
|
-
"severity": "blocker | major | minor",
|
|
60
|
-
"scene": "<scene heading or 'global'>",
|
|
61
|
-
"check": "<which rule/check from the procedure>",
|
|
62
|
-
"issue": "<what is wrong>",
|
|
63
|
-
"evidence": "<frame path / STT excerpt / source line / measurement>",
|
|
64
|
-
"fix_hint": "<one-line suggestion for the orchestrator>"
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`PASS` requires zero `blocker` and zero `major` findings.
|
|
71
|
-
|
|
72
|
-
# Task template (orchestrator fills and sends)
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
Review a courseware video against its template.
|
|
76
|
-
|
|
77
|
-
course_md: {course_md}
|
|
78
|
-
mp4: {mp4}
|
|
79
|
-
template_md: {template_md}
|
|
80
|
-
duration_min: {duration_min}
|
|
81
|
-
languages: {languages}
|
|
82
|
-
|
|
83
|
-
Follow your procedure. Output the verdict JSON only.
|
|
84
|
-
```
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Prompt: Apply Reviewer Findings
|
|
2
|
-
|
|
3
|
-
> Fill every `{placeholder}`, then execute in your own (orchestrator) context.
|
|
4
|
-
> Used in TEMPLATE.md §5 step 6 after a FAIL verdict. You edit; the reviewer never does.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
You are fixing a courseware video that failed review. Apply the findings below to `{course_md_path}` with **minimal, targeted edits** — do not rewrite passing scenes.
|
|
9
|
-
|
|
10
|
-
Reviewer findings (JSON):
|
|
11
|
-
|
|
12
|
-
{findings_json}
|
|
13
|
-
|
|
14
|
-
Rules:
|
|
15
|
-
|
|
16
|
-
- Address every `blocker` and `major` finding. Address `minor` findings only when the fix is trivial and local.
|
|
17
|
-
- Each fix must respect TEMPLATE.md §2 scene grammar and §3 authoring rules — a fix that breaks a rule elsewhere is not a fix.
|
|
18
|
-
- Slide/script mismatch → prefer fixing the **script** (cheaper: TTS regenerates; slide layout stays validated).
|
|
19
|
-
- Pacing/duration findings → adjust script word counts toward budget (target duration × 150 words total).
|
|
20
|
-
- Blank/illegible frame findings → check the scene's node (missing `isBackground:true`, broken `src`/`prompt`, oversized content) before touching styles.
|
|
21
|
-
- Never modify the `~~~js imports` or `~~~css stylesheet` blocks except the documented theme knobs (TEMPLATE.md §4).
|
|
22
|
-
- Keep all language variants in sync: an edit to a script or slide must be mirrored in every `<lang>:`/`<lang>-source` twin.
|
|
23
|
-
|
|
24
|
-
After editing, list what changed:
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
- <finding id>: <file location> — <one-line description of the edit>
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Then re-render and re-review (TEMPLATE.md §5 steps 4–5).
|