@pixerate/flair 0.18.0 → 0.20.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/CHANGELOG.md +25 -0
- package/README.md +13 -5
- package/dist/catalog/index.d.ts +3 -3
- package/dist/catalog/index.js +1 -1
- package/dist/{chunk-HA6FIMH2.js → chunk-ARKC7LDW.js} +46 -7
- package/dist/chunk-ARKC7LDW.js.map +1 -0
- package/dist/{chunk-MILF5MRS.js → chunk-MZ37DE7K.js} +2 -2
- package/dist/{chunk-HP7CYHS5.js → chunk-NGKNI3QX.js} +20 -10
- package/dist/chunk-NGKNI3QX.js.map +1 -0
- package/dist/{chunk-UI5X4D63.js → chunk-TCNONB22.js} +13161 -6409
- package/dist/chunk-TCNONB22.js.map +1 -0
- package/dist/{chunk-RM3I6WZH.js → chunk-YCQ5RU74.js} +52 -6
- package/dist/chunk-YCQ5RU74.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/editor/index.d.ts +3 -3
- package/dist/editor/index.js +3 -3
- package/dist/index.d.ts +35 -12
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/{renderer-Cn4_d5Qs.d.ts → renderer-C2P1qGzP.d.ts} +7 -0
- package/dist/svelte/index.d.ts +2 -2
- package/dist/svelte/index.js +3 -3
- package/dist/{svg-XugR-6Ec.d.ts → svg-BEOJriIW.d.ts} +5 -1
- package/dist/{types-kAFjaq2N.d.ts → types-CawMRRXq.d.ts} +2 -2
- package/dist/vector/index.d.ts +2 -2
- package/dist/vector/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-HA6FIMH2.js.map +0 -1
- package/dist/chunk-HP7CYHS5.js.map +0 -1
- package/dist/chunk-RM3I6WZH.js.map +0 -1
- package/dist/chunk-UI5X4D63.js.map +0 -1
- /package/dist/{chunk-MILF5MRS.js.map → chunk-MZ37DE7K.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @pixerate/flair
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- af954ba: Pointer trails, and 38 new effects.
|
|
8
|
+
|
|
9
|
+
- Shaders get a new built-in, `iTrail[32]`: where the pointer has just been over the canvas, newest first, about 30 points a second (xy pixels, z its `iTime`, w = 1). `ShaderRenderer` and standalone pages feed it; `ShaderFrames.render` takes a scripted `trail`; `PointerTracker.move` takes an optional time to record it. `trailUniform`, `MAX_TRAIL` and `TRAIL_SECONDS` are exported.
|
|
10
|
+
- **trail** (new): comet, ribbon, sparkle, brush and ghost trails and light painting that follow the pointer (each with an idle demo), and vector motion streak and dotted trail.
|
|
11
|
+
- **particles** (new): flow particles, magnetic swarm (follows the pointer, flung by clicks), dust in light, boids, particle text, confetti cannon (and at clicks) and fireflies.
|
|
12
|
+
- **debris** (new): rubble fall, glass shatter (from the click, video-ready), crumbling text, paper shreds, leaves blowing and sand erosion.
|
|
13
|
+
- **impact** (new): shockwave, impact frame and screen shake over a picture or video (at clicks), ground slam, meteor strike, and a vector hit marker (click).
|
|
14
|
+
- **comic** (new): comic book and cel shade looks for any picture or video, focus lines, action lines, a comic panels transition, and vector sound effect, speech bubble, fight cloud, dizzy stars, anime emotes, cartoon poof (click) and line boil.
|
|
15
|
+
|
|
16
|
+
## 0.19.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- adb0dfc: Catalog: 30 new effects, and SVG ids that never clash between pictures.
|
|
21
|
+
|
|
22
|
+
- **splatter** (new): paint splatter and goo splat (also landing at clicks), ink splash, splash crown, paint roller (a transition, video-ready), water splash, and a vector splat stamp.
|
|
23
|
+
- **bubble** (new): soap bubbles (thin-film colour, popping at clicks), underwater bubbles over a picture or video, fizz, foam, bubble wrap (click to pop), lava bubbles, and vector chat bubble and bubble-pop loader.
|
|
24
|
+
- **organic**: reaction diffusion, cell division, coral growth, veins, ferrofluid (follows the pointer), jellyfish and fur.
|
|
25
|
+
- **sticker** (new): holographic foil sticker (any shape, tilting with the pointer) and a sticker-peel transition, and vector sticker slap (click), sticker peel (hover), holo sticker, sticker sheet, stamp badge (click) and washi tape.
|
|
26
|
+
- Live SVG views (`svgView`, `SvgPlayer`, and the React and Svelte components) now give the ids a picture refers to — gradients, filters, clips, masks, `<use>`d shapes — a suffix unique on the page, and point `url(#…)` and `href="#…"` at them, so two pictures defining the same id no longer borrow each other's. Other ids stay as written; `onPick` still reports ids as written.
|
|
27
|
+
|
|
3
28
|
## 0.18.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -19,11 +19,11 @@ Flair gives products a library of good-looking, configurable visual effects (her
|
|
|
19
19
|
|
|
20
20
|
### Core Capabilities
|
|
21
21
|
|
|
22
|
-
- 🎨 **Effect catalog (`@pixerate/flair/catalog`)**:
|
|
22
|
+
- 🎨 **Effect catalog (`@pixerate/flair/catalog`)**: 255 effects, each with controls and presets. There are 176 WebGL2 shaders (gradients, organic textures and growth, patterns, light, space, retro and pixel art, image treatments, transparent overlays, text fills, image morphs, pointer interactions, pyrotechnics, time warps, melting, electricity, splatters, bubbles, stickers, pointer trails, particles, debris, impacts, comic looks, transitions) and 79 animated SVG effects (loaders, status icons, animated titles, shape morphs, UI micro-interactions, pixel sprites, stickers, cartoon effects, charts, counters, node graphs). [Browse them live](https://pixerate.github.io/flair/).
|
|
23
23
|
- ⚡ **Shader engine**:
|
|
24
24
|
- Shadertoy-style GLSL ES 3.00 fragment shaders, with controls declared in the source (`// @control`), including image inputs.
|
|
25
25
|
- Compile errors come back with line numbers that match the source.
|
|
26
|
-
- Pointer input beyond Shadertoy's `iMouse`: `iPointer` (position while over the canvas, eased hover amount, seconds held or since release)
|
|
26
|
+
- Pointer input beyond Shadertoy's `iMouse`: `iPointer` (position while over the canvas, eased hover amount, seconds held or since release) `iClicks[8]` (the latest clicks' positions and times) and `iTrail[32]` (where the pointer has been over the last second, newest first), so effects answer hovers, presses, taps and moves. `ShaderRenderer` and standalone pages feed them; `ShaderFrames` takes scripted `pointer`, `clicks` and `trail` for recordings.
|
|
27
27
|
- Live previews (`ShaderRenderer`, with optional `{ alpha: true }` for transparent overlays) and many animated or hover-activated thumbnails on one context (`ThumbnailPool`).
|
|
28
28
|
- Frames at exact times (`ShaderFrames`), looping GIFs, and standalone HTML pages.
|
|
29
29
|
- ✒️ **Vector motion (`@pixerate/flair/vector`)**: animated SVG described as data. Catalog vector effects declare their controls in the picture (`{{name: type …}}`), text included. It covers attribute tweens, draw-on strokes, motion along paths, counters (`animateCount` / `formatCount` for DOM elements too), text written letter by letter (`write`, with `scramble` and `caret`), shape morphs between any paths (`morphPaths`, `morphSequence`) and view zooms. Every frame is a pure function of time (`svgFrame`), and `SvgPlayer` plays it (with `scrollTrigger`, `hideWhenPaused`, `reset()`, and `trigger: "hover" | "press" | "click"` to play in answer to the pointer).
|
|
@@ -58,7 +58,7 @@ The `flair` CLI inspects shaders, writes standalone pages and directs scenes (se
|
|
|
58
58
|
```mermaid
|
|
59
59
|
graph TD
|
|
60
60
|
subgraph Effects["Effects (no dependencies)"]
|
|
61
|
-
Catalog["catalog:
|
|
61
|
+
Catalog["catalog: 255 effects + presets"]
|
|
62
62
|
Shader["shader engine: controls, compile, render, GIF, HTML"]
|
|
63
63
|
Vector["vector motion: SvgMotion, SvgPlayer"]
|
|
64
64
|
Time["time: clocks, easing, keyframes, noise"]
|
|
@@ -354,7 +354,7 @@ The exporters carry the camera too:
|
|
|
354
354
|
| Category | Effects |
|
|
355
355
|
| :--- | :--- |
|
|
356
356
|
| gradient | mesh gradient, aurora, gradient flow, conic sweep, silk |
|
|
357
|
-
| organic | liquid marble, smoke, lava lamp, plasma, caustics, flames, blob morph |
|
|
357
|
+
| organic | liquid marble, smoke, lava lamp, plasma, caustics, flames, reaction diffusion, cell division, coral growth, veins, ferrofluid, jellyfish, fur, blob morph |
|
|
358
358
|
| pattern | dot wave, Truchet flow, hex pulse, living cells, moiré rings, kaleidoscope, stripes, neon circuit |
|
|
359
359
|
| light / space | bokeh, god rays, sparkles, neon ripple, light ribbons, warp starfield, nebula, event horizon |
|
|
360
360
|
| retro | synthwave, digital rain, dithered gradient, TV static, 8-bit, Game Boy screen, pixel fire, pixel landscape, OutRun road, palette cycle, raster bars, tape loading, pixel water, pixel text, coin spin, space invader, heart health bar, chomp loader |
|
|
@@ -367,7 +367,15 @@ The exporters carry the camera too:
|
|
|
367
367
|
| timewarp | time echo, chrono split, time slice, VHS rewind, bullet time, time tunnel, speed ramp, stutter freeze, time-lapse clock |
|
|
368
368
|
| melt | drip melt, heat haze, wax melt, melting text, liquify, melting clock, lava flow, ice cube melt, candle |
|
|
369
369
|
| electric | lightning, Tesla arc, plasma ball, electric border, electric text, Jacob's ladder, short circuit, power surge, electric field, zap icon, power button, battery charge |
|
|
370
|
-
|
|
|
370
|
+
| splatter | paint splatter, ink splash, splash crown, goo splat, paint roller, water splash, splat stamp |
|
|
371
|
+
| bubble | soap bubbles, underwater bubbles, fizz, foam, bubble wrap, lava bubbles, chat bubble, bubble pop loader |
|
|
372
|
+
| sticker | holo foil sticker, sticker peel reveal, sticker slap, sticker peel, holo sticker, sticker sheet, stamp badge, washi tape |
|
|
373
|
+
| trail | comet trail, ribbon trail, sparkle trail, brush trail, ghost trail, light painting, motion streak, dotted trail |
|
|
374
|
+
| particles | flow particles, magnetic swarm, dust in light, boids, particle text, confetti cannon, fireflies |
|
|
375
|
+
| debris | rubble fall, glass shatter, crumbling text, paper shreds, leaves blowing, sand erosion |
|
|
376
|
+
| impact | shockwave, ground slam, impact frame, meteor strike, screen shake, hit marker |
|
|
377
|
+
| comic | comic book, cel shade, focus lines, action lines, sound effect, speech bubble, fight cloud, dizzy stars, anime emotes, cartoon poof, line boil |
|
|
378
|
+
| transition | liquid morph, displacement, luma fade, particle swap, ink bleed, pixel dissolve, comic panels, wipe, iris, block dissolve, grid wipe, glass wipe, twister, light leak, cross zoom |
|
|
371
379
|
| loader / icon / data | spinner, bouncing dots, pulse ping, equalizer, progress ring, bar chart, line chart, stat counter, route, timeline track, success check, error cross, heartbeat, signature, confetti, flow network |
|
|
372
380
|
|
|
373
381
|
Every shader is self-contained and compiles on WebGL2. Open [`#check`](https://pixerate.github.io/flair/#check) on the demo to compile the catalog on your own GPU.
|
package/dist/catalog/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as ControlDef, C as ControlValue } from '../controls-Ec6P0Vpy.js';
|
|
2
|
-
import { E as EffectDefinition, a as EffectCategory, b as VideoUse, S as ShaderEffect } from '../types-
|
|
3
|
-
export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-
|
|
4
|
-
import '../svg-
|
|
2
|
+
import { E as EffectDefinition, a as EffectCategory, b as VideoUse, S as ShaderEffect } from '../types-CawMRRXq.js';
|
|
3
|
+
export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-CawMRRXq.js';
|
|
4
|
+
import '../svg-BEOJriIW.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Sample pictures for the catalog's image effects: small SVGs as `data:` URLs,
|
package/dist/catalog/index.js
CHANGED
|
@@ -316,13 +316,21 @@ function applyTextureSettings(gl, settings) {
|
|
|
316
316
|
|
|
317
317
|
// src/shader/pointer.ts
|
|
318
318
|
var MAX_CLICKS = 8;
|
|
319
|
+
var MAX_TRAIL = 32;
|
|
320
|
+
var TRAIL_SECONDS = 1 / 30;
|
|
319
321
|
var HOVER_SECONDS = 0.25;
|
|
320
322
|
var smooth = (t) => t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t);
|
|
321
323
|
function clicksUniform(clicks, time) {
|
|
322
|
-
|
|
324
|
+
return latest(clicks, time, MAX_CLICKS);
|
|
325
|
+
}
|
|
326
|
+
function trailUniform(trail, time) {
|
|
327
|
+
return latest(trail, time, MAX_TRAIL);
|
|
328
|
+
}
|
|
329
|
+
function latest(events, time, max) {
|
|
330
|
+
const out = new Float32Array(max * 4);
|
|
323
331
|
let i = 0;
|
|
324
|
-
for (let k =
|
|
325
|
-
const c =
|
|
332
|
+
for (let k = events.length - 1; k >= 0 && i < max; k--) {
|
|
333
|
+
const c = events[k];
|
|
326
334
|
if (c.time > time) continue;
|
|
327
335
|
out.set([c.x, c.y, c.time, 1], i * 4);
|
|
328
336
|
i++;
|
|
@@ -338,10 +346,24 @@ var PointerTracker = class {
|
|
|
338
346
|
pressedAt = null;
|
|
339
347
|
releasedAt = null;
|
|
340
348
|
clicks = [];
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
trail = [];
|
|
350
|
+
/**
|
|
351
|
+
* The pointer moved to (x, y), in pixels from the bottom left. With a
|
|
352
|
+
* `time`, the move joins the trail: a new point at most every
|
|
353
|
+
* {@link TRAIL_SECONDS}, the newest point following the pointer between.
|
|
354
|
+
*/
|
|
355
|
+
move(x, y, time) {
|
|
343
356
|
this.x = x;
|
|
344
357
|
this.y = y;
|
|
358
|
+
if (time === void 0) return;
|
|
359
|
+
const last = this.trail[this.trail.length - 1];
|
|
360
|
+
if (last && time >= last.time && time - last.time < TRAIL_SECONDS) {
|
|
361
|
+
last.x = x;
|
|
362
|
+
last.y = y;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.trail.push({ x, y, time });
|
|
366
|
+
if (this.trail.length > MAX_TRAIL) this.trail.shift();
|
|
345
367
|
}
|
|
346
368
|
/** The pointer came over the canvas (`true`) or left it. */
|
|
347
369
|
hover(over, time) {
|
|
@@ -361,9 +383,10 @@ var PointerTracker = class {
|
|
|
361
383
|
if (this.pressedAt !== null) this.releasedAt = time;
|
|
362
384
|
this.pressedAt = null;
|
|
363
385
|
}
|
|
364
|
-
/** Forgets clicks and
|
|
386
|
+
/** Forgets clicks, presses and the trail (when time jumps back, say). */
|
|
365
387
|
clear() {
|
|
366
388
|
this.clicks = [];
|
|
389
|
+
this.trail = [];
|
|
367
390
|
this.pressedAt = null;
|
|
368
391
|
this.releasedAt = null;
|
|
369
392
|
}
|
|
@@ -387,6 +410,10 @@ var PointerTracker = class {
|
|
|
387
410
|
clicksAt(time) {
|
|
388
411
|
return clicksUniform(this.clicks, time);
|
|
389
412
|
}
|
|
413
|
+
/** `iTrail` at `time` (see {@link trailUniform}). */
|
|
414
|
+
trailAt(time) {
|
|
415
|
+
return trailUniform(this.trail, time);
|
|
416
|
+
}
|
|
390
417
|
};
|
|
391
418
|
|
|
392
419
|
// src/shader/program.ts
|
|
@@ -410,6 +437,12 @@ var BUILTIN_UNIFORMS = [
|
|
|
410
437
|
type: "vec4",
|
|
411
438
|
size: MAX_CLICKS,
|
|
412
439
|
doc: "the latest clicks, newest first: xy = where (pixels), z = when (iTime seconds), w = 1 for a click, 0 for an empty slot"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "iTrail",
|
|
443
|
+
type: "vec4",
|
|
444
|
+
size: MAX_TRAIL,
|
|
445
|
+
doc: "where the pointer has just been over the canvas, newest first (about 30 points a second, so a second of path): xy = where (pixels), z = when (iTime seconds), w = 1 for a point, 0 for an empty slot"
|
|
413
446
|
}
|
|
414
447
|
];
|
|
415
448
|
var PRELUDE = `#version 300 es
|
|
@@ -422,6 +455,7 @@ uniform int iFrame;
|
|
|
422
455
|
uniform vec4 iMouse;
|
|
423
456
|
uniform vec4 iPointer;
|
|
424
457
|
uniform vec4 iClicks[${MAX_CLICKS}];
|
|
458
|
+
uniform vec4 iTrail[${MAX_TRAIL}];
|
|
425
459
|
out vec4 flair_FragColor;
|
|
426
460
|
`;
|
|
427
461
|
var EPILOGUE = `
|
|
@@ -543,6 +577,7 @@ function compileShader(gl, source, controls) {
|
|
|
543
577
|
return { ok: true, shader: { program, builtins, uniforms } };
|
|
544
578
|
}
|
|
545
579
|
var NO_CLICKS = new Float32Array(MAX_CLICKS * 4);
|
|
580
|
+
var NO_TRAIL = new Float32Array(MAX_TRAIL * 4);
|
|
546
581
|
function drawFrame(gl, shader, controls, values, state, textures) {
|
|
547
582
|
gl.viewport(0, 0, state.width, state.height);
|
|
548
583
|
gl.useProgram(shader.program);
|
|
@@ -554,6 +589,7 @@ function drawFrame(gl, shader, controls, values, state, textures) {
|
|
|
554
589
|
if (b.iMouse) gl.uniform4f(b.iMouse, ...state.mouse);
|
|
555
590
|
if (b.iPointer) gl.uniform4f(b.iPointer, ...state.pointer ?? [0, 0, 0, 0]);
|
|
556
591
|
if (b.iClicks) gl.uniform4fv(b.iClicks, state.clicks ?? NO_CLICKS);
|
|
592
|
+
if (b.iTrail) gl.uniform4fv(b.iTrail, state.trail ?? NO_TRAIL);
|
|
557
593
|
let unit = 0;
|
|
558
594
|
for (const control of controls) {
|
|
559
595
|
const loc = shader.uniforms.get(control.name);
|
|
@@ -925,8 +961,11 @@ export {
|
|
|
925
961
|
TextureCache,
|
|
926
962
|
applyTextureSettings,
|
|
927
963
|
MAX_CLICKS,
|
|
964
|
+
MAX_TRAIL,
|
|
965
|
+
TRAIL_SECONDS,
|
|
928
966
|
HOVER_SECONDS,
|
|
929
967
|
clicksUniform,
|
|
968
|
+
trailUniform,
|
|
930
969
|
PointerTracker,
|
|
931
970
|
BUILTIN_UNIFORMS,
|
|
932
971
|
VERTEX_SOURCE,
|
|
@@ -958,4 +997,4 @@ export {
|
|
|
958
997
|
getOklabGLSL,
|
|
959
998
|
getColorGradingGLSL
|
|
960
999
|
};
|
|
961
|
-
//# sourceMappingURL=chunk-
|
|
1000
|
+
//# sourceMappingURL=chunk-ARKC7LDW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shader/textures.ts","../src/shader/pointer.ts","../src/shader/program.ts","../src/shader/color.ts"],"sourcesContent":["import type { TextureSettings } from \"./controls.js\";\n\n/** Why an image control's URL could not be shown. */\nexport class ImageLoadError extends Error {\n readonly url: string;\n\n constructor(url: string, cause?: unknown, message?: string) {\n super(\n message ??\n `Could not load the image at ${shortUrl(url)}. If it is on another site, that site must allow cross-origin use (CORS).`,\n { cause },\n );\n this.name = \"ImageLoadError\";\n this.url = url;\n }\n}\n\n/** A URL short enough for a message: a data URL is cut after its type. */\nexport function shortUrl(url: string): string {\n if (url.startsWith(\"data:\")) return `${url.slice(0, Math.min(url.indexOf(\",\") + 1 || 40, 40))}…`;\n return url.length > 120 ? `${url.slice(0, 119)}…` : url;\n}\n\n/**\n * The long edge, in pixels, SVG images are rasterised at. WebGL2 guarantees\n * textures this large on every device.\n */\nexport const SVG_EDGE = 2048;\n\nconst SVG_TYPE = \"image/svg+xml\";\n\n/** Whether `url` is an SVG: a `data:image/svg+xml` URL, or a path ending in `.svg`. */\nexport function isSvgUrl(url: string): boolean {\n return /^data:image\\/svg\\+xml[;,]/i.test(url) || /^(?!data:)[^?#]*\\.svg([?#]|$)/i.test(url);\n}\n\n/** A length attribute in user units, or undefined for a percentage, `auto` or nothing. */\nfunction svgLength(value: string | null): number | undefined {\n const match = value?.trim().match(/^([\\d.]+)(px)?$/);\n const n = match ? Number(match[1]) : NaN;\n return n > 0 ? n : undefined;\n}\n\n/**\n * An SVG's markup with `width` and `height` set so its long edge is `edge`\n * pixels, keeping its aspect ratio. An image decodes an SVG at its own size\n * (often icon-sized, or 300 × 150 when it has none), so it would make a\n * small, blurry texture; at this size it is rasterised crisply. A `viewBox`\n * is added where missing, so the drawing scales with the new size rather\n * than staying in the corner. Throws for markup that is not an SVG.\n */\nexport function sizeSvg(markup: string, edge: number): string {\n const doc = new DOMParser().parseFromString(markup, SVG_TYPE);\n const svg = doc.documentElement;\n if (svg.localName !== \"svg\" || doc.getElementsByTagName(\"parsererror\").length) {\n throw new Error(\"Not an SVG document.\");\n }\n const box = (svg.getAttribute(\"viewBox\") ?? \"\")\n .trim()\n .split(/[\\s,]+/)\n .map(Number);\n const hasBox = box.length === 4 && box[2]! > 0 && box[3]! > 0;\n const width = svgLength(svg.getAttribute(\"width\")) ?? (hasBox ? box[2]! : 300);\n const height = svgLength(svg.getAttribute(\"height\")) ?? (hasBox ? box[3]! : 150);\n if (!hasBox) svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n const scale = edge / Math.max(width, height);\n svg.setAttribute(\"width\", String(Math.max(1, Math.round(width * scale))));\n svg.setAttribute(\"height\", String(Math.max(1, Math.round(height * scale))));\n return new XMLSerializer().serializeToString(svg);\n}\n\n/**\n * Loads and decodes an image for WebGL. Images from other origins are\n * requested with CORS (`crossOrigin = \"anonymous\"`): WebGL refuses pixels a\n * page is not allowed to read. SVGs (see {@link isSvgUrl}) are fetched, also\n * with CORS, and sized to {@link SVG_EDGE} pixels on their long edge before\n * they are decoded, so they stay sharp however small they were drawn.\n */\nexport async function loadImage(url: string): Promise<HTMLImageElement> {\n if (isSvgUrl(url)) return loadSvg(url);\n return decode(url, url, !/^(data|blob):/i.test(url));\n}\n\nasync function loadSvg(url: string): Promise<HTMLImageElement> {\n let markup: string;\n try {\n const response = await fetch(url);\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n markup = sizeSvg(await response.text(), SVG_EDGE);\n } catch (e) {\n throw new ImageLoadError(url, e);\n }\n const sized = URL.createObjectURL(new Blob([markup], { type: SVG_TYPE }));\n try {\n return await decode(sized, url, false);\n } finally {\n URL.revokeObjectURL(sized);\n }\n}\n\nasync function decode(src: string, url: string, cors: boolean): Promise<HTMLImageElement> {\n const image = new Image();\n if (cors) image.crossOrigin = \"anonymous\";\n image.src = src;\n try {\n await image.decode();\n } catch (e) {\n throw new ImageLoadError(url, e);\n }\n return image;\n}\n\n/** Images decoded by {@link preloadImages}, by URL, for every context on the page. */\nconst preloaded = new Map<string, Promise<HTMLImageElement>>();\nconst decoded = new Map<string, HTMLImageElement>();\n\n/**\n * Loads and decodes images ahead of time, for every WebGL context on the\n * page: a texture cache that meets one of them afterwards (in a\n * `ShaderRenderer`, `ShaderFrames` or thumbnail) uploads it at once, so even\n * its first frame shows the image. Otherwise an image arrives a moment after\n * the first frame, which is black where it goes. Use it before drawing frames\n * that cannot wait, such as a video player asking a new `ShaderFrames` for\n * frame _n_.\n *\n * Each URL loads once, however often it is preloaded. Rejects with the first\n * {@link ImageLoadError}; the other images still load. Preloaded images stay\n * in memory until {@link releaseImages}.\n */\nexport async function preloadImages(urls: Iterable<string>): Promise<void> {\n const loads = [...new Set(urls)].filter(Boolean).map((url) => {\n let load = preloaded.get(url);\n if (!load) {\n load = loadImage(url).then(\n (image) => {\n if (preloaded.get(url) === load) decoded.set(url, image);\n return image;\n },\n (e: unknown) => {\n if (preloaded.get(url) === load) preloaded.delete(url);\n throw e;\n },\n );\n preloaded.set(url, load);\n }\n return load;\n });\n const results = await Promise.allSettled(loads);\n const failed = results.find((r) => r.status === \"rejected\");\n if (failed) throw failed.reason;\n}\n\n/** Forgets preloaded images (all of them without `urls`). Textures already made from them stay. */\nexport function releaseImages(urls?: Iterable<string>) {\n for (const url of urls ?? [...preloaded.keys()]) {\n preloaded.delete(url);\n decoded.delete(url);\n }\n}\n\n/** Something that hands `drawFrame` a texture for an image URL. */\nexport interface TextureSource {\n /** The texture for `url`: the image once loaded, a 1 × 1 opaque black placeholder until then. */\n texture(url: string): WebGLTexture | null;\n /**\n * The texture of the live source set for the image control `name` (see\n * {@link TextureCache.setLive}), with its current frame; undefined when\n * the control has none, so its URL is drawn instead.\n */\n liveTexture?(name: string): WebGLTexture | null | undefined;\n}\n\n/**\n * A picture that changes while it is shown, for an image control: a playing\n * `<video>`, or a canvas something else draws on. Videos re-upload when\n * their frame changes; canvases on every frame drawn.\n */\nexport type LiveSource = HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas;\n\ninterface Live {\n source: LiveSource;\n texture: WebGLTexture | null;\n /** The video time last uploaded (NaN before the first upload). */\n uploaded: number;\n /** Set once the source could not be uploaded: it shows black, and is not tried again. */\n error: ImageLoadError | null;\n}\n\n/** Whether `source` is a video element (without touching `HTMLVideoElement`, which tests and workers lack). */\nfunction isVideo(source: LiveSource): source is HTMLVideoElement {\n return \"readyState\" in source && \"currentTime\" in source;\n}\n\n/** `HAVE_CURRENT_DATA`: a video has a frame to show. */\nconst HAVE_CURRENT_DATA = 2;\n\n/** A name for a live source in messages: its video URL, or the control's name. */\nfunction liveName(name: string, source: LiveSource): string {\n return (isVideo(source) && (source.currentSrc || source.src)) || name;\n}\n\n/**\n * Seeks a video to `time` seconds and settles once the frame there can be\n * drawn, for stepping a video in time with `ShaderFrames`: `await\n * seekVideo(video, t); frames.render(t)`. Pause the video first, or it\n * moves on. The time is clamped to the video's length.\n */\nexport async function seekVideo(video: HTMLVideoElement, time: number): Promise<void> {\n if (video.readyState < 1) {\n await new Promise<void>((resolve, reject) => {\n video.addEventListener(\"loadedmetadata\", () => resolve(), { once: true });\n video.addEventListener(\"error\", () => reject(new Error(`Could not load the video ${shortUrl(video.currentSrc || video.src)}.`)), { once: true });\n });\n }\n const target = Math.max(0, Math.min(time, Number.isFinite(video.duration) ? video.duration : time));\n if (Math.abs(video.currentTime - target) < 1e-6 && video.readyState >= HAVE_CURRENT_DATA && !video.seeking) return;\n await new Promise<void>((resolve) => {\n video.addEventListener(\"seeked\", () => resolve(), { once: true });\n video.currentTime = target;\n });\n}\n\ninterface Entry {\n texture: WebGLTexture | null;\n /** Settles when the image is uploaded or has failed. */\n loaded: Promise<void>;\n error: ImageLoadError | null;\n}\n\n/**\n * The textures of one WebGL context, by image URL. A URL starts loading the\n * first time it is asked for; until it arrives the placeholder is drawn, and\n * `onLoad` hears when it does (or fails) so the owner can redraw. An image\n * already decoded by {@link preloadImages} is uploaded at once instead. Images are\n * uploaded upright (`fragCoord / iResolution` samples them the right way up)\n * with mipmaps.\n */\nexport class TextureCache implements TextureSource {\n private readonly gl: WebGL2RenderingContext;\n private readonly entries = new Map<string, Entry>();\n private readonly live = new Map<string, Live>();\n private readonly onLoad: (url: string, error: ImageLoadError | null) => void;\n private readonly load: (url: string) => Promise<TexImageSource>;\n private placeholder: WebGLTexture | null = null;\n private disposed = false;\n\n constructor(\n gl: WebGL2RenderingContext,\n onLoad: (url: string, error: ImageLoadError | null) => void = () => {},\n load: (url: string) => Promise<TexImageSource> = loadImage,\n ) {\n this.gl = gl;\n this.onLoad = onLoad;\n this.load = load;\n }\n\n texture(url: string): WebGLTexture | null {\n return (url && this.entry(url).texture) || this.blank();\n }\n\n /** Starts loading `url` if it is new. */\n request(url: string) {\n if (url) this.entry(url);\n }\n\n private entry(url: string): Entry {\n const existing = this.entries.get(url);\n if (existing) return existing;\n const created: Entry = { texture: null, loaded: Promise.resolve(), error: null };\n const ready = decoded.get(url);\n if (ready && !this.gl.isContextLost()) {\n created.texture = this.upload(ready);\n this.entries.set(url, created);\n return created;\n }\n created.loaded = this.load(url).then(\n (image) => {\n if (this.entries.get(url) !== created || this.disposed || this.gl.isContextLost()) return;\n created.texture = this.upload(image);\n this.onLoad(url, null);\n },\n (e: unknown) => {\n if (this.entries.get(url) !== created || this.disposed) return;\n created.error = e instanceof ImageLoadError ? e : new ImageLoadError(url, e);\n this.onLoad(url, created.error);\n },\n );\n this.entries.set(url, created);\n return created;\n }\n\n /**\n * Shows `source` in the image control `name` instead of its URL, frame by\n * frame (`null` goes back to the URL). Its texture is uploaded as it is\n * drawn: a video's whenever it has moved to another frame, a canvas's on\n * every frame. Until a video has a frame it shows black. A video from\n * another site needs CORS (`crossOrigin = \"anonymous\"` and the site's\n * consent); without it the upload fails, `onLoad` hears an\n * {@link ImageLoadError}, and the control shows black.\n */\n setLive(name: string, source: LiveSource | null) {\n const existing = this.live.get(name);\n if (existing?.source === source) return;\n if (existing?.texture) this.gl.deleteTexture(existing.texture);\n this.live.delete(name);\n if (source) this.live.set(name, { source, texture: null, uploaded: NaN, error: null });\n }\n\n liveTexture(name: string): WebGLTexture | null | undefined {\n const live = this.live.get(name);\n if (!live) return undefined;\n if (live.error || this.gl.isContextLost()) return this.blank();\n const { source } = live;\n if (isVideo(source)) {\n if (source.readyState < HAVE_CURRENT_DATA) return live.texture ?? this.blank();\n if (live.texture && live.uploaded === source.currentTime) return live.texture;\n live.uploaded = source.currentTime;\n }\n const gl = this.gl;\n live.texture ??= gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, live.texture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n try {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n } catch (e) {\n const url = liveName(name, source);\n live.error = new ImageLoadError(\n url,\n e,\n `Could not draw the video at ${shortUrl(url)}. If it is on another site, set crossOrigin = \"anonymous\" on the <video> and make sure that site allows cross-origin use (CORS).`,\n );\n this.onLoad(url, live.error);\n return this.blank();\n } finally {\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n }\n gl.generateMipmap(gl.TEXTURE_2D);\n return live.texture;\n }\n\n /**\n * Whether a live video has moved to a frame not yet drawn (or has its\n * first frame ready), so a paused preview knows to draw again.\n */\n liveChanged(): boolean {\n for (const live of this.live.values()) {\n const { source } = live;\n if (live.error || !isVideo(source) || source.readyState < HAVE_CURRENT_DATA) continue;\n if (!live.texture || live.uploaded !== source.currentTime) return true;\n }\n return false;\n }\n\n /**\n * Settles once every live video has a frame to show. Rejects with an\n * {@link ImageLoadError} when one fails to load.\n */\n async liveReady(): Promise<void> {\n await Promise.all(\n [...this.live.entries()].map(([name, { source }]) => {\n if (!isVideo(source) || source.readyState >= HAVE_CURRENT_DATA) return undefined;\n return new Promise<void>((resolve, reject) => {\n source.addEventListener(\"loadeddata\", () => resolve(), { once: true });\n source.addEventListener(\n \"error\",\n () => reject(new ImageLoadError(liveName(name, source), source.error, `Could not load the video at ${shortUrl(liveName(name, source))}.`)),\n { once: true },\n );\n });\n }),\n );\n }\n\n /** The error for `url`, if it failed to load. */\n error(url: string): ImageLoadError | null {\n return this.entries.get(url)?.error ?? null;\n }\n\n /**\n * Settles once every one of `urls` has loaded or failed (starting any that\n * are new). Rejects with the first {@link ImageLoadError}.\n */\n async ready(urls: Iterable<string>): Promise<void> {\n const entries = [...urls].filter(Boolean).map((url) => this.entry(url));\n await Promise.all(entries.map((e) => e.loaded));\n const failed = entries.find((e) => e.error);\n if (failed) throw failed.error;\n }\n\n /** Deletes the textures of every URL not in `urls`; a later request loads them again. */\n retain(urls: Iterable<string>) {\n const keep = new Set(urls);\n for (const [url, entry] of this.entries) {\n if (keep.has(url)) continue;\n if (entry.texture) this.gl.deleteTexture(entry.texture);\n this.entries.delete(url);\n }\n }\n\n /** Deletes every texture; loads still in flight are dropped. */\n dispose() {\n this.retain([]);\n for (const name of [...this.live.keys()]) this.setLive(name, null);\n if (this.placeholder) this.gl.deleteTexture(this.placeholder);\n this.placeholder = null;\n this.disposed = true;\n }\n\n private blank(): WebGLTexture | null {\n if (this.placeholder) return this.placeholder;\n const gl = this.gl;\n this.placeholder = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, this.placeholder);\n const black = new Uint8Array([0, 0, 0, 255]);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, black);\n return this.placeholder;\n }\n\n private upload(image: TexImageSource): WebGLTexture | null {\n const gl = this.gl;\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.generateMipmap(gl.TEXTURE_2D);\n return texture;\n }\n}\n\n/** Sets the bound `TEXTURE_2D`'s filter and wrap. */\nexport function applyTextureSettings(gl: WebGL2RenderingContext, settings: TextureSettings) {\n const min = { mipmap: gl.LINEAR_MIPMAP_LINEAR, linear: gl.LINEAR, nearest: gl.NEAREST }[\n settings.filter\n ];\n const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[\n settings.wrap\n ];\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min);\n gl.texParameteri(\n gl.TEXTURE_2D,\n gl.TEXTURE_MAG_FILTER,\n settings.filter === \"nearest\" ? gl.NEAREST : gl.LINEAR,\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n}\n","/**\n * The pointer as shaders see it, beyond Shadertoy's `iMouse`: where it is\n * while it is over the canvas, how hovered the canvas is (eased in and out),\n * how long it has been pressed, the last few clicks, and where it has just\n * been (its trail), so effects can answer hovers, presses, taps and moves.\n * Times are in `iTime` seconds, so a recording can script them (see\n * `ShaderFrames`' `pointer`, `clicks` and `trail`).\n */\n\n/** How many clicks `iClicks` holds; older ones drop off. */\nexport const MAX_CLICKS = 8;\n\n/** How many of the pointer's recent positions `iTrail` holds; older ones drop off. */\nexport const MAX_TRAIL = 32;\n\n/**\n * The least time between two positions in the trail, in seconds: moves in\n * between update the newest. With {@link MAX_TRAIL}, about a second of path.\n */\nexport const TRAIL_SECONDS = 1 / 30;\n\n/** How long hovering eases in or out, in seconds. */\nexport const HOVER_SECONDS = 0.25;\n\n/** A click, or a point of the trail: where (pixels from the bottom left, like `iMouse`) and when (`iTime` seconds). */\nexport interface ShaderClick {\n x: number;\n y: number;\n time: number;\n}\n\nconst smooth = (t: number) => (t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t));\n\n/**\n * `iClicks` from a list of clicks, newest first: `vec4(x, y, time, 1)` for\n * each click at or before `time`, zeros for the rest.\n */\nexport function clicksUniform(clicks: readonly ShaderClick[], time: number): Float32Array {\n return latest(clicks, time, MAX_CLICKS);\n}\n\n/**\n * `iTrail` from the pointer's positions, oldest first in the list: the\n * newest {@link MAX_TRAIL} at or before `time`, newest first, as\n * `vec4(x, y, time, 1)`, zeros for the rest.\n */\nexport function trailUniform(trail: readonly ShaderClick[], time: number): Float32Array {\n return latest(trail, time, MAX_TRAIL);\n}\n\nfunction latest(events: readonly ShaderClick[], time: number, max: number): Float32Array {\n const out = new Float32Array(max * 4);\n let i = 0;\n for (let k = events.length - 1; k >= 0 && i < max; k--) {\n const c = events[k]!;\n if (c.time > time) continue;\n out.set([c.x, c.y, c.time, 1], i * 4);\n i++;\n }\n return out;\n}\n\n/**\n * Follows one canvas's pointer for `iPointer` and `iClicks`: feed it pointer\n * events with the `iTime` they happen at, and read the uniforms for a frame.\n */\nexport class PointerTracker {\n private x = 0;\n private y = 0;\n private over = false;\n private hoverFrom = 0;\n private changedAt = -Infinity;\n private pressedAt: number | null = null;\n private releasedAt: number | null = null;\n private clicks: ShaderClick[] = [];\n private trail: ShaderClick[] = [];\n\n /**\n * The pointer moved to (x, y), in pixels from the bottom left. With a\n * `time`, the move joins the trail: a new point at most every\n * {@link TRAIL_SECONDS}, the newest point following the pointer between.\n */\n move(x: number, y: number, time?: number) {\n this.x = x;\n this.y = y;\n if (time === undefined) return;\n const last = this.trail[this.trail.length - 1];\n if (last && time >= last.time && time - last.time < TRAIL_SECONDS) {\n last.x = x;\n last.y = y;\n return;\n }\n this.trail.push({ x, y, time });\n if (this.trail.length > MAX_TRAIL) this.trail.shift();\n }\n\n /** The pointer came over the canvas (`true`) or left it. */\n hover(over: boolean, time: number) {\n if (over === this.over) return;\n this.hoverFrom = this.hovered(time);\n this.changedAt = time;\n this.over = over;\n }\n\n /** A press at (x, y): counts as a click. */\n down(x: number, y: number, time: number) {\n this.move(x, y);\n this.pressedAt = time;\n this.clicks.push({ x, y, time });\n if (this.clicks.length > MAX_CLICKS) this.clicks.shift();\n }\n\n up(time: number) {\n if (this.pressedAt !== null) this.releasedAt = time;\n this.pressedAt = null;\n }\n\n /** Forgets clicks, presses and the trail (when time jumps back, say). */\n clear() {\n this.clicks = [];\n this.trail = [];\n this.pressedAt = null;\n this.releasedAt = null;\n }\n\n /** How hovered the canvas is at `time`, 0–1, eased. */\n hovered(time: number): number {\n const t = smooth((time - this.changedAt) / HOVER_SECONDS);\n return this.hoverFrom + ((this.over ? 1 : 0) - this.hoverFrom) * t;\n }\n\n /**\n * `iPointer` at `time`: x, y, how hovered (0–1), and the press: seconds\n * held while pressed, minus the seconds since it was let go after, 0\n * before the first press.\n */\n pointer(time: number): [number, number, number, number] {\n let press = 0;\n if (this.pressedAt !== null) press = Math.max(0, time - this.pressedAt);\n else if (this.releasedAt !== null) press = -Math.max(1e-3, time - this.releasedAt);\n return [this.x, this.y, this.hovered(time), press];\n }\n\n /** `iClicks` at `time` (see {@link clicksUniform}). */\n clicksAt(time: number): Float32Array {\n return clicksUniform(this.clicks, time);\n }\n\n /** `iTrail` at `time` (see {@link trailUniform}). */\n trailAt(time: number): Float32Array {\n return trailUniform(this.trail, time);\n }\n}\n","import { controlValue, type ControlDef, type ControlValue } from \"./controls.js\";\nimport { MAX_CLICKS, MAX_TRAIL } from \"./pointer.js\";\nimport { applyTextureSettings, type TextureSource } from \"./textures.js\";\n\n/**\n * Shaders are written Shadertoy-style: the source defines\n * `void mainImage(out vec4 fragColor, in vec2 fragCoord)` and may use the\n * built-in uniforms below. It is compiled as GLSL ES 3.00 (WebGL2) inside a\n * small wrapper; `#line 1` keeps compiler line numbers matching the source.\n */\nexport const BUILTIN_UNIFORMS = [\n { name: \"iResolution\", type: \"vec3\", doc: \"viewport size in pixels (z = 1)\" },\n { name: \"iTime\", type: \"float\", doc: \"seconds since the shader started\" },\n { name: \"iTimeDelta\", type: \"float\", doc: \"seconds since the previous frame\" },\n { name: \"iFrame\", type: \"int\", doc: \"frame number\" },\n {\n name: \"iMouse\",\n type: \"vec4\",\n doc: \"xy = pointer while pressed (pixels), zw = press position (negative once released)\",\n },\n {\n name: \"iPointer\",\n type: \"vec4\",\n doc: \"xy = pointer while over the canvas (pixels), z = how hovered (0–1, eased), w = seconds held while pressed, minus seconds since release after (0 before any press)\",\n },\n {\n name: \"iClicks\",\n type: \"vec4\",\n size: MAX_CLICKS,\n doc: \"the latest clicks, newest first: xy = where (pixels), z = when (iTime seconds), w = 1 for a click, 0 for an empty slot\",\n },\n {\n name: \"iTrail\",\n type: \"vec4\",\n size: MAX_TRAIL,\n doc: \"where the pointer has just been over the canvas, newest first (about 30 points a second, so a second of path): xy = where (pixels), z = when (iTime seconds), w = 1 for a point, 0 for an empty slot\",\n },\n] as const;\n\nconst PRELUDE = `#version 300 es\nprecision highp float;\nprecision highp int;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iPointer;\nuniform vec4 iClicks[${MAX_CLICKS}];\nuniform vec4 iTrail[${MAX_TRAIL}];\nout vec4 flair_FragColor;\n`;\n\nconst EPILOGUE = `\nvoid main() {\n vec4 flair_color = vec4(0.0, 0.0, 0.0, 1.0);\n mainImage(flair_color, gl_FragCoord.xy);\n flair_FragColor = vec4(flair_color.rgb, clamp(flair_color.a, 0.0, 1.0));\n}\n`;\n\n/** A full-screen triangle from `gl_VertexID` alone: no buffers or attributes. */\nexport const VERTEX_SOURCE = `#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`;\n\n/** One compiler message, mapped back to the user's source. */\nexport interface CompileError {\n /** 1-based line in the user's source (0 when the driver gave none, or it is outside the source). */\n line: number;\n message: string;\n}\n\n/** A linked program and the uniform locations `drawFrame` sets. */\nexport interface CompiledShader {\n program: WebGLProgram;\n builtins: Record<string, WebGLUniformLocation | null>;\n uniforms: Map<string, WebGLUniformLocation | null>;\n}\n\nexport type CompileResult =\n { ok: true; shader: CompiledShader } | { ok: false; errors: CompileError[] };\n\n/** Thrown by the APIs that cannot carry on without a program (`ShaderFrames`, `recordGif`). */\nexport class ShaderCompileError extends Error {\n readonly errors: CompileError[];\n\n constructor(errors: CompileError[]) {\n super(formatCompileError(errors));\n this.name = \"ShaderCompileError\";\n this.errors = errors;\n }\n}\n\n/** \"The shader does not compile (line 3): 'x' : undeclared identifier\", from the first error. */\nexport function formatCompileError(errors: CompileError[]): string {\n const first = errors[0];\n return `The shader does not compile${first?.line ? ` (line ${first.line})` : \"\"}: ${first?.message ?? \"unknown error\"}`;\n}\n\n/** The complete GLSL ES 3.00 fragment shader for a `mainImage` source. */\nexport function wrapSource(source: string): string {\n // `#line 1` must follow the prelude; drop a user `#version` (the wrapper owns it).\n return `${PRELUDE}#line 1\\n${source.replace(/^\\s*#version[^\\n]*/m, \"\")}\\n${EPILOGUE}`;\n}\n\n/** Turns a driver log (\"ERROR: 0:12: 'foo' : undeclared identifier\") into line-numbered errors. */\nexport function parseCompileLog(log: string, lineCount: number): CompileError[] {\n const errors: CompileError[] = [];\n for (const raw of log.split(\"\\n\")) {\n const text = raw.trim();\n if (!text) continue;\n const m = /^(?:ERROR|WARNING):\\s*\\d+:(\\d+):\\s*(.*)$/.exec(text);\n if (m) {\n const line = Number(m[1]);\n errors.push({ line: line >= 1 && line <= lineCount ? line : 0, message: m[2]!.trim() });\n } else if (!/^\\W*$/.test(text)) {\n errors.push({ line: 0, message: text.replace(/^ERROR:\\s*/, \"\") });\n }\n }\n if (!errors.length) errors.push({ line: 0, message: \"The shader did not compile.\" });\n // Drivers often repeat the same error.\n const seen = new Set<string>();\n return errors.filter((e) => {\n const key = `${e.line}:${e.message}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\nlet vertexCache = new WeakMap<WebGL2RenderingContext, WebGLShader>();\n\nfunction vertexShader(gl: WebGL2RenderingContext): WebGLShader {\n let vs = vertexCache.get(gl);\n if (!vs) {\n vs = gl.createShader(gl.VERTEX_SHADER)!;\n gl.shaderSource(vs, VERTEX_SOURCE);\n gl.compileShader(vs);\n vertexCache.set(gl, vs);\n }\n return vs;\n}\n\n/** Forgets cached GL objects. Call it after a context loss, before compiling on the restored context. */\nexport function resetGlCaches() {\n vertexCache = new WeakMap();\n}\n\n/**\n * Compiles and links a `mainImage` source. Errors come back with lines in\n * the user's source, never thrown. `controls` (from `parseControls`) says\n * which uniform locations to look up.\n */\nexport function compileShader(\n gl: WebGL2RenderingContext,\n source: string,\n controls: ControlDef[],\n): CompileResult {\n const lineCount = source.split(\"\\n\").length;\n if (gl.isContextLost()) {\n return {\n ok: false,\n errors: [\n {\n line: 0,\n message:\n \"The graphics context was lost; reload the page if the preview does not come back.\",\n },\n ],\n };\n }\n if (!/\\bmainImage\\s*\\(/.test(source)) {\n return {\n ok: false,\n errors: [\n { line: 0, message: \"Define void mainImage(out vec4 fragColor, in vec2 fragCoord).\" },\n ],\n };\n }\n const fs = gl.createShader(gl.FRAGMENT_SHADER);\n if (!fs) {\n return {\n ok: false,\n errors: [{ line: 0, message: \"WebGL could not create a shader (the context may be lost).\" }],\n };\n }\n gl.shaderSource(fs, wrapSource(source));\n gl.compileShader(fs);\n if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {\n const errors = parseCompileLog(gl.getShaderInfoLog(fs) ?? \"\", lineCount);\n gl.deleteShader(fs);\n return { ok: false, errors };\n }\n const program = gl.createProgram()!;\n gl.attachShader(program, vertexShader(gl));\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.deleteShader(fs);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const errors = parseCompileLog(gl.getProgramInfoLog(program) ?? \"\", lineCount);\n gl.deleteProgram(program);\n return { ok: false, errors };\n }\n const builtins: CompiledShader[\"builtins\"] = {};\n for (const u of BUILTIN_UNIFORMS) builtins[u.name] = gl.getUniformLocation(program, u.name);\n const uniforms = new Map(\n controls.map((c) => [c.name, gl.getUniformLocation(program, c.name)] as const),\n );\n return { ok: true, shader: { program, builtins, uniforms } };\n}\n\nconst NO_CLICKS = new Float32Array(MAX_CLICKS * 4);\nconst NO_TRAIL = new Float32Array(MAX_TRAIL * 4);\n\n/** What one frame shows: its size in pixels, its time and the pointer. */\nexport interface FrameState {\n width: number;\n height: number;\n /** `iTime`, in seconds. */\n time: number;\n /** `iTimeDelta`, in seconds. */\n delta: number;\n /** `iFrame`. */\n frame: number;\n /** `iMouse`, Shadertoy semantics. */\n mouse: [number, number, number, number];\n /** `iPointer` (default all zero: nobody is pointing). */\n pointer?: readonly [number, number, number, number];\n /** `iClicks`: {@link MAX_CLICKS} × (x, y, time, 1), newest first (default none; see `clicksUniform`). */\n clicks?: Float32Array;\n /** `iTrail`: {@link MAX_TRAIL} × (x, y, time, 1), newest first (default none; see `trailUniform`). */\n trail?: Float32Array;\n}\n\n/**\n * Draws one frame of a compiled shader into the bound framebuffer, from (0, 0).\n * Image controls are bound to texture units 0, 1, … in source order, with\n * textures from `textures` (a {@link TextureCache} for the same context): a\n * control's live source when it has one, else its URL's image. Without\n * `textures` they sample nothing.\n */\nexport function drawFrame(\n gl: WebGL2RenderingContext,\n shader: CompiledShader,\n controls: ControlDef[],\n values: Record<string, ControlValue> | undefined,\n state: FrameState,\n textures?: TextureSource,\n) {\n gl.viewport(0, 0, state.width, state.height);\n gl.useProgram(shader.program);\n const b = shader.builtins;\n if (b.iResolution) gl.uniform3f(b.iResolution, state.width, state.height, 1);\n if (b.iTime) gl.uniform1f(b.iTime, state.time);\n if (b.iTimeDelta) gl.uniform1f(b.iTimeDelta, state.delta);\n if (b.iFrame) gl.uniform1i(b.iFrame, state.frame);\n if (b.iMouse) gl.uniform4f(b.iMouse, ...state.mouse);\n if (b.iPointer) gl.uniform4f(b.iPointer, ...(state.pointer ?? [0, 0, 0, 0]));\n if (b.iClicks) gl.uniform4fv(b.iClicks, state.clicks ?? NO_CLICKS);\n if (b.iTrail) gl.uniform4fv(b.iTrail, state.trail ?? NO_TRAIL);\n let unit = 0;\n for (const control of controls) {\n const loc = shader.uniforms.get(control.name);\n if (!loc) continue;\n const v = controlValue(control, values);\n switch (control.type) {\n case \"float\":\n gl.uniform1f(loc, v as number);\n break;\n case \"int\":\n gl.uniform1i(loc, Math.round(v as number));\n break;\n case \"bool\":\n gl.uniform1i(loc, v ? 1 : 0);\n break;\n case \"vec2\":\n gl.uniform2fv(loc, v as number[]);\n break;\n case \"vec3\":\n gl.uniform3fv(loc, v as number[]);\n break;\n case \"vec4\":\n gl.uniform4fv(loc, v as number[]);\n break;\n case \"sampler2D\": {\n gl.activeTexture(gl.TEXTURE0 + unit);\n const live = textures?.liveTexture?.(control.name);\n const texture = (live === undefined ? textures?.texture(v as string) : live) ?? null;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n if (texture && control.texture) applyTextureSettings(gl, control.texture);\n gl.uniform1i(loc, unit++);\n break;\n }\n }\n }\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\n/**\n * A WebGL2 context set up for full-screen fragment shaders: opaque by default,\n * no depth, stencil or antialiasing. `preserveDrawingBuffer` keeps the last\n * frame readable after the task that drew it (for captures). `alpha` enables a\n * transparent drawing buffer for overlay shaders. Null without WebGL2.\n */\nexport function createContext(\n canvas: HTMLCanvasElement | OffscreenCanvas,\n preserveDrawingBuffer = false,\n alpha = false,\n): WebGL2RenderingContext | null {\n return canvas.getContext(\"webgl2\", {\n alpha,\n antialias: false,\n depth: false,\n stencil: false,\n premultipliedAlpha: false,\n preserveDrawingBuffer,\n powerPreference: \"high-performance\",\n }) as WebGL2RenderingContext | null;\n}\n","/**\n * Perceptual Color Science & Film Grading Engine.\n *\n * Implements:\n * - Oklab & Oklch: Perceptually uniform color spaces that eliminate hue shifts and gray dead zones.\n * - Standard Color Spaces: sRGB, Linear sRGB, Rec.709, Display P3, and ACEScg.\n * - Film Color Grading: ASC CDL (Slope, Offset, Power), Lift/Gamma/Gain, Vibrance (skin tone protection),\n * and White Balance (Kelvin color temperature & tint).\n * - GLSL code generators for real-time WebGL fragment shader execution.\n */\n\nexport type ColorRGB = [number, number, number];\nexport type ColorRGBA = [number, number, number, number];\n\n// -----------------------------------------------------------------------------\n// sRGB <-> Linear Transfer Curves\n// -----------------------------------------------------------------------------\n\nexport function srgbToLinear(c: number): number {\n const v = Math.max(0, c);\n return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n}\n\nexport function linearToSrgb(c: number): number {\n const v = Math.max(0, c);\n return v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1.0 / 2.4) - 0.055;\n}\n\nexport function srgbToLinearRgb(rgb: ColorRGB): ColorRGB {\n return [srgbToLinear(rgb[0]), srgbToLinear(rgb[1]), srgbToLinear(rgb[2])];\n}\n\nexport function linearRgbToSrgb(rgb: ColorRGB): ColorRGB {\n return [linearToSrgb(rgb[0]), linearToSrgb(rgb[1]), linearToSrgb(rgb[2])];\n}\n\n// -----------------------------------------------------------------------------\n// Oklab & Oklch Perceptual Color Spaces (Björn Ottosson, 2020)\n// -----------------------------------------------------------------------------\n\nexport function linearSrgbToOklab(rgb: ColorRGB): [number, number, number] {\n const [r, g, b] = rgb;\n\n // Linear sRGB to LMS cone response\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n\n // LMS to Oklab (L: lightness [0..1], a: green-red, b: blue-yellow)\n const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;\n const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;\n const bVal = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;\n\n return [L, a, bVal];\n}\n\nexport function oklabToLinearSrgb(lab: [number, number, number]): ColorRGB {\n const [L, a, b] = lab;\n\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n\n const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const bVal = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n\n return [r, g, bVal];\n}\n\nexport function srgbToOklab(rgb: ColorRGB): [number, number, number] {\n return linearSrgbToOklab(srgbToLinearRgb(rgb));\n}\n\nexport function oklabToSrgb(lab: [number, number, number]): ColorRGB {\n const linear = oklabToLinearSrgb(lab);\n return linearRgbToSrgb(linear);\n}\n\nexport function oklabToOklch(lab: [number, number, number]): [number, number, number] {\n const [L, a, b] = lab;\n const C = Math.sqrt(a * a + b * b);\n let h = Math.atan2(b, a);\n if (h < 0) h += 2 * Math.PI;\n return [L, C, h];\n}\n\nexport function oklchToOklab(lch: [number, number, number]): [number, number, number] {\n const [L, C, h] = lch;\n return [L, C * Math.cos(h), C * Math.sin(h)];\n}\n\n/**\n * Perceptually uniform color interpolation using Oklab space.\n * Prevents intermediate grayish dead spots.\n */\nexport function interpolateOklab(c1: ColorRGB, c2: ColorRGB, t: number): ColorRGB {\n const lab1 = srgbToOklab(c1);\n const lab2 = srgbToOklab(c2);\n const clampedT = Math.max(0, Math.min(1, t));\n\n const lab: [number, number, number] = [\n lab1[0] + (lab2[0] - lab1[0]) * clampedT,\n lab1[1] + (lab2[1] - lab1[1]) * clampedT,\n lab1[2] + (lab2[2] - lab1[2]) * clampedT,\n ];\n\n const res = oklabToSrgb(lab);\n return [\n Math.max(0, Math.min(1, res[0])),\n Math.max(0, Math.min(1, res[1])),\n Math.max(0, Math.min(1, res[2])),\n ];\n}\n\n/**\n * Perceptually uniform color interpolation using Oklch cylindrical space.\n * Interpolates along the shortest hue arc for vibrant gradients.\n */\nexport function interpolateOklch(c1: ColorRGB, c2: ColorRGB, t: number): ColorRGB {\n const lch1 = oklabToOklch(srgbToOklab(c1));\n const lch2 = oklabToOklch(srgbToOklab(c2));\n const clampedT = Math.max(0, Math.min(1, t));\n\n const L = lch1[0] + (lch2[0] - lch1[0]) * clampedT;\n const C = lch1[1] + (lch2[1] - lch1[1]) * clampedT;\n\n // Shortest arc hue interpolation\n let dHue = lch2[2] - lch1[2];\n if (dHue > Math.PI) dHue -= 2 * Math.PI;\n if (dHue < -Math.PI) dHue += 2 * Math.PI;\n let h = lch1[2] + dHue * clampedT;\n if (h < 0) h += 2 * Math.PI;\n if (h >= 2 * Math.PI) h -= 2 * Math.PI;\n\n const res = oklabToSrgb(oklchToOklab([L, C, h]));\n return [\n Math.max(0, Math.min(1, res[0])),\n Math.max(0, Math.min(1, res[1])),\n Math.max(0, Math.min(1, res[2])),\n ];\n}\n\n// -----------------------------------------------------------------------------\n// Color Space Transformations (sRGB, Rec.709, Display P3, ACEScg)\n// -----------------------------------------------------------------------------\n\nexport type ColorSpace = 'srgb' | 'linear-srgb' | 'rec709' | 'display-p3' | 'acescg';\n\ntype Matrix3x3 = [\n number, number, number,\n number, number, number,\n number, number, number\n];\n\nfunction mulMat3(m: Matrix3x3, v: ColorRGB): ColorRGB {\n return [\n m[0] * v[0] + m[1] * v[1] + m[2] * v[2],\n m[3] * v[0] + m[4] * v[1] + m[5] * v[2],\n m[6] * v[0] + m[7] * v[1] + m[8] * v[2],\n ];\n}\n\n// Linear sRGB to Display P3 (linear)\nconst MAT_LINEAR_SRGB_TO_P3: Matrix3x3 = [\n 0.822462, 0.177538, 0.000000,\n 0.033194, 0.966806, 0.000000,\n 0.017083, 0.072397, 0.910520,\n];\n\n// Display P3 (linear) to Linear sRGB\nconst MAT_P3_TO_LINEAR_SRGB: Matrix3x3 = [\n 1.224940, -0.224940, 0.000000,\n -0.042057, 1.042057, 0.000000,\n -0.019638, -0.078636, 1.098274,\n];\n\n// Linear sRGB to ACEScg (AP1 primaries with Bradford adaptation to ACES white)\nconst MAT_LINEAR_SRGB_TO_ACESCG: Matrix3x3 = [\n 0.613097, 0.339523, 0.047379,\n 0.070194, 0.916354, 0.013452,\n 0.020616, 0.109570, 0.869815,\n];\n\n// ACEScg to Linear sRGB\nconst MAT_ACESCG_TO_LINEAR_SRGB: Matrix3x3 = [\n 1.704859, -0.621714, -0.083299,\n -0.130078, 1.140735, -0.010560,\n -0.023964, -0.128975, 1.153014,\n];\n\n/**\n * Converts a color between standard imaging color spaces.\n */\nexport function convertColorSpace(color: ColorRGB, from: ColorSpace, to: ColorSpace): ColorRGB {\n if (from === to) return [...color];\n\n // First convert `from` to Linear sRGB\n let lin: ColorRGB;\n switch (from) {\n case 'srgb':\n lin = srgbToLinearRgb(color);\n break;\n case 'linear-srgb':\n lin = [...color];\n break;\n case 'rec709':\n // BT.709 gamma 2.4 EOTF\n lin = [\n Math.pow(Math.max(0, color[0]), 2.4),\n Math.pow(Math.max(0, color[1]), 2.4),\n Math.pow(Math.max(0, color[2]), 2.4),\n ];\n break;\n case 'display-p3': {\n // P3 sRGB transfer to linear P3, then matrix to linear sRGB\n const p3Lin = srgbToLinearRgb(color);\n lin = mulMat3(MAT_P3_TO_LINEAR_SRGB, p3Lin);\n break;\n }\n case 'acescg':\n lin = mulMat3(MAT_ACESCG_TO_LINEAR_SRGB, color);\n break;\n }\n\n // Then convert from Linear sRGB to `to`\n switch (to) {\n case 'linear-srgb':\n return lin;\n case 'srgb':\n return linearRgbToSrgb(lin);\n case 'rec709':\n // BT.709 gamma 1/2.4\n return [\n Math.pow(Math.max(0, lin[0]), 1 / 2.4),\n Math.pow(Math.max(0, lin[1]), 1 / 2.4),\n Math.pow(Math.max(0, lin[2]), 1 / 2.4),\n ];\n case 'display-p3': {\n const p3Lin = mulMat3(MAT_LINEAR_SRGB_TO_P3, lin);\n return linearRgbToSrgb(p3Lin);\n }\n case 'acescg':\n return mulMat3(MAT_LINEAR_SRGB_TO_ACESCG, lin);\n }\n}\n\n// -----------------------------------------------------------------------------\n// Film Color Grading: ASC CDL, Lift/Gamma/Gain, Vibrance, White Balance\n// -----------------------------------------------------------------------------\n\nexport interface AscCdl {\n /** Multiplicative scaling (default [1, 1, 1]) */\n slope?: ColorRGB;\n /** Additive offset (default [0, 0, 0]) */\n offset?: ColorRGB;\n /** Exponential power (default [1, 1, 1]) */\n power?: ColorRGB;\n /** Master saturation (default 1.0) */\n saturation?: number;\n}\n\n/**\n * Standard ASC CDL (American Society of Cinematographers Color Decision List).\n * Formula: out = (in * slope + offset)^power, followed by Rec.709 luma-weighted saturation.\n */\nexport function applyAscCdl(color: ColorRGB, cdl: AscCdl): ColorRGB {\n const slope = cdl.slope ?? [1, 1, 1];\n const offset = cdl.offset ?? [0, 0, 0];\n const power = cdl.power ?? [1, 1, 1];\n const sat = cdl.saturation ?? 1.0;\n\n let r = Math.pow(Math.max(0, color[0] * slope[0] + offset[0]), power[0]);\n let g = Math.pow(Math.max(0, color[1] * slope[1] + offset[1]), power[1]);\n let b = Math.pow(Math.max(0, color[2] * slope[2] + offset[2]), power[2]);\n\n if (sat !== 1.0) {\n const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n r = luma + sat * (r - luma);\n g = luma + sat * (g - luma);\n b = luma + sat * (b - luma);\n }\n\n return [Math.max(0, r), Math.max(0, g), Math.max(0, b)];\n}\n\nexport interface LiftGammaGain {\n /** Shadows adjustment (default [0, 0, 0]) */\n lift?: ColorRGB;\n /** Midtones gamma adjustment (default [1, 1, 1]) */\n gamma?: ColorRGB;\n /** Highlights gain adjustment (default [1, 1, 1]) */\n gain?: ColorRGB;\n}\n\n/**\n * Three-way Lift / Gamma / Gain film color grading.\n * Formula: out = (gain * (in + lift * (1 - in)))^(1 / gamma)\n */\nexport function applyLiftGammaGain(color: ColorRGB, lgg: LiftGammaGain): ColorRGB {\n const lift = lgg.lift ?? [0, 0, 0];\n const gamma = lgg.gamma ?? [1, 1, 1];\n const gain = lgg.gain ?? [1, 1, 1];\n\n const processChannel = (val: number, l: number, gm: number, gn: number): number => {\n const lifted = val + l * (1.0 - val);\n const gained = Math.max(0, lifted * gn);\n const safeGamma = Math.max(1e-4, gm);\n return Math.pow(gained, 1.0 / safeGamma);\n };\n\n return [\n processChannel(color[0], lift[0], gamma[0], gain[0]),\n processChannel(color[1], lift[1], gamma[1], gain[1]),\n processChannel(color[2], lift[2], gamma[2], gain[2]),\n ];\n}\n\n/**\n * Vibrance adjustment with skin tone protection.\n * Selectively boosts muted colors while preventing already saturated colors and skin tones\n * from clipping.\n * @param amount Vibrance delta in [-1..1]\n */\nexport function applyVibrance(color: ColorRGB, amount: number): ColorRGB {\n if (Math.abs(amount) < 1e-4) return [...color];\n\n const [r, g, b] = color;\n const maxC = Math.max(r, g, b);\n const minC = Math.min(r, g, b);\n const sat = maxC > 1e-5 ? (maxC - minC) / maxC : 0;\n\n // Protect warm skin tones (hue around red/orange/yellow: r > g > b)\n let skinProtection = 1.0;\n if (r > g && g > b) {\n const skinFactor = (g - b) / (r - b + 1e-5);\n skinProtection = Math.max(0.2, 1.0 - skinFactor * 0.5);\n }\n\n // Weight inversely proportional to current saturation\n const boost = amount * (1.0 - sat) * skinProtection;\n const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n\n const newR = Math.max(0, Math.min(1, luma + (1 + boost) * (r - luma)));\n const newG = Math.max(0, Math.min(1, luma + (1 + boost) * (g - luma)));\n const newB = Math.max(0, Math.min(1, luma + (1 + boost) * (b - luma)));\n\n return [newR, newG, newB];\n}\n\n/**\n * Calculates RGB multiplier from Kelvin blackbody color temperature and green/magenta tint.\n * @param kelvin Temperature in Kelvin [2000..12000], D65 neutral is 6500K.\n * @param tint Green/magenta shift in [-100..100] (0 = neutral).\n */\nexport function getWhiteBalanceGains(kelvin: number, tint = 0): ColorRGB {\n const k = Math.max(2000, Math.min(12000, kelvin)) / 100.0;\n\n let r: number;\n let g: number;\n let b: number;\n\n if (k <= 66) {\n r = 255;\n g = 99.4708025861 * Math.log(k) - 161.1195681661;\n b = k <= 19 ? 0 : 138.5177312231 * Math.log(k - 10) - 305.0447927307;\n } else {\n r = 329.698727446 * Math.pow(k - 60, -0.1332047592);\n g = 288.1221695283 * Math.pow(k - 60, -0.0755148492);\n b = 255;\n }\n\n // Normalize D65 (6500K) to [1, 1, 1]\n const d65R = 255;\n const d65G = 250.7;\n const d65B = 255;\n\n let gainR = r / d65R;\n let gainG = g / d65G;\n let gainB = b / d65B;\n\n // Tint adjustment (green vs magenta)\n const tintFactor = tint / 100.0;\n gainG *= 1.0 - tintFactor * 0.15;\n gainR *= 1.0 + tintFactor * 0.05;\n gainB *= 1.0 + tintFactor * 0.05;\n\n return [gainR, gainG, gainB];\n}\n\n// -----------------------------------------------------------------------------\n// GLSL Shaders\n// -----------------------------------------------------------------------------\n\n/**\n * Self-contained GLSL functions for fast Oklab <-> Linear sRGB transforms.\n */\nexport function getOklabGLSL(): string {\n return `\n// Oklab <-> Linear sRGB (Björn Ottosson)\nvec3 flair_linear_to_oklab(vec3 c) {\n float l = pow(0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b, 1.0 / 3.0);\n float m = pow(0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b, 1.0 / 3.0);\n float s = pow(0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b, 1.0 / 3.0);\n return vec3(\n 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,\n 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,\n 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s\n );\n}\n\nvec3 flair_oklab_to_linear(vec3 lab) {\n float l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z;\n float m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z;\n float s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z;\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n return vec3(\n +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s\n );\n}\n`.trim();\n}\n\n/**\n * Self-contained GLSL functions for ASC CDL and Lift/Gamma/Gain grading.\n */\nexport function getColorGradingGLSL(): string {\n return `\n// ASC CDL Grading\nvec3 flair_cdl(vec3 color, vec3 slope, vec3 offset, vec3 power, float sat) {\n vec3 c = pow(max(color * slope + offset, vec3(0.0)), power);\n float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));\n return mix(vec3(luma), c, sat);\n}\n\n// 3-Way Lift / Gamma / Gain Grading\nvec3 flair_lift_gamma_gain(vec3 color, vec3 lift, vec3 gamma, vec3 gain) {\n vec3 lifted = color + lift * (vec3(1.0) - color);\n vec3 gained = max(lifted * gain, vec3(0.0));\n return pow(gained, 1.0 / max(gamma, vec3(0.0001)));\n}\n`.trim();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
SVG_PICTURE,
|
|
3
3
|
svgDuration,
|
|
4
4
|
svgFrame
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YCQ5RU74.js";
|
|
6
6
|
|
|
7
7
|
// src/vector/static.ts
|
|
8
8
|
var DROPPED = /* @__PURE__ */ new Set(["script", "foreignobject", "iframe", "object", "embed"]);
|
|
@@ -143,4 +143,4 @@ export {
|
|
|
143
143
|
svgInlineLayout,
|
|
144
144
|
svgFrameMarkup
|
|
145
145
|
};
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
146
|
+
//# sourceMappingURL=chunk-MZ37DE7K.js.map
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
HOVER_SECONDS,
|
|
12
12
|
MAX_CLICKS,
|
|
13
|
+
MAX_TRAIL,
|
|
13
14
|
PointerTracker,
|
|
14
15
|
SVG_EDGE,
|
|
15
16
|
ShaderCompileError,
|
|
17
|
+
TRAIL_SECONDS,
|
|
16
18
|
TextureCache,
|
|
17
19
|
VERTEX_SOURCE,
|
|
18
20
|
clicksUniform,
|
|
@@ -20,8 +22,9 @@ import {
|
|
|
20
22
|
createContext,
|
|
21
23
|
drawFrame,
|
|
22
24
|
resetGlCaches,
|
|
25
|
+
trailUniform,
|
|
23
26
|
wrapSource
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ARKC7LDW.js";
|
|
25
28
|
import {
|
|
26
29
|
controlValue,
|
|
27
30
|
imageUrls,
|
|
@@ -283,7 +286,8 @@ var ShaderRenderer = class {
|
|
|
283
286
|
frame: this.frame,
|
|
284
287
|
mouse: this.mouse,
|
|
285
288
|
pointer: this.pointer.pointer(this.time),
|
|
286
|
-
clicks: this.pointer.clicksAt(this.time)
|
|
289
|
+
clicks: this.pointer.clicksAt(this.time),
|
|
290
|
+
trail: this.pointer.trailAt(this.time)
|
|
287
291
|
},
|
|
288
292
|
this.textures ?? void 0
|
|
289
293
|
);
|
|
@@ -348,7 +352,7 @@ var ShaderRenderer = class {
|
|
|
348
352
|
};
|
|
349
353
|
onPointerMove = (e) => {
|
|
350
354
|
const [x, y] = this.toPixels(e);
|
|
351
|
-
this.pointer.move(x, y);
|
|
355
|
+
this.pointer.move(x, y, this.time);
|
|
352
356
|
if (this.mouse[2] <= 0 || e.buttons === 0) {
|
|
353
357
|
if (!this.playing) this.render();
|
|
354
358
|
return;
|
|
@@ -357,7 +361,7 @@ var ShaderRenderer = class {
|
|
|
357
361
|
if (!this.playing) this.render();
|
|
358
362
|
};
|
|
359
363
|
onPointerEnter = (e) => {
|
|
360
|
-
this.pointer.move(...this.toPixels(e));
|
|
364
|
+
this.pointer.move(...this.toPixels(e), this.time);
|
|
361
365
|
this.pointer.hover(true, this.time);
|
|
362
366
|
};
|
|
363
367
|
onPointerLeave = () => {
|
|
@@ -692,7 +696,8 @@ var ShaderFrames = class {
|
|
|
692
696
|
frame: options.frame ?? this.rendered,
|
|
693
697
|
mouse: options.mouse ?? [0, 0, 0, 0],
|
|
694
698
|
pointer: options.pointer,
|
|
695
|
-
clicks: options.clicks && clicksUniform(options.clicks, time)
|
|
699
|
+
clicks: options.clicks && clicksUniform(options.clicks, time),
|
|
700
|
+
trail: options.trail && trailUniform(options.trail, time)
|
|
696
701
|
},
|
|
697
702
|
this.textures
|
|
698
703
|
);
|
|
@@ -867,14 +872,16 @@ canvas.addEventListener("pointerdown", (e) => { const [x, y] = px(e); mouse = [x
|
|
|
867
872
|
canvas.addEventListener("pointermove", (e) => { if (e.buttons && mouse[2] > 0) { const [x, y] = px(e); mouse = [x, y, mouse[2], mouse[3]]; } });
|
|
868
873
|
addEventListener("pointerup", () => { if (mouse[2] > 0) mouse = [mouse[0], mouse[1], -mouse[2], -Math.abs(mouse[3])]; });
|
|
869
874
|
let start = performance.now(), last = start, frame = 0;
|
|
870
|
-
// iPointer (where, how hovered, seconds pressed)
|
|
875
|
+
// iPointer (where, how hovered, seconds pressed), iClicks (the latest clicks) and iTrail (where it has just been), newest first.
|
|
871
876
|
const seconds = () => (performance.now() - start) / 1000;
|
|
872
|
-
let at = [0, 0], over = false, hoverFrom = 0, hoverAt = -1e9, pressedAt = -1, releasedAt = -1, clicks = [];
|
|
877
|
+
let at = [0, 0], over = false, hoverFrom = 0, hoverAt = -1e9, pressedAt = -1, releasedAt = -1, clicks = [], trail = [];
|
|
873
878
|
const hovered = (t) => { const k = Math.min(1, Math.max(0, (t - hoverAt) / ${HOVER_SECONDS})); return hoverFrom + ((over ? 1 : 0) - hoverFrom) * k * k * (3 - 2 * k); };
|
|
874
879
|
const hover = (on) => { const t = seconds(); hoverFrom = hovered(t); hoverAt = t; over = on; };
|
|
875
|
-
canvas.addEventListener("pointerenter", (e) => { at = px(e); hover(true); });
|
|
880
|
+
canvas.addEventListener("pointerenter", (e) => { at = px(e); hover(true); toTrail(); });
|
|
876
881
|
canvas.addEventListener("pointerleave", () => hover(false));
|
|
877
|
-
|
|
882
|
+
// A trail point at most every ${TRAIL_SECONDS.toFixed(4)} s; moves in between update the newest.
|
|
883
|
+
const toTrail = () => { const t = seconds(); if (trail.length && t - trail[0][2] < ${TRAIL_SECONDS}) trail[0] = [at[0], at[1], trail[0][2], 1]; else trail = [[at[0], at[1], t, 1], ...trail].slice(0, ${MAX_TRAIL}); };
|
|
884
|
+
canvas.addEventListener("pointermove", (e) => { at = px(e); toTrail(); });
|
|
878
885
|
canvas.addEventListener("pointerdown", (e) => { at = px(e); pressedAt = seconds(); clicks.unshift([at[0], at[1], pressedAt, 1]); clicks = clicks.slice(0, ${MAX_CLICKS}); });
|
|
879
886
|
addEventListener("pointerup", () => { if (pressedAt >= 0) releasedAt = seconds(); pressedAt = -1; });
|
|
880
887
|
function draw(now) {
|
|
@@ -892,6 +899,9 @@ function draw(now) {
|
|
|
892
899
|
const c = new Float32Array(${MAX_CLICKS * 4});
|
|
893
900
|
clicks.forEach((k, i) => c.set(k, i * 4));
|
|
894
901
|
gl.uniform4fv(u("iClicks"), c);
|
|
902
|
+
const p = new Float32Array(${MAX_TRAIL * 4});
|
|
903
|
+
trail.forEach((k, i) => p.set(k, i * 4));
|
|
904
|
+
gl.uniform4fv(u("iTrail"), p);
|
|
895
905
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
896
906
|
last = now;
|
|
897
907
|
requestAnimationFrame(draw);
|
|
@@ -1652,4 +1662,4 @@ export {
|
|
|
1652
1662
|
validateScene,
|
|
1653
1663
|
standaloneSceneHtml
|
|
1654
1664
|
};
|
|
1655
|
-
//# sourceMappingURL=chunk-
|
|
1665
|
+
//# sourceMappingURL=chunk-NGKNI3QX.js.map
|