playwright-director 0.2.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.
Files changed (122) hide show
  1. package/README.md +860 -0
  2. package/dist/Tutorial.d.ts +108 -0
  3. package/dist/Tutorial.d.ts.map +1 -0
  4. package/dist/Tutorial.js +767 -0
  5. package/dist/Tutorial.js.map +1 -0
  6. package/dist/bin/export-transcript.d.ts +3 -0
  7. package/dist/bin/export-transcript.d.ts.map +1 -0
  8. package/dist/bin/export-transcript.js +111 -0
  9. package/dist/bin/export-transcript.js.map +1 -0
  10. package/dist/cursor.d.ts +24 -0
  11. package/dist/cursor.d.ts.map +1 -0
  12. package/dist/cursor.js +96 -0
  13. package/dist/cursor.js.map +1 -0
  14. package/dist/index.d.ts +16 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +3 -0
  19. package/dist/init.d.ts.map +1 -0
  20. package/dist/init.js +183 -0
  21. package/dist/init.js.map +1 -0
  22. package/dist/merge.d.ts +26 -0
  23. package/dist/merge.d.ts.map +1 -0
  24. package/dist/merge.js +65 -0
  25. package/dist/merge.js.map +1 -0
  26. package/dist/music.d.ts +29 -0
  27. package/dist/music.d.ts.map +1 -0
  28. package/dist/music.js +107 -0
  29. package/dist/music.js.map +1 -0
  30. package/dist/overlay-html.d.ts +44 -0
  31. package/dist/overlay-html.d.ts.map +1 -0
  32. package/dist/overlay-html.js +150 -0
  33. package/dist/overlay-html.js.map +1 -0
  34. package/dist/overlay.d.ts +39 -0
  35. package/dist/overlay.d.ts.map +1 -0
  36. package/dist/overlay.js +134 -0
  37. package/dist/overlay.js.map +1 -0
  38. package/dist/postinstall.d.ts +3 -0
  39. package/dist/postinstall.d.ts.map +1 -0
  40. package/dist/postinstall.js +38 -0
  41. package/dist/postinstall.js.map +1 -0
  42. package/dist/reporter.d.ts +22 -0
  43. package/dist/reporter.d.ts.map +1 -0
  44. package/dist/reporter.js +166 -0
  45. package/dist/reporter.js.map +1 -0
  46. package/dist/site/build-site.d.ts +2 -0
  47. package/dist/site/build-site.d.ts.map +1 -0
  48. package/dist/site/build-site.js +82 -0
  49. package/dist/site/build-site.js.map +1 -0
  50. package/dist/site/embed.d.ts +48 -0
  51. package/dist/site/embed.d.ts.map +1 -0
  52. package/dist/site/embed.js +45 -0
  53. package/dist/site/embed.js.map +1 -0
  54. package/dist/site/generate-manifest.d.ts +15 -0
  55. package/dist/site/generate-manifest.d.ts.map +1 -0
  56. package/dist/site/generate-manifest.js +106 -0
  57. package/dist/site/generate-manifest.js.map +1 -0
  58. package/dist/site/scaffold.d.ts +3 -0
  59. package/dist/site/scaffold.d.ts.map +1 -0
  60. package/dist/site/scaffold.js +70 -0
  61. package/dist/site/scaffold.js.map +1 -0
  62. package/dist/site/scan-tutorials.d.ts +12 -0
  63. package/dist/site/scan-tutorials.d.ts.map +1 -0
  64. package/dist/site/scan-tutorials.js +54 -0
  65. package/dist/site/scan-tutorials.js.map +1 -0
  66. package/dist/site/types.d.ts +75 -0
  67. package/dist/site/types.d.ts.map +1 -0
  68. package/dist/site/types.js +2 -0
  69. package/dist/site/types.js.map +1 -0
  70. package/dist/skill-stamp.d.ts +21 -0
  71. package/dist/skill-stamp.d.ts.map +1 -0
  72. package/dist/skill-stamp.js +39 -0
  73. package/dist/skill-stamp.js.map +1 -0
  74. package/dist/slugify.d.ts +19 -0
  75. package/dist/slugify.d.ts.map +1 -0
  76. package/dist/slugify.js +29 -0
  77. package/dist/slugify.js.map +1 -0
  78. package/dist/stage-presets.d.ts +51 -0
  79. package/dist/stage-presets.d.ts.map +1 -0
  80. package/dist/stage-presets.js +45 -0
  81. package/dist/stage-presets.js.map +1 -0
  82. package/dist/styles.css +637 -0
  83. package/dist/timeline.d.ts +87 -0
  84. package/dist/timeline.d.ts.map +1 -0
  85. package/dist/timeline.js +108 -0
  86. package/dist/timeline.js.map +1 -0
  87. package/dist/transcript.d.ts +87 -0
  88. package/dist/transcript.d.ts.map +1 -0
  89. package/dist/transcript.js +291 -0
  90. package/dist/transcript.js.map +1 -0
  91. package/dist/tts-provider.d.ts +69 -0
  92. package/dist/tts-provider.d.ts.map +1 -0
  93. package/dist/tts-provider.js +182 -0
  94. package/dist/tts-provider.js.map +1 -0
  95. package/dist/types.d.ts +109 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +2 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/voice-prerender.d.ts +41 -0
  100. package/dist/voice-prerender.d.ts.map +1 -0
  101. package/dist/voice-prerender.js +122 -0
  102. package/dist/voice-prerender.js.map +1 -0
  103. package/dist/voice.d.ts +76 -0
  104. package/dist/voice.d.ts.map +1 -0
  105. package/dist/voice.js +244 -0
  106. package/dist/voice.js.map +1 -0
  107. package/package.json +87 -0
  108. package/skills/tutorialize/SKILL.md +54 -0
  109. package/skills/tutorialize/agent.md +58 -0
  110. package/skills/tutorialize/references/api.md +489 -0
  111. package/skills/tutorialize/references/storytelling.md +193 -0
  112. package/src/styles.css +637 -0
  113. package/templates/site/public/.gitkeep +0 -0
  114. package/templates/site/public/widget.js +273 -0
  115. package/templates/site/src/components/TutorialsHome.astro +141 -0
  116. package/templates/site/src/components/VideoCard.astro +192 -0
  117. package/templates/site/src/components/VideoModal.astro +170 -0
  118. package/templates/site/src/data/.gitkeep +0 -0
  119. package/templates/site/src/layouts/Base.astro +56 -0
  120. package/templates/site/src/pages/[slug].astro +745 -0
  121. package/templates/site/src/pages/index.astro +5 -0
  122. package/templates/site/src/styles/tutorials.css +303 -0
