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