@arbidocs/blocks 0.4.106 → 0.4.108
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/bin/arbidocs-render-take.mjs +27 -0
- package/dist/NativeDocxEditor-UAZUKZ77.js +10 -0
- package/dist/NativeDocxEditor-UAZUKZ77.js.map +1 -0
- package/dist/NativeDocxEditor-YX7B7OFH.cjs +31 -0
- package/dist/NativeDocxEditor-YX7B7OFH.cjs.map +1 -0
- package/dist/{chunk-W2RTZOK3.js → chunk-A2MN2ZLX.js} +1750 -1779
- package/dist/chunk-A2MN2ZLX.js.map +1 -0
- package/dist/{chunk-3R6XBYL7.cjs → chunk-DTBSGG36.cjs} +7 -2
- package/dist/chunk-DTBSGG36.cjs.map +1 -0
- package/dist/{chunk-4GFB5U3K.js → chunk-EGDWNEAG.js} +4 -3
- package/dist/chunk-EGDWNEAG.js.map +1 -0
- package/dist/chunk-GYAUJ4KT.js +71 -0
- package/dist/chunk-GYAUJ4KT.js.map +1 -0
- package/dist/{chunk-PIT2YZCK.js → chunk-JQVPGMLU.js} +3 -3
- package/dist/{chunk-PIT2YZCK.js.map → chunk-JQVPGMLU.js.map} +1 -1
- package/dist/{chunk-LGX6UFYG.js → chunk-P2KPDPFF.js} +8 -3
- package/dist/chunk-P2KPDPFF.js.map +1 -0
- package/dist/{chunk-ZWWN4CUL.cjs → chunk-QT6EGMAJ.cjs} +1748 -1778
- package/dist/chunk-QT6EGMAJ.cjs.map +1 -0
- package/dist/chunk-S24XEVPS.cjs +73 -0
- package/dist/chunk-S24XEVPS.cjs.map +1 -0
- package/dist/{chunk-TP3FAP5J.cjs → chunk-TTXK62YI.cjs} +5 -5
- package/dist/{chunk-TP3FAP5J.cjs.map → chunk-TTXK62YI.cjs.map} +1 -1
- package/dist/{chunk-7LLLQUM7.cjs → chunk-ZYSY4L5L.cjs} +6 -5
- package/dist/chunk-ZYSY4L5L.cjs.map +1 -0
- package/dist/collaboration/index.cjs +15 -15
- package/dist/collaboration/index.js +1 -1
- package/dist/collaboration/mail/MailMessageView.d.ts +3 -1
- package/dist/connect/docx.cjs +7 -6
- package/dist/connect/docx.cjs.map +1 -1
- package/dist/connect/docx.js +5 -4
- package/dist/connect/docx.js.map +1 -1
- package/dist/connect/index.cjs +368 -52
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +356 -41
- package/dist/connect/index.js.map +1 -1
- package/dist/connection/mail/ConnectedMailComposer.d.ts +12 -2
- package/dist/connection/mail/ConnectedMailMessage.d.ts +19 -0
- package/dist/connection/mail/ConnectedMailbox.d.ts +7 -3
- package/dist/connection/mail/MailComposerForm.d.ts +12 -2
- package/dist/connection/mail/index.d.ts +2 -1
- package/dist/connection/mail/types.d.ts +15 -0
- package/dist/connection/mail/useWordBody.d.ts +48 -0
- package/dist/docx/NativeDocxEditor.d.ts +9 -1
- package/dist/docx/index.cjs +51 -50
- package/dist/docx/index.cjs.map +1 -1
- package/dist/docx/index.js +4 -3
- package/dist/docx/index.js.map +1 -1
- package/dist/docx/template/HouseTemplate.d.ts +5 -0
- package/dist/index.cjs +15 -15
- package/dist/index.js +1 -1
- package/dist/record/index.cjs +307 -66
- package/dist/record/index.cjs.map +1 -1
- package/dist/record/index.d.ts +145 -26
- package/dist/record/index.js +301 -67
- package/dist/record/index.js.map +1 -1
- package/node_modules/@arbidocs/coedit/package.json +1 -1
- package/package.json +8 -5
- package/record/render-overlays.mjs +152 -0
- package/record/render-take.py +985 -0
- package/src/docx/styles.css +22 -0
- package/dist/chunk-3R6XBYL7.cjs.map +0 -1
- package/dist/chunk-4GFB5U3K.js.map +0 -1
- package/dist/chunk-7LLLQUM7.cjs.map +0 -1
- package/dist/chunk-LGX6UFYG.js.map +0 -1
- package/dist/chunk-W2RTZOK3.js.map +0 -1
- package/dist/chunk-ZWWN4CUL.cjs.map +0 -1
package/dist/record/index.d.ts
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cinematic recording harness — host-agnostic.
|
|
3
|
-
*
|
|
4
|
-
* Turns a Playwright run into a narrated product demo: a visible cursor that
|
|
5
|
-
* glides between targets, a click ripple, a caption bar that narrates each step,
|
|
6
|
-
* full-screen chapter "cover" cards, and human-speed pacing. Every host app gets
|
|
7
|
-
* the same polished demo capability from one factory — brand colours, fonts,
|
|
8
|
-
* credentials and the login flow arrive as config, so nothing here names a host.
|
|
9
|
-
*
|
|
10
|
-
* Pair with a Playwright project that records video (see the host's
|
|
11
|
-
* `playwright.record.config.ts`: video on, a large viewport, some slowMo).
|
|
12
|
-
*
|
|
13
|
-
* Usage in a `*.record.ts` spec:
|
|
14
|
-
* const cine = createCinematic({ brand, creds })
|
|
15
|
-
* await cine.installStage(page) // before the first navigation
|
|
16
|
-
* await cine.cover(page, { kicker, title, subtitle })
|
|
17
|
-
* await cine.login(page)
|
|
18
|
-
* await cine.uncover(page)
|
|
19
|
-
* await cine.caption(page, "Opening the flagship matter…")
|
|
20
|
-
* await cine.click(page, "matters-row-…")
|
|
21
|
-
*
|
|
22
|
-
* `@playwright/test` is an optional peer used for TYPES only (no runtime import), so
|
|
23
|
-
* importing this entry never loads Playwright.
|
|
24
|
-
*/
|
|
25
1
|
import type { Page, Locator } from '@playwright/test';
|
|
26
2
|
/** Brand skin for the on-screen chrome. All optional — sensible neutral defaults. */
|
|
27
3
|
export interface CinematicBrand {
|
|
@@ -44,12 +20,22 @@ export interface CinematicBrand {
|
|
|
44
20
|
mid: string;
|
|
45
21
|
to: string;
|
|
46
22
|
};
|
|
47
|
-
/** Accent (kicker text + rule) on chapter cards. */
|
|
23
|
+
/** Accent (kicker text + rule) on chapter cards, callouts and lower-thirds. */
|
|
48
24
|
accent?: string;
|
|
49
25
|
/** Caption bar background base colour. */
|
|
50
26
|
ink?: string;
|
|
51
27
|
/** Cover title text colour. */
|
|
52
28
|
coverTitle?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Font files for the edit's overlays (cards, captions), when the display or sans font is not
|
|
31
|
+
* installed where the video is rendered: each a family name and a `file://` or `https://` URL.
|
|
32
|
+
*/
|
|
33
|
+
fontFiles?: {
|
|
34
|
+
family: string;
|
|
35
|
+
url: string;
|
|
36
|
+
weight?: string;
|
|
37
|
+
unicodeRange?: string;
|
|
38
|
+
}[];
|
|
53
39
|
}
|
|
54
40
|
export interface CinematicCreds {
|
|
55
41
|
email: string;
|
|
@@ -57,7 +43,8 @@ export interface CinematicCreds {
|
|
|
57
43
|
}
|
|
58
44
|
export interface CinematicConfig {
|
|
59
45
|
brand: CinematicBrand;
|
|
60
|
-
|
|
46
|
+
/** For `login`. */
|
|
47
|
+
creds?: CinematicCreds;
|
|
61
48
|
/** Login route (default "/app/login"). */
|
|
62
49
|
loginPath?: string;
|
|
63
50
|
/** URL the app lands on after auth (default /\/app$/). */
|
|
@@ -69,24 +56,156 @@ export interface CinematicConfig {
|
|
|
69
56
|
submit?: string;
|
|
70
57
|
};
|
|
71
58
|
}
|
|
59
|
+
/** A box on screen, in page pixels. */
|
|
60
|
+
export interface Box {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A moment the edit may frame: a click, typing, reading down a result, a glide-and-look (`point`,
|
|
68
|
+
* which lasts until the next gesture), or a key moment marked with `focusOn`.
|
|
69
|
+
*/
|
|
70
|
+
export interface FocusEvent {
|
|
71
|
+
kind: 'click' | 'type' | 'read' | 'point' | 'focus';
|
|
72
|
+
label?: string;
|
|
73
|
+
start: number;
|
|
74
|
+
end?: number;
|
|
75
|
+
box: Box;
|
|
76
|
+
/** Draw a highlight (and the label as its tag) round the box. */
|
|
77
|
+
callout?: boolean;
|
|
78
|
+
/** How to frame it: zoom in, a picture-in-picture inset, or not at all (default: the edit decides). */
|
|
79
|
+
mode?: 'zoom' | 'pip' | 'none';
|
|
80
|
+
}
|
|
81
|
+
/** A take's say in its edit. */
|
|
82
|
+
export interface TakeOptions {
|
|
83
|
+
/** The take's name: its files are `<dir>/<name>.waits.json` and `<dir>/<name>.focus.json`. */
|
|
84
|
+
name: string;
|
|
85
|
+
/** Where the take's files go (default `recordings`). */
|
|
86
|
+
dir?: string;
|
|
87
|
+
/** Zoom and pan onto the action (default true). */
|
|
88
|
+
zoom?: boolean;
|
|
89
|
+
/** At most this much (default 1.8). */
|
|
90
|
+
maxZoom?: number;
|
|
91
|
+
/** A picture-in-picture inset for long typing (default true). */
|
|
92
|
+
pip?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface Wait {
|
|
95
|
+
label: string;
|
|
96
|
+
start: number;
|
|
97
|
+
end: number;
|
|
98
|
+
}
|
|
99
|
+
interface Card {
|
|
100
|
+
kicker: string;
|
|
101
|
+
title: string;
|
|
102
|
+
subtitle?: string;
|
|
103
|
+
start: number;
|
|
104
|
+
end: number | null;
|
|
105
|
+
}
|
|
106
|
+
/** The timecode's square, in page pixels (the renderer reads it at this size). */
|
|
107
|
+
export declare const TIMECODE_CELL = 8;
|
|
108
|
+
/**
|
|
109
|
+
* One recorded take: its clock, its waits and its focus timeline. Start it first thing (the video
|
|
110
|
+
* starts with the page, a moment before; `markSync` lines the two clocks up exactly).
|
|
111
|
+
*/
|
|
112
|
+
export declare class Take {
|
|
113
|
+
readonly page: Page;
|
|
114
|
+
private readonly options;
|
|
115
|
+
/** The take running now (one at a time: recordings run one after another). */
|
|
116
|
+
private static active;
|
|
117
|
+
readonly t0: number;
|
|
118
|
+
readonly waits: Wait[];
|
|
119
|
+
/** The brand the edit paints the cards, captions and callouts in. */
|
|
120
|
+
private readonly brand;
|
|
121
|
+
readonly file: string;
|
|
122
|
+
readonly focusFile: string;
|
|
123
|
+
private readonly timeline;
|
|
124
|
+
constructor(page: Page, options: TakeOptions, brand?: CinematicBrand);
|
|
125
|
+
/** The take running on `page`, if any. */
|
|
126
|
+
static on(page: Page): Take | null;
|
|
127
|
+
/** Seconds since the take started. */
|
|
128
|
+
now(): number;
|
|
129
|
+
/** Log a wait (written at once, so a take that fails part-way still has its cut list). */
|
|
130
|
+
log(label: string, start: number, end?: number): void;
|
|
131
|
+
/** Wait for `until` (a UI state), logged as a wait the edit may cut. */
|
|
132
|
+
wait<T>(label: string, until: () => Promise<T>): Promise<T>;
|
|
133
|
+
/**
|
|
134
|
+
* Paint the sync marker: a magenta frame for a moment, at a logged time. The edit finds it in the
|
|
135
|
+
* video and lines the take's clock up with the video's. Call it before the first navigation.
|
|
136
|
+
*/
|
|
137
|
+
markSync(): Promise<void>;
|
|
138
|
+
/** Log a focus event (a `point` with no end lasts until the next event). */
|
|
139
|
+
record(event: Omit<FocusEvent, 'start' | 'end'> & {
|
|
140
|
+
start: number;
|
|
141
|
+
end?: number;
|
|
142
|
+
}): void;
|
|
143
|
+
/** @internal The stage's cards, lower-thirds and captions (drawn by the edit). */
|
|
144
|
+
cardStart(card: Omit<Card, 'start' | 'end'>): void;
|
|
145
|
+
cardEnd(): void;
|
|
146
|
+
lowerThird(kicker: string, title: string, holdMs: number): void;
|
|
147
|
+
captionStart(text: string): void;
|
|
148
|
+
captionEnd(): void;
|
|
149
|
+
private save;
|
|
150
|
+
}
|
|
72
151
|
export interface Cinematic {
|
|
152
|
+
/** Start a take on `page` (before `installStage`, so the stage paints its timecode). */
|
|
153
|
+
take(page: Page, options: TakeOptions): Promise<Take>;
|
|
73
154
|
installStage(page: Page): Promise<void>;
|
|
74
155
|
cover(page: Page, opts: {
|
|
75
156
|
kicker: string;
|
|
76
157
|
title: string;
|
|
77
158
|
subtitle?: string;
|
|
78
159
|
}, holdMs?: number): Promise<void>;
|
|
160
|
+
/** A cover the work can go on behind (clicks pass through it) until `uncover`. */
|
|
161
|
+
chapter(page: Page, opts: {
|
|
162
|
+
kicker: string;
|
|
163
|
+
title: string;
|
|
164
|
+
subtitle?: string;
|
|
165
|
+
}, holdMs?: number): Promise<void>;
|
|
79
166
|
uncover(page: Page): Promise<void>;
|
|
167
|
+
/** A section's name over the bottom left while the work goes on (drawn by the edit). */
|
|
168
|
+
lowerThird(page: Page, kicker: string, title: string, holdMs?: number): void;
|
|
80
169
|
caption(page: Page, text: string, holdMs?: number): Promise<void>;
|
|
81
170
|
clearCaption(page: Page): Promise<void>;
|
|
82
171
|
beat(page: Page, ms?: number): Promise<void>;
|
|
83
172
|
glideTo(page: Page, target: string | Locator): Promise<Locator>;
|
|
84
173
|
click(page: Page, target: string | Locator): Promise<void>;
|
|
85
174
|
type(page: Page, target: string | Locator, text: string): Promise<void>;
|
|
175
|
+
/** A key moment: glide to it and hold; the edit zooms onto it and draws a callout with `label`. */
|
|
176
|
+
focusOn(page: Page, target: string | Locator, label?: string, opts?: {
|
|
177
|
+
holdMs?: number;
|
|
178
|
+
callout?: boolean;
|
|
179
|
+
mode?: FocusEvent['mode'];
|
|
180
|
+
}): Promise<void>;
|
|
181
|
+
/** Read down a result at a reader's pace, then back to the top. */
|
|
182
|
+
readDown(page: Page, target: string | Locator, scrolls?: number, pauseMs?: number): Promise<void>;
|
|
86
183
|
/** Sign in. `{ show: false }` authenticates fast + silent (no animation/caption). */
|
|
87
184
|
login(page: Page, opts?: {
|
|
88
185
|
show?: boolean;
|
|
89
186
|
caption?: string;
|
|
90
187
|
}): Promise<void>;
|
|
91
188
|
}
|
|
189
|
+
/** A brand with every default filled in: what the stage paints and the take hands the edit. */
|
|
190
|
+
export declare function cinematicSkin(b: CinematicBrand): {
|
|
191
|
+
firmLabel: string;
|
|
192
|
+
displayFont: string;
|
|
193
|
+
sansFont: string;
|
|
194
|
+
cursorFrom: string;
|
|
195
|
+
cursorTo: string;
|
|
196
|
+
ripple: string;
|
|
197
|
+
coverFrom: string;
|
|
198
|
+
coverMid: string;
|
|
199
|
+
coverTo: string;
|
|
200
|
+
accent: string;
|
|
201
|
+
ink: string;
|
|
202
|
+
coverTitle: string;
|
|
203
|
+
fontFiles: {
|
|
204
|
+
family: string;
|
|
205
|
+
url: string;
|
|
206
|
+
weight?: string;
|
|
207
|
+
unicodeRange?: string;
|
|
208
|
+
}[];
|
|
209
|
+
};
|
|
92
210
|
export declare function createCinematic(config: CinematicConfig): Cinematic;
|
|
211
|
+
export {};
|
package/dist/record/index.js
CHANGED
|
@@ -1,4 +1,135 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
1
4
|
// src/record/index.ts
|
|
5
|
+
var TIMECODE_CELL = 8;
|
|
6
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
7
|
+
var Take = class _Take {
|
|
8
|
+
constructor(page, options, brand) {
|
|
9
|
+
this.page = page;
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.brand = brand ? cinematicSkin(brand) : {};
|
|
12
|
+
const dir = path.resolve(options.dir ?? "recordings");
|
|
13
|
+
this.file = path.join(dir, `${options.name}.waits.json`);
|
|
14
|
+
this.focusFile = path.join(dir, `${options.name}.focus.json`);
|
|
15
|
+
this.timeline.viewport = page.viewportSize();
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
_Take.active = this;
|
|
18
|
+
this.save();
|
|
19
|
+
}
|
|
20
|
+
page;
|
|
21
|
+
options;
|
|
22
|
+
/** The take running now (one at a time: recordings run one after another). */
|
|
23
|
+
static active = null;
|
|
24
|
+
t0 = Date.now();
|
|
25
|
+
waits = [];
|
|
26
|
+
/** The brand the edit paints the cards, captions and callouts in. */
|
|
27
|
+
brand;
|
|
28
|
+
file;
|
|
29
|
+
focusFile;
|
|
30
|
+
timeline = {
|
|
31
|
+
sync: null,
|
|
32
|
+
viewport: null,
|
|
33
|
+
focus: [],
|
|
34
|
+
cards: [],
|
|
35
|
+
lowerThirds: [],
|
|
36
|
+
captions: []
|
|
37
|
+
};
|
|
38
|
+
/** The take running on `page`, if any. */
|
|
39
|
+
static on(page) {
|
|
40
|
+
return _Take.active?.page === page ? _Take.active : null;
|
|
41
|
+
}
|
|
42
|
+
/** Seconds since the take started. */
|
|
43
|
+
now() {
|
|
44
|
+
return (Date.now() - this.t0) / 1e3;
|
|
45
|
+
}
|
|
46
|
+
/** Log a wait (written at once, so a take that fails part-way still has its cut list). */
|
|
47
|
+
log(label, start, end = this.now()) {
|
|
48
|
+
if (end - start < 1) return;
|
|
49
|
+
this.waits.push({ label, start: round(start), end: round(end) });
|
|
50
|
+
this.save();
|
|
51
|
+
}
|
|
52
|
+
/** Wait for `until` (a UI state), logged as a wait the edit may cut. */
|
|
53
|
+
async wait(label, until) {
|
|
54
|
+
const start = this.now();
|
|
55
|
+
try {
|
|
56
|
+
return await until();
|
|
57
|
+
} finally {
|
|
58
|
+
this.log(label, start);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Paint the sync marker: a magenta frame for a moment, at a logged time. The edit finds it in the
|
|
63
|
+
* video and lines the take's clock up with the video's. Call it before the first navigation.
|
|
64
|
+
*/
|
|
65
|
+
async markSync() {
|
|
66
|
+
await this.page.evaluate(
|
|
67
|
+
() => new Promise((done) => {
|
|
68
|
+
document.documentElement.style.background = "#ff00ff";
|
|
69
|
+
if (document.body) document.body.style.background = "#ff00ff";
|
|
70
|
+
requestAnimationFrame(() => requestAnimationFrame(() => done()));
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
this.timeline.sync = round(this.now());
|
|
74
|
+
await this.page.waitForTimeout(400);
|
|
75
|
+
this.save();
|
|
76
|
+
}
|
|
77
|
+
/** Log a focus event (a `point` with no end lasts until the next event). */
|
|
78
|
+
record(event) {
|
|
79
|
+
const { focus } = this.timeline;
|
|
80
|
+
const open = focus[focus.length - 1];
|
|
81
|
+
if (open && open.kind === "point" && open.end === void 0) open.end = round(event.start);
|
|
82
|
+
focus.push({
|
|
83
|
+
...event,
|
|
84
|
+
start: round(event.start),
|
|
85
|
+
end: event.end === void 0 ? void 0 : round(event.end)
|
|
86
|
+
});
|
|
87
|
+
this.save();
|
|
88
|
+
}
|
|
89
|
+
/** @internal The stage's cards, lower-thirds and captions (drawn by the edit). */
|
|
90
|
+
cardStart(card) {
|
|
91
|
+
this.cardEnd();
|
|
92
|
+
this.captionEnd();
|
|
93
|
+
this.timeline.cards.push({ ...card, start: round(this.now()), end: null });
|
|
94
|
+
this.save();
|
|
95
|
+
}
|
|
96
|
+
cardEnd() {
|
|
97
|
+
const last = this.timeline.cards[this.timeline.cards.length - 1];
|
|
98
|
+
if (last && last.end === null) {
|
|
99
|
+
last.end = round(this.now());
|
|
100
|
+
this.save();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
lowerThird(kicker, title, holdMs) {
|
|
104
|
+
const start = round(this.now());
|
|
105
|
+
this.timeline.lowerThirds.push({ kicker, title, start, end: round(start + holdMs / 1e3) });
|
|
106
|
+
this.save();
|
|
107
|
+
}
|
|
108
|
+
captionStart(text) {
|
|
109
|
+
this.captionEnd();
|
|
110
|
+
this.timeline.captions.push({ text, start: round(this.now()), end: null });
|
|
111
|
+
this.save();
|
|
112
|
+
}
|
|
113
|
+
captionEnd() {
|
|
114
|
+
const last = this.timeline.captions[this.timeline.captions.length - 1];
|
|
115
|
+
if (last && last.end === null) {
|
|
116
|
+
last.end = round(this.now());
|
|
117
|
+
this.save();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
save() {
|
|
121
|
+
const { zoom, maxZoom, pip } = this.options;
|
|
122
|
+
writeFileSync(this.file, JSON.stringify({ t0: this.t0, waits: this.waits }, null, 1));
|
|
123
|
+
writeFileSync(
|
|
124
|
+
this.focusFile,
|
|
125
|
+
JSON.stringify(
|
|
126
|
+
{ t0: this.t0, config: { zoom, maxZoom, pip }, brand: this.brand, ...this.timeline },
|
|
127
|
+
null,
|
|
128
|
+
1
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
2
133
|
var DEFAULTS = {
|
|
3
134
|
displayFont: "ui-serif, Georgia, serif",
|
|
4
135
|
sansFont: "Inter, ui-sans-serif, system-ui, sans-serif",
|
|
@@ -9,9 +140,8 @@ var DEFAULTS = {
|
|
|
9
140
|
ink: "#0e1b2d",
|
|
10
141
|
coverTitle: "#f4f6f8"
|
|
11
142
|
};
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
const skin = {
|
|
143
|
+
function cinematicSkin(b) {
|
|
144
|
+
return {
|
|
15
145
|
firmLabel: b.firmLabel,
|
|
16
146
|
displayFont: b.displayFont ?? DEFAULTS.displayFont,
|
|
17
147
|
sansFont: b.sansFont ?? DEFAULTS.sansFont,
|
|
@@ -23,8 +153,13 @@ function createCinematic(config) {
|
|
|
23
153
|
coverTo: b.cover?.to ?? DEFAULTS.cover.to,
|
|
24
154
|
accent: b.accent ?? DEFAULTS.accent,
|
|
25
155
|
ink: b.ink ?? DEFAULTS.ink,
|
|
26
|
-
coverTitle: b.coverTitle ?? DEFAULTS.coverTitle
|
|
156
|
+
coverTitle: b.coverTitle ?? DEFAULTS.coverTitle,
|
|
157
|
+
fontFiles: b.fontFiles ?? []
|
|
27
158
|
};
|
|
159
|
+
}
|
|
160
|
+
function createCinematic(config) {
|
|
161
|
+
const b = config.brand;
|
|
162
|
+
const skin = cinematicSkin(b);
|
|
28
163
|
const loginPath = config.loginPath ?? "/app/login";
|
|
29
164
|
const appUrlPattern = config.appUrlPattern ?? /\/app$/;
|
|
30
165
|
const ids = {
|
|
@@ -33,113 +168,207 @@ function createCinematic(config) {
|
|
|
33
168
|
submit: config.testIds?.submit ?? "auth-login-submit"
|
|
34
169
|
};
|
|
35
170
|
const resolve = (page, t) => typeof t === "string" ? page.getByTestId(t) : t;
|
|
171
|
+
async function boxOf(page, loc) {
|
|
172
|
+
const box = await loc.boundingBox().catch(() => null);
|
|
173
|
+
const vp = page.viewportSize();
|
|
174
|
+
if (!box || !vp) return box;
|
|
175
|
+
const x = Math.max(box.x, 0);
|
|
176
|
+
const y = Math.max(box.y, 0);
|
|
177
|
+
const width = Math.min(box.x + box.width, vp.width) - x;
|
|
178
|
+
const height = Math.min(box.y + box.height, vp.height) - y;
|
|
179
|
+
return width > 0 && height > 0 ? { x: round(x), y: round(y), width: round(width), height: round(height) } : null;
|
|
180
|
+
}
|
|
181
|
+
async function glide(page, target) {
|
|
182
|
+
const loc = resolve(page, target).first();
|
|
183
|
+
await loc.scrollIntoViewIfNeeded().catch(() => {
|
|
184
|
+
});
|
|
185
|
+
const box = await boxOf(page, loc);
|
|
186
|
+
if (box) {
|
|
187
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 });
|
|
188
|
+
await page.waitForTimeout(180);
|
|
189
|
+
}
|
|
190
|
+
return { loc, box };
|
|
191
|
+
}
|
|
192
|
+
async function take(page, options) {
|
|
193
|
+
return new Take(page, options, b);
|
|
194
|
+
}
|
|
36
195
|
async function installStage(page) {
|
|
37
|
-
await page.addInitScript(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
196
|
+
await page.addInitScript(
|
|
197
|
+
({ s, t0, TC }) => {
|
|
198
|
+
const boot = () => {
|
|
199
|
+
if (document.getElementById("__cine_cursor")) return;
|
|
200
|
+
const cursor = document.createElement("div");
|
|
201
|
+
cursor.id = "__cine_cursor";
|
|
202
|
+
cursor.style.cssText = `position:fixed;left:0;top:0;z-index:2147483647;width:22px;height:22px;margin:-11px 0 0 -11px;border-radius:50%;pointer-events:none;transition:transform .09s ease-out;background:radial-gradient(circle at 35% 35%, ${s.cursorFrom}, ${s.cursorTo});box-shadow:0 0 0 3px rgba(255,255,255,.6),0 2px 10px rgba(0,0,0,.4);`;
|
|
203
|
+
document.body.appendChild(cursor);
|
|
204
|
+
window.addEventListener(
|
|
205
|
+
"mousemove",
|
|
206
|
+
(e) => {
|
|
207
|
+
cursor.style.transform = `translate(${e.clientX}px,${e.clientY}px)`;
|
|
208
|
+
},
|
|
209
|
+
true
|
|
210
|
+
);
|
|
211
|
+
window.addEventListener(
|
|
212
|
+
"mousedown",
|
|
213
|
+
(e) => {
|
|
214
|
+
const m = e;
|
|
215
|
+
const r = document.createElement("div");
|
|
216
|
+
r.style.cssText = `position:fixed;left:${m.clientX}px;top:${m.clientY}px;z-index:2147483646;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;pointer-events:none;border:2px solid ${s.ripple};animation:__cine_ripple .55s ease-out forwards;`;
|
|
217
|
+
document.body.appendChild(r);
|
|
218
|
+
setTimeout(() => r.remove(), 600);
|
|
219
|
+
},
|
|
220
|
+
true
|
|
221
|
+
);
|
|
222
|
+
const cap = document.createElement("div");
|
|
223
|
+
cap.id = "__cine_cap";
|
|
224
|
+
cap.style.cssText = `position:fixed;left:50%;bottom:34px;transform:translateX(-50%) translateY(8px);z-index:2147483646;max-width:72%;padding:13px 24px;border-radius:13px;font:500 17px/1.45 ${s.sansFont};color:#fff;letter-spacing:.01em;background:${s.ink}e0;backdrop-filter:blur(7px);box-shadow:0 10px 34px rgba(0,0,0,.4);text-align:center;opacity:0;transition:opacity .35s,transform .35s;`;
|
|
225
|
+
document.body.appendChild(cap);
|
|
226
|
+
const style = document.createElement("style");
|
|
227
|
+
style.textContent = "@keyframes __cine_ripple{to{width:46px;height:46px;margin:-23px 0 0 -23px;opacity:0}}";
|
|
228
|
+
document.head.appendChild(style);
|
|
229
|
+
window.__cine_cap = (t) => {
|
|
230
|
+
cap.textContent = t;
|
|
231
|
+
cap.style.opacity = t ? "1" : "0";
|
|
232
|
+
cap.style.transform = `translateX(-50%) translateY(${t ? 0 : 8}px)`;
|
|
233
|
+
};
|
|
234
|
+
if (t0 !== null) {
|
|
235
|
+
const strip = document.createElement("div");
|
|
236
|
+
strip.id = "__cine_timecode";
|
|
237
|
+
strip.style.cssText = `position:fixed;right:0;bottom:0;z-index:2147483647;display:flex;pointer-events:none;height:${TC}px`;
|
|
238
|
+
const cells = Array.from({ length: 18 }, (_, i) => {
|
|
239
|
+
const cell = document.createElement("div");
|
|
240
|
+
cell.style.cssText = `width:${TC}px;height:${TC}px;background:${i === 0 ? "#fff" : "#000"}`;
|
|
241
|
+
strip.appendChild(cell);
|
|
242
|
+
return cell;
|
|
243
|
+
});
|
|
244
|
+
document.body.appendChild(strip);
|
|
245
|
+
const tick = () => {
|
|
246
|
+
const ds = Math.max(0, Math.round((Date.now() - t0) / 100)) & 65535;
|
|
247
|
+
for (let bit = 0; bit < 16; bit += 1)
|
|
248
|
+
cells[bit + 2].style.background = ds >> 15 - bit & 1 ? "#fff" : "#000";
|
|
249
|
+
requestAnimationFrame(tick);
|
|
250
|
+
};
|
|
251
|
+
tick();
|
|
252
|
+
}
|
|
73
253
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
254
|
+
if (document.body) boot();
|
|
255
|
+
else document.addEventListener("DOMContentLoaded", boot);
|
|
256
|
+
},
|
|
257
|
+
{ s: skin, t0: Take.on(page)?.t0 ?? null, TC: TIMECODE_CELL }
|
|
258
|
+
);
|
|
78
259
|
}
|
|
79
260
|
async function cover(page, { kicker, title, subtitle }, holdMs = 2600) {
|
|
261
|
+
const running = Take.on(page);
|
|
262
|
+
running?.cardStart({ kicker, title, subtitle });
|
|
80
263
|
await page.evaluate(
|
|
81
|
-
({ kicker: kicker2, title: title2, subtitle: subtitle2, s }) => {
|
|
264
|
+
({ kicker: kicker2, title: title2, subtitle: subtitle2, s, instant }) => {
|
|
82
265
|
document.getElementById("__cine_cover")?.remove();
|
|
83
266
|
const el = document.createElement("div");
|
|
84
267
|
el.id = "__cine_cover";
|
|
85
|
-
el.style.cssText = `position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 8%;background:linear-gradient(135deg,${s.coverFrom} 0%,${s.coverMid} 48%,${s.coverTo} 165%);opacity:0;transition:opacity .
|
|
268
|
+
el.style.cssText = `position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 8%;background:linear-gradient(135deg,${s.coverFrom} 0%,${s.coverMid} 48%,${s.coverTo} 165%);opacity:${instant ? 1 : 0};transition:opacity ${instant ? 0 : 0.5}s ease;font-family:${s.sansFont};`;
|
|
86
269
|
el.innerHTML = `<div style="text-transform:uppercase;letter-spacing:.26em;font-size:13px;font-weight:600;color:${s.accent};margin-bottom:22px">${kicker2}</div><div style="font-family:${s.displayFont};font-weight:600;font-size:52px;line-height:1.08;letter-spacing:-.02em;color:${s.coverTitle};max-width:16ch">${title2}</div>` + (subtitle2 ? `<div style="margin-top:22px;font-size:19px;line-height:1.5;color:${s.coverTitle}b8;max-width:46ch">${subtitle2}</div>` : "") + `<div style="margin-top:44px;display:flex;align-items:center;gap:10px;color:${s.coverTitle}80;font-size:13px;letter-spacing:.02em"><span style="width:26px;height:1px;background:${s.accent}"></span>${s.firmLabel}</div>`;
|
|
87
270
|
document.body.appendChild(el);
|
|
88
271
|
requestAnimationFrame(() => el.style.opacity = "1");
|
|
89
272
|
},
|
|
90
|
-
{ kicker, title, subtitle: subtitle ?? "", s: skin }
|
|
273
|
+
{ kicker, title, subtitle: subtitle ?? "", s: skin, instant: !!running }
|
|
91
274
|
);
|
|
92
275
|
await page.waitForTimeout(holdMs);
|
|
93
276
|
}
|
|
94
|
-
async function
|
|
277
|
+
async function chapter(page, card, holdMs) {
|
|
278
|
+
await cover(page, card, holdMs);
|
|
95
279
|
await page.evaluate(() => {
|
|
280
|
+
const el = document.getElementById("__cine_cover");
|
|
281
|
+
if (el) el.style.pointerEvents = "none";
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async function uncover(page) {
|
|
285
|
+
const running = Take.on(page);
|
|
286
|
+
await page.evaluate((instant) => {
|
|
96
287
|
const el = document.getElementById("__cine_cover");
|
|
97
288
|
if (!el) return;
|
|
289
|
+
if (instant) return el.remove();
|
|
98
290
|
el.style.opacity = "0";
|
|
99
291
|
setTimeout(() => el.remove(), 520);
|
|
100
|
-
});
|
|
292
|
+
}, !!running);
|
|
293
|
+
running?.cardEnd();
|
|
101
294
|
await page.waitForTimeout(560);
|
|
102
295
|
}
|
|
296
|
+
function lowerThird(page, kicker, title, holdMs = 3200) {
|
|
297
|
+
Take.on(page)?.lowerThird(kicker, title, holdMs);
|
|
298
|
+
}
|
|
103
299
|
async function caption(page, text, holdMs) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
300
|
+
const running = Take.on(page);
|
|
301
|
+
if (running) running.captionStart(text);
|
|
302
|
+
else
|
|
303
|
+
await page.evaluate(
|
|
304
|
+
(t) => window.__cine_cap?.(t),
|
|
305
|
+
text
|
|
306
|
+
);
|
|
108
307
|
const hold = holdMs ?? Math.min(4200, 900 + text.length * 45);
|
|
109
308
|
await page.waitForTimeout(hold);
|
|
110
309
|
}
|
|
111
310
|
async function clearCaption(page) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
311
|
+
const running = Take.on(page);
|
|
312
|
+
if (running) running.captionEnd();
|
|
313
|
+
else
|
|
314
|
+
await page.evaluate(
|
|
315
|
+
() => window.__cine_cap?.("")
|
|
316
|
+
);
|
|
115
317
|
}
|
|
116
318
|
async function beat(page, ms = 650) {
|
|
117
319
|
await page.waitForTimeout(ms);
|
|
118
320
|
}
|
|
119
321
|
async function glideTo(page, target) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
if (box) {
|
|
125
|
-
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 });
|
|
126
|
-
await page.waitForTimeout(180);
|
|
127
|
-
}
|
|
322
|
+
const running = Take.on(page);
|
|
323
|
+
const start = running?.now() ?? 0;
|
|
324
|
+
const { loc, box } = await glide(page, target);
|
|
325
|
+
if (running && box) running.record({ kind: "point", start, box });
|
|
128
326
|
return loc;
|
|
129
327
|
}
|
|
130
328
|
async function click(page, target) {
|
|
131
|
-
const
|
|
329
|
+
const running = Take.on(page);
|
|
330
|
+
const start = running?.now() ?? 0;
|
|
331
|
+
const { loc, box } = await glide(page, target);
|
|
132
332
|
await loc.click();
|
|
133
333
|
await page.waitForTimeout(260);
|
|
334
|
+
if (running && box) running.record({ kind: "click", start, end: running.now(), box });
|
|
134
335
|
}
|
|
135
336
|
async function type(page, target, text) {
|
|
136
|
-
const
|
|
337
|
+
const running = Take.on(page);
|
|
338
|
+
const start = running?.now() ?? 0;
|
|
339
|
+
const { loc, box } = await glide(page, target);
|
|
137
340
|
await loc.click();
|
|
138
341
|
await loc.fill("");
|
|
139
342
|
await loc.pressSequentially(text, { delay: 42 });
|
|
140
343
|
await page.waitForTimeout(240);
|
|
344
|
+
if (running && box) running.record({ kind: "type", start, end: running.now(), box });
|
|
345
|
+
}
|
|
346
|
+
async function focusOn(page, target, label, {
|
|
347
|
+
holdMs = 2200,
|
|
348
|
+
callout = true,
|
|
349
|
+
mode
|
|
350
|
+
} = {}) {
|
|
351
|
+
const running = Take.on(page);
|
|
352
|
+
const start = running?.now() ?? 0;
|
|
353
|
+
const { box } = await glide(page, target);
|
|
354
|
+
await page.waitForTimeout(holdMs);
|
|
355
|
+
if (running && box)
|
|
356
|
+
running.record({ kind: "focus", label, start, end: running.now(), box, callout, mode });
|
|
357
|
+
}
|
|
358
|
+
async function readDown(page, target, scrolls = 3, pauseMs = 1600) {
|
|
359
|
+
const running = Take.on(page);
|
|
360
|
+
const start = running?.now() ?? 0;
|
|
361
|
+
const { box } = await glide(page, target);
|
|
362
|
+
for (let i = 0; i < scrolls; i += 1) {
|
|
363
|
+
await page.mouse.wheel(0, 320);
|
|
364
|
+
await beat(page, pauseMs);
|
|
365
|
+
}
|
|
366
|
+
await page.mouse.wheel(0, -320 * scrolls);
|
|
367
|
+
await beat(page, 500);
|
|
368
|
+
if (running && box) running.record({ kind: "read", start, end: running.now(), box });
|
|
141
369
|
}
|
|
142
370
|
async function login(page, opts = {}) {
|
|
371
|
+
if (!config.creds) throw new Error("createCinematic: pass creds to sign in");
|
|
143
372
|
await page.goto(loginPath);
|
|
144
373
|
if (opts.show === false) {
|
|
145
374
|
await page.getByTestId(ids.email).fill(config.creds.email);
|
|
@@ -156,19 +385,24 @@ function createCinematic(config) {
|
|
|
156
385
|
await beat(page, 800);
|
|
157
386
|
}
|
|
158
387
|
return {
|
|
388
|
+
take,
|
|
159
389
|
installStage,
|
|
160
390
|
cover,
|
|
391
|
+
chapter,
|
|
161
392
|
uncover,
|
|
393
|
+
lowerThird,
|
|
162
394
|
caption,
|
|
163
395
|
clearCaption,
|
|
164
396
|
beat,
|
|
165
397
|
glideTo,
|
|
166
398
|
click,
|
|
167
399
|
type,
|
|
400
|
+
focusOn,
|
|
401
|
+
readDown,
|
|
168
402
|
login
|
|
169
403
|
};
|
|
170
404
|
}
|
|
171
405
|
|
|
172
|
-
export { createCinematic };
|
|
406
|
+
export { TIMECODE_CELL, Take, cinematicSkin, createCinematic };
|
|
173
407
|
//# sourceMappingURL=index.js.map
|
|
174
408
|
//# sourceMappingURL=index.js.map
|