@vib3code/sdk 2.0.1 → 2.0.3-canary.91a95f3

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +243 -0
  3. package/DOCS/CLI_ONBOARDING.md +1 -1
  4. package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +117 -0
  5. package/DOCS/EPIC_SCROLL_EVENTS.md +773 -0
  6. package/DOCS/HANDOFF_LANDING_PAGE.md +154 -0
  7. package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +493 -0
  8. package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +937 -0
  9. package/DOCS/PRODUCT_STRATEGY.md +63 -0
  10. package/DOCS/README.md +103 -0
  11. package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +97 -0
  12. package/DOCS/ROADMAP.md +111 -0
  13. package/DOCS/SCROLL_TIMELINE_v3.md +269 -0
  14. package/DOCS/SITE_REFACTOR_PLAN.md +100 -0
  15. package/DOCS/STATUS.md +24 -0
  16. package/DOCS/SYSTEM_INVENTORY.md +33 -30
  17. package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +85 -0
  18. package/DOCS/VISUAL_ANALYSIS_FACETAD.md +133 -0
  19. package/DOCS/VISUAL_ANALYSIS_SIMONE.md +95 -0
  20. package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +86 -0
  21. package/DOCS/{BLUEPRINT_EXECUTION_PLAN_2026-01-07.md → archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md} +1 -1
  22. package/DOCS/{DEV_TRACK_ANALYSIS.md → archive/DEV_TRACK_ANALYSIS.md} +3 -0
  23. package/DOCS/{SYSTEM_AUDIT_2026-01-30.md → archive/SYSTEM_AUDIT_2026-01-30.md} +3 -0
  24. package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +1 -1
  25. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +231 -0
  26. package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +114 -0
  27. package/DOCS/dev-tracks/README.md +10 -0
  28. package/README.md +26 -13
  29. package/cpp/CMakeLists.txt +236 -0
  30. package/cpp/bindings/embind.cpp +269 -0
  31. package/cpp/build.sh +129 -0
  32. package/cpp/geometry/Crystal.cpp +103 -0
  33. package/cpp/geometry/Fractal.cpp +136 -0
  34. package/cpp/geometry/GeometryGenerator.cpp +262 -0
  35. package/cpp/geometry/KleinBottle.cpp +71 -0
  36. package/cpp/geometry/Sphere.cpp +134 -0
  37. package/cpp/geometry/Tesseract.cpp +94 -0
  38. package/cpp/geometry/Tetrahedron.cpp +83 -0
  39. package/cpp/geometry/Torus.cpp +65 -0
  40. package/cpp/geometry/WarpFunctions.cpp +238 -0
  41. package/cpp/geometry/Wave.cpp +85 -0
  42. package/cpp/include/vib3_ffi.h +238 -0
  43. package/cpp/math/Mat4x4.cpp +409 -0
  44. package/cpp/math/Mat4x4.hpp +209 -0
  45. package/cpp/math/Projection.cpp +142 -0
  46. package/cpp/math/Projection.hpp +148 -0
  47. package/cpp/math/Rotor4D.cpp +322 -0
  48. package/cpp/math/Rotor4D.hpp +204 -0
  49. package/cpp/math/Vec4.cpp +303 -0
  50. package/cpp/math/Vec4.hpp +225 -0
  51. package/cpp/src/vib3_ffi.cpp +607 -0
  52. package/cpp/tests/Geometry_test.cpp +213 -0
  53. package/cpp/tests/Mat4x4_test.cpp +494 -0
  54. package/cpp/tests/Projection_test.cpp +298 -0
  55. package/cpp/tests/Rotor4D_test.cpp +423 -0
  56. package/cpp/tests/Vec4_test.cpp +489 -0
  57. package/package.json +31 -27
  58. package/src/agent/mcp/MCPServer.js +722 -0
  59. package/src/agent/mcp/stdio-server.js +264 -0
  60. package/src/agent/mcp/tools.js +367 -0
  61. package/src/cli/index.js +0 -0
  62. package/src/core/CanvasManager.js +97 -204
  63. package/src/core/ErrorReporter.js +1 -1
  64. package/src/core/Parameters.js +1 -1
  65. package/src/core/VIB3Engine.js +38 -1
  66. package/src/core/VitalitySystem.js +53 -0
  67. package/src/core/renderers/HolographicRendererAdapter.js +2 -2
  68. package/src/creative/AestheticMapper.js +628 -0
  69. package/src/creative/ChoreographyPlayer.js +481 -0
  70. package/src/export/TradingCardManager.js +3 -4
  71. package/src/faceted/FacetedSystem.js +237 -388
  72. package/src/holograms/HolographicVisualizer.js +29 -12
  73. package/src/holograms/RealHolographicSystem.js +68 -12
  74. package/src/polychora/PolychoraSystem.js +77 -0
  75. package/src/quantum/QuantumEngine.js +103 -66
  76. package/src/quantum/QuantumVisualizer.js +7 -2
  77. package/src/render/UnifiedRenderBridge.js +3 -0
  78. package/src/shaders/faceted/faceted.frag.glsl +220 -80
  79. package/src/shaders/faceted/faceted.frag.wgsl +138 -97
  80. package/src/shaders/holographic/holographic.frag.glsl +28 -9
  81. package/src/shaders/holographic/holographic.frag.wgsl +107 -38
  82. package/src/shaders/quantum/quantum.frag.glsl +1 -0
  83. package/src/shaders/quantum/quantum.frag.wgsl +1 -1
  84. package/src/viewer/index.js +1 -1
  85. package/tools/headless-renderer.js +258 -0
  86. package/tools/shader-sync-verify.js +8 -4
  87. package/tools/site-analysis/all-reports.json +32 -0
  88. package/tools/site-analysis/combined-analysis.md +50 -0
  89. package/tools/site-analyzer.mjs +779 -0
  90. package/tools/visual-catalog/capture.js +276 -0
  91. package/tools/visual-catalog/composite.js +138 -0
  92. /package/DOCS/{DEV_TRACK_PLAN_2026-01-07.md → archive/DEV_TRACK_PLAN_2026-01-07.md} +0 -0
  93. /package/DOCS/{SESSION_014_PLAN.md → archive/SESSION_014_PLAN.md} +0 -0
  94. /package/DOCS/{SESSION_LOG_2026-01-07.md → archive/SESSION_LOG_2026-01-07.md} +0 -0
  95. /package/DOCS/{STRATEGIC_BLUEPRINT_2026-01-07.md → archive/STRATEGIC_BLUEPRINT_2026-01-07.md} +0 -0
  96. /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VIB3+ Visual Catalog — Automated Screenshot Capture
