@arbidocs/blocks 0.3.118 → 0.3.120

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.
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ var test = require('@playwright/test');
4
+
5
+ // src/record/index.ts
6
+ var DEFAULTS = {
7
+ displayFont: "ui-serif, Georgia, serif",
8
+ sansFont: "Inter, ui-sans-serif, system-ui, sans-serif",
9
+ cursor: { from: "rgba(31,111,235,.98)", to: "rgba(11,42,74,.92)" },
10
+ ripple: "rgba(11,42,74,.85)",
11
+ cover: { from: "#0b1622", mid: "#0e1b2c", to: "#1f6feb" },
12
+ accent: "#c9a24b",
13
+ ink: "#0e1b2d",
14
+ coverTitle: "#f4f6f8"
15
+ };
16
+ function createCinematic(config) {
17
+ const b = config.brand;
18
+ const skin = {
19
+ firmLabel: b.firmLabel,
20
+ displayFont: b.displayFont ?? DEFAULTS.displayFont,
21
+ sansFont: b.sansFont ?? DEFAULTS.sansFont,
22
+ cursorFrom: b.cursor?.from ?? DEFAULTS.cursor.from,
23
+ cursorTo: b.cursor?.to ?? DEFAULTS.cursor.to,
24
+ ripple: b.ripple ?? DEFAULTS.ripple,
25
+ coverFrom: b.cover?.from ?? DEFAULTS.cover.from,
26
+ coverMid: b.cover?.mid ?? DEFAULTS.cover.mid,
27
+ coverTo: b.cover?.to ?? DEFAULTS.cover.to,
28
+ accent: b.accent ?? DEFAULTS.accent,
29
+ ink: b.ink ?? DEFAULTS.ink,
30
+ coverTitle: b.coverTitle ?? DEFAULTS.coverTitle
31
+ };
32
+ const loginPath = config.loginPath ?? "/app/login";
33
+ const appUrlPattern = config.appUrlPattern ?? /\/app$/;
34
+ const ids = {
35
+ email: config.testIds?.email ?? "auth-email",
36
+ password: config.testIds?.password ?? "auth-password",
37
+ submit: config.testIds?.submit ?? "auth-login-submit"
38
+ };
39
+ const resolve = (page, t) => typeof t === "string" ? page.getByTestId(t) : t;
40
+ async function installStage(page) {
41
+ await page.addInitScript((s) => {
42
+ const boot = () => {
43
+ if (document.getElementById("__cine_cursor")) return;
44
+ const cursor = document.createElement("div");
45
+ cursor.id = "__cine_cursor";
46
+ 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);`;
47
+ document.body.appendChild(cursor);
48
+ window.addEventListener(
49
+ "mousemove",
50
+ (e) => {
51
+ cursor.style.transform = `translate(${e.clientX}px,${e.clientY}px)`;
52
+ },
53
+ true
54
+ );
55
+ window.addEventListener(
56
+ "mousedown",
57
+ (e) => {
58
+ const m = e;
59
+ const r = document.createElement("div");
60
+ 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;`;
61
+ document.body.appendChild(r);
62
+ setTimeout(() => r.remove(), 600);
63
+ },
64
+ true
65
+ );
66
+ const cap = document.createElement("div");
67
+ cap.id = "__cine_cap";
68
+ 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;`;
69
+ document.body.appendChild(cap);
70
+ const style = document.createElement("style");
71
+ style.textContent = "@keyframes __cine_ripple{to{width:46px;height:46px;margin:-23px 0 0 -23px;opacity:0}}";
72
+ document.head.appendChild(style);
73
+ window.__cine_cap = (t) => {
74
+ cap.textContent = t;
75
+ cap.style.opacity = t ? "1" : "0";
76
+ cap.style.transform = `translateX(-50%) translateY(${t ? 0 : 8}px)`;
77
+ };
78
+ };
79
+ if (document.body) boot();
80
+ else document.addEventListener("DOMContentLoaded", boot);
81
+ }, skin);
82
+ }
83
+ async function cover(page, { kicker, title, subtitle }, holdMs = 2600) {
84
+ await page.evaluate(
85
+ ({ kicker: kicker2, title: title2, subtitle: subtitle2, s }) => {
86
+ document.getElementById("__cine_cover")?.remove();
87
+ const el = document.createElement("div");
88
+ el.id = "__cine_cover";
89
+ 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 .5s ease;font-family:${s.sansFont};`;
90
+ 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>`;
91
+ document.body.appendChild(el);
92
+ requestAnimationFrame(() => el.style.opacity = "1");
93
+ },
94
+ { kicker, title, subtitle: subtitle ?? "", s: skin }
95
+ );
96
+ await page.waitForTimeout(holdMs);
97
+ }
98
+ async function uncover(page) {
99
+ await page.evaluate(() => {
100
+ const el = document.getElementById("__cine_cover");
101
+ if (!el) return;
102
+ el.style.opacity = "0";
103
+ setTimeout(() => el.remove(), 520);
104
+ });
105
+ await page.waitForTimeout(560);
106
+ }
107
+ async function caption(page, text, holdMs) {
108
+ await page.evaluate(
109
+ (t) => window.__cine_cap?.(t),
110
+ text
111
+ );
112
+ const hold = holdMs ?? Math.min(4200, 900 + text.length * 45);
113
+ await page.waitForTimeout(hold);
114
+ }
115
+ async function clearCaption(page) {
116
+ await page.evaluate(
117
+ () => window.__cine_cap?.("")
118
+ );
119
+ }
120
+ async function beat(page, ms = 650) {
121
+ await page.waitForTimeout(ms);
122
+ }
123
+ async function glideTo(page, target) {
124
+ const loc = resolve(page, target).first();
125
+ await loc.scrollIntoViewIfNeeded().catch(() => {
126
+ });
127
+ const box = await loc.boundingBox();
128
+ if (box) {
129
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 });
130
+ await page.waitForTimeout(180);
131
+ }
132
+ return loc;
133
+ }
134
+ async function click(page, target) {
135
+ const loc = await glideTo(page, target);
136
+ await loc.click();
137
+ await page.waitForTimeout(260);
138
+ }
139
+ async function type(page, target, text) {
140
+ const loc = await glideTo(page, target);
141
+ await loc.click();
142
+ await loc.fill("");
143
+ await loc.pressSequentially(text, { delay: 42 });
144
+ await page.waitForTimeout(240);
145
+ }
146
+ async function login(page, opts = {}) {
147
+ await page.goto(loginPath);
148
+ if (opts.show === false) {
149
+ await page.getByTestId(ids.email).fill(config.creds.email);
150
+ await page.getByTestId(ids.password).fill(config.creds.password);
151
+ await page.getByTestId(ids.submit).click();
152
+ await test.expect(page).toHaveURL(appUrlPattern, { timeout: 45e3 });
153
+ return;
154
+ }
155
+ await caption(page, opts.caption ?? `${b.firmLabel} \u2014 signing in.`, 1600);
156
+ await type(page, ids.email, config.creds.email);
157
+ await type(page, ids.password, config.creds.password);
158
+ await click(page, ids.submit);
159
+ await test.expect(page).toHaveURL(appUrlPattern, { timeout: 45e3 });
160
+ await beat(page, 800);
161
+ }
162
+ return {
163
+ installStage,
164
+ cover,
165
+ uncover,
166
+ caption,
167
+ clearCaption,
168
+ beat,
169
+ glideTo,
170
+ click,
171
+ type,
172
+ login
173
+ };
174
+ }
175
+
176
+ exports.createCinematic = createCinematic;
177
+ //# sourceMappingURL=index.cjs.map
178
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/record/index.ts"],"names":["kicker","title","subtitle","expect"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Cinematic recording harness — firm-agnostic.\n *\n * Turns a Playwright run into a narrated product demo: a visible cursor that\n * glides between targets, a click ripple, a caption bar that narrates each step,\n * full-screen chapter \"cover\" cards, and human-speed pacing. Every firm app gets\n * the same polished demo capability from one factory — brand colours, fonts,\n * credentials and the login flow arrive as config, so nothing here names a firm.\n *\n * Pair with a Playwright project that records video (see the firm's\n * `playwright.record.config.ts`: video on, a large viewport, some slowMo).\n *\n * Usage in a `*.record.ts` spec:\n * const cine = createCinematic({ brand, creds })\n * await cine.installStage(page) // before the first navigation\n * await cine.cover(page, { kicker, title, subtitle })\n * await cine.login(page)\n * await cine.uncover(page)\n * await cine.caption(page, \"Opening the flagship matter…\")\n * await cine.click(page, \"matters-row-…\")\n *\n * `@playwright/test` is a peer dependency — it is only imported by e2e specs, so\n * it never reaches an app bundle.\n */\nimport { type Page, type Locator, expect } from '@playwright/test'\n\n/** Brand skin for the on-screen chrome. All optional — sensible neutral defaults. */\nexport interface CinematicBrand {\n /** Footer line on chapter cards, e.g. \"Meridian · AI-native counsel\". */\n firmLabel: string\n /** Serif display font for cover titles (CSS font-family value). */\n displayFont?: string\n /** Sans font for captions + cover body. */\n sansFont?: string\n /** Cursor dot radial-gradient stops. */\n cursor?: { from: string; to: string }\n /** Click-ripple ring colour. */\n ripple?: string\n /** Chapter-card background gradient stops. */\n cover?: { from: string; mid: string; to: string }\n /** Accent (kicker text + rule) on chapter cards. */\n accent?: string\n /** Caption bar background base colour. */\n ink?: string\n /** Cover title text colour. */\n coverTitle?: string\n}\n\nexport interface CinematicCreds {\n email: string\n password: string\n}\n\nexport interface CinematicConfig {\n brand: CinematicBrand\n creds: CinematicCreds\n /** Login route (default \"/app/login\"). */\n loginPath?: string\n /** URL the app lands on after auth (default /\\/app$/). */\n appUrlPattern?: RegExp\n /** Login form testids (defaults match the shared AppShell/login convention). */\n testIds?: { email?: string; password?: string; submit?: string }\n}\n\nexport interface Cinematic {\n installStage(page: Page): Promise<void>\n cover(\n page: Page,\n opts: { kicker: string; title: string; subtitle?: string },\n holdMs?: number\n ): Promise<void>\n uncover(page: Page): Promise<void>\n caption(page: Page, text: string, holdMs?: number): Promise<void>\n clearCaption(page: Page): Promise<void>\n beat(page: Page, ms?: number): Promise<void>\n glideTo(page: Page, target: string | Locator): Promise<Locator>\n click(page: Page, target: string | Locator): Promise<void>\n type(page: Page, target: string | Locator, text: string): Promise<void>\n /** Sign in. `{ show: false }` authenticates fast + silent (no animation/caption). */\n login(page: Page, opts?: { show?: boolean; caption?: string }): Promise<void>\n}\n\nconst DEFAULTS = {\n displayFont: 'ui-serif, Georgia, serif',\n sansFont: 'Inter, ui-sans-serif, system-ui, sans-serif',\n cursor: { from: 'rgba(31,111,235,.98)', to: 'rgba(11,42,74,.92)' },\n ripple: 'rgba(11,42,74,.85)',\n cover: { from: '#0b1622', mid: '#0e1b2c', to: '#1f6feb' },\n accent: '#c9a24b',\n ink: '#0e1b2d',\n coverTitle: '#f4f6f8',\n}\n\nexport function createCinematic(config: CinematicConfig): Cinematic {\n const b = config.brand\n const skin = {\n firmLabel: b.firmLabel,\n displayFont: b.displayFont ?? DEFAULTS.displayFont,\n sansFont: b.sansFont ?? DEFAULTS.sansFont,\n cursorFrom: b.cursor?.from ?? DEFAULTS.cursor.from,\n cursorTo: b.cursor?.to ?? DEFAULTS.cursor.to,\n ripple: b.ripple ?? DEFAULTS.ripple,\n coverFrom: b.cover?.from ?? DEFAULTS.cover.from,\n coverMid: b.cover?.mid ?? DEFAULTS.cover.mid,\n coverTo: b.cover?.to ?? DEFAULTS.cover.to,\n accent: b.accent ?? DEFAULTS.accent,\n ink: b.ink ?? DEFAULTS.ink,\n coverTitle: b.coverTitle ?? DEFAULTS.coverTitle,\n }\n const loginPath = config.loginPath ?? '/app/login'\n const appUrlPattern = config.appUrlPattern ?? /\\/app$/\n const ids = {\n email: config.testIds?.email ?? 'auth-email',\n password: config.testIds?.password ?? 'auth-password',\n submit: config.testIds?.submit ?? 'auth-login-submit',\n }\n\n const resolve = (page: Page, t: string | Locator): Locator =>\n typeof t === 'string' ? page.getByTestId(t) : t\n\n async function installStage(page: Page): Promise<void> {\n await page.addInitScript((s: typeof skin) => {\n const boot = () => {\n if (document.getElementById('__cine_cursor')) return\n\n const cursor = document.createElement('div')\n cursor.id = '__cine_cursor'\n cursor.style.cssText =\n 'position:fixed;left:0;top:0;z-index:2147483647;width:22px;height:22px;' +\n 'margin:-11px 0 0 -11px;border-radius:50%;pointer-events:none;' +\n 'transition:transform .09s ease-out;' +\n `background:radial-gradient(circle at 35% 35%, ${s.cursorFrom}, ${s.cursorTo});` +\n 'box-shadow:0 0 0 3px rgba(255,255,255,.6),0 2px 10px rgba(0,0,0,.4);'\n document.body.appendChild(cursor)\n\n window.addEventListener(\n 'mousemove',\n (e) => {\n cursor.style.transform = `translate(${(e as MouseEvent).clientX}px,${(e as MouseEvent).clientY}px)`\n },\n true\n )\n window.addEventListener(\n 'mousedown',\n (e) => {\n const m = e as MouseEvent\n const r = document.createElement('div')\n r.style.cssText =\n `position:fixed;left:${m.clientX}px;top:${m.clientY}px;z-index:2147483646;` +\n 'width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;pointer-events:none;' +\n `border:2px solid ${s.ripple};animation:__cine_ripple .55s ease-out forwards;`\n document.body.appendChild(r)\n setTimeout(() => r.remove(), 600)\n },\n true\n )\n\n const cap = document.createElement('div')\n cap.id = '__cine_cap'\n cap.style.cssText =\n 'position:fixed;left:50%;bottom:34px;transform:translateX(-50%) translateY(8px);' +\n 'z-index:2147483646;max-width:72%;padding:13px 24px;border-radius:13px;' +\n `font:500 17px/1.45 ${s.sansFont};color:#fff;letter-spacing:.01em;` +\n `background:${s.ink}e0;backdrop-filter:blur(7px);` +\n 'box-shadow:0 10px 34px rgba(0,0,0,.4);text-align:center;opacity:0;transition:opacity .35s,transform .35s;'\n document.body.appendChild(cap)\n\n const style = document.createElement('style')\n style.textContent =\n '@keyframes __cine_ripple{to{width:46px;height:46px;margin:-23px 0 0 -23px;opacity:0}}'\n document.head.appendChild(style)\n\n ;(window as unknown as { __cine_cap: (t: string) => void }).__cine_cap = (t: string) => {\n cap.textContent = t\n cap.style.opacity = t ? '1' : '0'\n cap.style.transform = `translateX(-50%) translateY(${t ? 0 : 8}px)`\n }\n }\n if (document.body) boot()\n else document.addEventListener('DOMContentLoaded', boot)\n }, skin)\n }\n\n async function cover(\n page: Page,\n { kicker, title, subtitle }: { kicker: string; title: string; subtitle?: string },\n holdMs = 2600\n ): Promise<void> {\n await page.evaluate(\n ({ kicker, title, subtitle, s }) => {\n document.getElementById('__cine_cover')?.remove()\n const el = document.createElement('div')\n el.id = '__cine_cover'\n el.style.cssText =\n 'position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;' +\n 'align-items:center;justify-content:center;text-align:center;padding:0 8%;' +\n `background:linear-gradient(135deg,${s.coverFrom} 0%,${s.coverMid} 48%,${s.coverTo} 165%);` +\n `opacity:0;transition:opacity .5s ease;font-family:${s.sansFont};`\n el.innerHTML =\n `<div style=\"text-transform:uppercase;letter-spacing:.26em;font-size:13px;font-weight:600;color:${s.accent};margin-bottom:22px\">${kicker}</div>` +\n `<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\">${title}</div>` +\n (subtitle\n ? `<div style=\"margin-top:22px;font-size:19px;line-height:1.5;color:${s.coverTitle}b8;max-width:46ch\">${subtitle}</div>`\n : '') +\n `<div style=\"margin-top:44px;display:flex;align-items:center;gap:10px;color:${s.coverTitle}80;font-size:13px;letter-spacing:.02em\">` +\n `<span style=\"width:26px;height:1px;background:${s.accent}\"></span>${s.firmLabel}</div>`\n document.body.appendChild(el)\n requestAnimationFrame(() => (el.style.opacity = '1'))\n },\n { kicker, title, subtitle: subtitle ?? '', s: skin }\n )\n await page.waitForTimeout(holdMs)\n }\n\n async function uncover(page: Page): Promise<void> {\n await page.evaluate(() => {\n const el = document.getElementById('__cine_cover')\n if (!el) return\n el.style.opacity = '0'\n setTimeout(() => el.remove(), 520)\n })\n await page.waitForTimeout(560)\n }\n\n async function caption(page: Page, text: string, holdMs?: number): Promise<void> {\n await page.evaluate(\n (t) => (window as unknown as { __cine_cap?: (s: string) => void }).__cine_cap?.(t),\n text\n )\n const hold = holdMs ?? Math.min(4200, 900 + text.length * 45)\n await page.waitForTimeout(hold)\n }\n\n async function clearCaption(page: Page): Promise<void> {\n await page.evaluate(() =>\n (window as unknown as { __cine_cap?: (s: string) => void }).__cine_cap?.('')\n )\n }\n\n async function beat(page: Page, ms = 650): Promise<void> {\n await page.waitForTimeout(ms)\n }\n\n async function glideTo(page: Page, target: string | Locator): Promise<Locator> {\n const loc = resolve(page, target).first()\n await loc.scrollIntoViewIfNeeded().catch(() => {})\n const box = await loc.boundingBox()\n if (box) {\n await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 })\n await page.waitForTimeout(180)\n }\n return loc\n }\n\n async function click(page: Page, target: string | Locator): Promise<void> {\n const loc = await glideTo(page, target)\n await loc.click()\n await page.waitForTimeout(260)\n }\n\n async function type(page: Page, target: string | Locator, text: string): Promise<void> {\n const loc = await glideTo(page, target)\n await loc.click()\n await loc.fill('')\n await loc.pressSequentially(text, { delay: 42 })\n await page.waitForTimeout(240)\n }\n\n async function login(page: Page, opts: { show?: boolean; caption?: string } = {}): Promise<void> {\n await page.goto(loginPath)\n if (opts.show === false) {\n await page.getByTestId(ids.email).fill(config.creds.email)\n await page.getByTestId(ids.password).fill(config.creds.password)\n await page.getByTestId(ids.submit).click()\n await expect(page).toHaveURL(appUrlPattern, { timeout: 45_000 })\n return\n }\n await caption(page, opts.caption ?? `${b.firmLabel} — signing in.`, 1600)\n await type(page, ids.email, config.creds.email)\n await type(page, ids.password, config.creds.password)\n await click(page, ids.submit)\n await expect(page).toHaveURL(appUrlPattern, { timeout: 45_000 })\n await beat(page, 800)\n }\n\n return {\n installStage,\n cover,\n uncover,\n caption,\n clearCaption,\n beat,\n glideTo,\n click,\n type,\n login,\n }\n}\n"]}
@@ -0,0 +1,96 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+
3
+ /**
4
+ * Cinematic recording harness — firm-agnostic.
5
+ *
6
+ * Turns a Playwright run into a narrated product demo: a visible cursor that
7
+ * glides between targets, a click ripple, a caption bar that narrates each step,
8
+ * full-screen chapter "cover" cards, and human-speed pacing. Every firm app gets
9
+ * the same polished demo capability from one factory — brand colours, fonts,
10
+ * credentials and the login flow arrive as config, so nothing here names a firm.
11
+ *
12
+ * Pair with a Playwright project that records video (see the firm's
13
+ * `playwright.record.config.ts`: video on, a large viewport, some slowMo).
14
+ *
15
+ * Usage in a `*.record.ts` spec:
16
+ * const cine = createCinematic({ brand, creds })
17
+ * await cine.installStage(page) // before the first navigation
18
+ * await cine.cover(page, { kicker, title, subtitle })
19
+ * await cine.login(page)
20
+ * await cine.uncover(page)
21
+ * await cine.caption(page, "Opening the flagship matter…")
22
+ * await cine.click(page, "matters-row-…")
23
+ *
24
+ * `@playwright/test` is a peer dependency — it is only imported by e2e specs, so
25
+ * it never reaches an app bundle.
26
+ */
27
+
28
+ /** Brand skin for the on-screen chrome. All optional — sensible neutral defaults. */
29
+ interface CinematicBrand {
30
+ /** Footer line on chapter cards, e.g. "Meridian · AI-native counsel". */
31
+ firmLabel: string;
32
+ /** Serif display font for cover titles (CSS font-family value). */
33
+ displayFont?: string;
34
+ /** Sans font for captions + cover body. */
35
+ sansFont?: string;
36
+ /** Cursor dot radial-gradient stops. */
37
+ cursor?: {
38
+ from: string;
39
+ to: string;
40
+ };
41
+ /** Click-ripple ring colour. */
42
+ ripple?: string;
43
+ /** Chapter-card background gradient stops. */
44
+ cover?: {
45
+ from: string;
46
+ mid: string;
47
+ to: string;
48
+ };
49
+ /** Accent (kicker text + rule) on chapter cards. */
50
+ accent?: string;
51
+ /** Caption bar background base colour. */
52
+ ink?: string;
53
+ /** Cover title text colour. */
54
+ coverTitle?: string;
55
+ }
56
+ interface CinematicCreds {
57
+ email: string;
58
+ password: string;
59
+ }
60
+ interface CinematicConfig {
61
+ brand: CinematicBrand;
62
+ creds: CinematicCreds;
63
+ /** Login route (default "/app/login"). */
64
+ loginPath?: string;
65
+ /** URL the app lands on after auth (default /\/app$/). */
66
+ appUrlPattern?: RegExp;
67
+ /** Login form testids (defaults match the shared AppShell/login convention). */
68
+ testIds?: {
69
+ email?: string;
70
+ password?: string;
71
+ submit?: string;
72
+ };
73
+ }
74
+ interface Cinematic {
75
+ installStage(page: Page): Promise<void>;
76
+ cover(page: Page, opts: {
77
+ kicker: string;
78
+ title: string;
79
+ subtitle?: string;
80
+ }, holdMs?: number): Promise<void>;
81
+ uncover(page: Page): Promise<void>;
82
+ caption(page: Page, text: string, holdMs?: number): Promise<void>;
83
+ clearCaption(page: Page): Promise<void>;
84
+ beat(page: Page, ms?: number): Promise<void>;
85
+ glideTo(page: Page, target: string | Locator): Promise<Locator>;
86
+ click(page: Page, target: string | Locator): Promise<void>;
87
+ type(page: Page, target: string | Locator, text: string): Promise<void>;
88
+ /** Sign in. `{ show: false }` authenticates fast + silent (no animation/caption). */
89
+ login(page: Page, opts?: {
90
+ show?: boolean;
91
+ caption?: string;
92
+ }): Promise<void>;
93
+ }
94
+ declare function createCinematic(config: CinematicConfig): Cinematic;
95
+
96
+ export { type Cinematic, type CinematicBrand, type CinematicConfig, type CinematicCreds, createCinematic };
@@ -0,0 +1,96 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+
3
+ /**
4
+ * Cinematic recording harness — firm-agnostic.
5
+ *
6
+ * Turns a Playwright run into a narrated product demo: a visible cursor that
7
+ * glides between targets, a click ripple, a caption bar that narrates each step,
8
+ * full-screen chapter "cover" cards, and human-speed pacing. Every firm app gets
9
+ * the same polished demo capability from one factory — brand colours, fonts,
10
+ * credentials and the login flow arrive as config, so nothing here names a firm.
11
+ *
12
+ * Pair with a Playwright project that records video (see the firm's
13
+ * `playwright.record.config.ts`: video on, a large viewport, some slowMo).
14
+ *
15
+ * Usage in a `*.record.ts` spec:
16
+ * const cine = createCinematic({ brand, creds })
17
+ * await cine.installStage(page) // before the first navigation
18
+ * await cine.cover(page, { kicker, title, subtitle })
19
+ * await cine.login(page)
20
+ * await cine.uncover(page)
21
+ * await cine.caption(page, "Opening the flagship matter…")
22
+ * await cine.click(page, "matters-row-…")
23
+ *
24
+ * `@playwright/test` is a peer dependency — it is only imported by e2e specs, so
25
+ * it never reaches an app bundle.
26
+ */
27
+
28
+ /** Brand skin for the on-screen chrome. All optional — sensible neutral defaults. */
29
+ interface CinematicBrand {
30
+ /** Footer line on chapter cards, e.g. "Meridian · AI-native counsel". */
31
+ firmLabel: string;
32
+ /** Serif display font for cover titles (CSS font-family value). */
33
+ displayFont?: string;
34
+ /** Sans font for captions + cover body. */
35
+ sansFont?: string;
36
+ /** Cursor dot radial-gradient stops. */
37
+ cursor?: {
38
+ from: string;
39
+ to: string;
40
+ };
41
+ /** Click-ripple ring colour. */
42
+ ripple?: string;
43
+ /** Chapter-card background gradient stops. */
44
+ cover?: {
45
+ from: string;
46
+ mid: string;
47
+ to: string;
48
+ };
49
+ /** Accent (kicker text + rule) on chapter cards. */
50
+ accent?: string;
51
+ /** Caption bar background base colour. */
52
+ ink?: string;
53
+ /** Cover title text colour. */
54
+ coverTitle?: string;
55
+ }
56
+ interface CinematicCreds {
57
+ email: string;
58
+ password: string;
59
+ }
60
+ interface CinematicConfig {
61
+ brand: CinematicBrand;
62
+ creds: CinematicCreds;
63
+ /** Login route (default "/app/login"). */
64
+ loginPath?: string;
65
+ /** URL the app lands on after auth (default /\/app$/). */
66
+ appUrlPattern?: RegExp;
67
+ /** Login form testids (defaults match the shared AppShell/login convention). */
68
+ testIds?: {
69
+ email?: string;
70
+ password?: string;
71
+ submit?: string;
72
+ };
73
+ }
74
+ interface Cinematic {
75
+ installStage(page: Page): Promise<void>;
76
+ cover(page: Page, opts: {
77
+ kicker: string;
78
+ title: string;
79
+ subtitle?: string;
80
+ }, holdMs?: number): Promise<void>;
81
+ uncover(page: Page): Promise<void>;
82
+ caption(page: Page, text: string, holdMs?: number): Promise<void>;
83
+ clearCaption(page: Page): Promise<void>;
84
+ beat(page: Page, ms?: number): Promise<void>;
85
+ glideTo(page: Page, target: string | Locator): Promise<Locator>;
86
+ click(page: Page, target: string | Locator): Promise<void>;
87
+ type(page: Page, target: string | Locator, text: string): Promise<void>;
88
+ /** Sign in. `{ show: false }` authenticates fast + silent (no animation/caption). */
89
+ login(page: Page, opts?: {
90
+ show?: boolean;
91
+ caption?: string;
92
+ }): Promise<void>;
93
+ }
94
+ declare function createCinematic(config: CinematicConfig): Cinematic;
95
+
96
+ export { type Cinematic, type CinematicBrand, type CinematicConfig, type CinematicCreds, createCinematic };
@@ -0,0 +1,176 @@
1
+ import { expect } from '@playwright/test';
2
+
3
+ // src/record/index.ts
4
+ var DEFAULTS = {
5
+ displayFont: "ui-serif, Georgia, serif",
6
+ sansFont: "Inter, ui-sans-serif, system-ui, sans-serif",
7
+ cursor: { from: "rgba(31,111,235,.98)", to: "rgba(11,42,74,.92)" },
8
+ ripple: "rgba(11,42,74,.85)",
9
+ cover: { from: "#0b1622", mid: "#0e1b2c", to: "#1f6feb" },
10
+ accent: "#c9a24b",
11
+ ink: "#0e1b2d",
12
+ coverTitle: "#f4f6f8"
13
+ };
14
+ function createCinematic(config) {
15
+ const b = config.brand;
16
+ const skin = {
17
+ firmLabel: b.firmLabel,
18
+ displayFont: b.displayFont ?? DEFAULTS.displayFont,
19
+ sansFont: b.sansFont ?? DEFAULTS.sansFont,
20
+ cursorFrom: b.cursor?.from ?? DEFAULTS.cursor.from,
21
+ cursorTo: b.cursor?.to ?? DEFAULTS.cursor.to,
22
+ ripple: b.ripple ?? DEFAULTS.ripple,
23
+ coverFrom: b.cover?.from ?? DEFAULTS.cover.from,
24
+ coverMid: b.cover?.mid ?? DEFAULTS.cover.mid,
25
+ coverTo: b.cover?.to ?? DEFAULTS.cover.to,
26
+ accent: b.accent ?? DEFAULTS.accent,
27
+ ink: b.ink ?? DEFAULTS.ink,
28
+ coverTitle: b.coverTitle ?? DEFAULTS.coverTitle
29
+ };
30
+ const loginPath = config.loginPath ?? "/app/login";
31
+ const appUrlPattern = config.appUrlPattern ?? /\/app$/;
32
+ const ids = {
33
+ email: config.testIds?.email ?? "auth-email",
34
+ password: config.testIds?.password ?? "auth-password",
35
+ submit: config.testIds?.submit ?? "auth-login-submit"
36
+ };
37
+ const resolve = (page, t) => typeof t === "string" ? page.getByTestId(t) : t;
38
+ async function installStage(page) {
39
+ await page.addInitScript((s) => {
40
+ const boot = () => {
41
+ if (document.getElementById("__cine_cursor")) return;
42
+ const cursor = document.createElement("div");
43
+ cursor.id = "__cine_cursor";
44
+ 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);`;
45
+ document.body.appendChild(cursor);
46
+ window.addEventListener(
47
+ "mousemove",
48
+ (e) => {
49
+ cursor.style.transform = `translate(${e.clientX}px,${e.clientY}px)`;
50
+ },
51
+ true
52
+ );
53
+ window.addEventListener(
54
+ "mousedown",
55
+ (e) => {
56
+ const m = e;
57
+ const r = document.createElement("div");
58
+ 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;`;
59
+ document.body.appendChild(r);
60
+ setTimeout(() => r.remove(), 600);
61
+ },
62
+ true
63
+ );
64
+ const cap = document.createElement("div");
65
+ cap.id = "__cine_cap";
66
+ 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;`;
67
+ document.body.appendChild(cap);
68
+ const style = document.createElement("style");
69
+ style.textContent = "@keyframes __cine_ripple{to{width:46px;height:46px;margin:-23px 0 0 -23px;opacity:0}}";
70
+ document.head.appendChild(style);
71
+ window.__cine_cap = (t) => {
72
+ cap.textContent = t;
73
+ cap.style.opacity = t ? "1" : "0";
74
+ cap.style.transform = `translateX(-50%) translateY(${t ? 0 : 8}px)`;
75
+ };
76
+ };
77
+ if (document.body) boot();
78
+ else document.addEventListener("DOMContentLoaded", boot);
79
+ }, skin);
80
+ }
81
+ async function cover(page, { kicker, title, subtitle }, holdMs = 2600) {
82
+ await page.evaluate(
83
+ ({ kicker: kicker2, title: title2, subtitle: subtitle2, s }) => {
84
+ document.getElementById("__cine_cover")?.remove();
85
+ const el = document.createElement("div");
86
+ el.id = "__cine_cover";
87
+ 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 .5s ease;font-family:${s.sansFont};`;
88
+ 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>`;
89
+ document.body.appendChild(el);
90
+ requestAnimationFrame(() => el.style.opacity = "1");
91
+ },
92
+ { kicker, title, subtitle: subtitle ?? "", s: skin }
93
+ );
94
+ await page.waitForTimeout(holdMs);
95
+ }
96
+ async function uncover(page) {
97
+ await page.evaluate(() => {
98
+ const el = document.getElementById("__cine_cover");
99
+ if (!el) return;
100
+ el.style.opacity = "0";
101
+ setTimeout(() => el.remove(), 520);
102
+ });
103
+ await page.waitForTimeout(560);
104
+ }
105
+ async function caption(page, text, holdMs) {
106
+ await page.evaluate(
107
+ (t) => window.__cine_cap?.(t),
108
+ text
109
+ );
110
+ const hold = holdMs ?? Math.min(4200, 900 + text.length * 45);
111
+ await page.waitForTimeout(hold);
112
+ }
113
+ async function clearCaption(page) {
114
+ await page.evaluate(
115
+ () => window.__cine_cap?.("")
116
+ );
117
+ }
118
+ async function beat(page, ms = 650) {
119
+ await page.waitForTimeout(ms);
120
+ }
121
+ async function glideTo(page, target) {
122
+ const loc = resolve(page, target).first();
123
+ await loc.scrollIntoViewIfNeeded().catch(() => {
124
+ });
125
+ const box = await loc.boundingBox();
126
+ if (box) {
127
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 });
128
+ await page.waitForTimeout(180);
129
+ }
130
+ return loc;
131
+ }
132
+ async function click(page, target) {
133
+ const loc = await glideTo(page, target);
134
+ await loc.click();
135
+ await page.waitForTimeout(260);
136
+ }
137
+ async function type(page, target, text) {
138
+ const loc = await glideTo(page, target);
139
+ await loc.click();
140
+ await loc.fill("");
141
+ await loc.pressSequentially(text, { delay: 42 });
142
+ await page.waitForTimeout(240);
143
+ }
144
+ async function login(page, opts = {}) {
145
+ await page.goto(loginPath);
146
+ if (opts.show === false) {
147
+ await page.getByTestId(ids.email).fill(config.creds.email);
148
+ await page.getByTestId(ids.password).fill(config.creds.password);
149
+ await page.getByTestId(ids.submit).click();
150
+ await expect(page).toHaveURL(appUrlPattern, { timeout: 45e3 });
151
+ return;
152
+ }
153
+ await caption(page, opts.caption ?? `${b.firmLabel} \u2014 signing in.`, 1600);
154
+ await type(page, ids.email, config.creds.email);
155
+ await type(page, ids.password, config.creds.password);
156
+ await click(page, ids.submit);
157
+ await expect(page).toHaveURL(appUrlPattern, { timeout: 45e3 });
158
+ await beat(page, 800);
159
+ }
160
+ return {
161
+ installStage,
162
+ cover,
163
+ uncover,
164
+ caption,
165
+ clearCaption,
166
+ beat,
167
+ glideTo,
168
+ click,
169
+ type,
170
+ login
171
+ };
172
+ }
173
+
174
+ export { createCinematic };
175
+ //# sourceMappingURL=index.js.map
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/record/index.ts"],"names":["kicker","title","subtitle"],"mappings":";;;AAkFA,IAAM,QAAA,GAAW;AAAA,EACf,WAAA,EAAa,0BAAA;AAAA,EACb,QAAA,EAAU,6CAAA;AAAA,EACV,MAAA,EAAQ,EAAE,IAAA,EAAM,sBAAA,EAAwB,IAAI,oBAAA,EAAqB;AAAA,EACjE,MAAA,EAAQ,oBAAA;AAAA,EACR,OAAO,EAAE,IAAA,EAAM,WAAW,GAAA,EAAK,SAAA,EAAW,IAAI,SAAA,EAAU;AAAA,EACxD,MAAA,EAAQ,SAAA;AAAA,EACR,GAAA,EAAK,SAAA;AAAA,EACL,UAAA,EAAY;AACd,CAAA;AAEO,SAAS,gBAAgB,MAAA,EAAoC;AAClE,EAAA,MAAM,IAAI,MAAA,CAAO,KAAA;AACjB,EAAA,MAAM,IAAA,GAAO;AAAA,IACX,WAAW,CAAA,CAAE,SAAA;AAAA,IACb,WAAA,EAAa,CAAA,CAAE,WAAA,IAAe,QAAA,CAAS,WAAA;AAAA,IACvC,QAAA,EAAU,CAAA,CAAE,QAAA,IAAY,QAAA,CAAS,QAAA;AAAA,IACjC,UAAA,EAAY,CAAA,CAAE,MAAA,EAAQ,IAAA,IAAQ,SAAS,MAAA,CAAO,IAAA;AAAA,IAC9C,QAAA,EAAU,CAAA,CAAE,MAAA,EAAQ,EAAA,IAAM,SAAS,MAAA,CAAO,EAAA;AAAA,IAC1C,MAAA,EAAQ,CAAA,CAAE,MAAA,IAAU,QAAA,CAAS,MAAA;AAAA,IAC7B,SAAA,EAAW,CAAA,CAAE,KAAA,EAAO,IAAA,IAAQ,SAAS,KAAA,CAAM,IAAA;AAAA,IAC3C,QAAA,EAAU,CAAA,CAAE,KAAA,EAAO,GAAA,IAAO,SAAS,KAAA,CAAM,GAAA;AAAA,IACzC,OAAA,EAAS,CAAA,CAAE,KAAA,EAAO,EAAA,IAAM,SAAS,KAAA,CAAM,EAAA;AAAA,IACvC,MAAA,EAAQ,CAAA,CAAE,MAAA,IAAU,QAAA,CAAS,MAAA;AAAA,IAC7B,GAAA,EAAK,CAAA,CAAE,GAAA,IAAO,QAAA,CAAS,GAAA;AAAA,IACvB,UAAA,EAAY,CAAA,CAAE,UAAA,IAAc,QAAA,CAAS;AAAA,GACvC;AACA,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,YAAA;AACtC,EAAA,MAAM,aAAA,GAAgB,OAAO,aAAA,IAAiB,QAAA;AAC9C,EAAA,MAAM,GAAA,GAAM;AAAA,IACV,KAAA,EAAO,MAAA,CAAO,OAAA,EAAS,KAAA,IAAS,YAAA;AAAA,IAChC,QAAA,EAAU,MAAA,CAAO,OAAA,EAAS,QAAA,IAAY,eAAA;AAAA,IACtC,MAAA,EAAQ,MAAA,CAAO,OAAA,EAAS,MAAA,IAAU;AAAA,GACpC;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,EAAY,CAAA,KAC3B,OAAO,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,CAAC,CAAA,GAAI,CAAA;AAEhD,EAAA,eAAe,aAAa,IAAA,EAA2B;AACrD,IAAA,MAAM,IAAA,CAAK,aAAA,CAAc,CAAC,CAAA,KAAmB;AAC3C,MAAA,MAAM,OAAO,MAAM;AACjB,QAAA,IAAI,QAAA,CAAS,cAAA,CAAe,eAAe,CAAA,EAAG;AAE9C,QAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC3C,QAAA,MAAA,CAAO,EAAA,GAAK,eAAA;AACZ,QAAA,MAAA,CAAO,MAAM,OAAA,GACX,CAAA,oNAAA,EAGiD,EAAE,UAAU,CAAA,EAAA,EAAK,EAAE,QAAQ,CAAA,sEAAA,CAAA;AAE9E,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAEhC,QAAA,MAAA,CAAO,gBAAA;AAAA,UACL,WAAA;AAAA,UACA,CAAC,CAAA,KAAM;AACL,YAAA,MAAA,CAAO,MAAM,SAAA,GAAY,CAAA,UAAA,EAAc,EAAiB,OAAO,CAAA,GAAA,EAAO,EAAiB,OAAO,CAAA,GAAA,CAAA;AAAA,UAChG,CAAA;AAAA,UACA;AAAA,SACF;AACA,QAAA,MAAA,CAAO,gBAAA;AAAA,UACL,WAAA;AAAA,UACA,CAAC,CAAA,KAAM;AACL,YAAA,MAAM,CAAA,GAAI,CAAA;AACV,YAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACtC,YAAA,CAAA,CAAE,KAAA,CAAM,OAAA,GACN,CAAA,oBAAA,EAAuB,CAAA,CAAE,OAAO,UAAU,CAAA,CAAE,OAAO,CAAA,yHAAA,EAE/B,CAAA,CAAE,MAAM,CAAA,gDAAA,CAAA;AAC9B,YAAA,QAAA,CAAS,IAAA,CAAK,YAAY,CAAC,CAAA;AAC3B,YAAA,UAAA,CAAW,MAAM,CAAA,CAAE,MAAA,EAAO,EAAG,GAAG,CAAA;AAAA,UAClC,CAAA;AAAA,UACA;AAAA,SACF;AAEA,QAAA,MAAM,GAAA,GAAM,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACxC,QAAA,GAAA,CAAI,EAAA,GAAK,YAAA;AACT,QAAA,GAAA,CAAI,MAAM,OAAA,GACR,CAAA,wKAAA,EAEsB,EAAE,QAAQ,CAAA,4CAAA,EAClB,EAAE,GAAG,CAAA,sIAAA,CAAA;AAErB,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,GAAG,CAAA;AAE7B,QAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,QAAA,KAAA,CAAM,WAAA,GACJ,uFAAA;AACF,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAE9B,QAAC,MAAA,CAA0D,UAAA,GAAa,CAAC,CAAA,KAAc;AACtF,UAAA,GAAA,CAAI,WAAA,GAAc,CAAA;AAClB,UAAA,GAAA,CAAI,KAAA,CAAM,OAAA,GAAU,CAAA,GAAI,GAAA,GAAM,GAAA;AAC9B,UAAA,GAAA,CAAI,KAAA,CAAM,SAAA,GAAY,CAAA,4BAAA,EAA+B,CAAA,GAAI,IAAI,CAAC,CAAA,GAAA,CAAA;AAAA,QAChE,CAAA;AAAA,MACF,CAAA;AACA,MAAA,IAAI,QAAA,CAAS,MAAM,IAAA,EAAK;AAAA,WACnB,QAAA,CAAS,gBAAA,CAAiB,kBAAA,EAAoB,IAAI,CAAA;AAAA,IACzD,GAAG,IAAI,CAAA;AAAA,EACT;AAEA,EAAA,eAAe,KAAA,CACb,MACA,EAAE,MAAA,EAAQ,OAAO,QAAA,EAAS,EAC1B,SAAS,IAAA,EACM;AACf,IAAA,MAAM,IAAA,CAAK,QAAA;AAAA,MACT,CAAC,EAAE,MAAA,EAAAA,OAAAA,EAAQ,OAAAC,MAAAA,EAAO,QAAA,EAAAC,SAAAA,EAAU,CAAA,EAAE,KAAM;AAClC,QAAA,QAAA,CAAS,cAAA,CAAe,cAAc,CAAA,EAAG,MAAA,EAAO;AAChD,QAAA,MAAM,EAAA,GAAK,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACvC,QAAA,EAAA,CAAG,EAAA,GAAK,cAAA;AACR,QAAA,EAAA,CAAG,KAAA,CAAM,OAAA,GACP,CAAA,wLAAA,EAEqC,CAAA,CAAE,SAAS,CAAA,IAAA,EAAO,CAAA,CAAE,QAAQ,CAAA,KAAA,EAAQ,CAAA,CAAE,OAAO,CAAA,yDAAA,EAC7B,EAAE,QAAQ,CAAA,CAAA,CAAA;AACjE,QAAA,EAAA,CAAG,SAAA,GACD,CAAA,+FAAA,EAAkG,CAAA,CAAE,MAAM,CAAA,qBAAA,EAAwBF,OAAM,CAAA,8BAAA,EAC7G,CAAA,CAAE,WAAW,CAAA,6EAAA,EAAgF,CAAA,CAAE,UAAU,oBAAoBC,MAAK,CAAA,MAAA,CAAA,IAC5JC,SAAAA,GACG,CAAA,iEAAA,EAAoE,CAAA,CAAE,UAAU,CAAA,mBAAA,EAAsBA,SAAQ,WAC9G,EAAA,CAAA,GACJ,CAAA,2EAAA,EAA8E,CAAA,CAAE,UAAU,CAAA,sFAAA,EACzC,CAAA,CAAE,MAAM,CAAA,SAAA,EAAY,EAAE,SAAS,CAAA,MAAA,CAAA;AAClF,QAAA,QAAA,CAAS,IAAA,CAAK,YAAY,EAAE,CAAA;AAC5B,QAAA,qBAAA,CAAsB,MAAO,EAAA,CAAG,KAAA,CAAM,OAAA,GAAU,GAAI,CAAA;AAAA,MACtD,CAAA;AAAA,MACA,EAAE,MAAA,EAAQ,KAAA,EAAO,UAAU,QAAA,IAAY,EAAA,EAAI,GAAG,IAAA;AAAK,KACrD;AACA,IAAA,MAAM,IAAA,CAAK,eAAe,MAAM,CAAA;AAAA,EAClC;AAEA,EAAA,eAAe,QAAQ,IAAA,EAA2B;AAChD,IAAA,MAAM,IAAA,CAAK,SAAS,MAAM;AACxB,MAAA,MAAM,EAAA,GAAK,QAAA,CAAS,cAAA,CAAe,cAAc,CAAA;AACjD,MAAA,IAAI,CAAC,EAAA,EAAI;AACT,MAAA,EAAA,CAAG,MAAM,OAAA,GAAU,GAAA;AACnB,MAAA,UAAA,CAAW,MAAM,EAAA,CAAG,MAAA,EAAO,EAAG,GAAG,CAAA;AAAA,IACnC,CAAC,CAAA;AACD,IAAA,MAAM,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,eAAe,OAAA,CAAQ,IAAA,EAAY,IAAA,EAAc,MAAA,EAAgC;AAC/E,IAAA,MAAM,IAAA,CAAK,QAAA;AAAA,MACT,CAAC,CAAA,KAAO,MAAA,CAA2D,UAAA,GAAa,CAAC,CAAA;AAAA,MACjF;AAAA,KACF;AACA,IAAA,MAAM,IAAA,GAAO,UAAU,IAAA,CAAK,GAAA,CAAI,MAAM,GAAA,GAAM,IAAA,CAAK,SAAS,EAAE,CAAA;AAC5D,IAAA,MAAM,IAAA,CAAK,eAAe,IAAI,CAAA;AAAA,EAChC;AAEA,EAAA,eAAe,aAAa,IAAA,EAA2B;AACrD,IAAA,MAAM,IAAA,CAAK,QAAA;AAAA,MAAS,MACjB,MAAA,CAA2D,UAAA,GAAa,EAAE;AAAA,KAC7E;AAAA,EACF;AAEA,EAAA,eAAe,IAAA,CAAK,IAAA,EAAY,EAAA,GAAK,GAAA,EAAoB;AACvD,IAAA,MAAM,IAAA,CAAK,eAAe,EAAE,CAAA;AAAA,EAC9B;AAEA,EAAA,eAAe,OAAA,CAAQ,MAAY,MAAA,EAA4C;AAC7E,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,IAAA,EAAM,MAAM,EAAE,KAAA,EAAM;AACxC,IAAA,MAAM,GAAA,CAAI,sBAAA,EAAuB,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AACjD,IAAA,MAAM,GAAA,GAAM,MAAM,GAAA,CAAI,WAAA,EAAY;AAClC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,IAAI,KAAA,GAAQ,CAAA,EAAG,GAAA,CAAI,CAAA,GAAI,IAAI,MAAA,GAAS,CAAA,EAAG,EAAE,KAAA,EAAO,IAAI,CAAA;AAClF,MAAA,MAAM,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,IAC/B;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,eAAe,KAAA,CAAM,MAAY,MAAA,EAAyC;AACxE,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AACtC,IAAA,MAAM,IAAI,KAAA,EAAM;AAChB,IAAA,MAAM,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,eAAe,IAAA,CAAK,IAAA,EAAY,MAAA,EAA0B,IAAA,EAA6B;AACrF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AACtC,IAAA,MAAM,IAAI,KAAA,EAAM;AAChB,IAAA,MAAM,GAAA,CAAI,KAAK,EAAE,CAAA;AACjB,IAAA,MAAM,IAAI,iBAAA,CAAkB,IAAA,EAAM,EAAE,KAAA,EAAO,IAAI,CAAA;AAC/C,IAAA,MAAM,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,eAAe,KAAA,CAAM,IAAA,EAAY,IAAA,GAA6C,EAAC,EAAkB;AAC/F,IAAA,MAAM,IAAA,CAAK,KAAK,SAAS,CAAA;AACzB,IAAA,IAAI,IAAA,CAAK,SAAS,KAAA,EAAO;AACvB,MAAA,MAAM,IAAA,CAAK,YAAY,GAAA,CAAI,KAAK,EAAE,IAAA,CAAK,MAAA,CAAO,MAAM,KAAK,CAAA;AACzD,MAAA,MAAM,IAAA,CAAK,YAAY,GAAA,CAAI,QAAQ,EAAE,IAAA,CAAK,MAAA,CAAO,MAAM,QAAQ,CAAA;AAC/D,MAAA,MAAM,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,MAAM,EAAE,KAAA,EAAM;AACzC,MAAA,MAAM,MAAA,CAAO,IAAI,CAAA,CAAE,SAAA,CAAU,eAAe,EAAE,OAAA,EAAS,MAAQ,CAAA;AAC/D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAA,CAAQ,MAAM,IAAA,CAAK,OAAA,IAAW,GAAG,CAAA,CAAE,SAAS,uBAAkB,IAAI,CAAA;AACxE,IAAA,MAAM,KAAK,IAAA,EAAM,GAAA,CAAI,KAAA,EAAO,MAAA,CAAO,MAAM,KAAK,CAAA;AAC9C,IAAA,MAAM,KAAK,IAAA,EAAM,GAAA,CAAI,QAAA,EAAU,MAAA,CAAO,MAAM,QAAQ,CAAA;AACpD,IAAA,MAAM,KAAA,CAAM,IAAA,EAAM,GAAA,CAAI,MAAM,CAAA;AAC5B,IAAA,MAAM,MAAA,CAAO,IAAI,CAAA,CAAE,SAAA,CAAU,eAAe,EAAE,OAAA,EAAS,MAAQ,CAAA;AAC/D,IAAA,MAAM,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACtB;AAEA,EAAA,OAAO;AAAA,IACL,YAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * Cinematic recording harness — firm-agnostic.\n *\n * Turns a Playwright run into a narrated product demo: a visible cursor that\n * glides between targets, a click ripple, a caption bar that narrates each step,\n * full-screen chapter \"cover\" cards, and human-speed pacing. Every firm app gets\n * the same polished demo capability from one factory — brand colours, fonts,\n * credentials and the login flow arrive as config, so nothing here names a firm.\n *\n * Pair with a Playwright project that records video (see the firm's\n * `playwright.record.config.ts`: video on, a large viewport, some slowMo).\n *\n * Usage in a `*.record.ts` spec:\n * const cine = createCinematic({ brand, creds })\n * await cine.installStage(page) // before the first navigation\n * await cine.cover(page, { kicker, title, subtitle })\n * await cine.login(page)\n * await cine.uncover(page)\n * await cine.caption(page, \"Opening the flagship matter…\")\n * await cine.click(page, \"matters-row-…\")\n *\n * `@playwright/test` is a peer dependency — it is only imported by e2e specs, so\n * it never reaches an app bundle.\n */\nimport { type Page, type Locator, expect } from '@playwright/test'\n\n/** Brand skin for the on-screen chrome. All optional — sensible neutral defaults. */\nexport interface CinematicBrand {\n /** Footer line on chapter cards, e.g. \"Meridian · AI-native counsel\". */\n firmLabel: string\n /** Serif display font for cover titles (CSS font-family value). */\n displayFont?: string\n /** Sans font for captions + cover body. */\n sansFont?: string\n /** Cursor dot radial-gradient stops. */\n cursor?: { from: string; to: string }\n /** Click-ripple ring colour. */\n ripple?: string\n /** Chapter-card background gradient stops. */\n cover?: { from: string; mid: string; to: string }\n /** Accent (kicker text + rule) on chapter cards. */\n accent?: string\n /** Caption bar background base colour. */\n ink?: string\n /** Cover title text colour. */\n coverTitle?: string\n}\n\nexport interface CinematicCreds {\n email: string\n password: string\n}\n\nexport interface CinematicConfig {\n brand: CinematicBrand\n creds: CinematicCreds\n /** Login route (default \"/app/login\"). */\n loginPath?: string\n /** URL the app lands on after auth (default /\\/app$/). */\n appUrlPattern?: RegExp\n /** Login form testids (defaults match the shared AppShell/login convention). */\n testIds?: { email?: string; password?: string; submit?: string }\n}\n\nexport interface Cinematic {\n installStage(page: Page): Promise<void>\n cover(\n page: Page,\n opts: { kicker: string; title: string; subtitle?: string },\n holdMs?: number\n ): Promise<void>\n uncover(page: Page): Promise<void>\n caption(page: Page, text: string, holdMs?: number): Promise<void>\n clearCaption(page: Page): Promise<void>\n beat(page: Page, ms?: number): Promise<void>\n glideTo(page: Page, target: string | Locator): Promise<Locator>\n click(page: Page, target: string | Locator): Promise<void>\n type(page: Page, target: string | Locator, text: string): Promise<void>\n /** Sign in. `{ show: false }` authenticates fast + silent (no animation/caption). */\n login(page: Page, opts?: { show?: boolean; caption?: string }): Promise<void>\n}\n\nconst DEFAULTS = {\n displayFont: 'ui-serif, Georgia, serif',\n sansFont: 'Inter, ui-sans-serif, system-ui, sans-serif',\n cursor: { from: 'rgba(31,111,235,.98)', to: 'rgba(11,42,74,.92)' },\n ripple: 'rgba(11,42,74,.85)',\n cover: { from: '#0b1622', mid: '#0e1b2c', to: '#1f6feb' },\n accent: '#c9a24b',\n ink: '#0e1b2d',\n coverTitle: '#f4f6f8',\n}\n\nexport function createCinematic(config: CinematicConfig): Cinematic {\n const b = config.brand\n const skin = {\n firmLabel: b.firmLabel,\n displayFont: b.displayFont ?? DEFAULTS.displayFont,\n sansFont: b.sansFont ?? DEFAULTS.sansFont,\n cursorFrom: b.cursor?.from ?? DEFAULTS.cursor.from,\n cursorTo: b.cursor?.to ?? DEFAULTS.cursor.to,\n ripple: b.ripple ?? DEFAULTS.ripple,\n coverFrom: b.cover?.from ?? DEFAULTS.cover.from,\n coverMid: b.cover?.mid ?? DEFAULTS.cover.mid,\n coverTo: b.cover?.to ?? DEFAULTS.cover.to,\n accent: b.accent ?? DEFAULTS.accent,\n ink: b.ink ?? DEFAULTS.ink,\n coverTitle: b.coverTitle ?? DEFAULTS.coverTitle,\n }\n const loginPath = config.loginPath ?? '/app/login'\n const appUrlPattern = config.appUrlPattern ?? /\\/app$/\n const ids = {\n email: config.testIds?.email ?? 'auth-email',\n password: config.testIds?.password ?? 'auth-password',\n submit: config.testIds?.submit ?? 'auth-login-submit',\n }\n\n const resolve = (page: Page, t: string | Locator): Locator =>\n typeof t === 'string' ? page.getByTestId(t) : t\n\n async function installStage(page: Page): Promise<void> {\n await page.addInitScript((s: typeof skin) => {\n const boot = () => {\n if (document.getElementById('__cine_cursor')) return\n\n const cursor = document.createElement('div')\n cursor.id = '__cine_cursor'\n cursor.style.cssText =\n 'position:fixed;left:0;top:0;z-index:2147483647;width:22px;height:22px;' +\n 'margin:-11px 0 0 -11px;border-radius:50%;pointer-events:none;' +\n 'transition:transform .09s ease-out;' +\n `background:radial-gradient(circle at 35% 35%, ${s.cursorFrom}, ${s.cursorTo});` +\n 'box-shadow:0 0 0 3px rgba(255,255,255,.6),0 2px 10px rgba(0,0,0,.4);'\n document.body.appendChild(cursor)\n\n window.addEventListener(\n 'mousemove',\n (e) => {\n cursor.style.transform = `translate(${(e as MouseEvent).clientX}px,${(e as MouseEvent).clientY}px)`\n },\n true\n )\n window.addEventListener(\n 'mousedown',\n (e) => {\n const m = e as MouseEvent\n const r = document.createElement('div')\n r.style.cssText =\n `position:fixed;left:${m.clientX}px;top:${m.clientY}px;z-index:2147483646;` +\n 'width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;pointer-events:none;' +\n `border:2px solid ${s.ripple};animation:__cine_ripple .55s ease-out forwards;`\n document.body.appendChild(r)\n setTimeout(() => r.remove(), 600)\n },\n true\n )\n\n const cap = document.createElement('div')\n cap.id = '__cine_cap'\n cap.style.cssText =\n 'position:fixed;left:50%;bottom:34px;transform:translateX(-50%) translateY(8px);' +\n 'z-index:2147483646;max-width:72%;padding:13px 24px;border-radius:13px;' +\n `font:500 17px/1.45 ${s.sansFont};color:#fff;letter-spacing:.01em;` +\n `background:${s.ink}e0;backdrop-filter:blur(7px);` +\n 'box-shadow:0 10px 34px rgba(0,0,0,.4);text-align:center;opacity:0;transition:opacity .35s,transform .35s;'\n document.body.appendChild(cap)\n\n const style = document.createElement('style')\n style.textContent =\n '@keyframes __cine_ripple{to{width:46px;height:46px;margin:-23px 0 0 -23px;opacity:0}}'\n document.head.appendChild(style)\n\n ;(window as unknown as { __cine_cap: (t: string) => void }).__cine_cap = (t: string) => {\n cap.textContent = t\n cap.style.opacity = t ? '1' : '0'\n cap.style.transform = `translateX(-50%) translateY(${t ? 0 : 8}px)`\n }\n }\n if (document.body) boot()\n else document.addEventListener('DOMContentLoaded', boot)\n }, skin)\n }\n\n async function cover(\n page: Page,\n { kicker, title, subtitle }: { kicker: string; title: string; subtitle?: string },\n holdMs = 2600\n ): Promise<void> {\n await page.evaluate(\n ({ kicker, title, subtitle, s }) => {\n document.getElementById('__cine_cover')?.remove()\n const el = document.createElement('div')\n el.id = '__cine_cover'\n el.style.cssText =\n 'position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;' +\n 'align-items:center;justify-content:center;text-align:center;padding:0 8%;' +\n `background:linear-gradient(135deg,${s.coverFrom} 0%,${s.coverMid} 48%,${s.coverTo} 165%);` +\n `opacity:0;transition:opacity .5s ease;font-family:${s.sansFont};`\n el.innerHTML =\n `<div style=\"text-transform:uppercase;letter-spacing:.26em;font-size:13px;font-weight:600;color:${s.accent};margin-bottom:22px\">${kicker}</div>` +\n `<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\">${title}</div>` +\n (subtitle\n ? `<div style=\"margin-top:22px;font-size:19px;line-height:1.5;color:${s.coverTitle}b8;max-width:46ch\">${subtitle}</div>`\n : '') +\n `<div style=\"margin-top:44px;display:flex;align-items:center;gap:10px;color:${s.coverTitle}80;font-size:13px;letter-spacing:.02em\">` +\n `<span style=\"width:26px;height:1px;background:${s.accent}\"></span>${s.firmLabel}</div>`\n document.body.appendChild(el)\n requestAnimationFrame(() => (el.style.opacity = '1'))\n },\n { kicker, title, subtitle: subtitle ?? '', s: skin }\n )\n await page.waitForTimeout(holdMs)\n }\n\n async function uncover(page: Page): Promise<void> {\n await page.evaluate(() => {\n const el = document.getElementById('__cine_cover')\n if (!el) return\n el.style.opacity = '0'\n setTimeout(() => el.remove(), 520)\n })\n await page.waitForTimeout(560)\n }\n\n async function caption(page: Page, text: string, holdMs?: number): Promise<void> {\n await page.evaluate(\n (t) => (window as unknown as { __cine_cap?: (s: string) => void }).__cine_cap?.(t),\n text\n )\n const hold = holdMs ?? Math.min(4200, 900 + text.length * 45)\n await page.waitForTimeout(hold)\n }\n\n async function clearCaption(page: Page): Promise<void> {\n await page.evaluate(() =>\n (window as unknown as { __cine_cap?: (s: string) => void }).__cine_cap?.('')\n )\n }\n\n async function beat(page: Page, ms = 650): Promise<void> {\n await page.waitForTimeout(ms)\n }\n\n async function glideTo(page: Page, target: string | Locator): Promise<Locator> {\n const loc = resolve(page, target).first()\n await loc.scrollIntoViewIfNeeded().catch(() => {})\n const box = await loc.boundingBox()\n if (box) {\n await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 26 })\n await page.waitForTimeout(180)\n }\n return loc\n }\n\n async function click(page: Page, target: string | Locator): Promise<void> {\n const loc = await glideTo(page, target)\n await loc.click()\n await page.waitForTimeout(260)\n }\n\n async function type(page: Page, target: string | Locator, text: string): Promise<void> {\n const loc = await glideTo(page, target)\n await loc.click()\n await loc.fill('')\n await loc.pressSequentially(text, { delay: 42 })\n await page.waitForTimeout(240)\n }\n\n async function login(page: Page, opts: { show?: boolean; caption?: string } = {}): Promise<void> {\n await page.goto(loginPath)\n if (opts.show === false) {\n await page.getByTestId(ids.email).fill(config.creds.email)\n await page.getByTestId(ids.password).fill(config.creds.password)\n await page.getByTestId(ids.submit).click()\n await expect(page).toHaveURL(appUrlPattern, { timeout: 45_000 })\n return\n }\n await caption(page, opts.caption ?? `${b.firmLabel} — signing in.`, 1600)\n await type(page, ids.email, config.creds.email)\n await type(page, ids.password, config.creds.password)\n await click(page, ids.submit)\n await expect(page).toHaveURL(appUrlPattern, { timeout: 45_000 })\n await beat(page, 800)\n }\n\n return {\n installStage,\n cover,\n uncover,\n caption,\n clearCaption,\n beat,\n glideTo,\n click,\n type,\n login,\n }\n}\n"]}