package/dist/voice.js ADDED
@@ -0,0 +1,244 @@
1
+ import { createHash } from 'crypto';
2
+ import { existsSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { createTTSProvider } from './tts-provider.js';
5
+ const TUTORIAL_MODE = process.env.TUTORIAL_MODE === 'true';
6
+ // Audio files stored in static folder to be served by dev server
7
+ const AUDIO_STATIC_DIR = join(process.cwd(), 'static', 'audio', 'tutorial-voice');
8
+ const AUDIO_URL_BASE = '/audio/tutorial-voice';
9
+ /**
10
+ * Get hash-based filename for text
11
+ */
12
+ function getAudioFilename(text, lang) {
13
+ const hash = createHash('md5').update(`${lang}:${text}`).digest('hex').slice(0, 12);
14
+ const ext = process.platform === 'darwin' ? 'wav' : 'mp3';
15
+ return `${hash}.${ext}`;
16
+ }
17
+ /**
18
+ * TTS voice generation for tutorials.
19
+ * Pre-generates audio files and tracks their durations.
20
+ * Optionally plays audio in browser during test.
21
+ */
22
+ export class TutorialVoice {
23
+ page;
24
+ options;
25
+ ttsProvider = null;
26
+ preloadedAudio = new Map();
27
+ constructor(page, options) {
28
+ this.page = page;
29
+ this.options = {
30
+ lang: options.lang,
31
+ voiceName: options.voiceName,
32
+ voiceRate: options.voiceRate,
33
+ voicePitch: options.voicePitch,
34
+ playInBrowser: options.playInBrowser ?? true,
35
+ audioBaseUrl: options.audioBaseUrl ?? 'http://localhost:5173'
36
+ };
37
+ }
38
+ switchPage(page) {
39
+ this.page = page;
40
+ }
41
+ /**
42
+ * Pre-generate a single audio file (non-blocking).
43
+ * Returns a promise that resolves when preload is complete.
44
+ * No-op when TUTORIAL_MODE is not enabled.
45
+ */
46
+ preloadSingle(text) {
47
+ if (!TUTORIAL_MODE)
48
+ return Promise.resolve();
49
+ return this.generateAudio(text).then(() => { });
50
+ }
51
+ /**
52
+ * Pre-generate all audio files before the test runs.
53
+ * No-op when TUTORIAL_MODE is not enabled.
54
+ */
55
+ async preload(texts) {
56
+ if (!TUTORIAL_MODE)
57
+ return;
58
+ // Ensure output directory exists
59
+ if (!existsSync(AUDIO_STATIC_DIR)) {
60
+ mkdirSync(AUDIO_STATIC_DIR, { recursive: true });
61
+ }
62
+ // Initialize TTS provider
63
+ if (!this.ttsProvider) {
64
+ this.ttsProvider = await createTTSProvider({
65
+ lang: this.options.lang,
66
+ voice: this.options.voiceName || undefined,
67
+ rate: this.options.voiceRate
68
+ });
69
+ console.log(`[Voice] Using TTS provider: ${this.ttsProvider.name}`);
70
+ }
71
+ console.log(`[Voice] Preloading ${texts.length} audio files...`);
72
+ const startTime = Date.now();
73
+ for (const text of texts) {
74
+ const filename = getAudioFilename(text, this.options.lang);
75
+ const filePath = join(AUDIO_STATIC_DIR, filename);
76
+ const url = `${AUDIO_URL_BASE}/${filename}`;
77
+ let durationMs;
78
+ // Check cache
79
+ if (existsSync(filePath)) {
80
+ // Get duration from cached file
81
+ durationMs = await this.getAudioDuration(filePath);
82
+ }
83
+ else {
84
+ // Generate audio file
85
+ const result = await this.ttsProvider.synthesize(text, filePath);
86
+ durationMs = result.duration;
87
+ }
88
+ this.preloadedAudio.set(text, {
89
+ text,
90
+ filename,
91
+ filePath,
92
+ url,
93
+ durationMs
94
+ });
95
+ }
96
+ console.log(`[Voice] Preloaded ${texts.length} files in ${Date.now() - startTime}ms`);
97
+ }
98
+ /**
99
+ * Get audio info for a text (must be preloaded first)
100
+ */
101
+ getAudio(text) {
102
+ return this.preloadedAudio.get(text);
103
+ }
104
+ /**
105
+ * Get duration in ms for a text (must be preloaded)
106
+ */
107
+ getDuration(text) {
108
+ return this.preloadedAudio.get(text)?.durationMs ?? 0;
109
+ }
110
+ /**
111
+ * Get the filename for a text (for timeline)
112
+ */
113
+ getFilename(text) {
114
+ const cached = this.preloadedAudio.get(text);
115
+ if (cached)
116
+ return cached.filename;
117
+ return getAudioFilename(text, this.options.lang);
118
+ }
119
+ /**
120
+ * Start audio playback without waiting for it to finish.
121
+ * Returns the clip duration in ms — the caller owns the wall clock
122
+ * (action offset + clamp to duration), so headed playback and the
123
+ * ffmpeg merge stay in sync.
124
+ * No-op when TUTORIAL_MODE is not enabled (returns 0).
125
+ */
126
+ async startPlayback(text) {
127
+ if (!TUTORIAL_MODE)
128
+ return 0;
129
+ let audio = this.preloadedAudio.get(text);
130
+ if (!audio)
131
+ audio = await this.generateAudio(text);
132
+ if (!audio) {
133
+ console.warn(`[Voice] Failed to generate audio for: "${text.substring(0, 30)}..."`);
134
+ return 0;
135
+ }
136
+ if (this.options.playInBrowser) {
137
+ // Fire-and-forget: a navigation during the step action may kill the
138
+ // context — playback loss is acceptable, timing comes from durationMs.
139
+ await this.page
140
+ .evaluate(({ url }) => {
141
+ new Audio(url).play().catch(() => { });
142
+ }, { url: `${this.options.audioBaseUrl}${audio.url}` })
143
+ .catch(() => { });
144
+ }
145
+ return audio.durationMs;
146
+ }
147
+ /**
148
+ * Play audio in browser (optional, for live viewing)
149
+ * Returns duration in ms
150
+ * Will generate audio on-demand if not preloaded.
151
+ * No-op when TUTORIAL_MODE is not enabled (returns 0).
152
+ */
153
+ async play(text) {
154
+ if (!TUTORIAL_MODE)
155
+ return 0;
156
+ let audio = this.preloadedAudio.get(text);
157
+ if (!audio) {
158
+ audio = await this.generateAudio(text);
159
+ }
160
+ if (!audio) {
161
+ console.warn(`[Voice] Failed to generate audio for: "${text.substring(0, 30)}..."`);
162
+ return 0;
163
+ }
164
+ if (this.options.playInBrowser) {
165
+ await this.page.evaluate(({ url }) => {
166
+ return new Promise((resolve) => {
167
+ const audio = new Audio(url);
168
+ audio.onended = () => resolve();
169
+ audio.onerror = () => resolve();
170
+ audio.play().catch(() => resolve());
171
+ });
172
+ }, { url: `${this.options.audioBaseUrl}${audio.url}` });
173
+ }
174
+ else {
175
+ // Just wait for the duration (silent)
176
+ await this.page.waitForTimeout(audio.durationMs);
177
+ }
178
+ return audio.durationMs;
179
+ }
180
+ /**
181
+ * Generate audio for a single text on demand
182
+ */
183
+ async generateAudio(text) {
184
+ // Ensure output directory exists
185
+ if (!existsSync(AUDIO_STATIC_DIR)) {
186
+ mkdirSync(AUDIO_STATIC_DIR, { recursive: true });
187
+ }
188
+ // Initialize TTS provider if needed
189
+ if (!this.ttsProvider) {
190
+ this.ttsProvider = await createTTSProvider({
191
+ lang: this.options.lang,
192
+ voice: this.options.voiceName || undefined,
193
+ rate: this.options.voiceRate
194
+ });
195
+ }
196
+ const filename = getAudioFilename(text, this.options.lang);
197
+ const filePath = join(AUDIO_STATIC_DIR, filename);
198
+ const url = `${AUDIO_URL_BASE}/${filename}`;
199
+ let durationMs;
200
+ try {
201
+ // Check cache first
202
+ if (existsSync(filePath)) {
203
+ durationMs = await this.getAudioDuration(filePath);
204
+ }
205
+ else {
206
+ // Generate audio file
207
+ const result = await this.ttsProvider.synthesize(text, filePath);
208
+ durationMs = result.duration;
209
+ }
210
+ const audio = {
211
+ text,
212
+ filename,
213
+ filePath,
214
+ url,
215
+ durationMs
216
+ };
217
+ // Cache for future use
218
+ this.preloadedAudio.set(text, audio);
219
+ return audio;
220
+ }
221
+ catch (error) {
222
+ console.error(`[Voice] Error generating audio: ${error}`);
223
+ return null;
224
+ }
225
+ }
226
+ /**
227
+ * Get audio duration using ffprobe or file stats
228
+ */
229
+ async getAudioDuration(filePath) {
230
+ try {
231
+ const { execSync } = await import('child_process');
232
+ const output = execSync(`ffprobe -v quiet -show_entries format=duration -of csv=p=0 "${filePath}"`, { encoding: 'utf-8' });
233
+ return Math.ceil(parseFloat(output.trim()) * 1000);
234
+ }
235
+ catch {
236
+ // Fallback: estimate based on file size (rough)
237
+ const { statSync } = await import('fs');
238
+ const stats = statSync(filePath);
239
+ // Rough estimate: ~16KB per second for wav at 44100Hz mono
240
+ return Math.ceil((stats.size / 16000) * 1000);
241
+ }
242
+ }
243
+ }
244
+ //# sourceMappingURL=voice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "playwright-director",
3
+ "version": "0.2.0",
4
+ "description": "AI-assisted Playwright tool that turns executable tests into directed video guides — demos, tutorials, and documentation that never lag behind the product.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Youniwemi/playwright-director.git"
8
+ },
9
+ "homepage": "https://github.com/Youniwemi/playwright-director#readme",
10
+ "type": "module",
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./reporter": {
19
+ "types": "./dist/reporter.d.ts",
20
+ "default": "./dist/reporter.js"
21
+ },
22
+ "./slugify": {
23
+ "types": "./dist/slugify.d.ts",
24
+ "default": "./dist/slugify.js"
25
+ },
26
+ "./site": {
27
+ "types": "./dist/site/types.d.ts",
28
+ "default": "./dist/site/types.js"
29
+ },
30
+ "./styles.css": "./src/styles.css"
31
+ },
32
+ "bin": {
33
+ "playwright-director": "./dist/init.js",
34
+ "tutorial-transcript": "./dist/bin/export-transcript.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src/styles.css",
39
+ "skills",
40
+ "templates",
41
+ "README.md"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsc && cp src/styles.css dist/styles.css",
45
+ "test": "vitest run",
46
+ "test:e2e": "playwright test",
47
+ "test:e2e:video": "TUTORIAL_MODE=true playwright test",
48
+ "prepublishOnly": "npm run build",
49
+ "postinstall": "node dist/postinstall.js"
50
+ },
51
+ "keywords": [
52
+ "playwright",
53
+ "tutorial",
54
+ "video",
55
+ "e2e",
56
+ "testing",
57
+ "narration",
58
+ "tts",
59
+ "text-to-speech",
60
+ "screen-recording",
61
+ "documentation",
62
+ "automated-docs",
63
+ "how-to",
64
+ "demo",
65
+ "walkthrough"
66
+ ],
67
+ "author": "Youniwemi",
68
+ "license": "MIT",
69
+ "dependencies": {
70
+ "astro": "^5.0.0"
71
+ },
72
+ "peerDependencies": {
73
+ "@playwright/test": ">=1.40.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "sharp": {
77
+ "optional": true
78
+ }
79
+ },
80
+ "devDependencies": {
81
+ "@playwright/test": "^1.52.0",
82
+ "@types/node": "^26.2.0",
83
+ "@types/sharp": "^0.31.1",
84
+ "typescript": "^5.7.0",
85
+ "vitest": "^3.1.0"
86
+ }
87
+ }
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: tutorialize
3
+ description: Transform a Playwright E2E test into a narrated tutorial video. Load before writing or reviewing any tutorial.step(), tutorial.context(), or tutorial.complete() call. Covers persona analysis, storytelling arc, choreography rules, and the playwright-director API.
4
+ ---
5
+
6
+ # /tutorialize — Tutorial Design Skill
7
+
8
+ Turn a Playwright test into a tutorial that **teaches**, not just demonstrates.
9
+
10
+ Two reference files support this skill:
11
+ - [`references/storytelling.md`](references/storytelling.md) — Persona analysis, emotional arc, narration voice, pacing decisions
12
+ - [`references/api.md`](references/api.md) — `playwright-director` API, timing model, technical rules, checklist
13
+
14
+ **Read both before tutorializing.** Storytelling comes first — it drives every API decision.
15
+
16
+ ## Process
17
+
18
+ ### 1. Understand (read `storytelling.md`)
19
+ - **Who** is watching? (role, expertise, emotional state)
20
+ - **What** are they trying to accomplish? (goal, not feature)
21
+ - **What** do they already know? (skip the obvious, explain the surprising)
22
+ - **How many profiles** does the story need? One is the default. Reach for a
23
+ second scene only when the lesson is the handoff between two people —
24
+ see `storytelling.md` §8 and `api.md` §10.
25
+
26
+ ### 2. Design the arc
27
+ ```
28
+ Navigate to first screen (before any tutorial call)
29
+ ↓
30
+ CONTEXT (goal): "Here's what we'll accomplish"
31
+ ↓
32
+ STEP (no-op): Describe what's on screen
33
+ ↓
34
+ STEPS: Grouped by concept, do/explain narration
35
+ ↓
36
+ CONTEXT (clarification): Only if a complex section needs framing
37
+ ↓
38
+ MORE STEPS...
39
+ ↓
40
+ COMPLETE: Celebrate the outcome, suggest what's next
41
+ ```
42
+
43
+ ### 3. Implement (read `api.md`)
44
+ - Use the Tutorial API: `context()`, `step()`, `complete()`
45
+ - Follow timing rules, blank-screen prevention, navigation handling
46
+ - Apply the polish checklist before submitting
47
+
48
+ ### 4. Verify
49
+ - Test passes without `TUTORIAL_MODE` (plain E2E)
50
+ - Test passes with `TUTORIAL_MODE=true` (video generation)
51
+ - Watch the generated video — does it feel human?
52
+ - Narration texts also become the gallery site's step-by-step guide
53
+ (`npx playwright-director build-site`) — even `skipVoice` steps show their
54
+ text there, so write every step text to read well on the page too.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: tutorial-crafter
3
+ description: Use this agent to convert Playwright tests into professional tutorial videos with voice narration, visual highlights, and proper choreography. Example: user: 'Tutorialize tests/free/02_clients.init.ts' → dispatch tutorial-crafter.
4
+ model: sonnet
5
+ tools: Bash, Glob, Grep, LS, Read, Edit, MultiEdit, Write, TodoWrite
6
+ color: purple
7
+ ---
8
+
9
+ You are an expert Tutorial Designer who transforms Playwright E2E tests into professional, engaging video tutorials. You understand cognitive load, visual pacing, and how humans learn from screencasts.
10
+
11
+ ## Before you start
12
+
13
+ Load the `/tutorialize` skill and read both reference files:
14
+ - `references/storytelling.md` — persona, emotional arc, narration voice
15
+ - `references/api.md` — API, timing model, technical rules, checklist
16
+
17
+ ## Your process
18
+
19
+ ### 1. Analyze the test
20
+ Read the original test file. Identify:
21
+ - **Persona**: who is this tutorial for? (role, expertise, emotional state)
22
+ - **Goal**: what will the viewer accomplish?
23
+ - **Key actions**: what needs explanation vs what is obvious?
24
+ - **Profiles**: does the lesson depend on a handoff between two people? If so,
25
+ the test becomes multi-scene (`storytelling.md` §8, `api.md` §10). Default to
26
+ a single profile — a second scene doubles the length and must earn it.
27
+
28
+ ### 2. Structure the tutorial
29
+ Follow the arc from `storytelling.md`:
30
+ ```
31
+ Navigate → Goal context → Steps (grouped) → Clarifications (if needed) → Complete
32
+ ```
33
+
34
+ ### 3. Write natural narration
35
+ - "Do" ≤ 8 words, action-oriented
36
+ - "Explain" gives the WHY
37
+ - Second person, conversational tone
38
+ - Persona-appropriate vocabulary
39
+
40
+ ### 4. Apply technical rules
41
+ Follow all rules from `api.md`:
42
+ - No blank-screen opening
43
+ - Navigation transitions properly awaited
44
+ - Dual-mode (test works with and without TUTORIAL_MODE)
45
+ - `@tutorial` tag on the test
46
+ - Acronyms have `voiceText` where TTS mispronounces
47
+ - Multi-scene only: every step touching a scene carries `{ scene }`, scenes sit
48
+ on distinct origins, each tab switch is acknowledged in the narration, and
49
+ split ratios (`focus(['a','b'], { ratio: [30, 70] })`) are used to keep the
50
+ acting pane readable
51
+
52
+ ### 5. Show and explain
53
+ - Present the diff or full transformed code
54
+ - Explain why certain groupings were chosen
55
+ - Describe what the pacing will feel like when the video plays
56
+
57
+ ## Remember
58
+ A great tutorial makes the viewer feel capable, not confused. When in doubt, slow down and explain.