@pixerate/flair 0.0.0-stage → 0.1.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +254 -2
  4. package/dist/catalog/index.d.ts +74 -0
  5. package/dist/catalog/index.js +47 -0
  6. package/dist/catalog/index.js.map +1 -0
  7. package/dist/chunk-36DTNSHU.js +160 -0
  8. package/dist/chunk-36DTNSHU.js.map +1 -0
  9. package/dist/chunk-3FAUZOGL.js +331 -0
  10. package/dist/chunk-3FAUZOGL.js.map +1 -0
  11. package/dist/chunk-CFCLT2XL.js +1 -0
  12. package/dist/chunk-CFCLT2XL.js.map +1 -0
  13. package/dist/chunk-EHDJ3CPJ.js +327 -0
  14. package/dist/chunk-EHDJ3CPJ.js.map +1 -0
  15. package/dist/chunk-G5FNZDTA.js +536 -0
  16. package/dist/chunk-G5FNZDTA.js.map +1 -0
  17. package/dist/chunk-HKXOHRCD.js +763 -0
  18. package/dist/chunk-HKXOHRCD.js.map +1 -0
  19. package/dist/chunk-J55UBNAT.js +164 -0
  20. package/dist/chunk-J55UBNAT.js.map +1 -0
  21. package/dist/chunk-JWZCPMFG.js +2074 -0
  22. package/dist/chunk-JWZCPMFG.js.map +1 -0
  23. package/dist/chunk-MWVTQFV5.js +1894 -0
  24. package/dist/chunk-MWVTQFV5.js.map +1 -0
  25. package/dist/chunk-UQQNZSYL.js +594 -0
  26. package/dist/chunk-UQQNZSYL.js.map +1 -0
  27. package/dist/chunk-WMZQCUFJ.js +73 -0
  28. package/dist/chunk-WMZQCUFJ.js.map +1 -0
  29. package/dist/cli.d.ts +1 -0
  30. package/dist/cli.js +331 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/clock-BhdXWH-n.d.ts +47 -0
  33. package/dist/controls-DdXMbovi.d.ts +97 -0
  34. package/dist/editor/index.d.ts +413 -0
  35. package/dist/editor/index.js +1009 -0
  36. package/dist/editor/index.js.map +1 -0
  37. package/dist/export-FEBPEEO7.js +22 -0
  38. package/dist/export-FEBPEEO7.js.map +1 -0
  39. package/dist/index.d.ts +1431 -0
  40. package/dist/index.js +1263 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/motion-D0TVv2CF.d.ts +148 -0
  43. package/dist/node.d.ts +24 -0
  44. package/dist/node.js +10 -0
  45. package/dist/node.js.map +1 -0
  46. package/dist/recorder-WJTWMEQN.js +10 -0
  47. package/dist/recorder-WJTWMEQN.js.map +1 -0
  48. package/dist/renderer-Oyynz-fs.d.ts +293 -0
  49. package/dist/types-CkKkavvB.d.ts +71 -0
  50. package/dist/vector/index.d.ts +83 -0
  51. package/dist/vector/index.js +39 -0
  52. package/dist/vector/index.js.map +1 -0
  53. package/package.json +96 -4