4
+ *
5
+ * Generates a comprehensive visual reference catalog using Playwright:
6
+ * 1. All 3 systems x 24 geometries = 72 base screenshots
7
+ * 2. Parameter effect pairs (before/after) for key params
8
+ * 3. Composite grid sheets for AI agent context
9
+ *
10
+ * Usage:
11
+ * npx playwright install chromium # first time
12
+ * node tools/visual-catalog/capture.js [--systems] [--params] [--grids] [--all]
13
+ *
14
+ * Output: visual-catalog/output/
15
+ * ├── systems/ # Per-system screenshots
16
+ * │ ├── quantum/ # quantum-geo00-tetrahedron.png ... quantum-geo23-ht-crystal.png
17
+ * │ ├── faceted/
18
+ * │ └── holographic/
19
+ * ├── params/ # Parameter before/after pairs
20
+ * │ ├── hue/ # hue-000.png, hue-120.png, hue-240.png
21
+ * │ ├── gridDensity/
22
+ * │ ├── chaos/
23
+ * │ └── ...
24
+ * ├── grids/ # Composite reference sheets
25
+ * │ ├── quantum-all-geometries.png
26
+ * │ ├── faceted-all-geometries.png
27
+ * │ ├── holographic-all-geometries.png
28
+ * │ └── param-effects-cheatsheet.png
29
+ * └── catalog.json # Index with metadata for AI agents
30
+ */
31
+
32
+ import { chromium } from '@playwright/test';
33
+ import { mkdirSync, writeFileSync, existsSync } from 'fs';
34
+ import { join, dirname } from 'path';
35
+ import { fileURLToPath } from 'url';
36
+ import { execSync } from 'child_process';
37
+
38
+ const __dirname = dirname(fileURLToPath(import.meta.url));
39
+ const ROOT = join(__dirname, '../..');
40
+ const OUTPUT = join(ROOT, 'visual-catalog', 'output');
41
+
42
+ // ─── Configuration ─────────────────────────────────────────────
43
+
44
+ const SYSTEMS = ['quantum', 'holographic', 'faceted'];
45
+ const SYSTEM_IDX = { quantum: 0, holographic: 1, faceted: 2 };
46
+
47
+ const GEOMETRIES = [
48
+ 'Tetrahedron', 'Hypercube', 'Sphere', 'Torus',
49
+ 'Klein Bottle', 'Fractal', 'Wave', 'Crystal',
50
+ 'Hyper-Tetra', 'Hyper-Cube', 'Hyper-Sphere', 'Hyper-Torus',
51
+ 'Hyper-Klein', 'Hyper-Fractal', 'Hyper-Wave', 'Hyper-Crystal',
52
+ 'HT-Tetra', 'HT-Cube', 'HT-Sphere', 'HT-Torus',
53
+ 'HT-Klein', 'HT-Fractal', 'HT-Wave', 'HT-Crystal',
54
+ ];
55
+
56
+ const DEFAULTS = {
57
+ geometry: 3, hue: 200, gridDensity: 24, speed: 1.0,
58
+ morphFactor: 0.5, chaos: 0.2, intensity: 0.7, saturation: 0.8,
59
+ dimension: 3.5, rot4dXW: 0, rot4dYW: 0, rot4dZW: 0,
60
+ rot4dXY: 0, rot4dXZ: 0, rot4dYZ: 0,
61
+ };
62
+
63
+ // Parameters to showcase with before/after values
64
+ const PARAM_SHOWCASES = {
65
+ hue: [0, 60, 120, 180, 240, 300],
66
+ gridDensity: [8, 20, 40, 60, 80],
67
+ chaos: [0, 0.25, 0.5, 0.75, 1.0],
68
+ morphFactor: [0, 0.5, 1.0, 1.5, 2.0],
69
+ intensity: [0.2, 0.5, 0.7, 0.9, 1.0],
70
+ dimension: [3.0, 3.25, 3.5, 3.75, 4.0, 4.5],
71
+ speed: [0.1, 0.5, 1.0, 2.0, 3.0],
72
+ rot4dXW: [0, 1.57, 3.14, 4.71],
73
+ rot4dYW: [0, 1.57, 3.14, 4.71],
74
+ saturation: [0, 0.25, 0.5, 0.75, 1.0],
75
+ };
76
+
77
+ const CANVAS_W = 600;
78
+ const CANVAS_H = 450;
79
+ const SETTLE_MS = 800; // ms to let the shader run before capturing
80
+
81
+ // ─── Helpers ───────────────────────────────────────────────────
82
+
83
+ function ensureDir(dir) {
84
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
85
+ }
86
+
87
+ function slug(name) {
88
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '');
89
+ }
90
+
91
+ // ─── Core Capture Functions ────────────────────────────────────
92
+
93
+ /**
94
+ * Navigate to the test page and set up system + params, then screenshot the canvas.
95
+ */
96
+ async function captureState(page, system, params, outputPath) {
97
+ const sysIdx = SYSTEM_IDX[system];
98
+
99
+ await page.evaluate(async ({ sysIdx, params }) => {
100
+ // Switch system
101
+ const btn = document.querySelector(`[data-pg-system="${sysIdx}"]`);
102
+ if (btn) btn.click();
103
+
104
+ // Wait a tick for system to initialize
105
+ await new Promise(r => setTimeout(r, 300));
106
+
107
+ // Set slider values
108
+ for (const [param, value] of Object.entries(params)) {
109
+ const input = document.getElementById(`ctrl-${param}`);
110
+ if (input) {
111
+ input.value = value;
112
+ input.dispatchEvent(new Event('input', { bubbles: true }));
113
+ }
114
+ }
115
+
116
+ // Set geometry via select
117
+ if ('geometry' in params) {
118
+ const geo = document.getElementById('ctrl-geometry');
119
+ if (geo) {
120
+ geo.value = params.geometry;
121
+ geo.dispatchEvent(new Event('change', { bubbles: true }));
122
+ }
123
+ }
124
+ }, { sysIdx, params });
125
+
126
+ // Let the shader settle
127
+ await page.waitForTimeout(SETTLE_MS);
128
+
129
+ // Screenshot the canvas element
130
+ const canvas = page.locator('#playground-canvas');
131
+ await canvas.screenshot({ path: outputPath });
132
+ }
133
+
134
+ // ─── Main Pipeline ─────────────────────────────────────────────
135
+
136
+ async function captureAllSystems(page, catalog) {
137
+ console.log('\n=== CAPTURING ALL SYSTEMS x GEOMETRIES ===\n');
138
+
139
+ for (const system of SYSTEMS) {
140
+ const dir = join(OUTPUT, 'systems', system);
141
+ ensureDir(dir);
142
+
143
+ for (let geo = 0; geo < 24; geo++) {
144
+ const geoName = GEOMETRIES[geo];
145
+ const filename = `${system}-geo${String(geo).padStart(2, '0')}-${slug(geoName)}.png`;
146
+ const outPath = join(dir, filename);
147
+
148
+ const params = { ...DEFAULTS, geometry: geo };
149
+ process.stdout.write(` ${system} / ${geoName} (${geo})...`);
150
+ await captureState(page, system, params, outPath);
151
+ console.log(' done');
152
+
153
+ catalog.systems.push({
154
+ system, geometry: geo, name: geoName,
155
+ path: `systems/${system}/${filename}`,
156
+ params: { ...params },
157
+ });
158
+ }
159
+ }
160
+ }
161
+
162
+ async function captureParamEffects(page, catalog) {
163
+ console.log('\n=== CAPTURING PARAMETER EFFECTS ===\n');
164
+
165
+ for (const [param, values] of Object.entries(PARAM_SHOWCASES)) {
166
+ const dir = join(OUTPUT, 'params', param);
167
+ ensureDir(dir);
168
+
169
+ for (const value of values) {
170
+ const params = { ...DEFAULTS, [param]: value };
171
+ const label = typeof value === 'number' ? value.toFixed(2).replace('.', '_') : String(value);
172
+ const filename = `${param}-${label}.png`;
173
+ const outPath = join(dir, filename);
174
+
175
+ process.stdout.write(` ${param}=${value}...`);
176
+ await captureState(page, 'faceted', params, outPath);
177
+ console.log(' done');
178
+
179
+ catalog.params.push({
180
+ param, value, path: `params/${param}/${filename}`,
181
+ system: 'faceted', params: { ...params },
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ async function compositeGrids(catalog) {
188
+ console.log('\n=== COMPOSITING GRID SHEETS ===\n');
189
+
190
+ const gridDir = join(OUTPUT, 'grids');
191
+ ensureDir(gridDir);
192
+
193
+ // Write a simple HTML-based grid compositor
194
+ // (uses browser-native canvas compositing via a helper page)
195
+ const gridManifest = {
196
+ note: 'Grid compositing requires manual or CI-based generation',
197
+ systems: SYSTEMS.map(sys => ({
198
+ name: `${sys}-all-geometries`,
199
+ images: catalog.systems
200
+ .filter(e => e.system === sys)
201
+ .map(e => e.path),
202
+ grid: '6x4',
203
+ })),
204
+ params: Object.keys(PARAM_SHOWCASES).map(param => ({
205
+ name: `param-${param}-effect`,
206
+ images: catalog.params
207
+ .filter(e => e.param === param)
208
+ .map(e => e.path),
209
+ grid: `${PARAM_SHOWCASES[param].length}x1`,
210
+ })),
211
+ };
212
+
213
+ writeFileSync(join(gridDir, 'grid-manifest.json'), JSON.stringify(gridManifest, null, 2));
214
+ console.log(' Grid manifest written (compose with tools/visual-catalog/composite.js)');
215
+
216
+ catalog.grids = gridManifest;
217
+ }
218
+
219
+ // ─── Entry Point ───────────────────────────────────────────────
220
+
221
+ async function main() {
222
+ const args = process.argv.slice(2);
223
+ const doSystems = args.includes('--systems') || args.includes('--all') || args.length === 0;
224
+ const doParams = args.includes('--params') || args.includes('--all') || args.length === 0;
225
+ const doGrids = args.includes('--grids') || args.includes('--all') || args.length === 0;
226
+
227
+ console.log('VIB3+ Visual Catalog Generator v2.0.3');
228
+ console.log(`Output: ${OUTPUT}`);
229
+ ensureDir(OUTPUT);
230
+
231
+ // Start dev server if not already running
232
+ let serverProcess = null;
233
+ try {
234
+ execSync('curl -s http://localhost:5173/ > /dev/null 2>&1');
235
+ console.log('Dev server already running on :5173');
236
+ } catch {
237
+ console.log('Starting dev server...');
238
+ const { spawn } = await import('child_process');
239
+ serverProcess = spawn('npx', ['vite', '--port', '5173'], { cwd: ROOT, stdio: 'ignore', detached: true });
240
+ serverProcess.unref();
241
+ await new Promise(r => setTimeout(r, 3000));
242
+ }
243
+
244
+ const browser = await chromium.launch({ headless: true });
245
+ const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
246
+ const page = await context.newPage();
247
+
248
+ // Navigate to landing page and scroll to playground section
249
+ await page.goto('http://localhost:5173/', { waitUntil: 'networkidle', timeout: 20000 });
250
+ await page.waitForTimeout(2000);
251
+
252
+ // Scroll playground into view to trigger GPU context acquisition
253
+ await page.evaluate(() => {
254
+ const el = document.getElementById('playgroundSection');
255
+ if (el) el.scrollIntoView({ behavior: 'instant', block: 'center' });
256
+ });
257
+ await page.waitForTimeout(1500);
258
+
259
+ const catalog = { version: '2.0.3', generated: new Date().toISOString(), systems: [], params: [], grids: null };
260
+
261
+ if (doSystems) await captureAllSystems(page, catalog);
262
+ if (doParams) await captureParamEffects(page, catalog);
263
+ if (doGrids) await compositeGrids(catalog);
264
+
265
+ // Write catalog index
266
+ writeFileSync(join(OUTPUT, 'catalog.json'), JSON.stringify(catalog, null, 2));
267
+ console.log(`\nCatalog written: ${join(OUTPUT, 'catalog.json')}`);
268
+ console.log(`Total screenshots: ${catalog.systems.length + catalog.params.length}`);
269
+
270
+ await browser.close();
271
+ if (serverProcess) {
272
+ try { process.kill(-serverProcess.pid); } catch {}
273
+ }
274
+ }
275
+
276
+ main().catch(e => { console.error(e); process.exit(1); });
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VIB3+ Visual Catalog — Grid Compositor
4
+ *
5
+ * Reads screenshots from visual-catalog/output/ and composites them into
6
+ * grid reference sheets for AI agent context. Uses HTML Canvas via Playwright.
7
+ *
8
+ * Usage:
9
+ * node tools/visual-catalog/composite.js
10
+ *
11
+ * Requires: visual-catalog/output/grids/grid-manifest.json (generated by capture.js)
12
+ */
13
+
14
+ import { chromium } from '@playwright/test';
15
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
16
+ import { join, dirname } from 'path';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const ROOT = join(__dirname, '../..');
21
+ const OUTPUT = join(ROOT, 'visual-catalog', 'output');
22
+ const GRIDS_DIR = join(OUTPUT, 'grids');
23
+
24
+ const THUMB_W = 200;
25
+ const THUMB_H = 150;
26
+ const LABEL_H = 24;
27
+ const PAD = 4;
28
+
29
+ async function main() {
30
+ const manifestPath = join(GRIDS_DIR, 'grid-manifest.json');
31
+ if (!existsSync(manifestPath)) {
32
+ console.error('Run capture.js first to generate the grid manifest');
33
+ process.exit(1);
34
+ }
35
+
36
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
37
+ const browser = await chromium.launch({ headless: true });
38
+ const context = await browser.newContext();
39
+
40
+ // Compose system grids (6 cols x 4 rows = 24 geometries)
41
+ for (const grid of manifest.systems) {
42
+ const [cols, rows] = grid.grid.split('x').map(Number);
43
+ const w = cols * (THUMB_W + PAD) + PAD;
44
+ const h = rows * (THUMB_H + LABEL_H + PAD) + PAD;
45
+
46
+ const page = await context.newPage({ viewport: { width: w, height: h } });
47
+
48
+ // Build HTML grid
49
+ const images = grid.images.map((imgPath, i) => {
50
+ const geoIdx = i;
51
+ const geoName = [
52
+ 'Tetrahedron', 'Hypercube', 'Sphere', 'Torus',
53
+ 'Klein Bottle', 'Fractal', 'Wave', 'Crystal',
54
+ 'H-Tetra', 'H-Cube', 'H-Sphere', 'H-Torus',
55
+ 'H-Klein', 'H-Fractal', 'H-Wave', 'H-Crystal',
56
+ 'HT-Tetra', 'HT-Cube', 'HT-Sphere', 'HT-Torus',
57
+ 'HT-Klein', 'HT-Fractal', 'HT-Wave', 'HT-Crystal',
58
+ ][geoIdx] || `Geo ${geoIdx}`;
59
+ const fullPath = join(OUTPUT, imgPath);
60
+ const b64 = existsSync(fullPath)
61
+ ? `data:image/png;base64,${readFileSync(fullPath).toString('base64')}`
62
+ : '';
63
+ return `<div class="cell">
64
+ <img src="${b64}" width="${THUMB_W}" height="${THUMB_H}">
65
+ <div class="label">${geoIdx}: ${geoName}</div>
66
+ </div>`;
67
+ });
68
+
69
+ const html = `<!DOCTYPE html><html><head><style>
70
+ body { margin:0; background:#0a0a14; font-family:monospace; }
71
+ .grid { display:grid; grid-template-columns:repeat(${cols}, ${THUMB_W + PAD}px); gap:${PAD}px; padding:${PAD}px; }
72
+ .cell { text-align:center; }
73
+ .cell img { border-radius:6px; border:1px solid rgba(0,240,255,0.2); display:block; }
74
+ .label { color:rgba(255,255,255,0.7); font-size:10px; height:${LABEL_H}px; line-height:${LABEL_H}px; }
75
+ h3 { color:#00f0ff; text-align:center; margin:8px 0 4px; font-size:14px; letter-spacing:2px; }
76
+ </style></head><body>
77
+ <h3>${grid.name.toUpperCase().replace(/-/g, ' ')}</h3>
78
+ <div class="grid">${images.join('')}</div>
79
+ </body></html>`;
80
+
81
+ await page.setContent(html, { waitUntil: 'load' });
82
+ await page.waitForTimeout(500);
83
+
84
+ const outPath = join(GRIDS_DIR, `${grid.name}.png`);
85
+ await page.screenshot({ path: outPath, fullPage: true });
86
+ console.log(`Composited: ${grid.name}.png (${cols}x${rows})`);
87
+ await page.close();
88
+ }
89
+
90
+ // Compose parameter effect strips
91
+ for (const paramGrid of manifest.params) {
92
+ const numImages = paramGrid.images.length;
93
+ const w = numImages * (THUMB_W + PAD) + PAD;
94
+ const h = THUMB_H + LABEL_H + PAD * 2 + 30;
95
+
96
+ const page = await context.newPage({ viewport: { width: w, height: h } });
97
+
98
+ const images = paramGrid.images.map((imgPath, i) => {
99
+ const fullPath = join(OUTPUT, imgPath);
100
+ const b64 = existsSync(fullPath)
101
+ ? `data:image/png;base64,${readFileSync(fullPath).toString('base64')}`
102
+ : '';
103
+ const paramName = paramGrid.name.replace('param-', '').replace('-effect', '');
104
+ // Extract value from filename
105
+ const match = imgPath.match(/[\d_]+\.png$/);
106
+ const valStr = match ? match[0].replace('.png', '').replace(/_/g, '.') : `${i}`;
107
+ return `<div class="cell">
108
+ <img src="${b64}" width="${THUMB_W}" height="${THUMB_H}">
109
+ <div class="label">${paramName}=${valStr}</div>
110
+ </div>`;
111
+ });
112
+
113
+ const html = `<!DOCTYPE html><html><head><style>
114
+ body { margin:0; background:#0a0a14; font-family:monospace; }
115
+ .strip { display:flex; gap:${PAD}px; padding:${PAD}px; }
116
+ .cell { text-align:center; }
117
+ .cell img { border-radius:6px; border:1px solid rgba(168,85,247,0.3); display:block; }
118
+ .label { color:rgba(255,255,255,0.7); font-size:10px; height:${LABEL_H}px; line-height:${LABEL_H}px; }
119
+ h3 { color:#a855f7; text-align:center; margin:8px 0 4px; font-size:13px; letter-spacing:2px; }
120
+ </style></head><body>
121
+ <h3>${paramGrid.name.toUpperCase().replace(/-/g, ' ')}</h3>
122
+ <div class="strip">${images.join('')}</div>
123
+ </body></html>`;
124
+
125
+ await page.setContent(html, { waitUntil: 'load' });
126
+ await page.waitForTimeout(300);
127
+
128
+ const outPath = join(GRIDS_DIR, `${paramGrid.name}.png`);
129
+ await page.screenshot({ path: outPath, fullPage: true });
130
+ console.log(`Composited: ${paramGrid.name}.png (${numImages} images)`);
131
+ await page.close();
132
+ }
133
+
134
+ await browser.close();
135
+ console.log('\nAll grids composited.');
136
+ }
137
+
138
+ main().catch(e => { console.error(e); process.exit(1); });