@navipartner/demo-video 0.7.0 → 0.7.2
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 +13 -9
- package/bin/demo-video.mjs +98 -17
- package/bin/init.d.mts +2 -0
- package/bin/init.mjs +17 -6
- package/dist/{adapter-CDOXdUZI.d.ts → adapter-DxZmb-QU.d.ts} +47 -8
- package/dist/{chunk-D7UYGNSF.js → chunk-XMOGUZJU.js} +211 -50
- package/dist/index.d.ts +119 -20
- package/dist/index.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/index.js +753 -217
- package/package.json +1 -1
- package/skills/np-demo-video/SKILL.md +242 -230
- package/skills/np-demo-video/look.md +182 -82
- package/skills/np-demo-video/reference.md +89 -30
|
@@ -4,29 +4,44 @@ import path from "path";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
// src/timing.ts
|
|
7
|
-
var ZOOM_MS =
|
|
7
|
+
var ZOOM_MS = 900;
|
|
8
8
|
var CAMERA_EASE = "cubic-bezier(0.65, 0, 0.35, 1)";
|
|
9
|
-
var CARD_IN_MS =
|
|
10
|
-
var CARD_OUT_MS =
|
|
11
|
-
var CURSOR_MIN_MS =
|
|
12
|
-
var CURSOR_MAX_MS =
|
|
13
|
-
var CURSOR_MS_PER_PX = 0.
|
|
14
|
-
var SETTLE_MS =
|
|
15
|
-
var
|
|
9
|
+
var CARD_IN_MS = 500;
|
|
10
|
+
var CARD_OUT_MS = 600;
|
|
11
|
+
var CURSOR_MIN_MS = 400;
|
|
12
|
+
var CURSOR_MAX_MS = 800;
|
|
13
|
+
var CURSOR_MS_PER_PX = 0.7;
|
|
14
|
+
var SETTLE_MS = 150;
|
|
15
|
+
var ESTABLISH_MS = 800;
|
|
16
|
+
var SETTLED_MS = 250;
|
|
17
|
+
var REACTION_MS = 1e3;
|
|
18
|
+
var COVERED_SETTLED_MS = 150;
|
|
19
|
+
var COVERED_MAX_MS = 600;
|
|
20
|
+
var PRE_CLICK_MS = 160;
|
|
16
21
|
var PRESS_MS = 110;
|
|
17
|
-
var POST_CLICK_MS =
|
|
18
|
-
var POST_KEY_MS =
|
|
19
|
-
var POST_ACTION_MS =
|
|
20
|
-
var
|
|
22
|
+
var POST_CLICK_MS = 450;
|
|
23
|
+
var POST_KEY_MS = 250;
|
|
24
|
+
var POST_ACTION_MS = 500;
|
|
25
|
+
var TYPE_CHAR_MS = 45;
|
|
26
|
+
var LINGER_MS = 900;
|
|
27
|
+
var TITLE_MAX_SECONDS = {
|
|
28
|
+
showcase: 3.5,
|
|
29
|
+
feature: 2.5
|
|
30
|
+
};
|
|
21
31
|
var POLL_MS = 100;
|
|
22
32
|
var wordCount = (text) => text ? text.replace(/\*/g, "").split(/\s+/).filter(Boolean).length : 0;
|
|
23
|
-
var cardSeconds = (spec) => {
|
|
24
|
-
const read =
|
|
25
|
-
return Math.max(
|
|
33
|
+
var cardSeconds = (spec, maxSeconds = Number.POSITIVE_INFINITY) => {
|
|
34
|
+
const read = 0.5 + 0.25 * wordCount(spec.heading) + 0.2 * wordCount(spec.sub) + 0.12 * wordCount(spec.eyebrow);
|
|
35
|
+
return Math.max(
|
|
36
|
+
spec.seconds,
|
|
37
|
+
Math.min(maxSeconds, Math.round(read * 10) / 10)
|
|
38
|
+
);
|
|
26
39
|
};
|
|
40
|
+
var titleSeconds = (spec, kind = "showcase") => cardSeconds(spec, TITLE_MAX_SECONDS[kind]);
|
|
27
41
|
|
|
28
42
|
// src/stage.ts
|
|
29
43
|
var STAGE_PATH = "/__demo/stage";
|
|
44
|
+
var STILLS_PATH = "/__demo/stills";
|
|
30
45
|
var STAGE_WIDTH = 1920;
|
|
31
46
|
var STAGE_HEIGHT = 1080;
|
|
32
47
|
var FRAME_WIDTH = 1728;
|
|
@@ -65,9 +80,15 @@ var stageScript = (layout, look) => String.raw`
|
|
|
65
80
|
// Nothing here waits: time is virtual (frames.ts), so each call starts a
|
|
66
81
|
// move and returns at once, and the director films its duration.
|
|
67
82
|
|
|
68
|
-
// A shot from camera.ts: the app's translation and total scale.
|
|
69
|
-
|
|
83
|
+
// A shot from camera.ts: the app's translation and total scale. Instant
|
|
84
|
+
// behind a card, so the chapter opens on it.
|
|
85
|
+
const setShot = (shot, instant) => {
|
|
86
|
+
if (instant) { camera.classList.add("instant"); }
|
|
70
87
|
camera.style.transform = "translate(" + shot.tx + "px, " + shot.ty + "px) scale(" + shot.k + ")";
|
|
88
|
+
if (instant) {
|
|
89
|
+
void camera.offsetWidth;
|
|
90
|
+
camera.classList.remove("instant");
|
|
91
|
+
}
|
|
71
92
|
};
|
|
72
93
|
|
|
73
94
|
// Behind an opaque card: back to wide with no move, so every chapter opens
|
|
@@ -122,14 +143,30 @@ var stageScript = (layout, look) => String.raw`
|
|
|
122
143
|
// (payoff into end card) without the app showing in between.
|
|
123
144
|
let current = null;
|
|
124
145
|
|
|
146
|
+
// Card stills, loaded and decoded before filming starts, so a card never
|
|
147
|
+
// shows one half-drawn.
|
|
148
|
+
const stills = new Map();
|
|
149
|
+
const preload = (urls) =>
|
|
150
|
+
Promise.all(urls.map((url) => {
|
|
151
|
+
const img = new Image();
|
|
152
|
+
img.alt = "";
|
|
153
|
+
img.src = url;
|
|
154
|
+
stills.set(url, img);
|
|
155
|
+
return img.decode();
|
|
156
|
+
})).then(() => undefined);
|
|
157
|
+
|
|
125
158
|
const buildCard = (spec) => {
|
|
126
159
|
const el = document.createElement("div");
|
|
127
|
-
el.className = "card " + (spec.style === "brand" ? "brand" : "pale");
|
|
160
|
+
el.className = "card " + (spec.style === "brand" ? "brand" : "pale") + (spec.still ? " split" : "");
|
|
161
|
+
// The words, in one column; beside them, a still of the result.
|
|
162
|
+
const text = document.createElement("div");
|
|
163
|
+
text.className = "text";
|
|
164
|
+
el.appendChild(text);
|
|
128
165
|
const add = (tag, cls, html) => {
|
|
129
166
|
const child = document.createElement(tag);
|
|
130
|
-
child.className = cls;
|
|
167
|
+
child.className = "part " + cls;
|
|
131
168
|
child.innerHTML = html;
|
|
132
|
-
|
|
169
|
+
text.appendChild(child);
|
|
133
170
|
return child;
|
|
134
171
|
};
|
|
135
172
|
if (spec.eyebrow) { add("p", "eyebrow", escapeHtml(spec.eyebrow)); }
|
|
@@ -142,6 +179,15 @@ var stageScript = (layout, look) => String.raw`
|
|
|
142
179
|
}
|
|
143
180
|
if (spec.heading) { add("h1", "heading", emphasise(spec.heading)); }
|
|
144
181
|
if (spec.sub) { add("p", "sub", escapeHtml(spec.sub)); }
|
|
182
|
+
if (spec.still) {
|
|
183
|
+
const img = stills.get(spec.still) || new Image();
|
|
184
|
+
img.src = spec.still;
|
|
185
|
+
img.className = "part still";
|
|
186
|
+
// A tall still (a pass, a kiosk screen) and a wide one (a dialog, a
|
|
187
|
+
// receipt row) are bounded differently.
|
|
188
|
+
img.classList.add(img.naturalHeight > img.naturalWidth ? "tall" : "broad");
|
|
189
|
+
el.appendChild(img);
|
|
190
|
+
}
|
|
145
191
|
return el;
|
|
146
192
|
};
|
|
147
193
|
|
|
@@ -181,7 +227,7 @@ var stageScript = (layout, look) => String.raw`
|
|
|
181
227
|
setTimeout(() => el.remove(), CARD_OUT_MS);
|
|
182
228
|
};
|
|
183
229
|
|
|
184
|
-
window.__demo = { setShot, resetCamera, cursorTo, parkNow, showCard, hideCard };
|
|
230
|
+
window.__demo = { setShot, resetCamera, cursorTo, parkNow, showCard, hideCard, preload };
|
|
185
231
|
cursor.style.transform = "translate(" + cursorX + "px, " + cursorY + "px)";
|
|
186
232
|
})();
|
|
187
233
|
`;
|
|
@@ -238,34 +284,41 @@ var stageHtml = (background, appUrl, look, options = {}) => {
|
|
|
238
284
|
through them; the dissolve is the card fading while the app blurs. */
|
|
239
285
|
.card {
|
|
240
286
|
position: absolute; inset: 0; z-index: 40; isolation: isolate;
|
|
241
|
-
display: flex;
|
|
242
|
-
padding: 0
|
|
287
|
+
display: flex; align-items: center; justify-content: center; gap: 104px;
|
|
288
|
+
padding: 0 200px; text-align: center;
|
|
243
289
|
opacity: 0; transition: opacity ${CARD_OUT_MS}ms ease;
|
|
244
290
|
pointer-events: none;
|
|
245
291
|
}
|
|
292
|
+
/* Words stay inside the central 1080\xD71080, so a square crop keeps them. */
|
|
293
|
+
.card .text { display: flex; flex-direction: column; align-items: center; gap: 28px; max-width: 1080px; }
|
|
294
|
+
/* Text and UI on one card: the words on the left, the result beside them. */
|
|
295
|
+
.card.split { padding: 0 150px; text-align: left; }
|
|
296
|
+
.card.split .text { align-items: flex-start; flex: 0 1 820px; }
|
|
246
297
|
.card.show { opacity: 1; transition: opacity ${CARD_IN_MS}ms ease; }
|
|
247
298
|
.card.pale { background: ${background}; }
|
|
248
299
|
.card.brand {
|
|
249
300
|
background: radial-gradient(1200px 800px at 20% 10%, rgba(255,255,255,0.10), transparent 60%), ${look.primary};
|
|
250
301
|
}
|
|
251
302
|
.card.instant, .card.instant * { transition: none !important; }
|
|
252
|
-
.card
|
|
303
|
+
.card .part {
|
|
253
304
|
margin: 0; opacity: 0; filter: blur(12px);
|
|
254
|
-
transition: opacity
|
|
305
|
+
transition: opacity 300ms ease, filter 300ms ease;
|
|
255
306
|
}
|
|
256
|
-
.card.show
|
|
257
|
-
.card.show
|
|
258
|
-
.card.show
|
|
259
|
-
.card.show
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.card.show.leaving
|
|
307
|
+
.card.show .part { opacity: 1; filter: blur(0); transition: opacity 500ms ease, filter 500ms ease; }
|
|
308
|
+
.card.show .eyebrow, .card.show .logo { transition-delay: 80ms; }
|
|
309
|
+
.card.show .heading { transition-delay: 160ms; }
|
|
310
|
+
.card.show .still { transition-delay: 260ms; }
|
|
311
|
+
.card.show .sub { transition-delay: 420ms; }
|
|
312
|
+
/* Beats every .card.show rule above (more specific). */
|
|
313
|
+
.card.show.leaving .part, .card.show.leaving .heading, .card.show.leaving .sub,
|
|
314
|
+
.card.show.leaving .eyebrow, .card.show.leaving .logo, .card.show.leaving .still {
|
|
263
315
|
opacity: 0; filter: blur(12px);
|
|
264
|
-
transition: opacity
|
|
316
|
+
transition: opacity 240ms ease 0ms, filter 240ms ease 0ms;
|
|
265
317
|
}
|
|
266
|
-
.card.show.after
|
|
267
|
-
.card.show.after
|
|
268
|
-
.card.show.after
|
|
318
|
+
.card.show.after .eyebrow, .card.show.after .logo { transition-delay: 300ms; }
|
|
319
|
+
.card.show.after .heading { transition-delay: 380ms; }
|
|
320
|
+
.card.show.after .still { transition-delay: 480ms; }
|
|
321
|
+
.card.show.after .sub { transition-delay: 620ms; }
|
|
269
322
|
/* Faint grain over the background and cards: it dithers their soft
|
|
270
323
|
gradients, which in 8-bit video would band and block. */
|
|
271
324
|
body::before, .card::before {
|
|
@@ -275,18 +328,29 @@ var stageHtml = (background, appUrl, look, options = {}) => {
|
|
|
275
328
|
opacity: 0.045;
|
|
276
329
|
}
|
|
277
330
|
.card::before { z-index: -1; }
|
|
278
|
-
.card .eyebrow {
|
|
331
|
+
.card .eyebrow {
|
|
332
|
+
font-size: 28px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
|
|
333
|
+
color: ${look.primary};
|
|
334
|
+
}
|
|
279
335
|
.card .logo.wide { width: 720px; height: auto; }
|
|
280
336
|
.card .logo.square { height: 240px; width: auto; }
|
|
281
337
|
.card .heading {
|
|
282
338
|
font-family: ${font(look.headingFont)}, Georgia, serif;
|
|
283
|
-
font-size:
|
|
339
|
+
font-size: 116px; font-weight: 400; line-height: 1.08; letter-spacing: -0.015em;
|
|
284
340
|
color: ${look.ink};
|
|
285
341
|
}
|
|
286
342
|
.card .heading em { font-style: italic; color: ${look.primary}; }
|
|
287
|
-
.card .sub { font-size:
|
|
343
|
+
.card .sub { font-size: 40px; font-weight: 400; line-height: 1.3; color: ${look.text}; opacity: 0.85; }
|
|
344
|
+
.card.split .heading { font-size: 92px; }
|
|
345
|
+
.card.split .sub { font-size: 36px; }
|
|
346
|
+
.card .still {
|
|
347
|
+
flex: 0 0 auto; border-radius: 18px; background: #fff;
|
|
348
|
+
box-shadow: 0 28px 90px rgba(15, 23, 42, 0.18), 0 2px 8px rgba(15, 23, 42, 0.08);
|
|
349
|
+
}
|
|
350
|
+
.card .still.tall { max-height: 760px; max-width: 640px; }
|
|
351
|
+
.card .still.broad { max-width: 760px; max-height: 620px; }
|
|
288
352
|
.card.brand .heading, .card.brand .heading em { color: #fff; }
|
|
289
|
-
.card.brand .sub, .card.brand .eyebrow { color: rgba(255,255,255,0.
|
|
353
|
+
.card.brand .sub, .card.brand .eyebrow { color: rgba(255,255,255,0.86); }
|
|
290
354
|
.card.brand .logo { filter: brightness(0) invert(1); }
|
|
291
355
|
</style>
|
|
292
356
|
</head>
|
|
@@ -385,6 +449,7 @@ var target = {
|
|
|
385
449
|
};
|
|
386
450
|
var scale = z.number().min(1).max(3);
|
|
387
451
|
var seconds = z.number().min(0.5).max(60);
|
|
452
|
+
var behindCard = { behindCard: z.boolean().optional() };
|
|
388
453
|
var cardFields = {
|
|
389
454
|
eyebrow: z.string().min(1).max(40).optional(),
|
|
390
455
|
heading: z.string().min(1).max(80).optional(),
|
|
@@ -393,7 +458,13 @@ var cardFields = {
|
|
|
393
458
|
logo: z.boolean().optional(),
|
|
394
459
|
/** "pale" = brand-tinted white; "brand" = full-bleed primary colour. */
|
|
395
460
|
style: z.enum(["pale", "brand"]).default("pale"),
|
|
396
|
-
seconds: z.number().min(0.5).max(10).default(
|
|
461
|
+
seconds: z.number().min(0.5).max(10).default(1.6)
|
|
462
|
+
};
|
|
463
|
+
var show = z.union([z.string().min(1), z.object({ ...target }).strict()]).optional();
|
|
464
|
+
var stepCardFields = {
|
|
465
|
+
...cardFields,
|
|
466
|
+
style: z.enum(["pale", "brand"]).optional(),
|
|
467
|
+
show
|
|
397
468
|
};
|
|
398
469
|
var hasContent = (c) => Boolean(c.heading || c.logo);
|
|
399
470
|
var CARD_EMPTY = "a card needs a heading or the logo";
|
|
@@ -401,15 +472,19 @@ var coreStepSchemas = [
|
|
|
401
472
|
z.object({
|
|
402
473
|
do: z.literal("click"),
|
|
403
474
|
...target,
|
|
404
|
-
zoom: scale.optional()
|
|
475
|
+
zoom: scale.optional(),
|
|
476
|
+
...behindCard
|
|
405
477
|
}).strict(),
|
|
406
478
|
z.object({
|
|
407
479
|
do: z.literal("type"),
|
|
408
480
|
...target,
|
|
409
481
|
text: z.string(),
|
|
410
|
-
perCharMs: z.number().int().min(0).max(500).optional()
|
|
482
|
+
perCharMs: z.number().int().min(0).max(500).optional(),
|
|
483
|
+
// Select what the field holds first, so the text replaces it.
|
|
484
|
+
replace: z.boolean().optional(),
|
|
485
|
+
...behindCard
|
|
411
486
|
}).strict(),
|
|
412
|
-
z.object({ do: z.literal("press"), key: z.string().min(1) }).strict(),
|
|
487
|
+
z.object({ do: z.literal("press"), key: z.string().min(1), ...behindCard }).strict(),
|
|
413
488
|
z.object({
|
|
414
489
|
do: z.literal("zoom"),
|
|
415
490
|
...target,
|
|
@@ -427,7 +502,8 @@ var coreStepSchemas = [
|
|
|
427
502
|
do: z.literal("waitFor"),
|
|
428
503
|
...target,
|
|
429
504
|
hidden: z.boolean().optional(),
|
|
430
|
-
timeout: seconds.default(15)
|
|
505
|
+
timeout: seconds.default(15),
|
|
506
|
+
...behindCard
|
|
431
507
|
}).strict(),
|
|
432
508
|
z.object({
|
|
433
509
|
do: z.literal("expectText"),
|
|
@@ -436,7 +512,7 @@ var coreStepSchemas = [
|
|
|
436
512
|
contains: z.boolean().optional(),
|
|
437
513
|
timeout: seconds.default(10)
|
|
438
514
|
}).strict(),
|
|
439
|
-
z.object({ do: z.literal("card"), ...
|
|
515
|
+
z.object({ do: z.literal("card"), ...stepCardFields }).strict(),
|
|
440
516
|
z.object({
|
|
441
517
|
do: z.literal("hold"),
|
|
442
518
|
seconds: z.number().min(0).max(10)
|
|
@@ -445,8 +521,9 @@ var coreStepSchemas = [
|
|
|
445
521
|
var CORE_STEP_NAMES = coreStepSchemas.map(
|
|
446
522
|
(s) => s.shape.do.value
|
|
447
523
|
);
|
|
448
|
-
var RESERVED_FIELDS = ["do"];
|
|
524
|
+
var RESERVED_FIELDS = ["do", "behindCard"];
|
|
449
525
|
var cardSchema = z.object(cardFields).strict().refine(hasContent, CARD_EMPTY);
|
|
526
|
+
var titleSchema = z.object({ ...cardFields, show }).strict().refine(hasContent, CARD_EMPTY);
|
|
450
527
|
var isCoreStep = (step) => CORE_STEP_NAMES.includes(step.do);
|
|
451
528
|
var buildStoryboardSchema = (adapter) => {
|
|
452
529
|
const actionSchemas = Object.entries(adapter.actions ?? {}).map(
|
|
@@ -461,7 +538,7 @@ var buildStoryboardSchema = (adapter) => {
|
|
|
461
538
|
);
|
|
462
539
|
}
|
|
463
540
|
}
|
|
464
|
-
return z.object({ do: z.literal(name) }).merge(action.params).strict();
|
|
541
|
+
return z.object({ do: z.literal(name), ...behindCard }).merge(action.params).strict();
|
|
465
542
|
}
|
|
466
543
|
);
|
|
467
544
|
const stepSchema = z.discriminatedUnion("do", [
|
|
@@ -471,8 +548,9 @@ var buildStoryboardSchema = (adapter) => {
|
|
|
471
548
|
return z.object({
|
|
472
549
|
app: z.literal(adapter.app),
|
|
473
550
|
description: z.string().min(1).max(280).optional(),
|
|
551
|
+
kind: z.enum(["showcase", "feature"]).optional(),
|
|
474
552
|
primary: z.boolean().optional(),
|
|
475
|
-
title:
|
|
553
|
+
title: titleSchema.optional(),
|
|
476
554
|
closing: cardSchema.optional(),
|
|
477
555
|
steps: z.array(stepSchema).min(1)
|
|
478
556
|
}).strict().superRefine((story, ctx) => {
|
|
@@ -515,6 +593,7 @@ var loadStoryboard = (file, adapter) => {
|
|
|
515
593
|
var MIN_ZOOM = MIN_SCALE;
|
|
516
594
|
var MAX_ZOOM = MAX_SCALE;
|
|
517
595
|
var MAX_PUSHES_PER_CHAPTER = 2;
|
|
596
|
+
var MAX_SHOWCASE_CHAPTERS = 4;
|
|
518
597
|
var MAX_HOLD_SECONDS = 6;
|
|
519
598
|
var MIN_PER_CHAR_MS = 40;
|
|
520
599
|
var MAX_WORDS = { eyebrow: 4, heading: 8, sub: 16 };
|
|
@@ -534,7 +613,8 @@ var CODE_ON_CARD = [
|
|
|
534
613
|
]
|
|
535
614
|
];
|
|
536
615
|
var INTERACTIONS = /* @__PURE__ */ new Set(["click", "type", "press"]);
|
|
537
|
-
var
|
|
616
|
+
var isBehindCard = (step) => step.behindCard === true;
|
|
617
|
+
var isInteraction = (step) => (INTERACTIONS.has(step.do) || !isCoreStep(step)) && !isBehindCard(step);
|
|
538
618
|
var lintStoryboard = (story) => {
|
|
539
619
|
const issues = [];
|
|
540
620
|
const steps = story.steps;
|
|
@@ -616,10 +696,32 @@ var lintStoryboard = (story) => {
|
|
|
616
696
|
steps.forEach((step, index) => {
|
|
617
697
|
const where = `steps.${index}`;
|
|
618
698
|
const s = step;
|
|
699
|
+
if (isBehindCard(step)) {
|
|
700
|
+
let k = index - 1;
|
|
701
|
+
while (k >= 0 && isBehindCard(steps[k])) {
|
|
702
|
+
k--;
|
|
703
|
+
}
|
|
704
|
+
const afterCard = k === -1 ? Boolean(story.title) : steps[k].do === "card";
|
|
705
|
+
if (!afterCard) {
|
|
706
|
+
issues.push(
|
|
707
|
+
`${where}: behindCard only right after a card, which covers the app while it's done`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
if (step.do === "click" && s.zoom !== void 0) {
|
|
711
|
+
issues.push(
|
|
712
|
+
`${where}: a click behind a card has no shot to push in to; drop its zoom`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
619
716
|
if (step.do === "card") {
|
|
620
717
|
endChapter(index);
|
|
621
718
|
chapterStart = index + 1;
|
|
622
719
|
lintCard(where, s);
|
|
720
|
+
if (s.eyebrow !== void 0) {
|
|
721
|
+
issues.push(
|
|
722
|
+
`${where}.eyebrow: cards between chapters have no eyebrow; the statement and its sub-line say it`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
623
725
|
const rest = steps.slice(index + 1);
|
|
624
726
|
const nextCard = rest.findIndex((x) => x.do === "card");
|
|
625
727
|
const chapter = nextCard === -1 ? rest : rest.slice(0, nextCard);
|
|
@@ -667,6 +769,54 @@ var lintStoryboard = (story) => {
|
|
|
667
769
|
}
|
|
668
770
|
});
|
|
669
771
|
endChapter(steps.length);
|
|
772
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
773
|
+
const showOf = (where, value) => {
|
|
774
|
+
if (value === void 0) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const t = value;
|
|
778
|
+
const key = typeof t === "string" ? `${t}#0` : `${t.target}#${t.nth ?? 0}`;
|
|
779
|
+
const first = snapshots.get(key);
|
|
780
|
+
if (first) {
|
|
781
|
+
issues.push(
|
|
782
|
+
`${where}.show: ${first} shows the same snapshot; show each result once`
|
|
783
|
+
);
|
|
784
|
+
} else {
|
|
785
|
+
snapshots.set(key, where);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
showOf("title", story.title?.show);
|
|
789
|
+
steps.forEach((step, index) => {
|
|
790
|
+
if (step.do === "card") {
|
|
791
|
+
showOf(`steps.${index}`, step.show);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
const kind = story.kind ?? "showcase";
|
|
795
|
+
if (story.primary && kind !== "showcase") {
|
|
796
|
+
issues.push(
|
|
797
|
+
"kind: the primary story walks the app's main journey, so it is a showcase"
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const chapters = steps.filter((step, i) => {
|
|
801
|
+
if (step.do !== "card") {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
const rest = steps.slice(i + 1);
|
|
805
|
+
const next = rest.findIndex((x) => x.do === "card");
|
|
806
|
+
return (next === -1 ? rest : rest.slice(0, next)).some(
|
|
807
|
+
(x) => !isBehindCard(x)
|
|
808
|
+
);
|
|
809
|
+
}).length;
|
|
810
|
+
if (kind === "feature" && chapters !== 1) {
|
|
811
|
+
issues.push(
|
|
812
|
+
`kind: a feature clip shows one feature, in one chapter; this has ${chapters}. Make it one, or a showcase`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (kind === "showcase" && chapters > MAX_SHOWCASE_CHAPTERS) {
|
|
816
|
+
issues.push(
|
|
817
|
+
`${chapters} chapters; a showcase has at most ${MAX_SHOWCASE_CHAPTERS}: make a second story`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
670
820
|
return issues;
|
|
671
821
|
};
|
|
672
822
|
var normaliseText = (text) => text.replace(/\s+/g, " ").trim();
|
|
@@ -675,16 +825,26 @@ export {
|
|
|
675
825
|
ZOOM_MS,
|
|
676
826
|
CARD_IN_MS,
|
|
677
827
|
CARD_OUT_MS,
|
|
828
|
+
CURSOR_MIN_MS,
|
|
829
|
+
CURSOR_MAX_MS,
|
|
678
830
|
SETTLE_MS,
|
|
831
|
+
ESTABLISH_MS,
|
|
832
|
+
SETTLED_MS,
|
|
833
|
+
REACTION_MS,
|
|
834
|
+
COVERED_SETTLED_MS,
|
|
835
|
+
COVERED_MAX_MS,
|
|
679
836
|
PRE_CLICK_MS,
|
|
680
837
|
PRESS_MS,
|
|
681
838
|
POST_CLICK_MS,
|
|
682
839
|
POST_KEY_MS,
|
|
683
840
|
POST_ACTION_MS,
|
|
841
|
+
TYPE_CHAR_MS,
|
|
684
842
|
LINGER_MS,
|
|
685
843
|
POLL_MS,
|
|
686
844
|
cardSeconds,
|
|
845
|
+
titleSeconds,
|
|
687
846
|
STAGE_PATH,
|
|
847
|
+
STILLS_PATH,
|
|
688
848
|
STAGE_WIDTH,
|
|
689
849
|
STAGE_HEIGHT,
|
|
690
850
|
DEFAULT_VIEWPORT,
|
|
@@ -703,5 +863,6 @@ export {
|
|
|
703
863
|
StoryboardError,
|
|
704
864
|
parseStoryboard,
|
|
705
865
|
loadStoryboard,
|
|
866
|
+
isBehindCard,
|
|
706
867
|
lintStoryboard
|
|
707
868
|
};
|