@@ -0,0 +1,331 @@
1
+ import {
2
+ controlValue,
3
+ parseControls
4
+ } from "./chunk-J55UBNAT.js";
5
+
6
+ // src/export/recorder.ts
7
+ import http from "http";
8
+ import { spawn } from "child_process";
9
+ import fs from "fs";
10
+ import path from "path";
11
+ var CHROME_CANDIDATES = [
12
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
13
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
14
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
15
+ "/usr/bin/google-chrome",
16
+ "/usr/bin/google-chrome-stable",
17
+ "/usr/bin/chromium",
18
+ "/usr/bin/chromium-browser"
19
+ ];
20
+ function findChrome() {
21
+ return CHROME_CANDIDATES.find((candidate) => fs.existsSync(candidate)) ?? null;
22
+ }
23
+ async function exportMotionGraphic(options) {
24
+ const {
25
+ source,
26
+ values = {},
27
+ outputPath,
28
+ width = 720,
29
+ height = 720,
30
+ fps = 30,
31
+ duration = 3,
32
+ onProgress
33
+ } = options;
34
+ const resolvedOut = path.resolve(process.cwd(), outputPath);
35
+ const ext = path.extname(resolvedOut).toLowerCase();
36
+ const isGif = ext === ".gif";
37
+ const totalFrames = Math.max(1, Math.round(duration * fps));
38
+ fs.mkdirSync(path.dirname(resolvedOut), { recursive: true });
39
+ const controls = parseControls(source);
40
+ const effectiveValues = {};
41
+ for (const ctrl of controls) {
42
+ effectiveValues[ctrl.name] = controlValue(ctrl, values);
43
+ }
44
+ for (const [k, v] of Object.entries(values)) {
45
+ effectiveValues[k] = v;
46
+ }
47
+ const chromeBin = process.env.CHROME_BIN || process.env.CHROME_PATH || findChrome();
48
+ if (!chromeBin) {
49
+ throw new Error("Google Chrome binary not found. Please install Chrome or set CHROME_PATH.");
50
+ }
51
+ const ffmpegArgs = [
52
+ "-y",
53
+ "-f",
54
+ "image2pipe",
55
+ "-vcodec",
56
+ "png",
57
+ "-r",
58
+ String(fps),
59
+ "-i",
60
+ "-"
61
+ ];
62
+ if (isGif) {
63
+ ffmpegArgs.push(
64
+ "-vf",
65
+ "split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3",
66
+ resolvedOut
67
+ );
68
+ } else {
69
+ ffmpegArgs.push(
70
+ "-c:v",
71
+ "libx264",
72
+ "-pix_fmt",
73
+ "yuv420p",
74
+ "-movflags",
75
+ "+faststart",
76
+ resolvedOut
77
+ );
78
+ }
79
+ const ffmpeg = spawn("ffmpeg", ffmpegArgs, { stdio: ["pipe", "ignore", "pipe"] });
80
+ let ffmpegErr = "";
81
+ ffmpeg.stderr.on("data", (d) => {
82
+ ffmpegErr += d.toString();
83
+ });
84
+ return new Promise((resolve, reject) => {
85
+ let currentFrame = 0;
86
+ let chromeProc = null;
87
+ const userDataDir = `/tmp/flair-chrome-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
88
+ ffmpeg.on("error", (err) => {
89
+ cleanup();
90
+ reject(new Error(`Failed to spawn ffmpeg: ${err.message}`));
91
+ });
92
+ ffmpeg.on("close", (code) => {
93
+ if (code !== 0) {
94
+ cleanup();
95
+ reject(new Error(`ffmpeg exited with code ${code}:
96
+ ${ffmpegErr}`));
97
+ } else {
98
+ cleanup();
99
+ resolve(resolvedOut);
100
+ }
101
+ });
102
+ const server = http.createServer((req, res) => {
103
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
104
+ if (url.pathname === "/") {
105
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
106
+ res.end(renderPageHtml(source, effectiveValues, width, height, fps, totalFrames));
107
+ return;
108
+ }
109
+ if (url.pathname === "/frame" && req.method === "POST") {
110
+ const chunks = [];
111
+ req.on("data", (chunk) => chunks.push(chunk));
112
+ req.on("end", () => {
113
+ const buf = Buffer.concat(chunks);
114
+ ffmpeg.stdin.write(buf);
115
+ currentFrame++;
116
+ onProgress?.(currentFrame, totalFrames);
117
+ res.writeHead(200);
118
+ res.end("ok");
119
+ if (currentFrame >= totalFrames) {
120
+ ffmpeg.stdin.end();
121
+ }
122
+ });
123
+ return;
124
+ }
125
+ if (url.pathname === "/error" && req.method === "POST") {
126
+ let errStr = "";
127
+ req.on("data", (c) => errStr += c);
128
+ req.on("end", () => {
129
+ cleanup();
130
+ reject(new Error(`Browser render error: ${errStr}`));
131
+ });
132
+ return;
133
+ }
134
+ res.writeHead(404);
135
+ res.end("Not found");
136
+ });
137
+ function cleanup() {
138
+ if (chromeProc) {
139
+ try {
140
+ chromeProc.kill();
141
+ } catch {
142
+ }
143
+ }
144
+ try {
145
+ server.close();
146
+ } catch {
147
+ }
148
+ try {
149
+ if (fs.existsSync(userDataDir)) {
150
+ fs.rmSync(userDataDir, { recursive: true, force: true });
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ server.listen(0, "127.0.0.1", () => {
156
+ const addr = server.address();
157
+ const port = addr.port;
158
+ chromeProc = spawn(chromeBin, [
159
+ "--headless=new",
160
+ `--user-data-dir=${userDataDir}`,
161
+ `--window-size=${width},${height}`,
162
+ "--use-gl=angle",
163
+ "--use-angle=metal",
164
+ "--enable-webgl",
165
+ "--disable-gpu-sandbox",
166
+ "--no-first-run",
167
+ "--no-default-browser-check",
168
+ "--disable-background-networking",
169
+ "--disable-extensions",
170
+ `http://127.0.0.1:${port}/`
171
+ ], {
172
+ stdio: "ignore"
173
+ });
174
+ chromeProc.on("error", (err) => {
175
+ cleanup();
176
+ reject(new Error(`Failed to launch Google Chrome: ${err.message}`));
177
+ });
178
+ });
179
+ });
180
+ }
181
+ function prepareFragmentSource(source) {
182
+ const hasVersion = /^\s*#version\b/m.test(source);
183
+ const hasMain = /\bvoid\s+main\s*\(\s*\)/.test(source);
184
+ const hasMainImage = /\bvoid\s+mainImage\s*\(/.test(source);
185
+ if (hasVersion && hasMain) {
186
+ return source;
187
+ }
188
+ const clean = source.replace(/^\s*#version[^\n]*/m, "");
189
+ const prelude = `#version 300 es
190
+ precision highp float;
191
+ precision highp int;
192
+ out vec4 fragColor;
193
+ uniform vec3 iResolution;
194
+ uniform vec2 u_resolution;
195
+ uniform float iTime;
196
+ uniform float u_time;
197
+ uniform float iTimeDelta;
198
+ uniform int iFrame;
199
+ uniform vec4 iMouse;
200
+ `;
201
+ let epilogue = "";
202
+ if (hasMainImage && !hasMain) {
203
+ epilogue = `
204
+ void main() {
205
+ vec4 col = vec4(0.0, 0.0, 0.0, 1.0);
206
+ mainImage(col, gl_FragCoord.xy);
207
+ fragColor = vec4(col.rgb, 1.0);
208
+ }
209
+ `;
210
+ }
211
+ return `${prelude}
212
+ #line 1
213
+ ${clean}
214
+ ${epilogue}`;
215
+ }
216
+ function renderPageHtml(glslSource, values, width, height, fps, totalFrames) {
217
+ const finalFs = prepareFragmentSource(glslSource);
218
+ const jsonValues = JSON.stringify(values);
219
+ return `<!DOCTYPE html>
220
+ <html>
221
+ <head>
222
+ <meta charset="utf-8">
223
+ <style>
224
+ body, html { margin: 0; padding: 0; background: #000; overflow: hidden; }
225
+ canvas { display: block; width: ${width}px; height: ${height}px; }
226
+ </style>
227
+ </head>
228
+ <body>
229
+ <canvas id="c" width="${width}" height="${height}"></canvas>
230
+ <script>
231
+ (async function() {
232
+ const canvas = document.getElementById('c');
233
+ const gl = canvas.getContext('webgl2', { preserveDrawingBuffer: true, antialias: true });
234
+ if (!gl) {
235
+ await reportError('WebGL2 context not supported in browser environment');
236
+ return;
237
+ }
238
+
239
+ const vs = gl.createShader(gl.VERTEX_SHADER);
240
+ gl.shaderSource(vs, \`#version 300 es
241
+ void main() {
242
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
243
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
244
+ }\`);
245
+ gl.compileShader(vs);
246
+
247
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
248
+ gl.shaderSource(fs, ${JSON.stringify(finalFs)});
249
+ gl.compileShader(fs);
250
+
251
+ if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
252
+ await reportError('Shader compile error: ' + gl.getShaderInfoLog(fs));
253
+ return;
254
+ }
255
+
256
+ const prog = gl.createProgram();
257
+ gl.attachShader(prog, vs);
258
+ gl.attachShader(prog, fs);
259
+ gl.linkProgram(prog);
260
+
261
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
262
+ await reportError('Shader link error: ' + gl.getProgramInfoLog(prog));
263
+ return;
264
+ }
265
+
266
+ gl.useProgram(prog);
267
+
268
+ const uRes = gl.getUniformLocation(prog, 'u_resolution');
269
+ const iRes = gl.getUniformLocation(prog, 'iResolution');
270
+ const uTime = gl.getUniformLocation(prog, 'u_time');
271
+ const iTime = gl.getUniformLocation(prog, 'iTime');
272
+
273
+ if (uRes) gl.uniform2f(uRes, ${width}, ${height});
274
+ if (iRes) gl.uniform3f(iRes, ${width}, ${height}, 1.0);
275
+
276
+ function parseColor(c) {
277
+ if (typeof c === 'string' && c.startsWith('#')) {
278
+ const hex = c.slice(1);
279
+ if (hex.length === 6) {
280
+ return [
281
+ parseInt(hex.slice(0, 2), 16) / 255,
282
+ parseInt(hex.slice(2, 4), 16) / 255,
283
+ parseInt(hex.slice(4, 6), 16) / 255
284
+ ];
285
+ }
286
+ }
287
+ return c;
288
+ }
289
+
290
+ const vals = ${jsonValues};
291
+ for (const [k, rawV] of Object.entries(vals)) {
292
+ const v = parseColor(rawV);
293
+ const loc = gl.getUniformLocation(prog, k);
294
+ if (loc) {
295
+ if (typeof v === 'number') gl.uniform1f(loc, v);
296
+ else if (typeof v === 'boolean') gl.uniform1i(loc, v ? 1 : 0);
297
+ else if (Array.isArray(v)) {
298
+ if (v.length === 2) gl.uniform2fv(loc, v);
299
+ else if (v.length === 3) gl.uniform3fv(loc, v);
300
+ else if (v.length === 4) gl.uniform4fv(loc, v);
301
+ }
302
+ }
303
+ }
304
+
305
+ gl.viewport(0, 0, ${width}, ${height});
306
+
307
+ for (let f = 0; f < ${totalFrames}; f++) {
308
+ const t = f / ${fps};
309
+ if (uTime) gl.uniform1f(uTime, t);
310
+ if (iTime) gl.uniform1f(iTime, t);
311
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
312
+
313
+ const blob = await new Promise(r => canvas.toBlob(r, 'image/png'));
314
+ const arrayBuf = await blob.arrayBuffer();
315
+ await fetch('/frame', { method: 'POST', body: arrayBuf });
316
+ }
317
+
318
+ async function reportError(msg) {
319
+ await fetch('/error', { method: 'POST', body: msg });
320
+ }
321
+ })();
322
+ </script>
323
+ </body>
324
+ </html>`;
325
+ }
326
+
327
+ export {
328
+ findChrome,
329
+ exportMotionGraphic
330
+ };
331
+ //# sourceMappingURL=chunk-3FAUZOGL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/export/recorder.ts"],"sourcesContent":["import http from 'http';\nimport { spawn } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport { parseControls, controlValue, type ControlValue } from '../shader/controls.js';\n\nconst CHROME_CANDIDATES = [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium',\n '/usr/bin/chromium-browser',\n];\n\n/** Finds a local Chrome or Chromium executable, or `null`. */\nexport function findChrome(): string | null {\n return CHROME_CANDIDATES.find((candidate) => fs.existsSync(candidate)) ?? null;\n}\n\nexport interface ExportOptions {\n /** GLSL fragment shader source */\n source: string;\n /** Uniform values override */\n values?: Record<string, ControlValue>;\n /** Destination file path (.gif or .mp4) */\n outputPath: string;\n width?: number;\n height?: number;\n fps?: number;\n duration?: number;\n onProgress?: (frame: number, total: number) => void;\n}\n\n/**\n * Renders a shader animation frame-by-frame via hardware-accelerated WebGL2 in\n * headless Chrome and encodes to an animated GIF or MP4 video using ffmpeg.\n */\nexport async function exportMotionGraphic(options: ExportOptions): Promise<string> {\n const {\n source,\n values = {},\n outputPath,\n width = 720,\n height = 720,\n fps = 30,\n duration = 3,\n onProgress,\n } = options;\n\n const resolvedOut = path.resolve(process.cwd(), outputPath);\n const ext = path.extname(resolvedOut).toLowerCase();\n const isGif = ext === '.gif';\n const totalFrames = Math.max(1, Math.round(duration * fps));\n\n fs.mkdirSync(path.dirname(resolvedOut), { recursive: true });\n\n // Compute effective control values: declared defaults merged with user overrides\n const controls = parseControls(source);\n const effectiveValues: Record<string, ControlValue> = {};\n for (const ctrl of controls) {\n effectiveValues[ctrl.name] = controlValue(ctrl, values);\n }\n for (const [k, v] of Object.entries(values)) {\n effectiveValues[k] = v;\n }\n\n const chromeBin = process.env.CHROME_BIN || process.env.CHROME_PATH || findChrome();\n if (!chromeBin) {\n throw new Error('Google Chrome binary not found. Please install Chrome or set CHROME_PATH.');\n }\n\n const ffmpegArgs = [\n '-y',\n '-f', 'image2pipe',\n '-vcodec', 'png',\n '-r', String(fps),\n '-i', '-',\n ];\n\n if (isGif) {\n ffmpegArgs.push(\n '-vf',\n 'split[s0][s1];[s0]palettegen=max_colors=256:stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3',\n resolvedOut\n );\n } else {\n ffmpegArgs.push(\n '-c:v', 'libx264',\n '-pix_fmt', 'yuv420p',\n '-movflags', '+faststart',\n resolvedOut\n );\n }\n\n const ffmpeg = spawn('ffmpeg', ffmpegArgs, { stdio: ['pipe', 'ignore', 'pipe'] });\n let ffmpegErr = '';\n ffmpeg.stderr.on('data', (d) => {\n ffmpegErr += d.toString();\n });\n\n return new Promise((resolve, reject) => {\n let currentFrame = 0;\n let chromeProc: any = null;\n const userDataDir = `/tmp/flair-chrome-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n\n ffmpeg.on('error', (err) => {\n cleanup();\n reject(new Error(`Failed to spawn ffmpeg: ${err.message}`));\n });\n\n ffmpeg.on('close', (code) => {\n if (code !== 0) {\n cleanup();\n reject(new Error(`ffmpeg exited with code ${code}:\\n${ffmpegErr}`));\n } else {\n cleanup();\n resolve(resolvedOut);\n }\n });\n\n const server = http.createServer((req, res) => {\n const url = new URL(req.url || '/', `http://${req.headers.host}`);\n\n if (url.pathname === '/') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(renderPageHtml(source, effectiveValues, width, height, fps, totalFrames));\n return;\n }\n\n if (url.pathname === '/frame' && req.method === 'POST') {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => chunks.push(chunk));\n req.on('end', () => {\n const buf = Buffer.concat(chunks);\n ffmpeg.stdin.write(buf);\n currentFrame++;\n onProgress?.(currentFrame, totalFrames);\n res.writeHead(200);\n res.end('ok');\n\n if (currentFrame >= totalFrames) {\n ffmpeg.stdin.end();\n }\n });\n return;\n }\n\n if (url.pathname === '/error' && req.method === 'POST') {\n let errStr = '';\n req.on('data', (c) => (errStr += c));\n req.on('end', () => {\n cleanup();\n reject(new Error(`Browser render error: ${errStr}`));\n });\n return;\n }\n\n res.writeHead(404);\n res.end('Not found');\n });\n\n function cleanup() {\n if (chromeProc) {\n try {\n chromeProc.kill();\n } catch {}\n }\n try {\n server.close();\n } catch {}\n try {\n if (fs.existsSync(userDataDir)) {\n fs.rmSync(userDataDir, { recursive: true, force: true });\n }\n } catch {}\n }\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address() as any;\n const port = addr.port;\n\n chromeProc = spawn(chromeBin, [\n '--headless=new',\n `--user-data-dir=${userDataDir}`,\n `--window-size=${width},${height}`,\n '--use-gl=angle',\n '--use-angle=metal',\n '--enable-webgl',\n '--disable-gpu-sandbox',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-background-networking',\n '--disable-extensions',\n `http://127.0.0.1:${port}/`,\n ], {\n stdio: 'ignore'\n });\n\n chromeProc.on('error', (err: any) => {\n cleanup();\n reject(new Error(`Failed to launch Google Chrome: ${err.message}`));\n });\n });\n });\n}\n\nfunction prepareFragmentSource(source: string): string {\n const hasVersion = /^\\s*#version\\b/m.test(source);\n const hasMain = /\\bvoid\\s+main\\s*\\(\\s*\\)/.test(source);\n const hasMainImage = /\\bvoid\\s+mainImage\\s*\\(/.test(source);\n\n if (hasVersion && hasMain) {\n return source;\n }\n\n const clean = source.replace(/^\\s*#version[^\\n]*/m, '');\n const prelude = `#version 300 es\nprecision highp float;\nprecision highp int;\nout vec4 fragColor;\nuniform vec3 iResolution;\nuniform vec2 u_resolution;\nuniform float iTime;\nuniform float u_time;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\n`;\n\n let epilogue = '';\n if (hasMainImage && !hasMain) {\n epilogue = `\nvoid main() {\n vec4 col = vec4(0.0, 0.0, 0.0, 1.0);\n mainImage(col, gl_FragCoord.xy);\n fragColor = vec4(col.rgb, 1.0);\n}\n`;\n }\n\n return `${prelude}\\n#line 1\\n${clean}\\n${epilogue}`;\n}\n\nfunction renderPageHtml(\n glslSource: string,\n values: Record<string, ControlValue>,\n width: number,\n height: number,\n fps: number,\n totalFrames: number\n): string {\n const finalFs = prepareFragmentSource(glslSource);\n const jsonValues = JSON.stringify(values);\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <style>\n body, html { margin: 0; padding: 0; background: #000; overflow: hidden; }\n canvas { display: block; width: ${width}px; height: ${height}px; }\n </style>\n</head>\n<body>\n <canvas id=\"c\" width=\"${width}\" height=\"${height}\"></canvas>\n <script>\n (async function() {\n const canvas = document.getElementById('c');\n const gl = canvas.getContext('webgl2', { preserveDrawingBuffer: true, antialias: true });\n if (!gl) {\n await reportError('WebGL2 context not supported in browser environment');\n return;\n }\n\n const vs = gl.createShader(gl.VERTEX_SHADER);\n gl.shaderSource(vs, \\`#version 300 es\nvoid main() {\n vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\\`);\n gl.compileShader(vs);\n\n const fs = gl.createShader(gl.FRAGMENT_SHADER);\n gl.shaderSource(fs, ${JSON.stringify(finalFs)});\n gl.compileShader(fs);\n\n if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {\n await reportError('Shader compile error: ' + gl.getShaderInfoLog(fs));\n return;\n }\n\n const prog = gl.createProgram();\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n await reportError('Shader link error: ' + gl.getProgramInfoLog(prog));\n return;\n }\n\n gl.useProgram(prog);\n\n const uRes = gl.getUniformLocation(prog, 'u_resolution');\n const iRes = gl.getUniformLocation(prog, 'iResolution');\n const uTime = gl.getUniformLocation(prog, 'u_time');\n const iTime = gl.getUniformLocation(prog, 'iTime');\n\n if (uRes) gl.uniform2f(uRes, ${width}, ${height});\n if (iRes) gl.uniform3f(iRes, ${width}, ${height}, 1.0);\n\n function parseColor(c) {\n if (typeof c === 'string' && c.startsWith('#')) {\n const hex = c.slice(1);\n if (hex.length === 6) {\n return [\n parseInt(hex.slice(0, 2), 16) / 255,\n parseInt(hex.slice(2, 4), 16) / 255,\n parseInt(hex.slice(4, 6), 16) / 255\n ];\n }\n }\n return c;\n }\n\n const vals = ${jsonValues};\n for (const [k, rawV] of Object.entries(vals)) {\n const v = parseColor(rawV);\n const loc = gl.getUniformLocation(prog, k);\n if (loc) {\n if (typeof v === 'number') gl.uniform1f(loc, v);\n else if (typeof v === 'boolean') gl.uniform1i(loc, v ? 1 : 0);\n else if (Array.isArray(v)) {\n if (v.length === 2) gl.uniform2fv(loc, v);\n else if (v.length === 3) gl.uniform3fv(loc, v);\n else if (v.length === 4) gl.uniform4fv(loc, v);\n }\n }\n }\n\n gl.viewport(0, 0, ${width}, ${height});\n\n for (let f = 0; f < ${totalFrames}; f++) {\n const t = f / ${fps};\n if (uTime) gl.uniform1f(uTime, t);\n if (iTime) gl.uniform1f(iTime, t);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n const blob = await new Promise(r => canvas.toBlob(r, 'image/png'));\n const arrayBuf = await blob.arrayBuffer();\n await fetch('/frame', { method: 'POST', body: arrayBuf });\n }\n\n async function reportError(msg) {\n await fetch('/error', { method: 'POST', body: msg });\n }\n })();\n </script>\n</body>\n</html>`;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-CFCLT2XL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}