@tangle-network/browser-agent-driver 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/index.d.ts +2 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +96 -10
- package/dist/brain/index.js.map +1 -1
- package/dist/browser-launch.d.ts +3 -0
- package/dist/browser-launch.d.ts.map +1 -1
- package/dist/browser-launch.js +12 -2
- package/dist/browser-launch.js.map +1 -1
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +250 -2
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-showcase.d.ts +28 -0
- package/dist/cli-showcase.d.ts.map +1 -0
- package/dist/cli-showcase.js +131 -0
- package/dist/cli-showcase.js.map +1 -0
- package/dist/cli-ui.d.ts.map +1 -1
- package/dist/cli-ui.js +23 -2
- package/dist/cli-ui.js.map +1 -1
- package/dist/cli.js +371 -211
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/design/compare.d.ts +9 -0
- package/dist/design/compare.d.ts.map +1 -0
- package/dist/design/compare.js +339 -0
- package/dist/design/compare.js.map +1 -0
- package/dist/design/index.d.ts +6 -0
- package/dist/design/index.d.ts.map +1 -0
- package/dist/design/index.js +5 -0
- package/dist/design/index.js.map +1 -0
- package/dist/design/page-interaction.d.ts +24 -0
- package/dist/design/page-interaction.d.ts.map +1 -0
- package/dist/design/page-interaction.js +244 -0
- package/dist/design/page-interaction.js.map +1 -0
- package/dist/design/rip.d.ts +9 -0
- package/dist/design/rip.d.ts.map +1 -0
- package/dist/design/rip.js +362 -0
- package/dist/design/rip.js.map +1 -0
- package/dist/design/types.d.ts +126 -0
- package/dist/design/types.d.ts.map +1 -0
- package/dist/design/types.js +3 -0
- package/dist/design/types.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +24 -0
- package/dist/runner/runner.js.map +1 -1
- package/dist/showcase/annotate.d.ts +28 -0
- package/dist/showcase/annotate.d.ts.map +1 -0
- package/dist/showcase/annotate.js +95 -0
- package/dist/showcase/annotate.js.map +1 -0
- package/dist/showcase/assemble.d.ts +31 -0
- package/dist/showcase/assemble.d.ts.map +1 -0
- package/dist/showcase/assemble.js +107 -0
- package/dist/showcase/assemble.js.map +1 -0
- package/dist/showcase/demo-player.d.ts +53 -0
- package/dist/showcase/demo-player.d.ts.map +1 -0
- package/dist/showcase/demo-player.js +325 -0
- package/dist/showcase/demo-player.js.map +1 -0
- package/dist/showcase/index.d.ts +21 -0
- package/dist/showcase/index.d.ts.map +1 -0
- package/dist/showcase/index.js +289 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/types.d.ts +111 -0
- package/dist/showcase/types.d.ts.map +1 -0
- package/dist/showcase/types.js +3 -0
- package/dist/showcase/types.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot annotation — highlight boxes, step numbers, cropping.
|
|
3
|
+
*
|
|
4
|
+
* Uses pngjs for pixel manipulation. No external image editing deps.
|
|
5
|
+
* All operations are pure buffer-in → buffer-out.
|
|
6
|
+
*/
|
|
7
|
+
// ── Highlight ──
|
|
8
|
+
/**
|
|
9
|
+
* Draw a colored rectangle overlay on a screenshot by injecting CSS into the page,
|
|
10
|
+
* then re-capturing. This avoids pixel-level drawing and handles any selector.
|
|
11
|
+
*/
|
|
12
|
+
export async function captureWithHighlight(page, opts) {
|
|
13
|
+
const color = opts.color ?? 'rgba(142, 89, 255, 0.35)';
|
|
14
|
+
const borderColor = opts.color?.replace(/[\d.]+\)$/, '0.8)') ?? 'rgba(142, 89, 255, 0.8)';
|
|
15
|
+
// Inject highlight overlay via CSS + pseudo-element
|
|
16
|
+
const highlightId = `__showcase_highlight_${Date.now()}`;
|
|
17
|
+
await page.evaluate(({ selector, color, borderColor, label, id }) => {
|
|
18
|
+
const el = document.querySelector(selector);
|
|
19
|
+
if (!el)
|
|
20
|
+
return;
|
|
21
|
+
// Create overlay
|
|
22
|
+
const overlay = document.createElement('div');
|
|
23
|
+
overlay.id = id;
|
|
24
|
+
overlay.style.cssText = `
|
|
25
|
+
position: absolute;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
z-index: 99999;
|
|
28
|
+
background: ${color};
|
|
29
|
+
border: 2px solid ${borderColor};
|
|
30
|
+
border-radius: 8px;
|
|
31
|
+
transition: none;
|
|
32
|
+
`;
|
|
33
|
+
const rect = el.getBoundingClientRect();
|
|
34
|
+
overlay.style.top = `${rect.top + window.scrollY - 4}px`;
|
|
35
|
+
overlay.style.left = `${rect.left + window.scrollX - 4}px`;
|
|
36
|
+
overlay.style.width = `${rect.width + 8}px`;
|
|
37
|
+
overlay.style.height = `${rect.height + 8}px`;
|
|
38
|
+
if (label) {
|
|
39
|
+
const labelEl = document.createElement('div');
|
|
40
|
+
labelEl.textContent = label;
|
|
41
|
+
labelEl.style.cssText = `
|
|
42
|
+
position: absolute;
|
|
43
|
+
top: -28px;
|
|
44
|
+
left: 0;
|
|
45
|
+
background: ${borderColor};
|
|
46
|
+
color: white;
|
|
47
|
+
font-family: -apple-system, sans-serif;
|
|
48
|
+
font-size: 12px;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
padding: 3px 10px;
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
`;
|
|
54
|
+
overlay.appendChild(labelEl);
|
|
55
|
+
}
|
|
56
|
+
document.body.appendChild(overlay);
|
|
57
|
+
}, { selector: opts.selector, color, borderColor, label: opts.label ?? null, id: highlightId });
|
|
58
|
+
// Capture
|
|
59
|
+
const buffer = await page.screenshot({
|
|
60
|
+
type: 'png',
|
|
61
|
+
fullPage: opts.fullPage ?? false,
|
|
62
|
+
});
|
|
63
|
+
// Clean up overlay
|
|
64
|
+
await page.evaluate((id) => document.getElementById(id)?.remove(), highlightId);
|
|
65
|
+
return buffer;
|
|
66
|
+
}
|
|
67
|
+
// ── Crop ──
|
|
68
|
+
/**
|
|
69
|
+
* Capture a screenshot cropped to a specific element's bounding box.
|
|
70
|
+
* Uses Playwright's element screenshot which handles scrolling automatically.
|
|
71
|
+
*/
|
|
72
|
+
export async function captureWithCrop(page, opts) {
|
|
73
|
+
const el = page.locator(opts.selector).first();
|
|
74
|
+
const visible = await el.isVisible({ timeout: 2000 }).catch(() => false);
|
|
75
|
+
if (!visible)
|
|
76
|
+
return null;
|
|
77
|
+
if (opts.padding && opts.padding > 0) {
|
|
78
|
+
// Use bounding box + padding for a padded crop
|
|
79
|
+
const box = await el.boundingBox();
|
|
80
|
+
if (!box)
|
|
81
|
+
return null;
|
|
82
|
+
return page.screenshot({
|
|
83
|
+
type: 'png',
|
|
84
|
+
clip: {
|
|
85
|
+
x: Math.max(0, box.x - opts.padding),
|
|
86
|
+
y: Math.max(0, box.y - opts.padding),
|
|
87
|
+
width: box.width + opts.padding * 2,
|
|
88
|
+
height: box.height + opts.padding * 2,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// No padding — use element screenshot directly
|
|
93
|
+
return el.screenshot({ type: 'png' });
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=annotate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotate.js","sourceRoot":"","sources":["../../src/showcase/annotate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame assembly — combine screenshot sequence into GIF or video.
|
|
3
|
+
*
|
|
4
|
+
* Uses ffmpeg-static which is already a dependency (unused until now).
|
|
5
|
+
* Falls back gracefully if ffmpeg is not available.
|
|
6
|
+
*/
|
|
7
|
+
import type { ShowcaseFrame } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Assemble PNG frames into an animated GIF.
|
|
10
|
+
*
|
|
11
|
+
* @param frames - Ordered screenshot frames
|
|
12
|
+
* @param outputPath - Where to write the GIF
|
|
13
|
+
* @param opts - Frame rate, loop count
|
|
14
|
+
* @returns true if successful, false if ffmpeg unavailable
|
|
15
|
+
*/
|
|
16
|
+
export declare function assembleGif(frames: ShowcaseFrame[], outputPath: string, opts?: {
|
|
17
|
+
/** Frames per second. Default: 1 (1 frame per second — slideshow style). */
|
|
18
|
+
fps?: number;
|
|
19
|
+
/** Number of loops. 0 = infinite. Default: 0. */
|
|
20
|
+
loop?: number;
|
|
21
|
+
/** Max width in px. Frames are scaled down if wider. Default: 1200. */
|
|
22
|
+
maxWidth?: number;
|
|
23
|
+
}): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Assemble PNG frames into a WebM video.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assembleVideo(frames: ShowcaseFrame[], outputPath: string, opts?: {
|
|
28
|
+
fps?: number;
|
|
29
|
+
maxWidth?: number;
|
|
30
|
+
}): boolean;
|
|
31
|
+
//# sourceMappingURL=assemble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../src/showcase/assemble.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAsB/C;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,aAAa,EAAE,EACvB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IACL,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GACA,OAAO,CAiCT;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EAAE,EACvB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GACA,OAAO,CAiCT"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame assembly — combine screenshot sequence into GIF or video.
|
|
3
|
+
*
|
|
4
|
+
* Uses ffmpeg-static which is already a dependency (unused until now).
|
|
5
|
+
* Falls back gracefully if ffmpeg is not available.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
let ffmpegPath = null;
|
|
11
|
+
function getFfmpeg() {
|
|
12
|
+
if (ffmpegPath !== null)
|
|
13
|
+
return ffmpegPath;
|
|
14
|
+
try {
|
|
15
|
+
// ffmpeg-static exports the path to the binary
|
|
16
|
+
ffmpegPath = require('ffmpeg-static');
|
|
17
|
+
if (!fs.existsSync(ffmpegPath))
|
|
18
|
+
ffmpegPath = null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Try system ffmpeg
|
|
22
|
+
try {
|
|
23
|
+
execFileSync('ffmpeg', ['-version'], { stdio: 'ignore' });
|
|
24
|
+
ffmpegPath = 'ffmpeg';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
ffmpegPath = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return ffmpegPath;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Assemble PNG frames into an animated GIF.
|
|
34
|
+
*
|
|
35
|
+
* @param frames - Ordered screenshot frames
|
|
36
|
+
* @param outputPath - Where to write the GIF
|
|
37
|
+
* @param opts - Frame rate, loop count
|
|
38
|
+
* @returns true if successful, false if ffmpeg unavailable
|
|
39
|
+
*/
|
|
40
|
+
export function assembleGif(frames, outputPath, opts) {
|
|
41
|
+
const ffmpeg = getFfmpeg();
|
|
42
|
+
if (!ffmpeg || frames.length === 0)
|
|
43
|
+
return false;
|
|
44
|
+
const fps = opts?.fps ?? 1;
|
|
45
|
+
const loop = opts?.loop ?? 0;
|
|
46
|
+
const maxWidth = opts?.maxWidth ?? 1200;
|
|
47
|
+
// Write frames to temp dir
|
|
48
|
+
const tmpDir = path.join(path.dirname(outputPath), '.gif-frames');
|
|
49
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
50
|
+
for (let i = 0; i < frames.length; i++) {
|
|
51
|
+
fs.writeFileSync(path.join(tmpDir, `frame-${String(i).padStart(4, '0')}.png`), frames[i].buffer);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
execFileSync(ffmpeg, [
|
|
55
|
+
'-y',
|
|
56
|
+
'-framerate', String(fps),
|
|
57
|
+
'-i', path.join(tmpDir, 'frame-%04d.png'),
|
|
58
|
+
'-vf', `scale='min(${maxWidth},iw)':-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5`,
|
|
59
|
+
'-loop', String(loop),
|
|
60
|
+
outputPath,
|
|
61
|
+
], { stdio: 'pipe', timeout: 60_000 });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
// Clean up temp frames
|
|
69
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Assemble PNG frames into a WebM video.
|
|
74
|
+
*/
|
|
75
|
+
export function assembleVideo(frames, outputPath, opts) {
|
|
76
|
+
const ffmpeg = getFfmpeg();
|
|
77
|
+
if (!ffmpeg || frames.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
const fps = opts?.fps ?? 1;
|
|
80
|
+
const maxWidth = opts?.maxWidth ?? 1440;
|
|
81
|
+
const tmpDir = path.join(path.dirname(outputPath), '.video-frames');
|
|
82
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
83
|
+
for (let i = 0; i < frames.length; i++) {
|
|
84
|
+
fs.writeFileSync(path.join(tmpDir, `frame-${String(i).padStart(4, '0')}.png`), frames[i].buffer);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
execFileSync(ffmpeg, [
|
|
88
|
+
'-y',
|
|
89
|
+
'-framerate', String(fps),
|
|
90
|
+
'-i', path.join(tmpDir, 'frame-%04d.png'),
|
|
91
|
+
'-vf', `scale='min(${maxWidth},iw)':-2`,
|
|
92
|
+
'-c:v', 'libvpx-vp9',
|
|
93
|
+
'-crf', '30',
|
|
94
|
+
'-b:v', '0',
|
|
95
|
+
'-pix_fmt', 'yuva420p',
|
|
96
|
+
outputPath,
|
|
97
|
+
], { stdio: 'pipe', timeout: 60_000 });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=assemble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../src/showcase/assemble.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive demo player generator.
|
|
3
|
+
*
|
|
4
|
+
* Takes showcase walkthrough results (screenshots + step metadata) and produces
|
|
5
|
+
* a self-contained HTML file with an interactive step-by-step player.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Click-through slideshow of annotated screenshots
|
|
9
|
+
* - Pulsing hotspot circles at click targets
|
|
10
|
+
* - Step description tooltips
|
|
11
|
+
* - Progress bar and step counter
|
|
12
|
+
* - Keyboard navigation (← →)
|
|
13
|
+
* - Self-contained — single HTML file, images base64-inlined
|
|
14
|
+
*/
|
|
15
|
+
import type { ShowcaseStep } from './types.js';
|
|
16
|
+
export interface DemoStep {
|
|
17
|
+
/** Screenshot as base64 PNG. */
|
|
18
|
+
imageBase64: string;
|
|
19
|
+
/** Width of the screenshot. */
|
|
20
|
+
width: number;
|
|
21
|
+
/** Height of the screenshot. */
|
|
22
|
+
height: number;
|
|
23
|
+
/** Description shown as tooltip. */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Click hotspot position (percentage of image dimensions). */
|
|
26
|
+
hotspot?: {
|
|
27
|
+
xPercent: number;
|
|
28
|
+
yPercent: number;
|
|
29
|
+
label?: string;
|
|
30
|
+
};
|
|
31
|
+
/** Action that was taken at this step. */
|
|
32
|
+
action: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DemoConfig {
|
|
35
|
+
title: string;
|
|
36
|
+
steps: DemoStep[];
|
|
37
|
+
/** Brand color for hotspots and UI. Default: #8e59ff */
|
|
38
|
+
accentColor?: string;
|
|
39
|
+
/** Auto-advance interval in ms. 0 = manual only. Default: 0 */
|
|
40
|
+
autoAdvance?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate a self-contained HTML demo player.
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateDemoHtml(config: DemoConfig): string;
|
|
46
|
+
/**
|
|
47
|
+
* Build DemoSteps from showcase results + original walkthrough steps.
|
|
48
|
+
*
|
|
49
|
+
* Reads screenshot PNGs from disk, converts to base64, and maps
|
|
50
|
+
* step actions to hotspot positions.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildDemoSteps(screenshotDir: string, walkthroughSteps: ShowcaseStep[], page?: import('playwright').Page): Promise<DemoStep[]>;
|
|
53
|
+
//# sourceMappingURL=demo-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-player.d.ts","sourceRoot":"","sources":["../../src/showcase/demo-player.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,MAAM,WAAW,QAAQ;IACvB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAiQ3D;AAMD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,YAAY,EAAE,EAChC,IAAI,CAAC,EAAE,OAAO,YAAY,EAAE,IAAI,GAC/B,OAAO,CAAC,QAAQ,EAAE,CAAC,CA2CrB"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive demo player generator.
|
|
3
|
+
*
|
|
4
|
+
* Takes showcase walkthrough results (screenshots + step metadata) and produces
|
|
5
|
+
* a self-contained HTML file with an interactive step-by-step player.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Click-through slideshow of annotated screenshots
|
|
9
|
+
* - Pulsing hotspot circles at click targets
|
|
10
|
+
* - Step description tooltips
|
|
11
|
+
* - Progress bar and step counter
|
|
12
|
+
* - Keyboard navigation (← →)
|
|
13
|
+
* - Self-contained — single HTML file, images base64-inlined
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
/**
|
|
18
|
+
* Generate a self-contained HTML demo player.
|
|
19
|
+
*/
|
|
20
|
+
export function generateDemoHtml(config) {
|
|
21
|
+
const accent = config.accentColor ?? '#8e59ff';
|
|
22
|
+
const steps = JSON.stringify(config.steps.map(s => ({
|
|
23
|
+
image: s.imageBase64,
|
|
24
|
+
description: s.description ?? '',
|
|
25
|
+
hotspot: s.hotspot ?? null,
|
|
26
|
+
action: s.action,
|
|
27
|
+
})));
|
|
28
|
+
return `<!DOCTYPE html>
|
|
29
|
+
<html lang="en">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8">
|
|
32
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
33
|
+
<title>${escapeHtml(config.title)}</title>
|
|
34
|
+
<style>
|
|
35
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
36
|
+
body {
|
|
37
|
+
background: #0a0a0a;
|
|
38
|
+
color: #fff;
|
|
39
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
min-height: 100vh;
|
|
45
|
+
padding: 1rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.demo-container {
|
|
49
|
+
position: relative;
|
|
50
|
+
max-width: 1200px;
|
|
51
|
+
width: 100%;
|
|
52
|
+
border-radius: 12px;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
55
|
+
background: #111;
|
|
56
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Browser chrome */
|
|
60
|
+
.demo-chrome {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 8px;
|
|
64
|
+
padding: 10px 16px;
|
|
65
|
+
background: #1a1a1a;
|
|
66
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
67
|
+
}
|
|
68
|
+
.demo-dot { width: 12px; height: 12px; border-radius: 50%; }
|
|
69
|
+
.demo-dot.r { background: rgba(255,95,87,0.8); }
|
|
70
|
+
.demo-dot.y { background: rgba(254,188,46,0.8); }
|
|
71
|
+
.demo-dot.p { background: ${accent}cc; }
|
|
72
|
+
.demo-title {
|
|
73
|
+
margin-left: 12px;
|
|
74
|
+
font-size: 13px;
|
|
75
|
+
color: rgba(255,255,255,0.4);
|
|
76
|
+
font-family: monospace;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Image viewport */
|
|
80
|
+
.demo-viewport {
|
|
81
|
+
position: relative;
|
|
82
|
+
width: 100%;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
.demo-viewport img {
|
|
87
|
+
width: 100%;
|
|
88
|
+
display: block;
|
|
89
|
+
transition: opacity 0.3s ease;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Hotspot */
|
|
93
|
+
.hotspot {
|
|
94
|
+
position: absolute;
|
|
95
|
+
transform: translate(-50%, -50%);
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
z-index: 10;
|
|
98
|
+
}
|
|
99
|
+
.hotspot-ring {
|
|
100
|
+
width: 40px;
|
|
101
|
+
height: 40px;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
border: 2px solid ${accent};
|
|
104
|
+
animation: pulse 2s ease-in-out infinite;
|
|
105
|
+
}
|
|
106
|
+
.hotspot-dot {
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 50%;
|
|
109
|
+
left: 50%;
|
|
110
|
+
transform: translate(-50%, -50%);
|
|
111
|
+
width: 12px;
|
|
112
|
+
height: 12px;
|
|
113
|
+
border-radius: 50%;
|
|
114
|
+
background: ${accent};
|
|
115
|
+
}
|
|
116
|
+
.hotspot-label {
|
|
117
|
+
position: absolute;
|
|
118
|
+
top: -32px;
|
|
119
|
+
left: 50%;
|
|
120
|
+
transform: translateX(-50%);
|
|
121
|
+
background: ${accent};
|
|
122
|
+
color: white;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
padding: 4px 12px;
|
|
126
|
+
border-radius: 6px;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
font-family: -apple-system, sans-serif;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@keyframes pulse {
|
|
132
|
+
0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
|
133
|
+
50% { transform: translate(-50%, -50%) scale(1.5); opacity: 0.5; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Controls */
|
|
137
|
+
.demo-controls {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
padding: 12px 16px;
|
|
142
|
+
background: #1a1a1a;
|
|
143
|
+
border-top: 1px solid rgba(255,255,255,0.06);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.demo-step-info {
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
color: rgba(255,255,255,0.5);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.demo-description {
|
|
152
|
+
font-size: 14px;
|
|
153
|
+
color: rgba(255,255,255,0.8);
|
|
154
|
+
text-align: center;
|
|
155
|
+
flex: 1;
|
|
156
|
+
padding: 0 1rem;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.demo-nav {
|
|
160
|
+
display: flex;
|
|
161
|
+
gap: 8px;
|
|
162
|
+
}
|
|
163
|
+
.demo-nav button {
|
|
164
|
+
background: rgba(255,255,255,0.06);
|
|
165
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
166
|
+
color: white;
|
|
167
|
+
padding: 6px 16px;
|
|
168
|
+
border-radius: 6px;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
font-size: 13px;
|
|
171
|
+
transition: background 0.2s;
|
|
172
|
+
}
|
|
173
|
+
.demo-nav button:hover {
|
|
174
|
+
background: ${accent}33;
|
|
175
|
+
border-color: ${accent}66;
|
|
176
|
+
}
|
|
177
|
+
.demo-nav button:disabled {
|
|
178
|
+
opacity: 0.3;
|
|
179
|
+
cursor: not-allowed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Progress bar */
|
|
183
|
+
.demo-progress {
|
|
184
|
+
height: 3px;
|
|
185
|
+
background: rgba(255,255,255,0.06);
|
|
186
|
+
position: relative;
|
|
187
|
+
}
|
|
188
|
+
.demo-progress-fill {
|
|
189
|
+
height: 100%;
|
|
190
|
+
background: ${accent};
|
|
191
|
+
transition: width 0.3s ease;
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
194
|
+
</head>
|
|
195
|
+
<body>
|
|
196
|
+
|
|
197
|
+
<div class="demo-container">
|
|
198
|
+
<div class="demo-chrome">
|
|
199
|
+
<div class="demo-dot r"></div>
|
|
200
|
+
<div class="demo-dot y"></div>
|
|
201
|
+
<div class="demo-dot p"></div>
|
|
202
|
+
<span class="demo-title">${escapeHtml(config.title)}</span>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="demo-progress">
|
|
206
|
+
<div class="demo-progress-fill" id="progress"></div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div class="demo-viewport" id="viewport" onclick="next()">
|
|
210
|
+
<img id="screenshot" alt="Demo step" />
|
|
211
|
+
<div class="hotspot" id="hotspot" style="display:none;">
|
|
212
|
+
<div class="hotspot-ring"></div>
|
|
213
|
+
<div class="hotspot-dot"></div>
|
|
214
|
+
<div class="hotspot-label" id="hotspot-label"></div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div class="demo-controls">
|
|
219
|
+
<div class="demo-step-info" id="step-info">Step 1 of N</div>
|
|
220
|
+
<div class="demo-description" id="description"></div>
|
|
221
|
+
<div class="demo-nav">
|
|
222
|
+
<button onclick="prev()" id="prev-btn">← Back</button>
|
|
223
|
+
<button onclick="next()" id="next-btn">Next →</button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<script>
|
|
229
|
+
const steps = ${steps};
|
|
230
|
+
let current = 0;
|
|
231
|
+
|
|
232
|
+
function render() {
|
|
233
|
+
const step = steps[current];
|
|
234
|
+
document.getElementById('screenshot').src = 'data:image/png;base64,' + step.image;
|
|
235
|
+
document.getElementById('step-info').textContent = 'Step ' + (current + 1) + ' of ' + steps.length;
|
|
236
|
+
document.getElementById('description').textContent = step.description || '';
|
|
237
|
+
document.getElementById('progress').style.width = ((current + 1) / steps.length * 100) + '%';
|
|
238
|
+
document.getElementById('prev-btn').disabled = current === 0;
|
|
239
|
+
document.getElementById('next-btn').disabled = current === steps.length - 1;
|
|
240
|
+
document.getElementById('next-btn').textContent = current === steps.length - 1 ? 'Done ✓' : 'Next →';
|
|
241
|
+
|
|
242
|
+
const hotspot = document.getElementById('hotspot');
|
|
243
|
+
const label = document.getElementById('hotspot-label');
|
|
244
|
+
if (step.hotspot) {
|
|
245
|
+
hotspot.style.display = 'block';
|
|
246
|
+
hotspot.style.left = step.hotspot.xPercent + '%';
|
|
247
|
+
hotspot.style.top = step.hotspot.yPercent + '%';
|
|
248
|
+
if (step.hotspot.label) {
|
|
249
|
+
label.textContent = step.hotspot.label;
|
|
250
|
+
label.style.display = 'block';
|
|
251
|
+
} else {
|
|
252
|
+
label.style.display = 'none';
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
hotspot.style.display = 'none';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function next() {
|
|
260
|
+
if (current < steps.length - 1) { current++; render(); }
|
|
261
|
+
}
|
|
262
|
+
function prev() {
|
|
263
|
+
if (current > 0) { current--; render(); }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
document.addEventListener('keydown', (e) => {
|
|
267
|
+
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); next(); }
|
|
268
|
+
if (e.key === 'ArrowLeft') { e.preventDefault(); prev(); }
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
render();
|
|
272
|
+
</script>
|
|
273
|
+
|
|
274
|
+
</body>
|
|
275
|
+
</html>`;
|
|
276
|
+
}
|
|
277
|
+
function escapeHtml(s) {
|
|
278
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Build DemoSteps from showcase results + original walkthrough steps.
|
|
282
|
+
*
|
|
283
|
+
* Reads screenshot PNGs from disk, converts to base64, and maps
|
|
284
|
+
* step actions to hotspot positions.
|
|
285
|
+
*/
|
|
286
|
+
export async function buildDemoSteps(screenshotDir, walkthroughSteps, page) {
|
|
287
|
+
const demoSteps = [];
|
|
288
|
+
const pngFiles = fs.readdirSync(screenshotDir)
|
|
289
|
+
.filter(f => f.endsWith('.png') && !f.includes('full-page'))
|
|
290
|
+
.sort();
|
|
291
|
+
for (let i = 0; i < pngFiles.length; i++) {
|
|
292
|
+
const filePath = path.join(screenshotDir, pngFiles[i]);
|
|
293
|
+
const buffer = fs.readFileSync(filePath);
|
|
294
|
+
const base64 = buffer.toString('base64');
|
|
295
|
+
const step = walkthroughSteps[i];
|
|
296
|
+
let hotspot = undefined;
|
|
297
|
+
// If the next step has a click action with a selector, calculate hotspot position
|
|
298
|
+
const nextStep = walkthroughSteps[i + 1];
|
|
299
|
+
if (nextStep?.action === 'click' && nextStep.selector && page) {
|
|
300
|
+
try {
|
|
301
|
+
const el = page.locator(nextStep.selector).first();
|
|
302
|
+
const box = await el.boundingBox();
|
|
303
|
+
const viewport = page.viewportSize();
|
|
304
|
+
if (box && viewport) {
|
|
305
|
+
hotspot = {
|
|
306
|
+
xPercent: ((box.x + box.width / 2) / viewport.width) * 100,
|
|
307
|
+
yPercent: ((box.y + box.height / 2) / viewport.height) * 100,
|
|
308
|
+
label: nextStep.capture?.name ?? `Click ${nextStep.selector}`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch { /* element not found — skip hotspot */ }
|
|
313
|
+
}
|
|
314
|
+
demoSteps.push({
|
|
315
|
+
imageBase64: base64,
|
|
316
|
+
width: 1200, // Will be overridden by actual image dimensions if available
|
|
317
|
+
height: 800,
|
|
318
|
+
description: step?.capture?.name?.replace(/-/g, ' ').replace(/^\d+-/, '') ?? `Step ${i + 1}`,
|
|
319
|
+
hotspot,
|
|
320
|
+
action: step?.action ?? 'screenshot',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return demoSteps;
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=demo-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-player.js","sourceRoot":"","sources":["../../src/showcase/demo-player.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Showcase — deterministic walkthrough capture for marketing assets.
|
|
3
|
+
*
|
|
4
|
+
* Unlike `bad run` (LLM-driven exploration) or `bad design-audit` (quality assessment),
|
|
5
|
+
* showcase executes a pre-scripted walkthrough and captures polished screenshots,
|
|
6
|
+
* GIFs, and videos. No LLM calls. No quality judgement. Just beautiful captures.
|
|
7
|
+
*
|
|
8
|
+
* Reuses:
|
|
9
|
+
* - page-interaction.ts: dismissModals() for cookie/popup cleanup
|
|
10
|
+
* - browser-launch.ts: buildBrowserLaunchPlan() for consistent browser config
|
|
11
|
+
* - cli-ui.ts: CliRenderer for progress output
|
|
12
|
+
*/
|
|
13
|
+
import type { ShowcaseConfig, ShowcaseResult, QuickCaptureConfig } from './types.js';
|
|
14
|
+
export declare function runShowcase(config: ShowcaseConfig): Promise<ShowcaseResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Quick capture mode — no script file needed.
|
|
17
|
+
* Captures named positions: 'hero' (viewport), 'full' (full page), 'scroll:N' (scroll N px first).
|
|
18
|
+
*/
|
|
19
|
+
export declare function quickCapture(config: QuickCaptureConfig): Promise<ShowcaseResult>;
|
|
20
|
+
export type { ShowcaseConfig, ShowcaseStep, ShowcaseFrame, ShowcaseResult, QuickCaptureConfig } from './types.js';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/showcase/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EACV,cAAc,EAGd,cAAc,EACd,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAOnB,wBAAsB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAgIjF;AAID;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0DtF;AAmHD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA"}
|