@deckio/deck-engine 1.7.6 → 1.7.7

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.
@@ -1,34 +1,34 @@
1
- ---
2
- description: "Use when working in any deck-project folder. Defines the role, scope, and guardrails for Copilot in deck presentation projects."
3
- applyTo: "deck-project-*/**"
4
- ---
5
-
6
- # Deck Project Scope
7
-
8
- You are a **slide builder** for a presentation deck built with `@deckio/deck-engine`.
9
-
10
- ## Your role
11
-
12
- - Create new slide components (JSX + CSS module pairs)
13
- - Edit existing slides
14
- - Manage data files in `src/data/`
15
- - Register / reorder slides in `deck.config.js`
16
-
17
- ## Out of scope — do NOT modify
18
-
19
- - `App.jsx`, `main.jsx` — these are generic, engine-driven, identical across projects
20
- - `vite.config.js`, `package.json`, `index.html` — project scaffolding, don't touch
21
- - Anything in `node_modules/` or the deck-engine package itself
22
-
23
- ## Project architecture
24
-
25
- - `deck.config.js` — single source of truth: metadata + slide array
26
- - `src/slides/` — one `PascalCase.jsx` + matching `.module.css` per slide
27
- - `src/data/` — ESM exports for logos, speakers, opportunity data, governance
28
- - The engine (`@deckio/deck-engine`) provides: `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
-
30
- ## Data conventions
31
-
32
- - Always use ESM imports for images: `import logo from '../data/logos/acme.png'`
33
- - Export data as named exports or default objects
34
- - Keep data files focused — one concern per file
1
+ ---
2
+ description: "Use when working in any deck-project folder. Defines the role, scope, and guardrails for Copilot in deck presentation projects."
3
+ applyTo: "deck-project-*/**"
4
+ ---
5
+
6
+ # Deck Project Scope
7
+
8
+ You are a **slide builder** for a presentation deck built with `@deckio/deck-engine`.
9
+
10
+ ## Your role
11
+
12
+ - Create new slide components (JSX + CSS module pairs)
13
+ - Edit existing slides
14
+ - Manage data files in `src/data/`
15
+ - Register / reorder slides in `deck.config.js`
16
+
17
+ ## Out of scope — do NOT modify
18
+
19
+ - `App.jsx`, `main.jsx` — these are generic, engine-driven, identical across projects
20
+ - `vite.config.js`, `package.json`, `index.html` — project scaffolding, don't touch
21
+ - Anything in `node_modules/` or the deck-engine package itself
22
+
23
+ ## Project architecture
24
+
25
+ - `deck.config.js` — single source of truth: metadata + slide array
26
+ - `src/slides/` — one `PascalCase.jsx` + matching `.module.css` per slide
27
+ - `src/data/` — ESM exports for logos, speakers, opportunity data, governance
28
+ - The engine (`@deckio/deck-engine`) provides: `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
+
30
+ ## Data conventions
31
+
32
+ - Always use ESM imports for images: `import logo from '../data/logos/acme.png'`
33
+ - Export data as named exports or default objects
34
+ - Keep data files focused — one concern per file
@@ -1,91 +1,91 @@
1
- ---
2
- description: "Use when creating or editing slide CSS modules in a deck project. Enforces required properties, orb positioning, and theme variables."
3
- applyTo: "**/slides/**/*.module.css"
4
- ---
5
-
6
- # Slide CSS Module Conventions
7
-
8
- ## Required root class
9
-
10
- ```css
11
- .mySlide {
12
- background: var(--bg-deep);
13
- flex-direction: column;
14
- padding: 0 0 44px 0; /* reserve BottomBar height */
15
- }
16
- ```
17
-
18
- Add `justify-content: center` for cover or thank-you slides.
19
-
20
- ## Orb positioning recipe
21
-
22
- ```css
23
- .orb1 {
24
- width: 420px; height: 420px;
25
- top: -100px; right: -60px;
26
- background: radial-gradient(circle at 40% 40%, var(--accent), var(--blue-glow) 50%, transparent 70%);
27
- }
28
- .orb2 {
29
- width: 320px; height: 320px;
30
- bottom: -40px; right: 100px;
31
- background: radial-gradient(circle at 50% 50%, var(--purple-deep), rgba(110,64,201,0.25) 60%, transparent 75%);
32
- }
33
- ```
34
-
35
- ## Body wrapper
36
-
37
- ```css
38
- .body {
39
- position: relative;
40
- z-index: 10;
41
- display: flex;
42
- flex-direction: column;
43
- justify-content: center;
44
- flex: 1;
45
- min-height: 0;
46
- }
47
- ```
48
-
49
- ## Theme variables (always use these, never hard-code colors)
50
-
51
- | Variable | Value |
52
- |----------|-------|
53
- | `--bg-deep` | `#080b10` |
54
- | `--surface` | `#161b22` |
55
- | `--border` | `#30363d` |
56
- | `--text` | `#e6edf3` |
57
- | `--text-muted` | `#8b949e` |
58
- | `--accent` | project-specific |
59
- | `--blue-glow` | `#1f6feb` |
60
- | `--purple` | `#bc8cff` |
61
- | `--purple-deep` | `#6e40c9` |
62
- | `--pink` | `#f778ba` |
63
- | `--cyan` | `#56d4dd` |
64
- | `--green` | `#3fb950` |
65
- | `--orange` | `#d29922` |
66
-
67
- ## Global classes (no import needed)
68
-
69
- `accent-bar`, `orb`, `grid-dots`, `content-frame`, `content-gutter`
70
-
71
- ## Card pattern
72
-
73
- ```css
74
- .card {
75
- background: var(--surface);
76
- border: 1px solid var(--border);
77
- border-radius: 16px;
78
- padding: 24px;
79
- }
80
- ```
81
-
82
- ## Typography
83
-
84
- | Element | Size | Weight | Spacing |
85
- |---------|------|--------|---------|
86
- | h1 | `clamp(42px, 5vw, 72px)` | 900 | `-2px` |
87
- | h2 | `clamp(28px, 3.2vw, 36px)` | 700 | `-0.8px` |
88
- | h3 | `16px–20px` | 700 | `-0.3px` |
89
- | Subtitle | `17px` | 300–400 | — |
90
- | Body | `13px–14px` | 400 | — |
91
- | Badge | `10px–11px` | 600–700 | `1.5px` |
1
+ ---
2
+ description: "Use when creating or editing slide CSS modules in a deck project. Enforces required properties, orb positioning, and theme variables."
3
+ applyTo: "**/slides/**/*.module.css"
4
+ ---
5
+
6
+ # Slide CSS Module Conventions
7
+
8
+ ## Required root class
9
+
10
+ ```css
11
+ .mySlide {
12
+ background: var(--bg-deep);
13
+ flex-direction: column;
14
+ padding: 0 0 44px 0; /* reserve BottomBar height */
15
+ }
16
+ ```
17
+
18
+ Add `justify-content: center` for cover or thank-you slides.
19
+
20
+ ## Orb positioning recipe
21
+
22
+ ```css
23
+ .orb1 {
24
+ width: 420px; height: 420px;
25
+ top: -100px; right: -60px;
26
+ background: radial-gradient(circle at 40% 40%, var(--accent), var(--blue-glow) 50%, transparent 70%);
27
+ }
28
+ .orb2 {
29
+ width: 320px; height: 320px;
30
+ bottom: -40px; right: 100px;
31
+ background: radial-gradient(circle at 50% 50%, var(--purple-deep), rgba(110,64,201,0.25) 60%, transparent 75%);
32
+ }
33
+ ```
34
+
35
+ ## Body wrapper
36
+
37
+ ```css
38
+ .body {
39
+ position: relative;
40
+ z-index: 10;
41
+ display: flex;
42
+ flex-direction: column;
43
+ justify-content: center;
44
+ flex: 1;
45
+ min-height: 0;
46
+ }
47
+ ```
48
+
49
+ ## Theme variables (always use these, never hard-code colors)
50
+
51
+ | Variable | Value |
52
+ |----------|-------|
53
+ | `--bg-deep` | `#080b10` |
54
+ | `--surface` | `#161b22` |
55
+ | `--border` | `#30363d` |
56
+ | `--text` | `#e6edf3` |
57
+ | `--text-muted` | `#8b949e` |
58
+ | `--accent` | project-specific |
59
+ | `--blue-glow` | `#1f6feb` |
60
+ | `--purple` | `#bc8cff` |
61
+ | `--purple-deep` | `#6e40c9` |
62
+ | `--pink` | `#f778ba` |
63
+ | `--cyan` | `#56d4dd` |
64
+ | `--green` | `#3fb950` |
65
+ | `--orange` | `#d29922` |
66
+
67
+ ## Global classes (no import needed)
68
+
69
+ `accent-bar`, `orb`, `grid-dots`, `content-frame`, `content-gutter`
70
+
71
+ ## Card pattern
72
+
73
+ ```css
74
+ .card {
75
+ background: var(--surface);
76
+ border: 1px solid var(--border);
77
+ border-radius: 16px;
78
+ padding: 24px;
79
+ }
80
+ ```
81
+
82
+ ## Typography
83
+
84
+ | Element | Size | Weight | Spacing |
85
+ |---------|------|--------|---------|
86
+ | h1 | `clamp(42px, 5vw, 72px)` | 900 | `-2px` |
87
+ | h2 | `clamp(28px, 3.2vw, 36px)` | 700 | `-0.8px` |
88
+ | h3 | `16px–20px` | 700 | `-0.3px` |
89
+ | Subtitle | `17px` | 300–400 | — |
90
+ | Body | `13px–14px` | 400 | — |
91
+ | Badge | `10px–11px` | 600–700 | `1.5px` |
@@ -1,34 +1,34 @@
1
- ---
2
- description: "Use when creating, editing, or reviewing slide JSX components in a deck project. Enforces the mandatory slide skeleton, imports, and anti-patterns."
3
- applyTo: "**/slides/**/*.jsx"
4
- ---
5
-
6
- # Slide JSX Conventions
7
-
8
- ## Imports
9
-
10
- ```jsx
11
- import { BottomBar, Slide } from '@deckio/deck-engine'
12
- import styles from './MySlide.module.css'
13
- ```
14
-
15
- ## Mandatory skeleton (in order inside `<Slide>`)
16
-
17
- 1. `<div className="accent-bar" />` — always first
18
- 2. 2–4 orbs: `<div className={\`orb ${styles.orbN}\`} />`
19
- 3. Content wrapper: `<div className={\`${styles.body} content-frame content-gutter\`}>` — all visible content here
20
- 4. `<BottomBar text="..." />` — always last child
21
-
22
- ## Props
23
-
24
- Every slide receives `{ index, project, title, subtitle }`. Pass `index` to `<Slide>`.
25
-
26
- ## Available engine exports
27
-
28
- `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
-
30
- ## Anti-patterns
31
-
32
- - Never omit `accent-bar`, `content-frame content-gutter`, or `BottomBar`
33
- - Never use string paths for images — always `import logo from '../data/...'`
34
- - Never hardcode slide indices — use `useSlides().goTo()` for navigation
1
+ ---
2
+ description: "Use when creating, editing, or reviewing slide JSX components in a deck project. Enforces the mandatory slide skeleton, imports, and anti-patterns."
3
+ applyTo: "**/slides/**/*.jsx"
4
+ ---
5
+
6
+ # Slide JSX Conventions
7
+
8
+ ## Imports
9
+
10
+ ```jsx
11
+ import { BottomBar, Slide } from '@deckio/deck-engine'
12
+ import styles from './MySlide.module.css'
13
+ ```
14
+
15
+ ## Mandatory skeleton (in order inside `<Slide>`)
16
+
17
+ 1. `<div className="accent-bar" />` — always first
18
+ 2. 2–4 orbs: `<div className={\`orb ${styles.orbN}\`} />`
19
+ 3. Content wrapper: `<div className={\`${styles.body} content-frame content-gutter\`}>` — all visible content here
20
+ 4. `<BottomBar text="..." />` — always last child
21
+
22
+ ## Props
23
+
24
+ Every slide receives `{ index, project, title, subtitle }`. Pass `index` to `<Slide>`.
25
+
26
+ ## Available engine exports
27
+
28
+ `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
+
30
+ ## Anti-patterns
31
+
32
+ - Never omit `accent-bar`, `content-frame content-gutter`, or `BottomBar`
33
+ - Never use string paths for images — always `import logo from '../data/...'`
34
+ - Never hardcode slide indices — use `useSlides().goTo()` for navigation
package/package.json CHANGED
@@ -1,45 +1,49 @@
1
- {
2
- "name": "@deckio/deck-engine",
3
- "version": "1.7.6",
4
- "type": "module",
5
- "publishConfig": {
6
- "registry": "https://registry.npmjs.org",
7
- "access": "public"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/lopezleandro03/github-ai-adoption-program.git",
12
- "directory": "packages/deck-engine"
13
- },
14
- "main": "./index.js",
15
- "module": "./index.js",
16
- "exports": {
17
- ".": "./index.js",
18
- "./slides/*": "./slides/*.jsx",
19
- "./components/*": "./components/*.jsx",
20
- "./styles/*": "./styles/*",
21
- "./vite": "./vite.js"
22
- },
23
- "files": [
24
- "index.js",
25
- "vite.js",
26
- "components",
27
- "context",
28
- "slides",
29
- "styles",
30
- "scripts",
31
- "skills",
32
- "instructions"
33
- ],
34
- "sideEffects": [
35
- "**/*.css"
36
- ],
37
- "peerDependencies": {
38
- "react": "^19.1.0",
39
- "react-dom": "^19.1.0"
40
- },
41
- "optionalDependencies": {
42
- "puppeteer": "^24.38.0",
43
- "puppeteer-core": "^24.38.0"
44
- }
45
- }
1
+ {
2
+ "name": "@deckio/deck-engine",
3
+ "version": "1.7.7",
4
+ "type": "module",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org",
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/deckio-art/deck-engine.git",
12
+ "directory": "packages/deck-engine"
13
+ },
14
+ "main": "./index.js",
15
+ "module": "./index.js",
16
+ "exports": {
17
+ ".": "./index.js",
18
+ "./slides/*": "./slides/*.jsx",
19
+ "./components/*": "./components/*.jsx",
20
+ "./styles/*": "./styles/*",
21
+ "./vite": "./vite.js"
22
+ },
23
+ "files": [
24
+ "index.js",
25
+ "vite.js",
26
+ "components",
27
+ "context",
28
+ "slides",
29
+ "styles",
30
+ "scripts",
31
+ "skills",
32
+ "instructions"
33
+ ],
34
+ "sideEffects": [
35
+ "**/*.css"
36
+ ],
37
+ "dependencies": {
38
+ "html2canvas": "^1.4.1",
39
+ "jspdf": "^3.0.3"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^19.1.0",
43
+ "react-dom": "^19.1.0"
44
+ },
45
+ "optionalDependencies": {
46
+ "puppeteer": "^24.38.0",
47
+ "puppeteer-core": "^24.38.0"
48
+ }
49
+ }
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Capture a screenshot of the deck app via headless Edge.
4
- *
5
- * Usage:
6
- * node scripts/capture-screen.mjs # project + port from state.md
7
- * node scripts/capture-screen.mjs --project dev-plan # specific project
8
- * node scripts/capture-screen.mjs --slide 3 # specific slide (1-based)
9
- * node scripts/capture-screen.mjs --project dev-plan --slide 2 --label "cover-check"
10
- */
11
- import puppeteer from 'puppeteer-core'
12
- import { existsSync, mkdirSync, readFileSync } from 'fs'
13
- import path from 'path'
14
-
15
- const EDGE = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
16
- const sleep = ms => new Promise(r => setTimeout(r, ms))
17
- const args = process.argv.slice(2)
18
- const arg = (name, fb) => {
19
- const i = args.indexOf(`--${name}`)
20
- return i !== -1 && args[i + 1] ? args[i + 1] : fb
21
- }
22
- const numArg = (name, fb) => {
23
- const v = arg(name, null)
24
- return v != null ? Number(v) : fb
25
- }
26
-
27
- const root = path.resolve(arg('root', process.cwd()))
28
- const eyesDir = path.join(root, '.github', 'eyes')
29
- const stateFile = path.join(root, '.github', 'memory', 'state.md')
30
-
31
- const state = readState()
32
- const PROJECT = arg('project', state.project || null)
33
- const PORT = arg('port', state.port || '5175')
34
- const SLIDE = numArg('slide', null)
35
- const LABEL = arg('label', null)
36
- const URL = `http://localhost:${PORT}/#/${PROJECT}`
37
-
38
- if (!PROJECT) {
39
- console.error('❌ No project. Use --project <id> or set it in .github/memory/state.md')
40
- process.exit(1)
41
- }
42
-
43
- function readState() {
44
- if (!existsSync(stateFile)) return {}
45
- const c = readFileSync(stateFile, 'utf-8')
46
- return {
47
- project: c.match(/^project:\s*(.+)$/m)?.[1]?.trim() || '',
48
- port: c.match(/^port:\s*(\d+)$/m)?.[1]?.trim() || '5175',
49
- }
50
- }
51
-
52
- async function main() {
53
- mkdirSync(eyesDir, { recursive: true })
54
- console.log(`👁️ ${PROJECT} ${URL}${SLIDE ? ` slide ${SLIDE}` : ''}`)
55
-
56
- try {
57
- const response = await fetch(`http://localhost:${PORT}`, { signal: AbortSignal.timeout(3000) })
58
- if (!response.ok) throw new Error('Dev server unavailable')
59
- } catch {
60
- console.error(`❌ Dev server not reachable on port ${PORT}`)
61
- process.exit(1)
62
- }
63
-
64
- const browser = await puppeteer.launch({
65
- executablePath: EDGE,
66
- headless: 'new',
67
- args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
68
- defaultViewport: { width: 1920, height: 1080, deviceScaleFactor: 2 },
69
- })
70
-
71
- const page = await browser.newPage()
72
- await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 15000 })
73
- await page.waitForFunction(() => document.querySelectorAll('.slide').length > 0, { timeout: 10000 })
74
- await sleep(1500)
75
-
76
- if (SLIDE && SLIDE > 1) {
77
- const total = await page.evaluate(() => document.querySelectorAll('.slide').length)
78
- for (let i = 0; i < total; i++) {
79
- await page.keyboard.press('ArrowLeft')
80
- await sleep(50)
81
- }
82
- for (let i = 0; i < SLIDE - 1; i++) {
83
- await page.keyboard.press('ArrowRight')
84
- await sleep(100)
85
- }
86
- await sleep(800)
87
- }
88
-
89
- await page.evaluate(() => {
90
- document.querySelectorAll('[class*="nav"],[class*="Nav"],[class*="progress"],[class*="Progress"],[class*="hint"],[class*="Hint"],[class*="bottomBar"],[class*="BottomBar"]')
91
- .forEach(el => {
92
- if (el.tagName !== 'BODY' && el.tagName !== 'HTML') el.style.display = 'none'
93
- })
94
- })
95
- await sleep(300)
96
-
97
- const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
98
- const name = [PROJECT, SLIDE && `slide${SLIDE}`, LABEL, ts].filter(Boolean).join('-') + '.png'
99
- const filepath = path.join(eyesDir, name)
100
- await page.screenshot({ path: filepath, type: 'png' })
101
- await browser.close()
102
-
103
- const rel = path.relative(root, filepath).replace(/\\/g, '/')
104
- console.log(`📸 ${rel}`)
105
- }
106
-
107
- main().catch(err => {
108
- console.error('❌', err?.message || err)
109
- process.exit(1)
110
- })
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Capture a screenshot of the deck app via headless Edge.
4
+ *
5
+ * Usage:
6
+ * node scripts/capture-screen.mjs # project + port from state.md
7
+ * node scripts/capture-screen.mjs --project dev-plan # specific project
8
+ * node scripts/capture-screen.mjs --slide 3 # specific slide (1-based)
9
+ * node scripts/capture-screen.mjs --project dev-plan --slide 2 --label "cover-check"
10
+ */
11
+ import puppeteer from 'puppeteer-core'
12
+ import { existsSync, mkdirSync, readFileSync } from 'fs'
13
+ import path from 'path'
14
+
15
+ const EDGE = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
16
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
17
+ const args = process.argv.slice(2)
18
+ const arg = (name, fb) => {
19
+ const i = args.indexOf(`--${name}`)
20
+ return i !== -1 && args[i + 1] ? args[i + 1] : fb
21
+ }
22
+ const numArg = (name, fb) => {
23
+ const v = arg(name, null)
24
+ return v != null ? Number(v) : fb
25
+ }
26
+
27
+ const root = path.resolve(arg('root', process.cwd()))
28
+ const eyesDir = path.join(root, '.github', 'eyes')
29
+ const stateFile = path.join(root, '.github', 'memory', 'state.md')
30
+
31
+ const state = readState()
32
+ const PROJECT = arg('project', state.project || null)
33
+ const PORT = arg('port', state.port || '5175')
34
+ const SLIDE = numArg('slide', null)
35
+ const LABEL = arg('label', null)
36
+ const URL = `http://localhost:${PORT}/#/${PROJECT}`
37
+
38
+ if (!PROJECT) {
39
+ console.error('❌ No project. Use --project <id> or set it in .github/memory/state.md')
40
+ process.exit(1)
41
+ }
42
+
43
+ function readState() {
44
+ if (!existsSync(stateFile)) return {}
45
+ const c = readFileSync(stateFile, 'utf-8')
46
+ return {
47
+ project: c.match(/^project:\s*(.+)$/m)?.[1]?.trim() || '',
48
+ port: c.match(/^port:\s*(\d+)$/m)?.[1]?.trim() || '5175',
49
+ }
50
+ }
51
+
52
+ async function main() {
53
+ mkdirSync(eyesDir, { recursive: true })
54
+ console.log(`👁️ ${PROJECT} ${URL}${SLIDE ? ` slide ${SLIDE}` : ''}`)
55
+
56
+ try {
57
+ const response = await fetch(`http://localhost:${PORT}`, { signal: AbortSignal.timeout(3000) })
58
+ if (!response.ok) throw new Error('Dev server unavailable')
59
+ } catch {
60
+ console.error(`❌ Dev server not reachable on port ${PORT}`)
61
+ process.exit(1)
62
+ }
63
+
64
+ const browser = await puppeteer.launch({
65
+ executablePath: EDGE,
66
+ headless: 'new',
67
+ args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
68
+ defaultViewport: { width: 1920, height: 1080, deviceScaleFactor: 2 },
69
+ })
70
+
71
+ const page = await browser.newPage()
72
+ await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 15000 })
73
+ await page.waitForFunction(() => document.querySelectorAll('.slide').length > 0, { timeout: 10000 })
74
+ await sleep(1500)
75
+
76
+ if (SLIDE && SLIDE > 1) {
77
+ const total = await page.evaluate(() => document.querySelectorAll('.slide').length)
78
+ for (let i = 0; i < total; i++) {
79
+ await page.keyboard.press('ArrowLeft')
80
+ await sleep(50)
81
+ }
82
+ for (let i = 0; i < SLIDE - 1; i++) {
83
+ await page.keyboard.press('ArrowRight')
84
+ await sleep(100)
85
+ }
86
+ await sleep(800)
87
+ }
88
+
89
+ await page.evaluate(() => {
90
+ document.querySelectorAll('[class*="nav"],[class*="Nav"],[class*="progress"],[class*="Progress"],[class*="hint"],[class*="Hint"],[class*="bottomBar"],[class*="BottomBar"]')
91
+ .forEach(el => {
92
+ if (el.tagName !== 'BODY' && el.tagName !== 'HTML') el.style.display = 'none'
93
+ })
94
+ })
95
+ await sleep(300)
96
+
97
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
98
+ const name = [PROJECT, SLIDE && `slide${SLIDE}`, LABEL, ts].filter(Boolean).join('-') + '.png'
99
+ const filepath = path.join(eyesDir, name)
100
+ await page.screenshot({ path: filepath, type: 'png' })
101
+ await browser.close()
102
+
103
+ const rel = path.relative(root, filepath).replace(/\\/g, '/')
104
+ console.log(`📸 ${rel}`)
105
+ }
106
+
107
+ main().catch(err => {
108
+ console.error('❌', err?.message || err)
109
+ process.exit(1)
110
+ })