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
@@ -0,0 +1,745 @@
1
+ ---
2
+ import Base from '../layouts/Base.astro';
3
+ import tutorials from '../data/tutorials.json';
4
+ import siteConfig from '../data/site-config.json';
5
+
6
+ export function getStaticPaths() {
7
+ const all = (tutorials as any).videos;
8
+ return all.map((v: any, i: number) => ({
9
+ params: { slug: v.id },
10
+ props: { video: v, index: i, allVideos: all },
11
+ }));
12
+ }
13
+
14
+ const { video, index, allVideos } = Astro.props as any;
15
+ const base = import.meta.env.BASE_URL.replace(/\/$/, '');
16
+
17
+ // 'text' | 'cards' | 'full' | 'none' — see SiteConfig.tutorials.stepsLayout
18
+ const layout = (siteConfig as any).stepsLayout || 'text';
19
+ // The thumbnail strip is independent of the guide layout (SiteConfig.tutorials.showStrip)
20
+ const showStrip = (siteConfig as any).showStrip !== false;
21
+
22
+ type Step = { n?: number; title?: string; text?: string; image?: string; context?: boolean };
23
+ const stepsDetail: Step[] = video.stepsDetail ?? [];
24
+
25
+ // Sequential display numbers for numbered entries (contexts don't count)
26
+ let counter = 0;
27
+ const numbered = stepsDetail.map((s) => ({ ...s, num: s.context ? undefined : ++counter }));
28
+ const shots = numbered.filter((s) => s.image);
29
+
30
+ const poster = shots[0]?.image;
31
+ const prev = index > 0 ? allVideos[index - 1] : null;
32
+ const next = index < allVideos.length - 1 ? allVideos[index + 1] : null;
33
+ ---
34
+
35
+ <Base title={video.title}>
36
+ <div class="video-page">
37
+ <a href={`${base}/`} class="back-link">← All tutorials</a>
38
+
39
+ <div class="video-player-wrap">
40
+ <video controls class="video-player" poster={poster ? `${base}/videos/${poster}` : undefined}>
41
+ <source src={`${base}/videos/${video.file}`} type="video/webm" />
42
+ </video>
43
+ </div>
44
+
45
+ <div class="video-page-info">
46
+ <h1 class="video-page-title">{video.title}</h1>
47
+ {video.description && <p class="video-page-desc">{video.description}</p>}
48
+ <div class="video-page-meta">
49
+ <span class="video-duration">&#x1F553; {video.duration}</span>
50
+ </div>
51
+ </div>
52
+
53
+ {showStrip && shots.length > 0 && (
54
+ <div class="steps-section">
55
+ <div class="step-strip">
56
+ {shots.map((s) => (
57
+ <button
58
+ class="step-frame"
59
+ data-full={`${base}/videos/${s.image}`}
60
+ data-num={s.num}
61
+ data-title={s.title || ''}
62
+ data-text={s.text || ''}
63
+ aria-label={`Step ${s.num}${s.title ? `: ${s.title}` : ''}`}
64
+ >
65
+ <img src={`${base}/videos/${s.image}`} alt={s.title || `Step ${s.num}`} loading={s.num === 1 ? 'eager' : 'lazy'} />
66
+ <span class="step-num">{s.num}</span>
67
+ </button>
68
+ ))}
69
+ </div>
70
+ </div>
71
+ )}
72
+
73
+ {layout === 'full' && numbered.length > 0 && (
74
+ <section class="steps-guide">
75
+ {numbered.map((s) => (
76
+ s.context ? (
77
+ <p class="step-context">{s.text}</p>
78
+ ) : (
79
+ <div class="step-block">
80
+ <div class="step-header">
81
+ <span class="step-number">{s.num}</span>
82
+ <div class="step-copy">
83
+ {s.title && <h2 class="step-title">{s.title}</h2>}
84
+ {s.text && <p class="step-text">{s.text}</p>}
85
+ </div>
86
+ </div>
87
+ {s.image && (
88
+ <img
89
+ class="step-shot"
90
+ src={`${base}/videos/${s.image}`}
91
+ alt={s.title || `Step ${s.num}`}
92
+ loading={s.num === 1 ? 'eager' : 'lazy'}
93
+ />
94
+ )}
95
+ </div>
96
+ )
97
+ ))}
98
+ </section>
99
+ )}
100
+
101
+ {layout === 'cards' && numbered.length > 0 && (
102
+ <section class="steps-cards">
103
+ {numbered.map((s) => (
104
+ s.context ? (
105
+ <p class="step-context">{s.text}</p>
106
+ ) : (
107
+ <div class="step-card">
108
+ <span class="step-number">{s.num}</span>
109
+ <div class="step-copy">
110
+ {s.title && <h2 class="step-title">{s.title}</h2>}
111
+ {s.text && <p class="step-text">{s.text}</p>}
112
+ </div>
113
+ {s.image && (
114
+ <button
115
+ class="step-thumb"
116
+ data-full={`${base}/videos/${s.image}`}
117
+ data-num={s.num}
118
+ data-title={s.title || ''}
119
+ data-text={s.text || ''}
120
+ aria-label={`Enlarge screenshot of step ${s.num}`}
121
+ >
122
+ <img src={`${base}/videos/${s.image}`} alt={s.title || `Step ${s.num}`} loading="lazy" />
123
+ <span class="step-zoom">⤢</span>
124
+ </button>
125
+ )}
126
+ </div>
127
+ )
128
+ ))}
129
+ </section>
130
+ )}
131
+
132
+ {layout === 'text' && numbered.length > 0 && (
133
+ <section class="steps-text">
134
+ {numbered.map((s) => (
135
+ s.context ? (
136
+ <p class="step-context">{s.text}</p>
137
+ ) : (
138
+ <div class:list={['step-row', { 'has-shot': !!s.image }]}>
139
+ <button class="step-row-head" aria-expanded="false" disabled={!s.image}>
140
+ <span class="step-number">{s.num}</span>
141
+ <span class="step-copy">
142
+ {s.title && <span class="step-title">{s.title}</span>}
143
+ {s.text && <span class="step-text">{s.text}</span>}
144
+ </span>
145
+ {s.image && <span class="step-chevron">▾</span>}
146
+ </button>
147
+ {s.image && (
148
+ <div class="step-reveal">
149
+ <img src={`${base}/videos/${s.image}`} alt={s.title || `Step ${s.num}`} loading="lazy" />
150
+ </div>
151
+ )}
152
+ </div>
153
+ )
154
+ ))}
155
+ </section>
156
+ )}
157
+
158
+ <nav class="video-nav-row" aria-label="navigation">
159
+ <div class="nav-prev">
160
+ {prev && (
161
+ <a href={`${base}/${prev.id}`} class="nav-link nav-link-prev">
162
+ <span class="nav-arrow">‹</span>
163
+ <span class="nav-title">{prev.title}</span>
164
+ </a>
165
+ )}
166
+ </div>
167
+ <div class="nav-next">
168
+ {next && (
169
+ <a href={`${base}/${next.id}`} class="nav-link nav-link-next">
170
+ <span class="nav-title">{next.title}</span>
171
+ <span class="nav-arrow">›</span>
172
+ </a>
173
+ )}
174
+ </div>
175
+ </nav>
176
+ </div>
177
+
178
+ {(showStrip || layout === 'cards') && (
179
+ <div class="lightbox" id="step-lightbox" hidden>
180
+ <figure class="lightbox-figure">
181
+ <img alt="" />
182
+ <figcaption class="lightbox-caption" hidden>
183
+ <span class="lightbox-num"></span>
184
+ <span class="lightbox-copy">
185
+ <strong class="lightbox-title"></strong>
186
+ <span class="lightbox-text"></span>
187
+ </span>
188
+ </figcaption>
189
+ </figure>
190
+ </div>
191
+ )}
192
+ </Base>
193
+
194
+ <script>
195
+ // Strip + cards thumbnails: click to enlarge in the lightbox, with the
196
+ // step's number, title and text as a caption.
197
+ const lightbox = document.getElementById('step-lightbox');
198
+ if (lightbox) {
199
+ const img = lightbox.querySelector('img')!;
200
+ const caption = lightbox.querySelector<HTMLElement>('.lightbox-caption')!;
201
+ const num = lightbox.querySelector<HTMLElement>('.lightbox-num')!;
202
+ const title = lightbox.querySelector<HTMLElement>('.lightbox-title')!;
203
+ const text = lightbox.querySelector<HTMLElement>('.lightbox-text')!;
204
+
205
+ const open = (source: HTMLElement) => {
206
+ img.src = source.dataset.full || '';
207
+ num.textContent = source.dataset.num || '';
208
+ title.textContent = source.dataset.title || '';
209
+ text.textContent = source.dataset.text || '';
210
+ caption.hidden = !(source.dataset.title || source.dataset.text);
211
+ lightbox.hidden = false;
212
+ document.body.style.overflow = 'hidden';
213
+ };
214
+
215
+ document.querySelectorAll<HTMLElement>('.step-thumb, .step-frame').forEach((thumb) => {
216
+ thumb.addEventListener('click', () => open(thumb));
217
+ });
218
+
219
+ const close = () => {
220
+ lightbox.hidden = true;
221
+ document.body.style.overflow = '';
222
+ };
223
+ lightbox.addEventListener('click', close);
224
+ document.addEventListener('keydown', (e) => {
225
+ if (e.key === 'Escape' && !lightbox.hidden) close();
226
+ });
227
+ }
228
+
229
+ // Text layout: click a step to reveal its screenshot
230
+ document.querySelectorAll<HTMLElement>('.step-row.has-shot .step-row-head').forEach((head) => {
231
+ head.addEventListener('click', () => {
232
+ const row = head.closest('.step-row')!;
233
+ const open = row.classList.toggle('open');
234
+ head.setAttribute('aria-expanded', String(open));
235
+ });
236
+ });
237
+ </script>
238
+
239
+ <style>
240
+ .video-page {
241
+ max-width: 900px;
242
+ margin: 0 auto;
243
+ }
244
+
245
+ .back-link {
246
+ display: inline-block;
247
+ margin-bottom: 1.5rem;
248
+ color: var(--color-text-muted);
249
+ text-decoration: none;
250
+ font-size: 0.9rem;
251
+ transition: color 0.2s;
252
+ }
253
+
254
+ .back-link:hover {
255
+ color: var(--color-primary);
256
+ }
257
+
258
+ .video-player-wrap {
259
+ width: 100%;
260
+ border-radius: var(--radius);
261
+ overflow: hidden;
262
+ background: #000;
263
+ box-shadow: var(--shadow);
264
+ margin-bottom: 1.5rem;
265
+ }
266
+
267
+ .video-player {
268
+ width: 100%;
269
+ display: block;
270
+ max-height: 70vh;
271
+ }
272
+
273
+ .video-page-info {
274
+ margin-bottom: 1.5rem;
275
+ }
276
+
277
+ .video-page-title {
278
+ font-size: 1.75rem;
279
+ font-weight: 700;
280
+ margin-bottom: 0.5rem;
281
+ line-height: 1.3;
282
+ }
283
+
284
+ .video-page-desc {
285
+ color: var(--color-text-muted);
286
+ font-size: 1rem;
287
+ line-height: 1.6;
288
+ margin-bottom: 0.75rem;
289
+ }
290
+
291
+ .video-page-meta {
292
+ display: flex;
293
+ align-items: center;
294
+ gap: 0.75rem;
295
+ color: var(--color-text-muted);
296
+ font-size: 0.9rem;
297
+ }
298
+
299
+ .video-duration {
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 0.25rem;
303
+ }
304
+
305
+ /* ── Shared step bits ─────────────────────────────────────────── */
306
+
307
+ .step-number {
308
+ flex-shrink: 0;
309
+ width: 2rem;
310
+ height: 2rem;
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ border-radius: 50%;
315
+ background: var(--color-primary);
316
+ color: #fff;
317
+ font-weight: 700;
318
+ font-size: 0.95rem;
319
+ }
320
+
321
+ .step-copy {
322
+ min-width: 0;
323
+ flex: 1;
324
+ }
325
+
326
+ .step-title {
327
+ display: block;
328
+ font-size: 1.1rem;
329
+ font-weight: 600;
330
+ line-height: 1.4;
331
+ margin: 0.2rem 0 0.35rem;
332
+ color: var(--color-text);
333
+ }
334
+
335
+ .step-text {
336
+ display: block;
337
+ color: var(--color-text-muted);
338
+ line-height: 1.6;
339
+ margin: 0;
340
+ font-size: 0.95rem;
341
+ }
342
+
343
+ .step-context {
344
+ color: var(--color-text-muted);
345
+ font-style: italic;
346
+ line-height: 1.6;
347
+ border-inline-start: 3px solid var(--color-primary);
348
+ padding-inline-start: 1rem;
349
+ margin: 0;
350
+ }
351
+
352
+ /* ── strip layout ─────────────────────────────────────────────── */
353
+
354
+ .steps-section {
355
+ margin-bottom: 2rem;
356
+ }
357
+
358
+ .step-strip {
359
+ display: flex;
360
+ gap: 0.75rem;
361
+ overflow-x: auto;
362
+ padding-bottom: 0.5rem;
363
+ }
364
+
365
+ .step-strip::-webkit-scrollbar {
366
+ height: 4px;
367
+ }
368
+
369
+ .step-strip::-webkit-scrollbar-track {
370
+ background: var(--color-border);
371
+ border-radius: 2px;
372
+ }
373
+
374
+ .step-strip::-webkit-scrollbar-thumb {
375
+ background: var(--color-text-muted);
376
+ border-radius: 2px;
377
+ }
378
+
379
+ .step-frame {
380
+ position: relative;
381
+ flex-shrink: 0;
382
+ width: 200px;
383
+ border-radius: 0.5rem;
384
+ overflow: hidden;
385
+ box-shadow: var(--shadow);
386
+ padding: 0;
387
+ border: 0;
388
+ background: none;
389
+ cursor: zoom-in;
390
+ }
391
+
392
+ .step-frame img {
393
+ width: 100%;
394
+ height: 113px;
395
+ object-fit: cover;
396
+ display: block;
397
+ transition: transform 0.2s;
398
+ }
399
+
400
+ .step-frame:hover img {
401
+ transform: scale(1.04);
402
+ }
403
+
404
+ .step-num {
405
+ position: absolute;
406
+ top: 6px;
407
+ left: 6px;
408
+ background: rgba(0, 0, 0, 0.65);
409
+ color: white;
410
+ font-size: 0.7rem;
411
+ font-weight: 600;
412
+ padding: 0.1rem 0.4rem;
413
+ border-radius: 0.25rem;
414
+ }
415
+
416
+ /* ── full layout ──────────────────────────────────────────────── */
417
+
418
+ .steps-guide {
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: 2.5rem;
422
+ margin-bottom: 3rem;
423
+ }
424
+
425
+ .step-block {
426
+ display: flex;
427
+ flex-direction: column;
428
+ gap: 1rem;
429
+ }
430
+
431
+ .step-header {
432
+ display: flex;
433
+ align-items: flex-start;
434
+ gap: 1rem;
435
+ }
436
+
437
+ .step-shot {
438
+ width: 100%;
439
+ display: block;
440
+ border-radius: var(--radius);
441
+ border: 1px solid var(--color-border);
442
+ box-shadow: var(--shadow);
443
+ }
444
+
445
+ /* ── cards layout ─────────────────────────────────────────────── */
446
+
447
+ .steps-cards {
448
+ display: flex;
449
+ flex-direction: column;
450
+ gap: 1rem;
451
+ margin-bottom: 3rem;
452
+ }
453
+
454
+ .step-card {
455
+ display: flex;
456
+ align-items: flex-start;
457
+ gap: 1rem;
458
+ padding: 1rem 1.25rem;
459
+ border: 1px solid var(--color-border);
460
+ border-radius: var(--radius);
461
+ background: var(--color-bg-card);
462
+ box-shadow: var(--shadow);
463
+ }
464
+
465
+ .step-thumb {
466
+ position: relative;
467
+ flex-shrink: 0;
468
+ width: 180px;
469
+ padding: 0;
470
+ border: 1px solid var(--color-border);
471
+ border-radius: 0.5rem;
472
+ overflow: hidden;
473
+ cursor: zoom-in;
474
+ background: none;
475
+ display: block;
476
+ }
477
+
478
+ .step-thumb img {
479
+ width: 100%;
480
+ height: 102px;
481
+ object-fit: cover;
482
+ display: block;
483
+ transition: transform 0.2s;
484
+ }
485
+
486
+ .step-thumb:hover img {
487
+ transform: scale(1.04);
488
+ }
489
+
490
+ .step-zoom {
491
+ position: absolute;
492
+ right: 6px;
493
+ bottom: 6px;
494
+ background: rgba(0, 0, 0, 0.65);
495
+ color: #fff;
496
+ font-size: 0.8rem;
497
+ line-height: 1;
498
+ padding: 0.25rem 0.35rem;
499
+ border-radius: 0.3rem;
500
+ }
501
+
502
+ /* ── text layout ──────────────────────────────────────────────── */
503
+
504
+ .steps-text {
505
+ display: flex;
506
+ flex-direction: column;
507
+ gap: 0.75rem;
508
+ margin-bottom: 3rem;
509
+ }
510
+
511
+ .step-row {
512
+ border: 1px solid var(--color-border);
513
+ border-radius: var(--radius);
514
+ background: var(--color-bg-card);
515
+ overflow: hidden;
516
+ }
517
+
518
+ .step-row-head {
519
+ display: flex;
520
+ align-items: flex-start;
521
+ gap: 1rem;
522
+ width: 100%;
523
+ padding: 1rem 1.25rem;
524
+ border: 0;
525
+ background: none;
526
+ text-align: inherit;
527
+ font: inherit;
528
+ color: inherit;
529
+ }
530
+
531
+ .step-row.has-shot .step-row-head {
532
+ cursor: pointer;
533
+ }
534
+
535
+ .step-row.has-shot .step-row-head:hover {
536
+ background: color-mix(in srgb, var(--color-primary) 5%, transparent);
537
+ }
538
+
539
+ .step-chevron {
540
+ flex-shrink: 0;
541
+ align-self: center;
542
+ color: var(--color-text-muted);
543
+ transition: transform 0.2s;
544
+ }
545
+
546
+ .step-row.open .step-chevron {
547
+ transform: rotate(180deg);
548
+ }
549
+
550
+ .step-reveal {
551
+ display: none;
552
+ padding: 0 1.25rem 1.25rem;
553
+ }
554
+
555
+ .step-row.open .step-reveal {
556
+ display: block;
557
+ }
558
+
559
+ .step-reveal img {
560
+ width: 100%;
561
+ display: block;
562
+ border-radius: 0.5rem;
563
+ border: 1px solid var(--color-border);
564
+ }
565
+
566
+ /* ── lightbox ─────────────────────────────────────────────────── */
567
+
568
+ .lightbox[hidden] {
569
+ display: none;
570
+ }
571
+
572
+ .lightbox {
573
+ position: fixed;
574
+ inset: 0;
575
+ z-index: 100;
576
+ background: rgba(0, 0, 0, 0.85);
577
+ display: flex;
578
+ align-items: center;
579
+ justify-content: center;
580
+ padding: 2rem;
581
+ cursor: zoom-out;
582
+ }
583
+
584
+ .lightbox-figure {
585
+ max-width: min(1100px, 100%);
586
+ max-height: 100%;
587
+ margin: 0;
588
+ display: flex;
589
+ flex-direction: column;
590
+ gap: 0.9rem;
591
+ align-items: center;
592
+ }
593
+
594
+ .lightbox img {
595
+ max-width: 100%;
596
+ max-height: 75vh;
597
+ border-radius: 0.5rem;
598
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
599
+ }
600
+
601
+ .lightbox-caption {
602
+ display: flex;
603
+ align-items: flex-start;
604
+ gap: 0.75rem;
605
+ max-width: 700px;
606
+ color: #e2e8f0;
607
+ cursor: default;
608
+ }
609
+
610
+ .lightbox-caption[hidden] {
611
+ display: none;
612
+ }
613
+
614
+ .lightbox-num {
615
+ flex-shrink: 0;
616
+ width: 1.7rem;
617
+ height: 1.7rem;
618
+ display: flex;
619
+ align-items: center;
620
+ justify-content: center;
621
+ border-radius: 50%;
622
+ background: var(--color-primary);
623
+ color: #fff;
624
+ font-weight: 700;
625
+ font-size: 0.85rem;
626
+ }
627
+
628
+ .lightbox-num:empty {
629
+ display: none;
630
+ }
631
+
632
+ .lightbox-copy {
633
+ display: flex;
634
+ flex-direction: column;
635
+ gap: 0.2rem;
636
+ text-align: start;
637
+ }
638
+
639
+ .lightbox-title {
640
+ font-size: 1rem;
641
+ line-height: 1.4;
642
+ color: #fff;
643
+ }
644
+
645
+ .lightbox-text {
646
+ font-size: 0.9rem;
647
+ line-height: 1.55;
648
+ color: #cbd5e1;
649
+ }
650
+
651
+ /* ── prev/next nav ────────────────────────────────────────────── */
652
+
653
+ .video-nav-row {
654
+ display: flex;
655
+ justify-content: space-between;
656
+ align-items: stretch;
657
+ gap: 1rem;
658
+ padding-top: 2rem;
659
+ border-top: 1px solid var(--color-border);
660
+ margin-top: 1rem;
661
+ }
662
+
663
+ .nav-prev,
664
+ .nav-next {
665
+ flex: 1;
666
+ }
667
+
668
+ .nav-next {
669
+ text-align: right;
670
+ }
671
+
672
+ .nav-link {
673
+ display: inline-flex;
674
+ align-items: center;
675
+ gap: 0.5rem;
676
+ text-decoration: none;
677
+ color: var(--color-text);
678
+ padding: 0.75rem 1rem;
679
+ border-radius: var(--radius);
680
+ border: 1px solid var(--color-border);
681
+ background: var(--color-bg-card);
682
+ transition: all 0.2s;
683
+ font-size: 0.9rem;
684
+ max-width: 100%;
685
+ }
686
+
687
+ .nav-link:hover {
688
+ border-color: var(--color-primary);
689
+ color: var(--color-primary);
690
+ box-shadow: var(--shadow);
691
+ }
692
+
693
+ .nav-link-next {
694
+ flex-direction: row-reverse;
695
+ }
696
+
697
+ .nav-arrow {
698
+ font-size: 1.25rem;
699
+ line-height: 1;
700
+ flex-shrink: 0;
701
+ color: var(--color-primary);
702
+ }
703
+
704
+ .nav-title {
705
+ overflow: hidden;
706
+ text-overflow: ellipsis;
707
+ white-space: nowrap;
708
+ }
709
+
710
+ @media (max-width: 600px) {
711
+ .video-page-title {
712
+ font-size: 1.25rem;
713
+ }
714
+
715
+ .step-frame {
716
+ width: 150px;
717
+ }
718
+
719
+ .step-frame img {
720
+ height: 85px;
721
+ }
722
+
723
+ .step-card {
724
+ flex-wrap: wrap;
725
+ }
726
+
727
+ .step-thumb {
728
+ width: 100%;
729
+ }
730
+
731
+ .step-thumb img {
732
+ height: auto;
733
+ }
734
+
735
+ .step-number {
736
+ width: 1.6rem;
737
+ height: 1.6rem;
738
+ font-size: 0.85rem;
739
+ }
740
+
741
+ .nav-title {
742
+ display: none;
743
+ }
744
+ }
745
+ </style>