asciify-engine 4.11.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/field.d.cts CHANGED
@@ -2,7 +2,21 @@
2
2
  type FieldColor = 'ink' | 'source';
3
3
  type FieldFit = 'cover' | 'contain';
4
4
  /** Generated motion used when there is no media source. */
5
- type FieldScene = 'none' | 'flow' | 'orb';
5
+ type FieldScene = 'none' | 'flow' | 'orb' | 'drift';
6
+ /** `glyph` draws characters from the atlas; `dot` draws a halftone: one circle per cell, its area following the tone. */
7
+ type FieldMark = 'glyph' | 'dot';
8
+ /**
9
+ * An area the field keeps quiet, so content above it stays readable. Coordinates are fractions of the field,
10
+ * from the top left; `feather` is the soft edge, as a fraction of the field's shorter side.
11
+ */
12
+ interface FieldClearZone {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ feather: number;
18
+ shape: 'ellipse' | 'rect';
19
+ }
6
20
  /**
7
21
  * How the field answers the pointer.
8
22
  * - `fluid`: an incompressible fluid that bends the image and swirls the characters. Bold, liquid.
@@ -62,6 +76,9 @@ interface GlyphFieldSettings {
62
76
  fontWeight: number;
63
77
  /** Glyph height relative to the cell. */
64
78
  glyphScale: number;
79
+ mark: FieldMark;
80
+ /** Largest dot as a fraction of the cell, for `mark: 'dot'`. */
81
+ dotScale: number;
65
82
  color: FieldColor;
66
83
  ink: string;
67
84
  /** Color the wake lifts characters toward. 'auto' brightens each character's own color, so the wake never turns white. */
@@ -90,6 +107,9 @@ interface GlyphFieldSettings {
90
107
  /** Slow ambient sampling drift that keeps a still image alive. 0 is still. */
91
108
  drift: number;
92
109
  scene: FieldScene;
110
+ /** Size of generated scenes' features; higher is finer. */
111
+ scale: number;
112
+ clear: FieldClearZone | false;
93
113
  /** Speed of generated scenes and drift. */
94
114
  speed: number;
95
115
  /** Which pointer response runs. */
@@ -106,7 +126,8 @@ interface GlyphFieldSettings {
106
126
  }
107
127
  declare const DEFAULT_FIELD_FLUID: FieldFluidOptions;
108
128
  declare const DEFAULT_FIELD_SETTINGS: GlyphFieldSettings;
109
- type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lantern' | 'trail'>> & {
129
+ type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lantern' | 'trail' | 'clear'>> & {
130
+ clear?: Partial<FieldClearZone> | false;
110
131
  trail?: Partial<FieldTrailOptions>;
111
132
  fluid?: Partial<FieldFluidOptions> | boolean;
112
133
  lantern?: Partial<FieldLanternOptions>;
@@ -114,7 +135,15 @@ type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lan
114
135
  };
115
136
  /** Splits a charset into graphemes so emoji, block elements and accented letters each take one cell. */
116
137
  declare function fieldGlyphs(charset: string): string[];
117
- declare function normalizeFieldSettings(input?: GlyphFieldInput, base?: GlyphFieldSettings): GlyphFieldSettings;
138
+ /** Mount options may carry an element-based clear zone; normalizing ignores it, since only a mounted field can measure it. */
139
+ type ClearElementLike = {
140
+ element: unknown;
141
+ } | {
142
+ nodeType: number;
143
+ };
144
+ declare function normalizeFieldSettings(input?: Omit<GlyphFieldInput, 'clear'> & {
145
+ clear?: GlyphFieldInput['clear'] | ClearElementLike;
146
+ }, base?: GlyphFieldSettings): GlyphFieldSettings;
118
147
  /** Maps field UV (0–1, top left) to source UV as `source = uv * scale + offset`. */
119
148
  declare function fieldSourceRect(fieldWidth: number, fieldHeight: number, sourceWidth: number, sourceHeight: number, fit: FieldFit, focus?: {
120
149
  x: number;
@@ -136,9 +165,26 @@ declare function fieldCellLayout(cellSize: number, cellAspect: number, pixelRati
136
165
  declare function fieldLevels(pixels: Uint8ClampedArray | Uint8Array): [number, number];
137
166
  /** The hover that actually runs: fluid needs its settings, and anything is off without a pointer response. */
138
167
  declare const activeFieldHover: (settings: GlyphFieldSettings) => FieldHover;
168
+ /** The clear zone around an element, in field fractions; `padding` is CSS pixels added on every side. */
169
+ declare function fieldClearFromRects(field: {
170
+ left: number;
171
+ top: number;
172
+ width: number;
173
+ height: number;
174
+ }, element: {
175
+ left: number;
176
+ top: number;
177
+ width: number;
178
+ height: number;
179
+ }, padding?: number, feather?: number, shape?: FieldClearZone['shape']): FieldClearZone | false;
139
180
 
140
181
  type FieldSource = string | HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | ImageBitmap;
141
- interface GlyphFieldOptions extends GlyphFieldInput {
182
+ interface GlyphFieldOptions extends Omit<GlyphFieldInput, 'clear'> {
183
+ /**
184
+ * Keep the field quiet around content: an element (followed through layout changes), an element with
185
+ * `padding` (CSS px), `feather` (fraction of the shorter side) and `shape`, a zone in field fractions, or `false`.
186
+ */
187
+ clear?: GlyphFieldInput['clear'] | HTMLElement | FieldClearElement;
142
188
  /** Image or video URL, or a decoded element. Without one, `scene` draws generated motion. */
143
189
  source?: FieldSource | null;
144
190
  /** Where pointer movement is read. Defaults to the window, so overlaid text never blocks the wake. */
@@ -147,6 +193,12 @@ interface GlyphFieldOptions extends GlyphFieldInput {
147
193
  label?: string;
148
194
  onError?: (error: Error) => void;
149
195
  }
196
+ interface FieldClearElement {
197
+ element: HTMLElement;
198
+ padding?: number;
199
+ feather?: number;
200
+ shape?: 'ellipse' | 'rect';
201
+ }
150
202
  interface GlyphField {
151
203
  readonly canvas: HTMLCanvasElement;
152
204
  /** Resolves once the first frame with the current source has been drawn. */
@@ -158,6 +210,16 @@ interface GlyphField {
158
210
  pointer(x: number | null, y?: number): void;
159
211
  /** Draws the current frame now, so the canvas can be read with drawImage in the same task. */
160
212
  redraw(): void;
213
+ /**
214
+ * Sends a ring through the field, for interface events such as a save or a message.
215
+ * `x` and `y` are CSS pixels relative to the canvas, or pass an element to start from its centre. Ignored under reduced motion.
216
+ */
217
+ pulse(options?: {
218
+ x?: number;
219
+ y?: number;
220
+ element?: Element;
221
+ strength?: number;
222
+ }): void;
161
223
  play(): void;
162
224
  pause(): void;
163
225
  resize(): void;
@@ -177,7 +239,8 @@ declare function isGlyphFieldSupported(options?: {
177
239
  * A GPU character field: every cell samples the source once and draws its glyph texel for texel,
178
240
  * while a small advected fluid bends the sampling and lifts characters under the cursor.
179
241
  * Idle fields stop drawing until the pointer, a video frame or generated motion needs a new frame.
242
+ * Each frame shades one fragment per cell, then draws the glyphs per pixel, so the cost of the scene does not grow with pixel density.
180
243
  */
181
244
  declare function mountGlyphField(canvas: HTMLCanvasElement, options?: GlyphFieldOptions): GlyphField;
182
245
 
183
- export { DEFAULT_FIELD_FLUID, DEFAULT_FIELD_LANTERN, DEFAULT_FIELD_SETTINGS, DEFAULT_FIELD_TRAIL, FIELD_HOVERS, type FieldColor, type FieldFit, type FieldFluidOptions, type FieldHover, type FieldLanternOptions, type FieldScene, type FieldSource, type FieldTrailOptions, type GlyphField, type GlyphFieldInput, type GlyphFieldOptions, type GlyphFieldSettings, activeFieldHover, fieldCellLayout, fieldGlyphs, fieldLevels, fieldSourceRect, glyphFieldTier, isGlyphFieldSupported, mountGlyphField, normalizeFieldSettings };
246
+ export { DEFAULT_FIELD_FLUID, DEFAULT_FIELD_LANTERN, DEFAULT_FIELD_SETTINGS, DEFAULT_FIELD_TRAIL, FIELD_HOVERS, type FieldClearElement, type FieldClearZone, type FieldColor, type FieldFit, type FieldFluidOptions, type FieldHover, type FieldLanternOptions, type FieldMark, type FieldScene, type FieldSource, type FieldTrailOptions, type GlyphField, type GlyphFieldInput, type GlyphFieldOptions, type GlyphFieldSettings, activeFieldHover, fieldCellLayout, fieldClearFromRects, fieldGlyphs, fieldLevels, fieldSourceRect, glyphFieldTier, isGlyphFieldSupported, mountGlyphField, normalizeFieldSettings };
package/dist/field.d.ts CHANGED
@@ -2,7 +2,21 @@
2
2
  type FieldColor = 'ink' | 'source';
3
3
  type FieldFit = 'cover' | 'contain';
4
4
  /** Generated motion used when there is no media source. */
5
- type FieldScene = 'none' | 'flow' | 'orb';
5
+ type FieldScene = 'none' | 'flow' | 'orb' | 'drift';
6
+ /** `glyph` draws characters from the atlas; `dot` draws a halftone: one circle per cell, its area following the tone. */
7
+ type FieldMark = 'glyph' | 'dot';
8
+ /**
9
+ * An area the field keeps quiet, so content above it stays readable. Coordinates are fractions of the field,
10
+ * from the top left; `feather` is the soft edge, as a fraction of the field's shorter side.
11
+ */
12
+ interface FieldClearZone {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ feather: number;
18
+ shape: 'ellipse' | 'rect';
19
+ }
6
20
  /**
7
21
  * How the field answers the pointer.
8
22
  * - `fluid`: an incompressible fluid that bends the image and swirls the characters. Bold, liquid.
@@ -62,6 +76,9 @@ interface GlyphFieldSettings {
62
76
  fontWeight: number;
63
77
  /** Glyph height relative to the cell. */
64
78
  glyphScale: number;
79
+ mark: FieldMark;
80
+ /** Largest dot as a fraction of the cell, for `mark: 'dot'`. */
81
+ dotScale: number;
65
82
  color: FieldColor;
66
83
  ink: string;
67
84
  /** Color the wake lifts characters toward. 'auto' brightens each character's own color, so the wake never turns white. */
@@ -90,6 +107,9 @@ interface GlyphFieldSettings {
90
107
  /** Slow ambient sampling drift that keeps a still image alive. 0 is still. */
91
108
  drift: number;
92
109
  scene: FieldScene;
110
+ /** Size of generated scenes' features; higher is finer. */
111
+ scale: number;
112
+ clear: FieldClearZone | false;
93
113
  /** Speed of generated scenes and drift. */
94
114
  speed: number;
95
115
  /** Which pointer response runs. */
@@ -106,7 +126,8 @@ interface GlyphFieldSettings {
106
126
  }
107
127
  declare const DEFAULT_FIELD_FLUID: FieldFluidOptions;
108
128
  declare const DEFAULT_FIELD_SETTINGS: GlyphFieldSettings;
109
- type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lantern' | 'trail'>> & {
129
+ type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lantern' | 'trail' | 'clear'>> & {
130
+ clear?: Partial<FieldClearZone> | false;
110
131
  trail?: Partial<FieldTrailOptions>;
111
132
  fluid?: Partial<FieldFluidOptions> | boolean;
112
133
  lantern?: Partial<FieldLanternOptions>;
@@ -114,7 +135,15 @@ type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lan
114
135
  };
115
136
  /** Splits a charset into graphemes so emoji, block elements and accented letters each take one cell. */
116
137
  declare function fieldGlyphs(charset: string): string[];
117
- declare function normalizeFieldSettings(input?: GlyphFieldInput, base?: GlyphFieldSettings): GlyphFieldSettings;
138
+ /** Mount options may carry an element-based clear zone; normalizing ignores it, since only a mounted field can measure it. */
139
+ type ClearElementLike = {
140
+ element: unknown;
141
+ } | {
142
+ nodeType: number;
143
+ };
144
+ declare function normalizeFieldSettings(input?: Omit<GlyphFieldInput, 'clear'> & {
145
+ clear?: GlyphFieldInput['clear'] | ClearElementLike;
146
+ }, base?: GlyphFieldSettings): GlyphFieldSettings;
118
147
  /** Maps field UV (0–1, top left) to source UV as `source = uv * scale + offset`. */
119
148
  declare function fieldSourceRect(fieldWidth: number, fieldHeight: number, sourceWidth: number, sourceHeight: number, fit: FieldFit, focus?: {
120
149
  x: number;
@@ -136,9 +165,26 @@ declare function fieldCellLayout(cellSize: number, cellAspect: number, pixelRati
136
165
  declare function fieldLevels(pixels: Uint8ClampedArray | Uint8Array): [number, number];
137
166
  /** The hover that actually runs: fluid needs its settings, and anything is off without a pointer response. */
138
167
  declare const activeFieldHover: (settings: GlyphFieldSettings) => FieldHover;
168
+ /** The clear zone around an element, in field fractions; `padding` is CSS pixels added on every side. */
169
+ declare function fieldClearFromRects(field: {
170
+ left: number;
171
+ top: number;
172
+ width: number;
173
+ height: number;
174
+ }, element: {
175
+ left: number;
176
+ top: number;
177
+ width: number;
178
+ height: number;
179
+ }, padding?: number, feather?: number, shape?: FieldClearZone['shape']): FieldClearZone | false;
139
180
 
140
181
  type FieldSource = string | HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | ImageBitmap;
141
- interface GlyphFieldOptions extends GlyphFieldInput {
182
+ interface GlyphFieldOptions extends Omit<GlyphFieldInput, 'clear'> {
183
+ /**
184
+ * Keep the field quiet around content: an element (followed through layout changes), an element with
185
+ * `padding` (CSS px), `feather` (fraction of the shorter side) and `shape`, a zone in field fractions, or `false`.
186
+ */
187
+ clear?: GlyphFieldInput['clear'] | HTMLElement | FieldClearElement;
142
188
  /** Image or video URL, or a decoded element. Without one, `scene` draws generated motion. */
143
189
  source?: FieldSource | null;
144
190
  /** Where pointer movement is read. Defaults to the window, so overlaid text never blocks the wake. */
@@ -147,6 +193,12 @@ interface GlyphFieldOptions extends GlyphFieldInput {
147
193
  label?: string;
148
194
  onError?: (error: Error) => void;
149
195
  }
196
+ interface FieldClearElement {
197
+ element: HTMLElement;
198
+ padding?: number;
199
+ feather?: number;
200
+ shape?: 'ellipse' | 'rect';
201
+ }
150
202
  interface GlyphField {
151
203
  readonly canvas: HTMLCanvasElement;
152
204
  /** Resolves once the first frame with the current source has been drawn. */
@@ -158,6 +210,16 @@ interface GlyphField {
158
210
  pointer(x: number | null, y?: number): void;
159
211
  /** Draws the current frame now, so the canvas can be read with drawImage in the same task. */
160
212
  redraw(): void;
213
+ /**
214
+ * Sends a ring through the field, for interface events such as a save or a message.
215
+ * `x` and `y` are CSS pixels relative to the canvas, or pass an element to start from its centre. Ignored under reduced motion.
216
+ */
217
+ pulse(options?: {
218
+ x?: number;
219
+ y?: number;
220
+ element?: Element;
221
+ strength?: number;
222
+ }): void;
161
223
  play(): void;
162
224
  pause(): void;
163
225
  resize(): void;
@@ -177,7 +239,8 @@ declare function isGlyphFieldSupported(options?: {
177
239
  * A GPU character field: every cell samples the source once and draws its glyph texel for texel,
178
240
  * while a small advected fluid bends the sampling and lifts characters under the cursor.
179
241
  * Idle fields stop drawing until the pointer, a video frame or generated motion needs a new frame.
242
+ * Each frame shades one fragment per cell, then draws the glyphs per pixel, so the cost of the scene does not grow with pixel density.
180
243
  */
181
244
  declare function mountGlyphField(canvas: HTMLCanvasElement, options?: GlyphFieldOptions): GlyphField;
182
245
 
183
- export { DEFAULT_FIELD_FLUID, DEFAULT_FIELD_LANTERN, DEFAULT_FIELD_SETTINGS, DEFAULT_FIELD_TRAIL, FIELD_HOVERS, type FieldColor, type FieldFit, type FieldFluidOptions, type FieldHover, type FieldLanternOptions, type FieldScene, type FieldSource, type FieldTrailOptions, type GlyphField, type GlyphFieldInput, type GlyphFieldOptions, type GlyphFieldSettings, activeFieldHover, fieldCellLayout, fieldGlyphs, fieldLevels, fieldSourceRect, glyphFieldTier, isGlyphFieldSupported, mountGlyphField, normalizeFieldSettings };
246
+ export { DEFAULT_FIELD_FLUID, DEFAULT_FIELD_LANTERN, DEFAULT_FIELD_SETTINGS, DEFAULT_FIELD_TRAIL, FIELD_HOVERS, type FieldClearElement, type FieldClearZone, type FieldColor, type FieldFit, type FieldFluidOptions, type FieldHover, type FieldLanternOptions, type FieldMark, type FieldScene, type FieldSource, type FieldTrailOptions, type GlyphField, type GlyphFieldInput, type GlyphFieldOptions, type GlyphFieldSettings, activeFieldHover, fieldCellLayout, fieldClearFromRects, fieldGlyphs, fieldLevels, fieldSourceRect, glyphFieldTier, isGlyphFieldSupported, mountGlyphField, normalizeFieldSettings };
package/dist/field.js CHANGED
@@ -1 +1 @@
1
- var e=e=>e?"\nconst float VSCALE=6.;\nvec4 decodeFluid(vec4 t){return vec4((t.xy*2.-1.)*VSCALE,t.z,t.w);}\nvec4 encodeFluid(vec4 v){return vec4(clamp(v.xy/VSCALE*.5+.5,0.,1.),clamp(v.z,0.,1.),1.);}":"\nvec4 decodeFluid(vec4 t){return t;}\nvec4 encodeFluid(vec4 v){return vec4(v.xyz,1.);}",t="#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform vec2 uTexel;\nout vec4 outColor;\nvec2 here(){return gl_FragCoord.xy*uTexel;}\n",n=t+"\nuniform sampler2D uVelocity;\nuniform vec2 uFrom, uTo, uForce;\nuniform float uAspect, uRadius;\nfloat segment(vec2 p,vec2 a,vec2 b){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/max(dot(ba,ba),1e-8),0.,1.);return length(pa-ba*h);}\nvoid main(){\n vec2 uv=here(),scale=vec2(uAspect,1.);\n float d=segment(uv*scale,uFrom*scale,uTo*scale);\n outColor=vec4(texture(uVelocity,uv).xy+uForce*exp(-d*d/(uRadius*uRadius)),0.,1.);\n}",r=t+"\nuniform sampler2D uVelocity;\nvoid main(){\n vec2 uv=here();\n float l=texture(uVelocity,uv-vec2(uTexel.x,0.)).y,r=texture(uVelocity,uv+vec2(uTexel.x,0.)).y;\n float t=texture(uVelocity,uv+vec2(0.,uTexel.y)).x,b=texture(uVelocity,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4(.5*(r-l-t+b),0.,0.,1.);\n}",i=t+"\nuniform sampler2D uVelocity, uCurl;\nuniform float uStrength, uDt;\nvoid main(){\n vec2 uv=here();\n float l=texture(uCurl,uv-vec2(uTexel.x,0.)).x,r=texture(uCurl,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uCurl,uv+vec2(0.,uTexel.y)).x,b=texture(uCurl,uv-vec2(0.,uTexel.y)).x,c=texture(uCurl,uv).x;\n vec2 force=.5*vec2(abs(t)-abs(b),abs(r)-abs(l));\n force=force/(length(force)+1e-4)*uStrength*c;\n force.y=-force.y;\n outColor=vec4(clamp(texture(uVelocity,uv).xy+force*uDt,-1000.,1000.),0.,1.);\n}",o=t+"\nuniform sampler2D uVelocity;\nvoid main(){\n vec2 uv=here(),c=texture(uVelocity,uv).xy;\n float l=texture(uVelocity,uv-vec2(uTexel.x,0.)).x,r=texture(uVelocity,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uVelocity,uv+vec2(0.,uTexel.y)).y,b=texture(uVelocity,uv-vec2(0.,uTexel.y)).y;\n if(uv.x-uTexel.x<0.)l=-c.x;\n if(uv.x+uTexel.x>1.)r=-c.x;\n if(uv.y+uTexel.y>1.)t=-c.y;\n if(uv.y-uTexel.y<0.)b=-c.y;\n outColor=vec4(.5*(r-l+t-b),0.,0.,1.);\n}",u=t+"\nuniform sampler2D uSource;\nuniform float uAmount;\nvoid main(){outColor=vec4(texture(uSource,here()).x*uAmount,0.,0.,1.);}",a=t+"\nuniform sampler2D uPressure, uDivergence;\nvoid main(){\n vec2 uv=here();\n float l=texture(uPressure,uv-vec2(uTexel.x,0.)).x,r=texture(uPressure,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uPressure,uv+vec2(0.,uTexel.y)).x,b=texture(uPressure,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4((l+r+b+t-texture(uDivergence,uv).x)*.25,0.,0.,1.);\n}",l=t+"\nuniform sampler2D uPressure, uVelocity;\nvoid main(){\n vec2 uv=here();\n float l=texture(uPressure,uv-vec2(uTexel.x,0.)).x,r=texture(uPressure,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uPressure,uv+vec2(0.,uTexel.y)).x,b=texture(uPressure,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4(texture(uVelocity,uv).xy-vec2(r-l,t-b),0.,1.);\n}",s=t+"\nuniform sampler2D uVelocity;\nuniform float uDt, uDissipation;\nvoid main(){\n vec2 uv=here();\n vec2 back=uv-uDt*texture(uVelocity,uv).xy*uTexel;\n outColor=vec4(texture(uVelocity,back).xy/(1.+uDissipation*uDt),0.,1.);\n}";var c=class{constructor(e,t){this.previous=null,this.peak=0,this.width=e,this.height=t;const n=e*t;this.pixels=new Uint8Array(4*n),this.signal=new Float32Array(n),this.offsetX=new Float32Array(n),this.offsetY=new Float32Array(n),this.driftX=new Float32Array(n),this.driftY=new Float32Array(n),this.u=new Float32Array(n),this.v=new Float32Array(n),this.ink=new Float32Array(n),this.a=new Float32Array(n),this.b=new Float32Array(n),this.c=new Float32Array(n),this.pressure=new Float32Array(n),this.pressureNext=new Float32Array(n),this.divergence=new Float32Array(n),this.curl=new Float32Array(n)}get active(){return this.peak>2e-4}clear(){for(const e of[this.u,this.v,this.ink,this.a,this.b,this.c,this.pressure,this.pressureNext,this.divergence,this.curl,this.signal,this.pixels,this.offsetX,this.offsetY,this.driftX,this.driftY])e.fill(0);this.previous=null,this.peak=0}leave(){this.previous=null}move(e,t,n,r=performance.now()){if(!Number.isFinite(e+t+n+r))return;e=Math.max(0,Math.min(1,e)),t=Math.max(0,Math.min(1,t)),n=Math.max(.1,Math.min(1,n));const i=this.previous;if(this.previous={x:e,y:t,time:r},!i)return;const o=(e-i.x)*(this.width-1),u=(t-i.y)*(this.height-1),a=Math.hypot(o,u);if(a<.001)return;const l=Math.min(this.width,this.height),s=r>i.time?Math.max(1/240,(r-i.time)/1e3):1/60,c=Math.min(12,a/l/s),h=1-Math.exp(-c/2.2),f=l*(.022+.075*n)*(.65+.95*h),d=Math.min(128,Math.max(1,Math.ceil(a/(.4*f)))),m=a/f*(.32+.22*h)/d,v=o/a,g=u/a,p=-g,x=v,E=a*(2+18*h)/d;for(let n=0;n<d;n++){const r=(n+.5)/d,o=(i.x+(e-i.x)*r)*(this.width-1),u=(i.y+(t-i.y)*r)*(this.height-1),a=Math.max(1,Math.floor(o-3*f)),l=Math.min(this.width-2,Math.ceil(o+3*f)),s=Math.max(1,Math.floor(u-3*f)),c=Math.min(this.height-2,Math.ceil(u+3*f));for(let e=s;e<=c;e++)for(let t=a;t<=l;t++){const n=((t-o)*v+(e-u)*g)/f,r=((t-o)*p+(e-u)*x)/f,i=Math.exp(-n*n*.65-r*r*1.7),a=e*this.width+t;this.ink[a]=1-(1-this.ink[a])*Math.exp(-i*m);const l=1-r*r*.55,s=n*r*.7;this.u[a]=Math.max(-80,Math.min(80,this.u[a]+(v*l+p*s)*i*E)),this.v[a]=Math.max(-80,Math.min(80,this.v[a]+(g*l+x*s)*i*E))}}this.peak=1}read(e,t,n){const r=0|(t=Math.max(.5,Math.min(this.width-1.5,t))),i=0|(n=Math.max(.5,Math.min(this.height-1.5,n))),o=t-r,u=n-i,a=i*this.width+r;return(e[a]*(1-o)+e[a+1]*o)*(1-u)+(e[a+this.width]*(1-o)+e[a+this.width+1]*o)*u}sample(e,t){return this.read(this.signal,e*(this.width-1),t*(this.height-1))}displacement(e,t,n){n[0]=this.read(this.offsetX,e*(this.width-1),t*(this.height-1)),n[1]=this.read(this.offsetY,e*(this.width-1),t*(this.height-1))}step(e){if(!this.active||!Number.isFinite(e))return;let t=Math.max(0,Math.min(.05,e));const n=this.width,r=this.height;for(;t>1e-7;){const e=Math.min(1/60,t);t-=e;const i=Math.exp(-1.9*e),o=Math.exp(-3.2*e);for(let t=1;t<r-1;t++)for(let u=1;u<n-1;u++){const a=t*n+u,l=Math.max(.5,Math.min(n-1.5,u-this.u[a]*e)),s=Math.max(.5,Math.min(r-1.5,t-this.v[a]*e)),c=0|l,h=0|s,f=l-c,d=s-h,m=h*n+c,v=(1-f)*(1-d),g=f*(1-d),p=(1-f)*d,x=f*d;this.a[a]=(this.u[m]*v+this.u[m+1]*g+this.u[m+n]*p+this.u[m+n+1]*x)*i,this.b[a]=(this.v[m]*v+this.v[m+1]*g+this.v[m+n]*p+this.v[m+n+1]*x)*i,this.c[a]=(this.ink[m]*v+this.ink[m+1]*g+this.ink[m+n]*p+this.ink[m+n+1]*x)*o}[this.u,this.a]=[this.a,this.u],[this.v,this.b]=[this.b,this.v],[this.ink,this.c]=[this.c,this.ink];for(let e=1;e<r-1;e++)for(let t=1;t<n-1;t++){const r=e*n+t;this.curl[r]=.5*(this.v[r+1]-this.v[r-1]-this.u[r+n]+this.u[r-n])}for(let t=1;t<r-1;t++)for(let r=1;r<n-1;r++){const i=t*n+r,o=Math.abs(this.curl[i+1])-Math.abs(this.curl[i-1]),u=Math.abs(this.curl[i+n])-Math.abs(this.curl[i-n]),a=Math.hypot(o,u)+1e-4,l=this.curl[i]*e*10,s=Math.hypot(this.u[i],this.v[i]);this.u[i]+=u/a*l,this.v[i]-=o/a*l;const c=Math.min(1,s/(Math.hypot(this.u[i],this.v[i])+1e-6));this.u[i]*=c,this.v[i]*=c,this.divergence[i]=-.5*(this.u[i+1]-this.u[i-1]+this.v[i+n]-this.v[i-n])}this.pressure.fill(0);for(let e=0;e<4;e++){for(let e=1;e<r-1;e++)for(let t=1;t<n-1;t++){const r=e*n+t;this.pressureNext[r]=.25*(this.divergence[r]+this.pressure[r-1]+this.pressure[r+1]+this.pressure[r-n]+this.pressure[r+n])}[this.pressure,this.pressureNext]=[this.pressureNext,this.pressure]}let u=0;for(let t=1;t<r-1;t++)for(let r=1;r<n-1;r++){const i=t*n+r;this.u[i]=Math.max(-80,Math.min(80,this.u[i]-.5*(this.pressure[i+1]-this.pressure[i-1]))),this.v[i]=Math.max(-80,Math.min(80,this.v[i]-.5*(this.pressure[i+n]-this.pressure[i-n]))),this.driftX[i]+=(3.6*this.u[i]-58*this.offsetX[i]-12*this.driftX[i])*e,this.driftY[i]+=(3.6*this.v[i]-58*this.offsetY[i]-12*this.driftY[i])*e,this.offsetX[i]=Math.max(-2,Math.min(2,this.offsetX[i]+this.driftX[i]*e)),this.offsetY[i]=Math.max(-2,Math.min(2,this.offsetY[i]+this.driftY[i]*e)),this.signal[i]=1-Math.exp(.06*-Math.hypot(this.u[i],this.v[i])-.16*this.ink[i]),u=Math.max(u,this.signal[i])}this.peak=u}if(this.active)for(let e=0;e<this.ink.length;e++){const t=Math.round(65535*Math.min(1,this.signal[e]));this.pixels[4*e]=t>>>8,this.pixels[4*e+1]=255&t,this.pixels[4*e+2]=Math.round(128+63.5*this.offsetX[e]),this.pixels[4*e+3]=Math.round(128+63.5*this.offsetY[e])}else this.clear()}},h=[{value:"fluid",label:"Fluid",description:"An ink-in-water wake that bends the image and swirls the characters."},{value:"lantern",label:"Lantern",description:"A soft light follows the pointer and lifts the characters it passes, without distortion."},{value:"trail",label:"Trail",description:"The Asciify Trail: in the wake, sparse characters fill in and dense ones open up, then settle. No distortion."},{value:"none",label:"Off",description:"The field stays still under the pointer."}],f={radius:.13,strength:.5,trail:.45,follow:12},d={strength:.55,radius:.45},m={radius:.075,force:1,dissipation:1.6,refraction:.03,glow:.55,resolution:128,curl:24},v={charset:" .,:-=+*#%@",cellSize:12,cellAspect:1,font:'ui-monospace, "SF Mono", Menlo, Consolas, monospace',fontWeight:500,glyphScale:.8,color:"ink",ink:"#f3ba32",highlight:"auto",background:"transparent",brightness:0,contrast:1,gamma:1,invert:!1,levels:"auto",saturation:1.1,shade:.5,backdrop:0,fit:"cover",focus:{x:.5,y:.5},vignette:0,drift:0,scene:"flow",speed:1,hover:"fluid",fluid:m,lantern:f,trail:d,maxPixelRatio:2,respectReducedMotion:!0},g=(e,t,n,r)=>"number"==typeof e&&Number.isFinite(e)?Math.min(n,Math.max(t,e)):r,p=(e,t)=>"string"==typeof e&&/^#([\da-f]{3}|[\da-f]{6})$/i.test(e.trim())?e.trim().toLowerCase():t;function x(e){const t=Intl.Segmenter;return(t?Array.from(new t(void 0,{granularity:"grapheme"}).segment(e),e=>e.segment):Array.from(e)).filter(e=>"\n"!==e&&"\r"!==e&&"\t"!==e).slice(0,256)}function E(e={},t=v){const n="string"==typeof e.charset&&x(e.charset).length>=2?e.charset:t.charset,r=t.fluid||m,i=void 0===e.fluid?t.fluid:e.fluid,o=!1!==i&&(()=>{const e=!0!==i&&i?{...r,...i}:r;return{radius:g(e.radius,.005,.5,m.radius),force:g(e.force,0,8,m.force),dissipation:g(e.dissipation,.05,20,m.dissipation),refraction:g(e.refraction,0,.5,m.refraction),glow:g(e.glow,0,2,m.glow),resolution:Math.round(g(e.resolution,16,512,m.resolution)),curl:g(e.curl,0,80,m.curl)}})(),u="none"===e.scene||"flow"===e.scene||"orb"===e.scene?e.scene:t.scene;return{charset:n,cellSize:g(e.cellSize,3,96,t.cellSize),cellAspect:g(e.cellAspect,.3,2,t.cellAspect),font:"string"==typeof e.font&&e.font.trim()?e.font:t.font,fontWeight:Math.round(g(e.fontWeight,100,900,t.fontWeight)),glyphScale:g(e.glyphScale,.3,1.6,t.glyphScale),color:"source"===e.color||"ink"===e.color?e.color:t.color,ink:p(e.ink,t.ink),highlight:"auto"===e.highlight?"auto":p(e.highlight,t.highlight),background:"transparent"===e.background?"transparent":p(e.background,t.background),brightness:g(e.brightness,-1,1,t.brightness),contrast:g(e.contrast,0,4,t.contrast),gamma:g(e.gamma,.2,5,t.gamma),invert:"boolean"==typeof e.invert?e.invert:t.invert,levels:"none"===e.levels||"auto"===e.levels?e.levels:t.levels,saturation:g(e.saturation,0,3,t.saturation),shade:g(e.shade,0,1,t.shade),backdrop:g(e.backdrop,0,1,t.backdrop),fit:"contain"===e.fit||"cover"===e.fit?e.fit:t.fit,focus:{x:g(e.focus?.x,0,1,t.focus.x),y:g(e.focus?.y,0,1,t.focus.y)},vignette:g(e.vignette,0,1,t.vignette),drift:g(e.drift,0,2,t.drift),scene:u,speed:g(e.speed,0,5,t.speed),hover:"fluid"===e.hover||"lantern"===e.hover||"trail"===e.hover||"none"===e.hover?e.hover:t.hover,fluid:o,lantern:{radius:g(e.lantern?.radius,.02,.6,t.lantern.radius),strength:g(e.lantern?.strength,0,1.5,t.lantern.strength),trail:g(e.lantern?.trail,0,3,t.lantern.trail),follow:g(e.lantern?.follow,1,60,t.lantern.follow)},trail:{strength:g(e.trail?.strength,0,1,t.trail.strength),radius:g(e.trail?.radius,.1,1,t.trail.radius)},maxPixelRatio:g(e.maxPixelRatio,.5,4,t.maxPixelRatio),respectReducedMotion:"boolean"==typeof e.respectReducedMotion?e.respectReducedMotion:t.respectReducedMotion}}function T(e,t,n,r,i,o={x:.5,y:.5}){if(!(e>0&&t>0&&n>0&&r>0))return{scale:[1,1],offset:[0,0]};const u=e/t,a=n/r,l=a>u,s="cover"===i?l?[u/a,1]:[1,a/u]:l?[1,a/u]:[u/a,1];return{scale:s,offset:"cover"===i?[(1-s[0])*o.x,(1-s[1])*o.y]:[.5*(1-s[0]),.5*(1-s[1])]}}function y(e,t,n,r){const i=Math.max(2,Math.round(e*n)),o=Math.max(2,Math.round(i*t)),u=Math.min(16,Math.max(1,r)),a=Math.ceil(r/u);return{width:o,height:i,columns:u,rows:a,atlasWidth:u*o,atlasHeight:a*i}}function b(e){const t=e.replace("#",""),n=3===t.length?t.split("").map(e=>e+e).join(""):t,r=Number.parseInt(n,16);return Number.isFinite(r)&&6===n.length?[(r>>16&255)/255,(r>>8&255)/255,(255&r)/255]:[0,0,0]}function M(e){const t=new Uint32Array(256);let n=0;for(let r=0;r+3<e.length;r+=4)e[r+3]<8||(t[Math.round(.2126*e[r]+.7152*e[r+1]+.0722*e[r+2])]++,n++);if(!n)return[0,1];const r=e=>{let r=0;for(let i=0;i<256;i++)if(r+=t[i],r>=n*e)return i/255;return 1},i=r(.02),o=r(.98);return o-i<.08?[0,1]:[i,o]}var w,R=e=>"fluid"!==e.hover||e.fluid?e.hover:"none",A=/\.(mp4|webm|mov|m4v|ogv)(\?|#|$)/i,F=/swiftshader|llvmpipe|softpipe|software|basic render/i;function L(){if(w)return w;if("undefined"==typeof document||void 0===globalThis.WebGL2RenderingContext)return w="none";try{const e=document.createElement("canvas").getContext("webgl2");if(!e)return w="none";const t=e.getExtension("WEBGL_debug_renderer_info"),n=String(e.getParameter(t?t.UNMASKED_RENDERER_WEBGL:e.RENDERER)??"");return e.getExtension("WEBGL_lose_context")?.loseContext(),w=F.test(n)?"software":"gpu"}catch{return w="none"}}function S(e={}){const t=L();return"gpu"===t||"software"===t&&!!e.allowSoftware}function D(t,h={}){let f=E(h);const d=h.onError,m=t.getContext("webgl2",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,powerPreference:"high-performance"});if(!m)throw new Error("WebGL2 is not available for the glyph field.");const v=m;t.setAttribute("role","img"),t.setAttribute("aria-label",h.label??"ASCII artwork"),t.dataset.renderer="glyph-field";let g,p,w,F,L=!1,S={},D={},_=[],k=[],C=null,P=!1,U=null;const B=[];let I=0,V=0,X=1,G=0,N=y(f.cellSize,f.cellAspect,1,2),H=2,z=0,O=0,W=0,Y=null,q=null,$=0,j=0,K=0,J=!0,Q=0,Z=[0,1],ee=-1/0,te=0,ne=0,re=0,ie=!0,oe=!1,ue=!1,ae=!1,le=!0,se=null,ce=null,he=1/60,fe=0,de=0;const me={target:null,at:null,strength:0,trail:[]},ve=new Float32Array(36);let ge=null,pe=null,xe=!1;const Ee=()=>{if(!I||!V)return null;const e=I/V,t=Math.round(e>=1?128:128*e),n=Math.round(e>=1?128/e:128);return ge&&ge.width===t&&ge.height===n||(ge=new c(t,n),pe||(pe=Le(v.LINEAR)),v.bindTexture(v.TEXTURE_2D,pe),v.texImage2D(v.TEXTURE_2D,0,v.RGBA8,t,n,0,v.RGBA,v.UNSIGNED_BYTE,ge.pixels),xe=!0),ge};let Te=0;const ye="function"==typeof matchMedia?matchMedia("(prefers-reduced-motion: reduce)"):null,be=()=>f.respectReducedMotion&&!!ye?.matches;let Me;const we=new Promise(e=>{Me=e});let Re=!0;const Ae=(e,t)=>{const n=v.createShader(e);if(B.push(n),v.shaderSource(n,t),v.compileShader(n),!v.getShaderParameter(n,v.COMPILE_STATUS))throw new Error(v.getShaderInfoLog(n)||"Glyph field shader failed to compile.");return n},Fe=(e,t)=>{const n=v.createProgram();if(v.attachShader(n,Ae(v.VERTEX_SHADER,"#version 300 es\nvoid main(){vec2 p=vec2((gl_VertexID<<1)&2,gl_VertexID&2);gl_Position=vec4(p*2.-1.,0.,1.);}")),v.attachShader(n,Ae(v.FRAGMENT_SHADER,e)),v.linkProgram(n),!v.getProgramParameter(n,v.LINK_STATUS))throw new Error(v.getProgramInfoLog(n)||"Glyph field program failed to link.");return{program:n,at:Object.fromEntries(t.map(e=>[e,v.getUniformLocation(n,e)]))}},Le=e=>{const t=v.createTexture();return v.bindTexture(v.TEXTURE_2D,t),v.texParameteri(v.TEXTURE_2D,v.TEXTURE_MIN_FILTER,e),v.texParameteri(v.TEXTURE_2D,v.TEXTURE_MAG_FILTER,e===v.LINEAR_MIPMAP_LINEAR?v.LINEAR:e),v.texParameteri(v.TEXTURE_2D,v.TEXTURE_WRAP_S,v.CLAMP_TO_EDGE),v.texParameteri(v.TEXTURE_2D,v.TEXTURE_WRAP_T,v.CLAMP_TO_EDGE),t},Se=()=>{C?.release(),P=!1,_.forEach(e=>v.deleteTexture(e)),k.forEach(e=>v.deleteFramebuffer(e)),_=[],k=[]},De=()=>{if(Se(),!f.fluid||!I||!V)return;const e=f.fluid.resolution,t=I/V;if(z=t>=1?e:Math.max(8,Math.round(e*t)),O=t>=1?Math.max(8,Math.round(e/t)):e,P=!!C&&C.resize(z,O),P)return void(W=0);const n=()=>{Se();for(let e=0;e<2;e++){const e=Le(v.LINEAR);v.texImage2D(v.TEXTURE_2D,0,L?v.RGBA8:v.RGBA16F,z,O,0,v.RGBA,L?v.UNSIGNED_BYTE:v.HALF_FLOAT,null);const t=v.createFramebuffer();v.bindFramebuffer(v.FRAMEBUFFER,t),v.framebufferTexture2D(v.FRAMEBUFFER,v.COLOR_ATTACHMENT0,v.TEXTURE_2D,e,0),_.push(e),k.push(t)}return v.checkFramebufferStatus(v.FRAMEBUFFER)===v.FRAMEBUFFER_COMPLETE};if(!n()){if(L)throw new Error("The glyph field could not allocate its fluid buffers.");L=!0,_e(),n()}for(const e of k)v.bindFramebuffer(v.FRAMEBUFFER,e),v.viewport(0,0,z,O),v.clearColor(L?.5:0,L?.5:0,0,1),v.clear(v.COLOR_BUFFER_BIT);v.bindFramebuffer(v.FRAMEBUFFER,null),W=0};function _e(){g&&v.deleteProgram(g),p&&v.deleteProgram(p);const t=Fe((t=>`#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uSource, uAtlas, uFluid;\nuniform vec2 uResolution, uCell;\nuniform float uCount, uColumns;\nuniform vec4 uSourceMap;\nuniform int uScene;\nuniform vec3 uInk, uHighlight;\nuniform vec4 uBackground;\nuniform float uBrightness, uContrast, uGamma, uInvert, uVignette, uSourceColor;\nuniform float uRefraction, uGlow, uTime, uDrift, uFluidOn, uLod, uSaturation, uBackdrop, uShade, uHighlightAuto, uFluidMode;\nuniform vec2 uFluidTexel;\nuniform int uHoverMode;\nuniform vec3 uLight;\nuniform vec3 uTrail[12];\nuniform float uLightRadius, uLightGain, uClock, uFlicker, uTrailAmount;\nuniform sampler2D uTrailMap;\nuniform vec2 uLevels;\nout vec4 outColor;\n${e(t)}\nfloat hash(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+45.32);return fract(p.x*p.y);}\nfloat noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.-2.*f);\n return mix(mix(hash(i),hash(i+vec2(1,0)),u.x),mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),u.x),u.y);}\nfloat fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<5;i++){v+=a*noise(p);p=p*2.03+vec2(1.7,9.2);a*=.5;}return v;}\nvec3 generated(vec2 uv,float aspect){\n vec2 p=(uv-.5)*vec2(aspect,1.);\n if(uScene==1){\n float t=uTime*.08;\n vec2 q=vec2(fbm(p*1.6+t),fbm(p*1.6-t+3.1));\n return vec3(smoothstep(.28,.86,fbm(p*1.8+q*1.6+vec2(t*.6,-t*.4))));\n }\n if(uScene==2){\n float r=.36,d=length(p),a=uTime*.25;\n vec3 l=normalize(vec3(cos(a)*.8,.55,sin(a)*.6+.6));\n if(d<r){\n vec3 n=normalize(vec3(p.x,-p.y,sqrt(r*r-d*d)));\n float diffuse=max(dot(n,l),0.),rim=pow(1.-n.z,2.5);\n float lon=atan(n.x,n.z)+uTime*.35,lat=asin(clamp(n.y,-1.,1.));\n float bands=.5+.25*sin(lat*14.)+.25*sin(lon*6.);\n return vec3(clamp(diffuse*.85+rim*.5+bands*.12,0.,1.));\n }\n return vec3(exp(-pow((d-r)*9.,2.))*.35);\n }\n return vec3(0.);\n}\nvoid main(){\n vec2 frag=vec2(gl_FragCoord.x,uResolution.y-gl_FragCoord.y);\n vec2 cell=floor(frag/uCell);\n vec2 uv=(cell+.5)*uCell/uResolution;\n float aspect=uResolution.x/uResolution.y;\n vec4 fluid=vec4(0.);\n // lift: signed tone change. The solver's flow brightens one side of each eddy and opens the other into gaps.\n float lift=0.,wake=0.,bend=1.,swirl=0.,speed=0.;\n if(uFluidOn>.5){\n vec4 raw=texture(uFluid,vec2(uv.x,1.-uv.y));\n if(uFluidMode>.5){\n vec2 v=raw.xy*uFluidTexel*vec2(aspect,1.);\n fluid=vec4(v,0.,1.);\n swirl=clamp(dot(v,vec2(.4,.65))*uGlow*.32,-.65,.65);\n speed=clamp(length(v)*uGlow*.055,0.,.38);\n bend=.4;\n } else {\n fluid=decodeFluid(raw);\n wake=clamp(fluid.z*uGlow,0.,1.);\n lift=wake*.55;\n }\n }\n vec2 drift=uDrift*.006*vec2(sin(uv.y*5.3+uTime*.6),cos(uv.x*4.1-uTime*.5));\n vec2 sampleAt=uv-vec2(fluid.x/aspect,-fluid.y)*uRefraction*bend+drift;\n vec3 rgb,glow=vec3(0.);\n if(uScene==0){\n vec2 s=sampleAt*uSourceMap.xy+uSourceMap.zw;\n bool inside=!(s.x<0.||s.y<0.||s.x>1.||s.y>1.);\n rgb=inside?textureLod(uSource,s,uLod).rgb:vec3(0.);\n if(uBackdrop>0.){\n vec2 b=(uv-vec2(fluid.x/aspect,-fluid.y)*uRefraction*bend*.5)*uSourceMap.xy+uSourceMap.zw;\n glow=(b.x<0.||b.y<0.||b.x>1.||b.y>1.)?vec3(0.):textureLod(uSource,b,uLod+3.).rgb*uBackdrop;\n }\n } else rgb=generated(sampleAt,aspect);\n float tone=dot(rgb,vec3(.2126,.7152,.0722));\n tone=clamp((tone-uLevels.x)/max(uLevels.y-uLevels.x,.01),0.,1.);\n tone=clamp((tone-.5)*uContrast+.5+uBrightness,0.,1.);\n tone=pow(tone,1./uGamma);\n if(uInvert>.5)tone=1.-tone;\n float flick=0.,trailFold=0.;\n if(uHoverMode==2){\n // Lantern: a soft light and its fading tail lift the characters they pass. Nothing moves the image.\n vec2 ap=vec2(aspect,1.),d=(uv-uLight.xy)*ap;\n float r2=uLightRadius*uLightRadius,glowL=uLight.z*exp(-dot(d,d)/r2);\n for(int i=0;i<12;i++){vec2 e=(uv-uTrail[i].xy)*ap;glowL+=uTrail[i].z*exp(-dot(e,e)/(r2*.55));}\n glowL=min(glowL,1.);\n float grain=fract(sin(dot(cell,vec2(12.9898,78.233)))*43758.5453);\n lift=glowL*uLightGain*(.7+.6*grain);\n wake=glowL*.4;\n // A few letters at the light's rim step up the ramp and back, as if being retyped.\n flick=uFlicker*step(.9,fract(grain*13.7+floor(uClock*7.+grain*5.)*.37))*glowL*(1.-glowL)*4.;\n }\n if(uHoverMode==3){\n // Trail: the studio's ink field, as a 16-bit density. Sparse marks fill in, dense marks open up.\n vec4 ink=texture(uTrailMap,uv);\n float density=(ink.r*65280.+ink.g*255.)/65535.*uTrailAmount;\n float t=clamp(density/.38,0.,1.);\n trailFold=t*t*(3.-2.*t);\n }\n if(uFluidMode>.5&&uHoverMode==1){\n // Dark ground: any motion lights the characters. Lit ground: the flow lights one side of each eddy and opens the other.\n // A little cell-to-cell grain keeps the lit area a shimmer of letters rather than a solid block.\n float grain=fract(sin(dot(cell,vec2(12.9898,78.233)))*43758.5453);\n lift=mix(abs(swirl)*.5+speed,swirl+speed*.35,smoothstep(.12,.55,tone))*(.65+.7*grain);\n wake=clamp(lift*1.6,0.,1.);\n }\n tone=clamp(tone+lift,0.,1.);\n tone+=(1.-2.*tone)*trailFold;\n tone*=1.-uVignette*smoothstep(.35,1.05,length((uv-.5)*vec2(min(aspect,1.8),1.)*2.));\n float index=min(uCount-1.,floor(tone*(uCount-1.)+.5)+(flick>.5&&tone>.02?1.:0.));\n vec2 local=frag-cell*uCell;\n ivec2 tile=ivec2(int(mod(index,uColumns)),int(floor(index/uColumns)));\n float coverage=texelFetch(uAtlas,tile*ivec2(uCell)+ivec2(local),0).a;\n vec3 hue=rgb/max(max(rgb.r,rgb.g),max(rgb.b,.03));\n hue=mix(vec3(dot(hue,vec3(.2126,.7152,.0722))),hue,uSaturation);\n vec3 ink=mix(uInk*mix(1.,.3+.7*tone,uShade),clamp(hue*(.38+.62*tone),0.,1.),uSourceColor);\n vec3 lifted=uHighlightAuto>.5?ink/max(max(ink.r,ink.g),max(ink.b,.06)):uHighlight;\n ink=mix(ink,lifted,wake*.7);\n float underAlpha=max(uBackground.a,min(1.,max(glow.r,max(glow.g,glow.b))));\n vec3 under=min(uBackground.rgb*uBackground.a+glow,vec3(underAlpha));\n outColor=vec4(ink*coverage+under*(1.-coverage),coverage+underAlpha*(1.-coverage));\n}`)(L),["uSource","uAtlas","uFluid","uResolution","uCell","uCount","uColumns","uSourceMap","uScene","uInk","uHighlight","uBackground","uBrightness","uContrast","uGamma","uInvert","uVignette","uSourceColor","uRefraction","uGlow","uTime","uDrift","uFluidOn","uLod","uSaturation","uBackdrop","uLevels","uShade","uHighlightAuto","uFluidMode","uFluidTexel","uHoverMode","uLight","uTrail","uLightRadius","uLightGain","uClock","uFlicker","uTrailMap","uTrailAmount"]),c=Fe((t=>`#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uPrevious;\nuniform vec2 uSize;\nuniform float uAspect, uDt, uDecay, uEnergyDecay, uRadius, uSplat;\nuniform vec2 uFrom, uTo, uForce;\nout vec4 outColor;\n${e(t)}\nfloat segment(vec2 p,vec2 a,vec2 b){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/max(dot(ba,ba),1e-8),0.,1.);return length(pa-ba*h);}\nvoid main(){\n vec2 uv=gl_FragCoord.xy/uSize;\n vec4 here=decodeFluid(texture(uPrevious,uv));\n vec4 state=decodeFluid(texture(uPrevious,uv-uDt*here.xy*vec2(1./uAspect,1.)));\n state.xy*=exp(-uDt*uDecay);\n state.z*=exp(-uDt*uEnergyDecay);\n if(uSplat>.5){\n vec2 scale=vec2(uAspect,1.);\n float d=segment(uv*scale,uFrom*scale,uTo*scale);\n float g=exp(-d*d/(uRadius*uRadius));\n state.xy+=uForce*g;\n state.z=min(1.,state.z+g*min(1.,length(uForce)*.5+.08));\n }\n float speed=length(state.xy);\n if(speed>5.)state.xy*=5./speed;\n outColor=encodeFluid(state);\n}`)(L),["uPrevious","uSize","uAspect","uDt","uDecay","uEnergyDecay","uRadius","uSplat","uFrom","uTo","uForce"]);g=t.program,S=t.at,p=c.program,D=c.at,C?.dispose(),C=L?null:function(e,t){const c={splat:t(n,["uTexel","uVelocity","uFrom","uTo","uForce","uAspect","uRadius"]),curl:t(r,["uTexel","uVelocity"]),vorticity:t(i,["uTexel","uVelocity","uCurl","uStrength","uDt"]),divergence:t(o,["uTexel","uVelocity"]),fade:t(u,["uTexel","uSource","uAmount"]),pressure:t(a,["uTexel","uPressure","uDivergence"]),gradient:t(l,["uTexel","uPressure","uVelocity"]),advect:t(s,["uTexel","uVelocity","uDt","uDissipation"])};let h=0,f=0,d=[],m=[],v=null,g=null;const p=t=>{const n=e.createTexture();e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texImage2D(e.TEXTURE_2D,0,e.RGBA16F,h,f,0,e.RGBA,e.HALF_FLOAT,null);const r=e.createFramebuffer();return e.bindFramebuffer(e.FRAMEBUFFER,r),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,n,0),e.checkFramebufferStatus(e.FRAMEBUFFER)!==e.FRAMEBUFFER_COMPLETE?(e.deleteFramebuffer(r),e.deleteTexture(n),null):(e.viewport(0,0,h,f),e.clearColor(0,0,0,1),e.clear(e.COLOR_BUFFER_BIT),{texture:n,buffer:r})},x=t=>{t&&(e.deleteTexture(t.texture),e.deleteFramebuffer(t.buffer))},E=()=>{[...d,...m,v,g].forEach(x),d=[],m=[],v=g=null},T=(t,n,r,i={})=>{const{program:o,at:u}=c[t];e.useProgram(o),e.bindFramebuffer(e.FRAMEBUFFER,n.buffer),e.uniform2f(u.uTexel,1/h,1/f);let a=0;for(const[t,n]of Object.entries(r))e.activeTexture(e.TEXTURE0+a),e.bindTexture(e.TEXTURE_2D,n),e.uniform1i(u[t],a++);for(const[t,n]of Object.entries(i))Array.isArray(n)?e.uniform2f(u[t],n[0],n[1]):e.uniform1f(u[t],n);e.drawArrays(e.TRIANGLES,0,3)};return{resize(t,n){E(),h=t,f=n;const r=[p(e.LINEAR),p(e.LINEAR),p(e.NEAREST),p(e.NEAREST),p(e.NEAREST),p(e.NEAREST)];return e.bindFramebuffer(e.FRAMEBUFFER,null),r.some(e=>!e)?(r.forEach(x),!1):(d=[r[0],r[1]],m=[r[2],r[3]],v=r[4],g=r[5],!0)},step(t,n){if(!d.length||!v||!g)return;e.viewport(0,0,h,f);const r=e=>{e.reverse()};t&&(T("splat",d[1],{uVelocity:d[0].texture},{uFrom:t.from,uTo:t.to,uForce:t.force,uAspect:n.aspect,uRadius:n.radius}),r(d)),n.curl>0&&(T("curl",g,{uVelocity:d[0].texture}),T("vorticity",d[1],{uVelocity:d[0].texture,uCurl:g.texture},{uStrength:n.curl,uDt:n.dt}),r(d)),T("divergence",v,{uVelocity:d[0].texture}),T("fade",m[1],{uSource:m[0].texture},{uAmount:.8}),r(m);for(let e=0;e<20;e++)T("pressure",m[1],{uPressure:m[0].texture,uDivergence:v.texture}),r(m);T("gradient",d[1],{uPressure:m[0].texture,uVelocity:d[0].texture}),r(d),T("advect",d[1],{uVelocity:d[0].texture},{uDt:n.dt,uDissipation:n.dissipation}),r(d),e.bindFramebuffer(e.FRAMEBUFFER,null)},get velocity(){return d[0]?.texture??null},get size(){return[h,f]},release:E,dispose(){E(),Object.values(c).forEach(({program:t})=>e.deleteProgram(t))}}}(v,Fe)}const ke=()=>`${f.fontWeight} ${Math.max(1,N.height*f.glyphScale)}px ${f.font}`,Ce=()=>{const e=x(f.charset);H=e.length,N=y(f.cellSize,f.cellAspect,X,H),G=X;const t=document.createElement("canvas");t.width=N.atlasWidth,t.height=N.atlasHeight;const n=t.getContext("2d");n.fillStyle="#fff",n.textAlign="center",n.textBaseline="alphabetic",n.font=ke();const r=n.measureText("M"),i=r.actualBoundingBoxAscent||N.height*f.glyphScale*.7,o=r.actualBoundingBoxDescent||0,u=(N.height+i-o)/2;e.forEach((e,t)=>{const r=t%N.columns*N.width+N.width/2,i=Math.floor(t/N.columns)*N.height+u,o=n.measureText(e).width;n.save(),n.beginPath(),n.rect(r-N.width/2,i-u,N.width,N.height),n.clip(),n.translate(r,i),o>.96*N.width&&n.scale(.96*N.width/o,1),n.fillText(e,0,0),n.restore()}),v.bindTexture(v.TEXTURE_2D,w),v.pixelStorei(v.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),v.texImage2D(v.TEXTURE_2D,0,v.RGBA8,v.RGBA,v.UNSIGNED_BYTE,t),t.width=t.height=1,ie=!0},Pe=()=>{const e=document.fonts;if(!e?.load)return;const t=ke();e.load(t,x(f.charset).join("")).then(()=>{oe||ue||ke()!==t||(Ce(),He())},()=>{})},Ue=()=>{if(Z=[0,1],Y&&"none"!==f.levels)try{const e=document.createElement("canvas");e.width=e.height=64;const t=e.getContext("2d",{willReadFrequently:!0});t.drawImage(Y,0,0,64,64),Z=M(t.getImageData(0,0,64,64).data)}catch{Z=[0,1]}},Be=()=>{if(!Y)return;const e=performance.now();Y instanceof HTMLVideoElement&&!(e-ee>1500)||(Ue(),ee=e),v.bindTexture(v.TEXTURE_2D,F),v.pixelStorei(v.UNPACK_FLIP_Y_WEBGL,!1),v.pixelStorei(v.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1);try{v.texImage2D(v.TEXTURE_2D,0,v.RGBA8,v.RGBA,v.UNSIGNED_BYTE,Y),v.generateMipmap(v.TEXTURE_2D)}catch(e){d?.(e instanceof Error?e:new Error(String(e)))}},Ie=()=>{L=!v.getExtension("EXT_color_buffer_float")&&!v.getExtension("EXT_color_buffer_half_float"),_e(),U=v.createVertexArray(),w=Le(v.NEAREST),F=Le(v.LINEAR_MIPMAP_LINEAR),v.bindTexture(v.TEXTURE_2D,F),v.texImage2D(v.TEXTURE_2D,0,v.RGBA8,1,1,0,v.RGBA,v.UNSIGNED_BYTE,new Uint8Array([0,0,0,255])),v.generateMipmap(v.TEXTURE_2D),I=V=0,Ve(),Ce(),Pe(),De(),Y&&Be()};function Ve(){const e=t.getBoundingClientRect(),n=Math.min(globalThis.devicePixelRatio||1,f.maxPixelRatio),r=Math.max(1,Math.round(e.width*n)),i=Math.max(1,Math.round(e.height*n));if(r===I&&i===V&&n===X)return!1;const o=!I||Math.abs(r/i-I/V)>.01;return X=n,I=r,V=i,t.width=I,t.height=V,w&&G!==X&&Ce(),!p||!o&&_.length||De(),ie=!0,!0}const Xe=()=>{const e=f.fluid,t=T(I,V,$||1,j||1,f.fit,f.focus),n=Y&&j?N.height/V*j*t.scale[1]:1,r="transparent"===f.background?[0,0,0,0]:[...b(f.background),1];v.bindFramebuffer(v.FRAMEBUFFER,null),v.viewport(0,0,I,V),v.useProgram(g),v.activeTexture(v.TEXTURE0),v.bindTexture(v.TEXTURE_2D,F),v.uniform1i(S.uSource,0),v.activeTexture(v.TEXTURE1),v.bindTexture(v.TEXTURE_2D,w),v.uniform1i(S.uAtlas,1),v.activeTexture(v.TEXTURE2),v.bindTexture(v.TEXTURE_2D,(P?C?.velocity:_[W])??F),v.uniform1i(S.uFluid,2),v.uniform1f(S.uFluidMode,P?1:0);const i=R(f);v.uniform1i(S.uHoverMode,"fluid"===i?1:"lantern"===i?2:"trail"===i&&xe?3:0),v.activeTexture(v.TEXTURE3),v.bindTexture(v.TEXTURE_2D,pe??F),v.uniform1i(S.uTrailMap,3),v.uniform1f(S.uTrailAmount,f.trail.strength),v.uniform3f(S.uLight,me.at?.[0]??-1,me.at?.[1]??-1,"lantern"===i?me.strength:0),v.uniform3fv(S.uTrail,ve),v.uniform1f(S.uLightRadius,f.lantern.radius),v.uniform1f(S.uLightGain,f.lantern.strength),v.uniform1f(S.uClock,performance.now()/1e3),v.uniform1f(S.uFlicker,be()?0:1),v.uniform2f(S.uFluidTexel,1/Math.max(1,z),1/Math.max(1,O)),v.uniform2f(S.uResolution,I,V),v.uniform2f(S.uCell,N.width,N.height),v.uniform1f(S.uCount,H),v.uniform1f(S.uColumns,N.columns),v.uniform4f(S.uSourceMap,t.scale[0],t.scale[1],t.offset[0],t.offset[1]),v.uniform1i(S.uScene,Y?0:"flow"===f.scene?1:"orb"===f.scene?2:3),v.uniform3fv(S.uInk,b(f.ink)),v.uniform3fv(S.uHighlight,b("auto"===f.highlight?"#ffffff":f.highlight)),v.uniform1f(S.uHighlightAuto,"auto"===f.highlight?1:0),v.uniform4fv(S.uBackground,r),v.uniform1f(S.uBrightness,f.brightness),v.uniform1f(S.uContrast,f.contrast),v.uniform1f(S.uGamma,f.gamma),v.uniform1f(S.uInvert,f.invert?1:0),v.uniform1f(S.uVignette,f.vignette),v.uniform1f(S.uSourceColor,"source"===f.color?1:0),v.uniform1f(S.uRefraction,e?e.refraction*(be()?.25:1):0),v.uniform1f(S.uGlow,e?e.glow:0),v.uniform1f(S.uTime,re),v.uniform1f(S.uDrift,be()?0:f.drift),v.uniform1f(S.uFluidOn,e&&"fluid"===i&&(_.length||P)?1:0),v.uniform1f(S.uLod,Math.max(0,Math.log2(Math.max(1,n))-.5)),v.uniform1f(S.uSaturation,f.saturation),v.uniform1f(S.uShade,f.shade),v.uniform1f(S.uBackdrop,Y?f.backdrop:0),v.uniform2f(S.uLevels,Y&&"auto"===f.levels?Z[0]:0,Y&&"auto"===f.levels?Z[1]:1),v.bindVertexArray(U),v.drawArrays(v.TRIANGLES,0,3)},Ge=()=>!oe&&!ue&&le&&!("undefined"!=typeof document&&document.hidden),Ne=e=>{if(te=0,!Ge())return;const t=(e-ne)/1e3,n=Math.min(1/30,Math.max(1/480,t));ne=e,he=.9*he+.1*Math.min(t,.25),fe=he>1/24?fe+1:0,fe>45&&de<2&&(fe=0,de++,1===de&&X>1?(f={...f,maxPixelRatio:1},Ve()):de=2);const r=!(ae||de>=2&&!(Y instanceof HTMLVideoElement)||(!(Y instanceof HTMLVideoElement)||Y.paused||Y.ended)&&(be()||!(!Y&&"none"!==f.scene||f.drift>0)));r&&(re+=n*f.speed),Y instanceof HTMLVideoElement&&Y.readyState>=2&&J&&(J=!("requestVideoFrameCallback"in Y),Be(),ie=!0);const i=R(f);let o="fluid"===i&&(null!==ce||e<Te);if(o?((e,t)=>{const n=f.fluid;if(!n||!k.length&&!P)return;const r=I/V,i=ce;if(ce=null,P&&C){const o=5200*n.force*(be()?.35:1);return i&&(Te=t+1e3*Math.min(9,6/Math.max(.3,n.dissipation))),void C.step(i?{from:i.from,to:i.to,force:[(i.to[0]-i.from[0])*o,(i.to[1]-i.from[1])*o]}:null,{dt:Math.min(e,1/60),radius:1.1*n.radius,curl:n.curl,dissipation:n.dissipation,aspect:r})}let o=[0,0];if(i){const e=(be()?1:3)*n.force;o=[(i.to[0]-i.from[0])*r*e*4,(i.to[1]-i.from[1])*e*4],Te=t+7/Math.min(n.dissipation,1.1*n.dissipation)*1e3}v.bindFramebuffer(v.FRAMEBUFFER,k[1-W]),v.viewport(0,0,z,O),v.useProgram(p),v.activeTexture(v.TEXTURE0),v.bindTexture(v.TEXTURE_2D,_[W]),v.uniform1i(D.uPrevious,0),v.uniform2f(D.uSize,z,O),v.uniform1f(D.uAspect,r),v.uniform1f(D.uDt,e),v.uniform1f(D.uDecay,n.dissipation),v.uniform1f(D.uEnergyDecay,1.1*n.dissipation),v.uniform1f(D.uRadius,n.radius),v.uniform1f(D.uSplat,i?1:0),v.uniform2f(D.uFrom,i?.from[0]??0,i?.from[1]??0),v.uniform2f(D.uTo,i?.to[0]??0,i?.to[1]??0),v.uniform2f(D.uForce,o[0],o[1]),v.drawArrays(v.TRIANGLES,0,3),W=1-W})(n,e):ce=null,"lantern"===i&&(o=((e,t)=>{const n=f.lantern;if(me.target){if(!me.at||be())me.at=[...me.target];else{const t=1-Math.exp(-n.follow*e);me.at=[me.at[0]+(me.target[0]-me.at[0])*t,me.at[1]+(me.target[1]-me.at[1])*t]}const r=me.trail[me.trail.length-1];n.trail>0&&(!r||t-r.born>28&&Math.hypot(me.at[0]-r.x,me.at[1]-r.y)>.004)&&(me.trail.push({x:me.at[0],y:me.at[1],born:t}),me.trail.length>12&&me.trail.shift())}return me.strength+=((me.target?1:0)-me.strength)*(1-Math.exp(-e*(me.target?9:5))),me.trail=me.trail.filter(e=>n.trail>0&&t-e.born<4e3*n.trail),ve.fill(0),me.trail.forEach((e,r)=>{ve[3*r]=e.x,ve[3*r+1]=e.y,ve[3*r+2]=.55*Math.exp(-(t-e.born)/1e3/Math.max(.01,n.trail))}),me.strength>.003||me.trail.length>0})(n,e)||o),"trail"===i){const e=!!ge?.active;o=(e=>{const t=Ee();return!(!t||!t.active||(t.step(e),v.bindTexture(v.TEXTURE_2D,pe),v.texSubImage2D(v.TEXTURE_2D,0,0,0,t.width,t.height,v.RGBA,v.UNSIGNED_BYTE,t.pixels),xe=!0,0))})(n)||o,e&&!ge?.active&&(ie=!0)}(r||o||ie)&&(Xe(),ie=!1,!Re||K&&!Y||(Re=!1,Me())),(r||o||ce)&&(te=requestAnimationFrame(Ne))};function He(){!te&&Ge()&&(ne=performance.now(),te=requestAnimationFrame(Ne))}const ze=(e,n)=>{const r=t.getBoundingClientRect();if(!r.width||!r.height)return null;const i=(e-r.left)/r.width,o=(n-r.top)/r.height;return i<-.05||i>1.05||o<-.05||o>1.05?null:[i,1-o]},Oe=e=>{const t=R(f);if("none"===t||ae)return se=null,void(me.target=null);if("lantern"===t)return me.target=e?[e[0],1-e[1]]:null,se=null,void He();if("trail"===t){const t=Ee();if(!t)return;return e?t.move(e[0],1-e[1],f.trail.radius,performance.now()):t.leave(),se=null,void He()}e?(se&&(ce=ce?{from:ce.from,to:e}:{from:se,to:e}),se=e,He()):se=null},We=e=>Oe(ze(e.clientX,e.clientY)),Ye=()=>{se=null,ge?.leave(),me.target&&(me.target=null,He())},qe=h.interactionTarget??window;qe.addEventListener("pointermove",We,{passive:!0}),qe.addEventListener("pointerdown",We,{passive:!0}),document.addEventListener("pointerleave",Ye),qe!==window&&qe.addEventListener("pointerleave",Ye);const $e="function"==typeof ResizeObserver?new ResizeObserver(()=>{Ve()&&He()}):null;$e?.observe(t);const je="function"==typeof IntersectionObserver?new IntersectionObserver(([e])=>{le=e.isIntersecting,le&&(ie=!0,He())},{rootMargin:"120px"}):null;je?.observe(t);const Ke=()=>{document.hidden||(ie=!0,He())};document.addEventListener("visibilitychange",Ke);const Je=()=>{ie=!0,He()};ye?.addEventListener?.("change",Je);const Qe=e=>{e.preventDefault(),ue=!0,te&&cancelAnimationFrame(te),te=0},Ze=()=>{ue=!1,B.length=0,_=[],k=[],C=null,P=!1,pe=null,ge=null,xe=!1;try{Ie(),ie=!0,He()}catch(e){d?.(e instanceof Error?e:new Error(String(e)))}};t.addEventListener("webglcontextlost",Qe),t.addEventListener("webglcontextrestored",Ze);const et=()=>{q&&(q.pause(),q.removeAttribute("src"),q.load(),q=null),Q&&Y instanceof HTMLVideoElement&&"cancelVideoFrameCallback"in Y&&Y.cancelVideoFrameCallback(Q),Q=0},tt=async e=>{const t=++K;if(!e)return et(),Y=null,$=j=0,ie=!0,void He();try{const{element:n,owned:r}=await(e=>"string"!=typeof e?e instanceof HTMLImageElement&&!e.complete?e.decode().then(()=>({element:e,owned:!1})):e instanceof HTMLVideoElement&&e.readyState<2?new Promise((t,n)=>{e.addEventListener("loadeddata",()=>t({element:e,owned:!1}),{once:!0}),e.addEventListener("error",()=>n(new Error("The glyph field could not load this video.")),{once:!0})}):Promise.resolve({element:e,owned:!1}):A.test(e)?new Promise((t,n)=>{const r=document.createElement("video");r.crossOrigin="anonymous",r.muted=!0,r.loop=!0,r.playsInline=!0,r.preload="auto",r.addEventListener("loadeddata",()=>{r.play().catch(()=>{}),t({element:r,owned:!0})},{once:!0}),r.addEventListener("error",()=>n(new Error("The glyph field could not load this video.")),{once:!0}),r.src=e}):new Promise((t,n)=>{const r=new Image;r.crossOrigin="anonymous",r.decoding="async",r.onload=()=>t({element:r,owned:!1}),r.onerror=()=>n(new Error("The glyph field could not load this image.")),r.src=e}))(e);if(oe||t!==K)return void(r&&n instanceof HTMLVideoElement&&(n.pause(),n.removeAttribute("src"),n.load()));et(),q=r&&n instanceof HTMLVideoElement?n:null,Y=n,$=n instanceof HTMLVideoElement?n.videoWidth:n instanceof HTMLImageElement?n.naturalWidth:n.width,j=n instanceof HTMLVideoElement?n.videoHeight:n instanceof HTMLImageElement?n.naturalHeight:n.height,J=!0,n instanceof HTMLVideoElement&&(e=>{const t=e;if(!t.requestVideoFrameCallback)return;const n=()=>{oe||Y!==e||(J=!0,He(),Q=t.requestVideoFrameCallback(n))};Q=t.requestVideoFrameCallback(n)})(n),ue||Be(),ie=!0,He()}catch(e){Re&&t===K&&(Re=!1,Me()),d?.(e instanceof Error?e:new Error(String(e)))}};Ie();const nt=h.source??null;return nt?(K=1,tt(nt)):He(),{canvas:t,ready:we,get settings(){return f},update(e){const n=f;f=E(e,f),e.label&&t.setAttribute("aria-label",e.label),f.maxPixelRatio!==n.maxPixelRatio&&Ve(),f.charset===n.charset&&f.cellSize===n.cellSize&&f.cellAspect===n.cellAspect&&f.font===n.font&&f.fontWeight===n.fontWeight&&f.glyphScale===n.glyphScale||(Ce(),Pe()),f.levels!==n.levels&&Ue();(!!f.fluid!=!!n.fluid||f.fluid&&n.fluid&&f.fluid.resolution!==n.fluid.resolution)&&De(),"source"in e&&tt(e.source??null),ie=!0,He()},setSource:tt,pointer(e,n=0){if(null===e)return void Oe(null);const r=t.getBoundingClientRect();Oe(ze(r.left+e,r.top+n))},redraw(){oe||ue||!I||Xe()},play(){ae=!1,Y instanceof HTMLVideoElement&&Y.play().catch(()=>{}),ie=!0,He()},pause(){ae=!0,Y instanceof HTMLVideoElement&&Y.pause()},resize(){Ve()&&He()},destroy(){oe||(oe=!0,te&&cancelAnimationFrame(te),qe.removeEventListener("pointermove",We),qe.removeEventListener("pointerdown",We),document.removeEventListener("pointerleave",Ye),qe.removeEventListener("pointerleave",Ye),document.removeEventListener("visibilitychange",Ke),ye?.removeEventListener?.("change",Je),t.removeEventListener("webglcontextlost",Qe),t.removeEventListener("webglcontextrestored",Ze),$e?.disconnect(),je?.disconnect(),et(),Y=null,ue||(Se(),v.deleteTexture(w),v.deleteTexture(F),v.deleteProgram(g),v.deleteProgram(p),C?.dispose(),pe&&v.deleteTexture(pe),B.forEach(e=>v.deleteShader(e)),U&&v.deleteVertexArray(U)))}}}export{m as DEFAULT_FIELD_FLUID,f as DEFAULT_FIELD_LANTERN,v as DEFAULT_FIELD_SETTINGS,d as DEFAULT_FIELD_TRAIL,h as FIELD_HOVERS,R as activeFieldHover,y as fieldCellLayout,x as fieldGlyphs,M as fieldLevels,T as fieldSourceRect,L as glyphFieldTier,S as isGlyphFieldSupported,D as mountGlyphField,E as normalizeFieldSettings};//# sourceMappingURL=field.js.map
1
+ var e=e=>e?"\nconst float VSCALE=6.;\nvec4 decodeFluid(vec4 t){return vec4((t.xy*2.-1.)*VSCALE,t.z,t.w);}\nvec4 encodeFluid(vec4 v){return vec4(clamp(v.xy/VSCALE*.5+.5,0.,1.),clamp(v.z,0.,1.),1.);}":"\nvec4 decodeFluid(vec4 t){return t;}\nvec4 encodeFluid(vec4 v){return vec4(v.xyz,1.);}",t="#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform vec2 uTexel;\nout vec4 outColor;\nvec2 here(){return gl_FragCoord.xy*uTexel;}\n",n=t+"\nuniform sampler2D uVelocity;\nuniform vec2 uFrom, uTo, uForce;\nuniform float uAspect, uRadius;\nfloat segment(vec2 p,vec2 a,vec2 b){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/max(dot(ba,ba),1e-8),0.,1.);return length(pa-ba*h);}\nvoid main(){\n vec2 uv=here(),scale=vec2(uAspect,1.);\n float d=segment(uv*scale,uFrom*scale,uTo*scale);\n outColor=vec4(texture(uVelocity,uv).xy+uForce*exp(-d*d/(uRadius*uRadius)),0.,1.);\n}",r=t+"\nuniform sampler2D uVelocity;\nvoid main(){\n vec2 uv=here();\n float l=texture(uVelocity,uv-vec2(uTexel.x,0.)).y,r=texture(uVelocity,uv+vec2(uTexel.x,0.)).y;\n float t=texture(uVelocity,uv+vec2(0.,uTexel.y)).x,b=texture(uVelocity,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4(.5*(r-l-t+b),0.,0.,1.);\n}",i=t+"\nuniform sampler2D uVelocity, uCurl;\nuniform float uStrength, uDt;\nvoid main(){\n vec2 uv=here();\n float l=texture(uCurl,uv-vec2(uTexel.x,0.)).x,r=texture(uCurl,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uCurl,uv+vec2(0.,uTexel.y)).x,b=texture(uCurl,uv-vec2(0.,uTexel.y)).x,c=texture(uCurl,uv).x;\n vec2 force=.5*vec2(abs(t)-abs(b),abs(r)-abs(l));\n force=force/(length(force)+1e-4)*uStrength*c;\n force.y=-force.y;\n outColor=vec4(clamp(texture(uVelocity,uv).xy+force*uDt,-1000.,1000.),0.,1.);\n}",o=t+"\nuniform sampler2D uVelocity;\nvoid main(){\n vec2 uv=here(),c=texture(uVelocity,uv).xy;\n float l=texture(uVelocity,uv-vec2(uTexel.x,0.)).x,r=texture(uVelocity,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uVelocity,uv+vec2(0.,uTexel.y)).y,b=texture(uVelocity,uv-vec2(0.,uTexel.y)).y;\n if(uv.x-uTexel.x<0.)l=-c.x;\n if(uv.x+uTexel.x>1.)r=-c.x;\n if(uv.y+uTexel.y>1.)t=-c.y;\n if(uv.y-uTexel.y<0.)b=-c.y;\n outColor=vec4(.5*(r-l+t-b),0.,0.,1.);\n}",a=t+"\nuniform sampler2D uSource;\nuniform float uAmount;\nvoid main(){outColor=vec4(texture(uSource,here()).x*uAmount,0.,0.,1.);}",l=t+"\nuniform sampler2D uPressure, uDivergence;\nvoid main(){\n vec2 uv=here();\n float l=texture(uPressure,uv-vec2(uTexel.x,0.)).x,r=texture(uPressure,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uPressure,uv+vec2(0.,uTexel.y)).x,b=texture(uPressure,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4((l+r+b+t-texture(uDivergence,uv).x)*.25,0.,0.,1.);\n}",u=t+"\nuniform sampler2D uPressure, uVelocity;\nvoid main(){\n vec2 uv=here();\n float l=texture(uPressure,uv-vec2(uTexel.x,0.)).x,r=texture(uPressure,uv+vec2(uTexel.x,0.)).x;\n float t=texture(uPressure,uv+vec2(0.,uTexel.y)).x,b=texture(uPressure,uv-vec2(0.,uTexel.y)).x;\n outColor=vec4(texture(uVelocity,uv).xy-vec2(r-l,t-b),0.,1.);\n}",s=t+"\nuniform sampler2D uVelocity;\nuniform float uDt, uDissipation;\nvoid main(){\n vec2 uv=here();\n vec2 back=uv-uDt*texture(uVelocity,uv).xy*uTexel;\n outColor=vec4(texture(uVelocity,back).xy/(1.+uDissipation*uDt),0.,1.);\n}";var c=class{constructor(e,t){this.previous=null,this.peak=0,this.width=e,this.height=t;const n=e*t;this.pixels=new Uint8Array(4*n),this.signal=new Float32Array(n),this.offsetX=new Float32Array(n),this.offsetY=new Float32Array(n),this.driftX=new Float32Array(n),this.driftY=new Float32Array(n),this.u=new Float32Array(n),this.v=new Float32Array(n),this.ink=new Float32Array(n),this.a=new Float32Array(n),this.b=new Float32Array(n),this.c=new Float32Array(n),this.pressure=new Float32Array(n),this.pressureNext=new Float32Array(n),this.divergence=new Float32Array(n),this.curl=new Float32Array(n)}get active(){return this.peak>2e-4}clear(){for(const e of[this.u,this.v,this.ink,this.a,this.b,this.c,this.pressure,this.pressureNext,this.divergence,this.curl,this.signal,this.pixels,this.offsetX,this.offsetY,this.driftX,this.driftY])e.fill(0);this.previous=null,this.peak=0}leave(){this.previous=null}move(e,t,n,r=performance.now()){if(!Number.isFinite(e+t+n+r))return;e=Math.max(0,Math.min(1,e)),t=Math.max(0,Math.min(1,t)),n=Math.max(.1,Math.min(1,n));const i=this.previous;if(this.previous={x:e,y:t,time:r},!i)return;const o=(e-i.x)*(this.width-1),a=(t-i.y)*(this.height-1),l=Math.hypot(o,a);if(l<.001)return;const u=Math.min(this.width,this.height),s=r>i.time?Math.max(1/240,(r-i.time)/1e3):1/60,c=Math.min(12,l/u/s),f=1-Math.exp(-c/2.2),h=u*(.022+.075*n)*(.65+.95*f),d=Math.min(128,Math.max(1,Math.ceil(l/(.4*h)))),m=l/h*(.32+.22*f)/d,v=o/l,g=a/l,p=-g,x=v,E=l*(2+18*f)/d;for(let n=0;n<d;n++){const r=(n+.5)/d,o=(i.x+(e-i.x)*r)*(this.width-1),a=(i.y+(t-i.y)*r)*(this.height-1),l=Math.max(1,Math.floor(o-3*h)),u=Math.min(this.width-2,Math.ceil(o+3*h)),s=Math.max(1,Math.floor(a-3*h)),c=Math.min(this.height-2,Math.ceil(a+3*h));for(let e=s;e<=c;e++)for(let t=l;t<=u;t++){const n=((t-o)*v+(e-a)*g)/h,r=((t-o)*p+(e-a)*x)/h,i=Math.exp(-n*n*.65-r*r*1.7),l=e*this.width+t;this.ink[l]=1-(1-this.ink[l])*Math.exp(-i*m);const u=1-r*r*.55,s=n*r*.7;this.u[l]=Math.max(-80,Math.min(80,this.u[l]+(v*u+p*s)*i*E)),this.v[l]=Math.max(-80,Math.min(80,this.v[l]+(g*u+x*s)*i*E))}}this.peak=1}read(e,t,n){const r=0|(t=Math.max(.5,Math.min(this.width-1.5,t))),i=0|(n=Math.max(.5,Math.min(this.height-1.5,n))),o=t-r,a=n-i,l=i*this.width+r;return(e[l]*(1-o)+e[l+1]*o)*(1-a)+(e[l+this.width]*(1-o)+e[l+this.width+1]*o)*a}sample(e,t){return this.read(this.signal,e*(this.width-1),t*(this.height-1))}displacement(e,t,n){n[0]=this.read(this.offsetX,e*(this.width-1),t*(this.height-1)),n[1]=this.read(this.offsetY,e*(this.width-1),t*(this.height-1))}step(e){if(!this.active||!Number.isFinite(e))return;let t=Math.max(0,Math.min(.05,e));const n=this.width,r=this.height;for(;t>1e-7;){const e=Math.min(1/60,t);t-=e;const i=Math.exp(-1.9*e),o=Math.exp(-3.2*e);for(let t=1;t<r-1;t++)for(let a=1;a<n-1;a++){const l=t*n+a,u=Math.max(.5,Math.min(n-1.5,a-this.u[l]*e)),s=Math.max(.5,Math.min(r-1.5,t-this.v[l]*e)),c=0|u,f=0|s,h=u-c,d=s-f,m=f*n+c,v=(1-h)*(1-d),g=h*(1-d),p=(1-h)*d,x=h*d;this.a[l]=(this.u[m]*v+this.u[m+1]*g+this.u[m+n]*p+this.u[m+n+1]*x)*i,this.b[l]=(this.v[m]*v+this.v[m+1]*g+this.v[m+n]*p+this.v[m+n+1]*x)*i,this.c[l]=(this.ink[m]*v+this.ink[m+1]*g+this.ink[m+n]*p+this.ink[m+n+1]*x)*o}[this.u,this.a]=[this.a,this.u],[this.v,this.b]=[this.b,this.v],[this.ink,this.c]=[this.c,this.ink];for(let e=1;e<r-1;e++)for(let t=1;t<n-1;t++){const r=e*n+t;this.curl[r]=.5*(this.v[r+1]-this.v[r-1]-this.u[r+n]+this.u[r-n])}for(let t=1;t<r-1;t++)for(let r=1;r<n-1;r++){const i=t*n+r,o=Math.abs(this.curl[i+1])-Math.abs(this.curl[i-1]),a=Math.abs(this.curl[i+n])-Math.abs(this.curl[i-n]),l=Math.hypot(o,a)+1e-4,u=this.curl[i]*e*10,s=Math.hypot(this.u[i],this.v[i]);this.u[i]+=a/l*u,this.v[i]-=o/l*u;const c=Math.min(1,s/(Math.hypot(this.u[i],this.v[i])+1e-6));this.u[i]*=c,this.v[i]*=c,this.divergence[i]=-.5*(this.u[i+1]-this.u[i-1]+this.v[i+n]-this.v[i-n])}this.pressure.fill(0);for(let e=0;e<4;e++){for(let e=1;e<r-1;e++)for(let t=1;t<n-1;t++){const r=e*n+t;this.pressureNext[r]=.25*(this.divergence[r]+this.pressure[r-1]+this.pressure[r+1]+this.pressure[r-n]+this.pressure[r+n])}[this.pressure,this.pressureNext]=[this.pressureNext,this.pressure]}let a=0;for(let t=1;t<r-1;t++)for(let r=1;r<n-1;r++){const i=t*n+r;this.u[i]=Math.max(-80,Math.min(80,this.u[i]-.5*(this.pressure[i+1]-this.pressure[i-1]))),this.v[i]=Math.max(-80,Math.min(80,this.v[i]-.5*(this.pressure[i+n]-this.pressure[i-n]))),this.driftX[i]+=(3.6*this.u[i]-58*this.offsetX[i]-12*this.driftX[i])*e,this.driftY[i]+=(3.6*this.v[i]-58*this.offsetY[i]-12*this.driftY[i])*e,this.offsetX[i]=Math.max(-2,Math.min(2,this.offsetX[i]+this.driftX[i]*e)),this.offsetY[i]=Math.max(-2,Math.min(2,this.offsetY[i]+this.driftY[i]*e)),this.signal[i]=1-Math.exp(.06*-Math.hypot(this.u[i],this.v[i])-.16*this.ink[i]),a=Math.max(a,this.signal[i])}this.peak=a}if(this.active)for(let e=0;e<this.ink.length;e++){const t=Math.round(65535*Math.min(1,this.signal[e]));this.pixels[4*e]=t>>>8,this.pixels[4*e+1]=255&t,this.pixels[4*e+2]=Math.round(128+63.5*this.offsetX[e]),this.pixels[4*e+3]=Math.round(128+63.5*this.offsetY[e])}else this.clear()}},f=[{value:"fluid",label:"Fluid",description:"An ink-in-water wake that bends the image and swirls the characters."},{value:"lantern",label:"Lantern",description:"A soft light follows the pointer and lifts the characters it passes, without distortion."},{value:"trail",label:"Trail",description:"The Asciify Trail: in the wake, sparse characters fill in and dense ones open up, then settle. No distortion."},{value:"none",label:"Off",description:"The field stays still under the pointer."}],h={radius:.13,strength:.5,trail:.45,follow:12},d={strength:.55,radius:.45},m={radius:.075,force:1,dissipation:1.6,refraction:.03,glow:.55,resolution:128,curl:24},v={charset:" .,:-=+*#%@",cellSize:12,cellAspect:1,font:'ui-monospace, "SF Mono", Menlo, Consolas, monospace',fontWeight:500,glyphScale:.8,mark:"glyph",dotScale:.9,color:"ink",ink:"#f3ba32",highlight:"auto",background:"transparent",brightness:0,contrast:1,gamma:1,invert:!1,levels:"auto",saturation:1.1,shade:.5,backdrop:0,fit:"cover",focus:{x:.5,y:.5},vignette:0,drift:0,scene:"flow",scale:1,clear:!1,speed:1,hover:"fluid",fluid:m,lantern:h,trail:d,maxPixelRatio:2,respectReducedMotion:!0},g=(e,t,n,r)=>"number"==typeof e&&Number.isFinite(e)?Math.min(n,Math.max(t,e)):r,p=(e,t)=>"string"==typeof e&&/^#([\da-f]{3}|[\da-f]{6})$/i.test(e.trim())?e.trim().toLowerCase():t;function x(e){const t=Intl.Segmenter;return(t?Array.from(new t(void 0,{granularity:"grapheme"}).segment(e),e=>e.segment):Array.from(e)).filter(e=>"\n"!==e&&"\r"!==e&&"\t"!==e).slice(0,256)}function E(e={},t=v){const n="string"==typeof e.charset&&x(e.charset).length>=2?e.charset:t.charset,r=t.fluid||m,i=void 0===e.fluid?t.fluid:e.fluid,o=!1!==i&&(()=>{const e=!0!==i&&i?{...r,...i}:r;return{radius:g(e.radius,.005,.5,m.radius),force:g(e.force,0,8,m.force),dissipation:g(e.dissipation,.05,20,m.dissipation),refraction:g(e.refraction,0,.5,m.refraction),glow:g(e.glow,0,2,m.glow),resolution:Math.round(g(e.resolution,16,512,m.resolution)),curl:g(e.curl,0,80,m.curl)}})(),a="none"===e.scene||"flow"===e.scene||"orb"===e.scene||"drift"===e.scene?e.scene:t.scene,l=!!e.clear&&"object"==typeof e.clear&&("element"in e.clear||"nodeType"in e.clear),u=void 0===e.clear||l?t.clear:e.clear,s=!!u&&{x:g(u.x,-.5,1.5,.25),y:g(u.y,-.5,1.5,.3),width:g(u.width,0,2,.5),height:g(u.height,0,2,.4),feather:g(u.feather,.005,1,.12),shape:"rect"===u.shape?"rect":"ellipse"};return{charset:n,cellSize:g(e.cellSize,3,96,t.cellSize),cellAspect:g(e.cellAspect,.3,2,t.cellAspect),font:"string"==typeof e.font&&e.font.trim()?e.font:t.font,fontWeight:Math.round(g(e.fontWeight,100,900,t.fontWeight)),glyphScale:g(e.glyphScale,.3,1.6,t.glyphScale),mark:"dot"===e.mark||"glyph"===e.mark?e.mark:t.mark,dotScale:g(e.dotScale,.1,1.4,t.dotScale),color:"source"===e.color||"ink"===e.color?e.color:t.color,ink:p(e.ink,t.ink),highlight:"auto"===e.highlight?"auto":p(e.highlight,t.highlight),background:"transparent"===e.background?"transparent":p(e.background,t.background),brightness:g(e.brightness,-1,1,t.brightness),contrast:g(e.contrast,0,4,t.contrast),gamma:g(e.gamma,.2,5,t.gamma),invert:"boolean"==typeof e.invert?e.invert:t.invert,levels:"none"===e.levels||"auto"===e.levels?e.levels:t.levels,saturation:g(e.saturation,0,3,t.saturation),shade:g(e.shade,0,1,t.shade),backdrop:g(e.backdrop,0,1,t.backdrop),fit:"contain"===e.fit||"cover"===e.fit?e.fit:t.fit,focus:{x:g(e.focus?.x,0,1,t.focus.x),y:g(e.focus?.y,0,1,t.focus.y)},vignette:g(e.vignette,0,1,t.vignette),drift:g(e.drift,0,2,t.drift),scene:a,scale:g(e.scale,.2,6,t.scale),clear:s,speed:g(e.speed,0,5,t.speed),hover:"fluid"===e.hover||"lantern"===e.hover||"trail"===e.hover||"none"===e.hover?e.hover:t.hover,fluid:o,lantern:{radius:g(e.lantern?.radius,.02,.6,t.lantern.radius),strength:g(e.lantern?.strength,0,1.5,t.lantern.strength),trail:g(e.lantern?.trail,0,3,t.lantern.trail),follow:g(e.lantern?.follow,1,60,t.lantern.follow)},trail:{strength:g(e.trail?.strength,0,1,t.trail.strength),radius:g(e.trail?.radius,.1,1,t.trail.radius)},maxPixelRatio:g(e.maxPixelRatio,.5,4,t.maxPixelRatio),respectReducedMotion:"boolean"==typeof e.respectReducedMotion?e.respectReducedMotion:t.respectReducedMotion}}function T(e,t,n,r,i,o={x:.5,y:.5}){if(!(e>0&&t>0&&n>0&&r>0))return{scale:[1,1],offset:[0,0]};const a=e/t,l=n/r,u=l>a,s="cover"===i?u?[a/l,1]:[1,l/a]:u?[1,l/a]:[a/l,1];return{scale:s,offset:"cover"===i?[(1-s[0])*o.x,(1-s[1])*o.y]:[.5*(1-s[0]),.5*(1-s[1])]}}function y(e,t,n,r){const i=Math.max(2,Math.round(e*n)),o=Math.max(2,Math.round(i*t)),a=Math.min(16,Math.max(1,r)),l=Math.ceil(r/a);return{width:o,height:i,columns:a,rows:l,atlasWidth:a*o,atlasHeight:l*i}}function b(e){const t=e.replace("#",""),n=3===t.length?t.split("").map(e=>e+e).join(""):t,r=Number.parseInt(n,16);return Number.isFinite(r)&&6===n.length?[(r>>16&255)/255,(r>>8&255)/255,(255&r)/255]:[0,0,0]}function w(e){const t=new Uint32Array(256);let n=0;for(let r=0;r+3<e.length;r+=4)e[r+3]<8||(t[Math.round(.2126*e[r]+.7152*e[r+1]+.0722*e[r+2])]++,n++);if(!n)return[0,1];const r=e=>{let r=0;for(let i=0;i<256;i++)if(r+=t[i],r>=n*e)return i/255;return 1},i=r(.02),o=r(.98);return o-i<.08?[0,1]:[i,o]}var M=e=>"fluid"!==e.hover||e.fluid?e.hover:"none";function R(e,t,n=24,r=.12,i="ellipse"){return e.width>0&&e.height>0&&{x:(t.left-n-e.left)/e.width,y:(t.top-n-e.top)/e.height,width:(t.width+2*n)/e.width,height:(t.height+2*n)/e.height,feather:r,shape:i}}var A,F=/\.(mp4|webm|mov|m4v|ogv)(\?|#|$)/i,L=/swiftshader|llvmpipe|softpipe|software|basic render/i;function _(){if(A)return A;if("undefined"==typeof document||void 0===globalThis.WebGL2RenderingContext)return A="none";try{const e=document.createElement("canvas").getContext("webgl2");if(!e)return A="none";const t=e.getExtension("WEBGL_debug_renderer_info"),n=String(e.getParameter(t?t.UNMASKED_RENDERER_WEBGL:e.RENDERER)??"");return e.getExtension("WEBGL_lose_context")?.loseContext(),A=L.test(n)?"software":"gpu"}catch{return A="none"}}function k(e={}){const t=_();return"gpu"===t||"software"===t&&!!e.allowSoftware}function S(t,f={}){const h=e=>"undefined"!=typeof HTMLElement&&e instanceof HTMLElement,d=e=>{const t=e.clear;return h(t)?{element:{element:t},rest:{...e,clear:void 0}}:t&&"object"==typeof t&&h(t.element)?{element:t,rest:{...e,clear:void 0}}:{element:null,rest:e}};let m=d(f).element,v=E(d(f).rest);const g=f.onError,p=t.getContext("webgl2",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,powerPreference:"high-performance"});if(!p)throw new Error("WebGL2 is not available for the glyph field.");const A=p;t.setAttribute("role","img"),t.setAttribute("aria-label",f.label??"ASCII artwork"),t.dataset.renderer="glyph-field";let L=!1;const _=A.getExtension("KHR_parallel_shader_compile");let k,S,C=null,D=null,U=null,P=null,B=[],I=[],X=null,N=!1,V=null,G=null;const H=[];let z=0,O=0,W=1,Y=0,q=y(v.cellSize,v.cellAspect,1,2),$=2,j=0,K=0,J=0,Q=null,Z=null,ee=0,te=0,ne=0,re=null,ie=0,oe=!0,ae=0,le=[0,1],ue=-1/0,se=0,ce=0,fe=0,he=!0,de=!1,me=!1,ve=!1,ge=!1,pe=!0,xe=null,Ee=null,Te=1/60,ye=0,be=0,we=[];const Me=new Float32Array(16),Re=()=>{if(!m)return;const{element:e,padding:n=32,feather:r=.16,shape:i="rect"}=m,o=R(t.getBoundingClientRect(),e.getBoundingClientRect(),n,r,i),a=v.clear;JSON.stringify(a)!==JSON.stringify(o)&&(v={...v,clear:o},he=!0)},Ae={target:null,at:null,strength:0,trail:[]},Fe=new Float32Array(36);let Le=null,_e=null,ke=!1;const Se=()=>{if(!z||!O)return null;const e=z/O,t=Math.round(e>=1?128:128*e),n=Math.round(e>=1?128/e:128);return Le&&Le.width===t&&Le.height===n||(Le=new c(t,n),_e||(_e=ze(A.LINEAR)),A.bindTexture(A.TEXTURE_2D,_e),A.texImage2D(A.TEXTURE_2D,0,A.RGBA8,t,n,0,A.RGBA,A.UNSIGNED_BYTE,Le.pixels),ke=!0),Le};let Ce=0;const De="function"==typeof matchMedia?matchMedia("(prefers-reduced-motion: reduce)"):null,Ue=()=>v.respectReducedMotion&&!!De?.matches;let Pe;const Be=new Promise(e=>{Pe=e});let Ie=!0;const Xe=e=>g?.(e instanceof Error?e:new Error(String(e))),Ne=(e,t)=>{const n=A.createShader(e);return H.push(n),A.shaderSource(n,t),A.compileShader(n),n},Ve=e=>{G??(G=Ne(A.VERTEX_SHADER,"#version 300 es\nvoid main(){vec2 p=vec2((gl_VertexID<<1)&2,gl_VertexID&2);gl_Position=vec4(p*2.-1.,0.,1.);}"));const t=A.createProgram(),n=Ne(A.FRAGMENT_SHADER,e);return A.attachShader(t,G),A.attachShader(t,n),A.linkProgram(t),{program:t,locations:null,fragment:n}},Ge=(e,t=!1)=>{if(e.locations)return!0;const{program:n,fragment:r}=e;if(!t&&_&&!A.getProgramParameter(n,_.COMPLETION_STATUS_KHR))return!1;if(!A.getProgramParameter(n,A.LINK_STATUS)){if(A.isContextLost())return!1;throw new Error(A.getShaderInfoLog(r)||A.getProgramInfoLog(n)||"Glyph field program failed to link.")}const i={};for(let e=A.getProgramParameter(n,A.ACTIVE_UNIFORMS)-1;e>=0;e--){const t=A.getActiveUniform(n,e).name.replace(/\[0\]$/,"");i[t]=A.getUniformLocation(n,t)}return e.locations=i,!0},He=(e=!1)=>!!C&&!!D&&Ge(C,e)&&Ge(D,e),ze=e=>{const t=A.createTexture();return A.bindTexture(A.TEXTURE_2D,t),A.texParameteri(A.TEXTURE_2D,A.TEXTURE_MIN_FILTER,e),A.texParameteri(A.TEXTURE_2D,A.TEXTURE_MAG_FILTER,e===A.LINEAR_MIPMAP_LINEAR?A.LINEAR:e),A.texParameteri(A.TEXTURE_2D,A.TEXTURE_WRAP_S,A.CLAMP_TO_EDGE),A.texParameteri(A.TEXTURE_2D,A.TEXTURE_WRAP_T,A.CLAMP_TO_EDGE),t},Oe=()=>{P&&(A.deleteTexture(P.ink),A.deleteTexture(P.under),A.deleteFramebuffer(P.buffer),P=null)},We=()=>!!v.fluid&&"fluid"===M(v),Ye=()=>{X?.release(),N=!1,B.forEach(e=>A.deleteTexture(e)),I.forEach(e=>A.deleteFramebuffer(e)),B=[],I=[]};function qe(){U&&A.deleteProgram(U.program),U=Ve((t=>`#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uPrevious;\nuniform vec2 uSize;\nuniform float uAspect, uDt, uDecay, uEnergyDecay, uRadius, uSplat;\nuniform vec2 uFrom, uTo, uForce;\nout vec4 outColor;\n${e(t)}\nfloat segment(vec2 p,vec2 a,vec2 b){vec2 pa=p-a,ba=b-a;float h=clamp(dot(pa,ba)/max(dot(ba,ba),1e-8),0.,1.);return length(pa-ba*h);}\nvoid main(){\n vec2 uv=gl_FragCoord.xy/uSize;\n vec4 here=decodeFluid(texture(uPrevious,uv));\n vec4 state=decodeFluid(texture(uPrevious,uv-uDt*here.xy*vec2(1./uAspect,1.)));\n state.xy*=exp(-uDt*uDecay);\n state.z*=exp(-uDt*uEnergyDecay);\n if(uSplat>.5){\n vec2 scale=vec2(uAspect,1.);\n float d=segment(uv*scale,uFrom*scale,uTo*scale);\n float g=exp(-d*d/(uRadius*uRadius));\n state.xy+=uForce*g;\n state.z=min(1.,state.z+g*min(1.,length(uForce)*.5+.08));\n }\n float speed=length(state.xy);\n if(speed>5.)state.xy*=5./speed;\n outColor=encodeFluid(state);\n}`)(L)),X?.dispose(),X=L?null:function(e,t,c){const f={splat:t(n),curl:t(r),vorticity:t(i),divergence:t(o),fade:t(a),pressure:t(l),gradient:t(u),advect:t(s)},h=Object.values(f);let d=0,m=0,v=[],g=[],p=null,x=null;const E=t=>{const n=e.createTexture();e.bindTexture(e.TEXTURE_2D,n),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texImage2D(e.TEXTURE_2D,0,e.RGBA16F,d,m,0,e.RGBA,e.HALF_FLOAT,null);const r=e.createFramebuffer();return e.bindFramebuffer(e.FRAMEBUFFER,r),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,n,0),e.checkFramebufferStatus(e.FRAMEBUFFER)!==e.FRAMEBUFFER_COMPLETE?(e.deleteFramebuffer(r),e.deleteTexture(n),null):(e.viewport(0,0,d,m),e.clearColor(0,0,0,1),e.clear(e.COLOR_BUFFER_BIT),{texture:n,buffer:r})},T=t=>{t&&(e.deleteTexture(t.texture),e.deleteFramebuffer(t.buffer))},y=()=>{[...v,...g,p,x].forEach(T),v=[],g=[],p=x=null},b=(t,n)=>{const r=t.locations;return e.useProgram(t.program),e.bindFramebuffer(e.FRAMEBUFFER,n.buffer),e.uniform2f(r.uTexel,1/d,1/m),r},w=(t,n,r,i)=>{e.activeTexture(e.TEXTURE0+r),e.bindTexture(e.TEXTURE_2D,i),e.uniform1i(t[n],r)},M=()=>e.drawArrays(e.TRIANGLES,0,3),R=e=>{const t=e[0];e[0]=e[1],e[1]=t};return{resize(t,n){y(),d=t,m=n;const r=[E(e.LINEAR),E(e.LINEAR),E(e.NEAREST),E(e.NEAREST),E(e.NEAREST),E(e.NEAREST)];return e.bindFramebuffer(e.FRAMEBUFFER,null),r.some(e=>!e)?(r.forEach(T),!1):(v=[r[0],r[1]],g=[r[2],r[3]],p=r[4],x=r[5],!0)},ready:()=>h.every(c),step(t,n){if(!v.length||!p||!x)return;let r;e.viewport(0,0,d,m),t&&(r=b(f.splat,v[1]),w(r,"uVelocity",0,v[0].texture),e.uniform2f(r.uFrom,t.from[0],t.from[1]),e.uniform2f(r.uTo,t.to[0],t.to[1]),e.uniform2f(r.uForce,t.force[0],t.force[1]),e.uniform1f(r.uAspect,n.aspect),e.uniform1f(r.uRadius,n.radius),M(),R(v)),n.curl>0&&(r=b(f.curl,x),w(r,"uVelocity",0,v[0].texture),M(),r=b(f.vorticity,v[1]),w(r,"uVelocity",0,v[0].texture),w(r,"uCurl",1,x.texture),e.uniform1f(r.uStrength,n.curl),e.uniform1f(r.uDt,n.dt),M(),R(v)),r=b(f.divergence,p),w(r,"uVelocity",0,v[0].texture),M(),r=b(f.fade,g[1]),w(r,"uSource",0,g[0].texture),e.uniform1f(r.uAmount,.8),M(),R(g);for(let e=0;e<20;e++)r=b(f.pressure,g[1]),w(r,"uPressure",0,g[0].texture),w(r,"uDivergence",1,p.texture),M(),R(g);r=b(f.gradient,v[1]),w(r,"uPressure",0,g[0].texture),w(r,"uVelocity",1,v[0].texture),M(),R(v),r=b(f.advect,v[1]),w(r,"uVelocity",0,v[0].texture),e.uniform1f(r.uDt,n.dt),e.uniform1f(r.uDissipation,n.dissipation),M(),R(v),e.bindFramebuffer(e.FRAMEBUFFER,null)},get velocity(){return v[0]?.texture??null},get size(){return[d,m]},release:y,dispose(){y(),h.forEach(({program:t})=>e.deleteProgram(t))}}}(A,Ve,e=>Ge(e))}const $e=()=>{Ye();const e=v.fluid;if(!(e&&We()&&z&&O))return;U||qe();const t=e.resolution,n=z/O;if(j=n>=1?t:Math.max(8,Math.round(t*n)),K=n>=1?Math.max(8,Math.round(t/n)):t,N=!!X&&X.resize(j,K),N)return void(J=0);const r=()=>{Ye();for(let e=0;e<2;e++){const e=ze(A.LINEAR);A.texImage2D(A.TEXTURE_2D,0,L?A.RGBA8:A.RGBA16F,j,K,0,A.RGBA,L?A.UNSIGNED_BYTE:A.HALF_FLOAT,null);const t=A.createFramebuffer();A.bindFramebuffer(A.FRAMEBUFFER,t),A.framebufferTexture2D(A.FRAMEBUFFER,A.COLOR_ATTACHMENT0,A.TEXTURE_2D,e,0),B.push(e),I.push(t)}return A.checkFramebufferStatus(A.FRAMEBUFFER)===A.FRAMEBUFFER_COMPLETE};if(!r()){if(L)throw new Error("The glyph field could not allocate its fluid buffers.");L=!0,je(),qe(),r()}for(const e of I)A.bindFramebuffer(A.FRAMEBUFFER,e),A.viewport(0,0,j,K),A.clearColor(L?.5:0,L?.5:0,0,1),A.clear(A.COLOR_BUFFER_BIT);A.bindFramebuffer(A.FRAMEBUFFER,null),J=0};function je(){C&&A.deleteProgram(C.program),D&&A.deleteProgram(D.program),Oe(),C=Ve((t=>`#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uSource, uFluid;\nuniform vec2 uResolution, uCell;\nuniform float uCount;\nuniform vec4 uSourceMap;\nuniform int uScene;\nuniform vec3 uInk, uHighlight;\nuniform vec4 uBackground;\nuniform float uBrightness, uContrast, uGamma, uInvert, uVignette, uSourceColor;\nuniform float uRefraction, uGlow, uTime, uDrift, uFluidOn, uLod, uSaturation, uBackdrop, uShade, uHighlightAuto, uFluidMode;\nuniform vec2 uFluidTexel;\nuniform int uHoverMode;\nuniform vec3 uLight;\nuniform vec3 uTrail[12];\nuniform float uLightRadius, uLightGain, uClock, uFlicker, uTrailAmount;\nuniform sampler2D uTrailMap;\nuniform vec2 uLevels;\nuniform int uMark;\nuniform float uScale, uClearFeather, uClearShape;\nuniform vec4 uClear;\nuniform vec4 uPulse[4];\nlayout(location=0) out vec4 outInk;\nlayout(location=1) out vec4 outUnder;\n${e(t)}\nfloat hash(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+45.32);return fract(p.x*p.y);}\nfloat noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.-2.*f);\n return mix(mix(hash(i),hash(i+vec2(1,0)),u.x),mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),u.x),u.y);}\nfloat fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<5;i++){v+=a*noise(p);p=p*2.03+vec2(1.7,9.2);a*=.5;}return v;}\nvec3 generated(vec2 uv,float aspect){\n vec2 p=(uv-.5)*vec2(aspect,1.)*uScale;\n if(uScene==1){\n float t=uTime*.08;\n vec2 q=vec2(fbm(p*1.6+t),fbm(p*1.6-t+3.1));\n return vec3(smoothstep(.28,.86,fbm(p*1.8+q*1.6+vec2(t*.6,-t*.4))));\n }\n if(uScene==2){\n float r=.36,d=length(p),a=uTime*.25;\n vec3 l=normalize(vec3(cos(a)*.8,.55,sin(a)*.6+.6));\n if(d<r){\n vec3 n=normalize(vec3(p.x,-p.y,sqrt(r*r-d*d)));\n float diffuse=max(dot(n,l),0.),rim=pow(1.-n.z,2.5);\n float lon=atan(n.x,n.z)+uTime*.35,lat=asin(clamp(n.y,-1.,1.));\n float bands=.5+.25*sin(lat*14.)+.25*sin(lon*6.);\n return vec3(clamp(diffuse*.85+rim*.5+bands*.12,0.,1.));\n }\n return vec3(exp(-pow((d-r)*9.,2.))*.35);\n }\n if(uScene==4){\n // Drift: a few broad lobes that wander slowly. Built for halftone, where large soft shapes read best.\n float t=uTime*.045;\n vec2 q=p*1.15;\n vec2 w=vec2(fbm(q*.7+vec2(t,-t*.7)),fbm(q*.7+vec2(4.3-t*.8,t+1.7)));\n float n=fbm(q+w*1.3+vec2(t*.4,t*.25));\n return vec3(smoothstep(.36,.74,n));\n }\n return vec3(0.);\n}\nvoid main(){\n vec2 cell=floor(gl_FragCoord.xy);\n vec2 uv=(cell+.5)*uCell/uResolution;\n float aspect=uResolution.x/uResolution.y;\n vec4 fluid=vec4(0.);\n // lift: signed tone change. The solver's flow brightens one side of each eddy and opens the other into gaps.\n float lift=0.,wake=0.,bend=1.,swirl=0.,speed=0.;\n if(uFluidOn>.5){\n vec4 raw=texture(uFluid,vec2(uv.x,1.-uv.y));\n if(uFluidMode>.5){\n vec2 v=raw.xy*uFluidTexel*vec2(aspect,1.);\n fluid=vec4(v,0.,1.);\n swirl=clamp(dot(v,vec2(.4,.65))*uGlow*.32,-.65,.65);\n speed=clamp(length(v)*uGlow*.055,0.,.38);\n bend=.4;\n } else {\n fluid=decodeFluid(raw);\n wake=clamp(fluid.z*uGlow,0.,1.);\n lift=wake*.55;\n }\n }\n vec2 drift=uDrift*.006*vec2(sin(uv.y*5.3+uTime*.6),cos(uv.x*4.1-uTime*.5));\n vec2 sampleAt=uv-vec2(fluid.x/aspect,-fluid.y)*uRefraction*bend+drift;\n vec3 rgb,glow=vec3(0.);\n if(uScene==0){\n vec2 s=sampleAt*uSourceMap.xy+uSourceMap.zw;\n bool inside=!(s.x<0.||s.y<0.||s.x>1.||s.y>1.);\n rgb=inside?textureLod(uSource,s,uLod).rgb:vec3(0.);\n if(uBackdrop>0.){\n vec2 b=(uv-vec2(fluid.x/aspect,-fluid.y)*uRefraction*bend*.5)*uSourceMap.xy+uSourceMap.zw;\n glow=(b.x<0.||b.y<0.||b.x>1.||b.y>1.)?vec3(0.):textureLod(uSource,b,uLod+3.).rgb*uBackdrop;\n }\n } else rgb=generated(sampleAt,aspect);\n float tone=dot(rgb,vec3(.2126,.7152,.0722));\n tone=clamp((tone-uLevels.x)/max(uLevels.y-uLevels.x,.01),0.,1.);\n tone=clamp((tone-.5)*uContrast+.5+uBrightness,0.,1.);\n tone=pow(tone,1./uGamma);\n if(uInvert>.5)tone=1.-tone;\n float flick=0.,trailFold=0.;\n if(uHoverMode==2){\n // Lantern: a soft light and its fading tail lift the characters they pass. Nothing moves the image.\n vec2 ap=vec2(aspect,1.),d=(uv-uLight.xy)*ap;\n float r2=uLightRadius*uLightRadius,glowL=uLight.z*exp(-dot(d,d)/r2);\n for(int i=0;i<12;i++){vec2 e=(uv-uTrail[i].xy)*ap;glowL+=uTrail[i].z*exp(-dot(e,e)/(r2*.55));}\n glowL=min(glowL,1.);\n float grain=fract(sin(dot(cell,vec2(12.9898,78.233)))*43758.5453);\n lift=glowL*uLightGain*(.7+.6*grain);\n wake=glowL*.4;\n // A few letters at the light's rim step up the ramp and back, as if being retyped.\n flick=uFlicker*step(.9,fract(grain*13.7+floor(uClock*7.+grain*5.)*.37))*glowL*(1.-glowL)*4.;\n }\n if(uHoverMode==3){\n // Trail: the studio's ink field, as a 16-bit density. Sparse marks fill in, dense marks open up.\n vec4 ink=texture(uTrailMap,uv);\n float density=(ink.r*65280.+ink.g*255.)/65535.*uTrailAmount;\n float t=clamp(density/.38,0.,1.);\n trailFold=t*t*(3.-2.*t);\n }\n if(uFluidMode>.5&&uHoverMode==1){\n // Dark ground: any motion lights the characters. Lit ground: the flow lights one side of each eddy and opens the other.\n // A little cell-to-cell grain keeps the lit area a shimmer of letters rather than a solid block.\n float grain=fract(sin(dot(cell,vec2(12.9898,78.233)))*43758.5453);\n lift=mix(abs(swirl)*.5+speed,swirl+speed*.35,smoothstep(.12,.55,tone))*(.65+.7*grain);\n wake=clamp(lift*1.6,0.,1.);\n }\n tone=clamp(tone+lift,0.,1.);\n tone+=(1.-2.*tone)*trailFold;\n // Pulses: rings sent by the interface (field.pulse()), e.g. on a save or a notification.\n for(int i=0;i<4;i++){\n if(uPulse[i].w<=0.)continue;\n float dist=length((uv-uPulse[i].xy)*vec2(aspect,1.)),front=uPulse[i].z*.95;\n float ring=exp(-pow((dist-front)/.1,2.))*uPulse[i].w*exp(-uPulse[i].z*.85);\n tone=clamp(tone+ring*.85,0.,1.);\n }\n tone*=1.-uVignette*smoothstep(.35,1.05,length((uv-.5)*vec2(min(aspect,1.8),1.)*2.));\n if(uClear.z>0.){\n // Clear zone: the field fades out around content so text above it stays readable.\n vec2 half_=uClear.zw*.5*vec2(aspect,1.),q=(uv-uClear.xy-uClear.zw*.5)*vec2(aspect,1.);\n float dz;\n if(uClearShape>.5){vec2 e=abs(q)-half_;dz=length(max(e,0.))+min(max(e.x,e.y),0.);}\n else dz=(length(q/max(half_,vec2(1e-4)))-1.)*min(half_.x,half_.y);\n tone*=smoothstep(0.,uClearFeather,dz);\n }\n float index=min(uCount-1.,floor(tone*(uCount-1.)+.5)+(flick>.5&&tone>.02?1.:0.));\n vec3 hue=rgb/max(max(rgb.r,rgb.g),max(rgb.b,.03));\n hue=mix(vec3(dot(hue,vec3(.2126,.7152,.0722))),hue,uSaturation);\n vec3 ink=mix(uInk*mix(1.,.3+.7*tone,uShade),clamp(hue*(.38+.62*tone),0.,1.),uSourceColor);\n vec3 lifted=uHighlightAuto>.5?ink/max(max(ink.r,ink.g),max(ink.b,.06)):uHighlight;\n ink=mix(ink,lifted,wake*.7);\n float underAlpha=max(uBackground.a,min(1.,max(glow.r,max(glow.g,glow.b))));\n vec3 under=min(uBackground.rgb*uBackground.a+glow,vec3(underAlpha));\n // Halftone keeps the tone for the dot size; glyphs keep their atlas index, exact in 8 bits.\n outInk=vec4(ink,uMark==1?(tone>.003?tone:0.):index/255.);\n outUnder=vec4(under,underAlpha);\n}`)(L)),D=Ve("#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uAtlas, uCells, uUnder;\nuniform vec2 uResolution, uCell;\nuniform float uColumns, uDotScale;\nuniform int uMark;\nout vec4 outColor;\nvoid main(){\n vec2 frag=vec2(gl_FragCoord.x,uResolution.y-gl_FragCoord.y);\n vec2 cell=floor(frag/uCell);\n vec4 ink=texelFetch(uCells,ivec2(cell),0),under=texelFetch(uUnder,ivec2(cell),0);\n vec2 local=frag-cell*uCell;\n float coverage;\n if(uMark==1){\n // Halftone: dot area follows tone, so midtones read true; edges are antialiased to one device pixel.\n float r=sqrt(ink.a)*min(uCell.x,uCell.y)*.5*uDotScale;\n coverage=ink.a>.003?clamp(r-length(local-uCell*.5)+.5,0.,1.):0.;\n } else {\n float index=floor(ink.a*255.+.5);\n ivec2 tile=ivec2(int(mod(index,uColumns)),int(floor(index/uColumns)));\n coverage=texelFetch(uAtlas,tile*ivec2(uCell)+ivec2(local),0).a;\n }\n outColor=vec4(ink.rgb*coverage+under.rgb*(1.-coverage),coverage+under.a*(1.-coverage));\n}"),_||He(!0)}const Ke=()=>`${v.fontWeight} ${Math.max(1,q.height*v.glyphScale)}px ${v.font}`,Je=()=>{const e=x(v.charset);$=e.length,q=y(v.cellSize,v.cellAspect,W,$),Y=W;const t=document.createElement("canvas");t.width=q.atlasWidth,t.height=q.atlasHeight;const n=t.getContext("2d");n.fillStyle="#fff",n.textAlign="center",n.textBaseline="alphabetic",n.font=Ke();const r=n.measureText("M"),i=r.actualBoundingBoxAscent||q.height*v.glyphScale*.7,o=r.actualBoundingBoxDescent||0,a=(q.height+i-o)/2;e.forEach((e,t)=>{const r=t%q.columns*q.width+q.width/2,i=Math.floor(t/q.columns)*q.height+a,o=n.measureText(e).width;n.save(),n.beginPath(),n.rect(r-q.width/2,i-a,q.width,q.height),n.clip(),n.translate(r,i),o>.96*q.width&&n.scale(.96*q.width/o,1),n.fillText(e,0,0),n.restore()}),A.bindTexture(A.TEXTURE_2D,k),A.pixelStorei(A.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),A.texImage2D(A.TEXTURE_2D,0,A.RGBA8,A.RGBA,A.UNSIGNED_BYTE,t),t.width=t.height=1,he=!0},Qe="undefined"==typeof document?void 0:document.fonts,Ze=()=>{if(!Qe?.load)return;const e=Ke();Qe.load(e,x(v.charset).join("")).then(()=>{de||me||Ke()!==e||(Je(),Et())},()=>{})},et=e=>e.trim().replace(/^["']|["']$/g,"").toLowerCase(),tt=e=>{const t=e.fontfaces??[],n=v.font.split(",").map(et);t.some(e=>n.includes(et(e.family)))&&Ze()},nt=()=>{if(le=[0,1],Q&&"none"!==v.levels)try{const e=document.createElement("canvas");e.width=e.height=64;const t=e.getContext("2d",{willReadFrequently:!0});Q===re&&(t.imageSmoothingQuality="medium"),t.drawImage(Q,0,0,64,64),le=w(t.getImageData(0,0,64,64).data)}catch{le=[0,1]}},rt=()=>{if(!Q)return;const e=performance.now();Q instanceof HTMLVideoElement&&!(e-ue>1500)||(nt(),ue=e),A.bindTexture(A.TEXTURE_2D,S),A.pixelStorei(A.UNPACK_FLIP_Y_WEBGL,!1),A.pixelStorei(A.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1);try{A.texImage2D(A.TEXTURE_2D,0,A.RGBA8,A.RGBA,A.UNSIGNED_BYTE,Q),A.generateMipmap(A.TEXTURE_2D)}catch(e){Xe(e)}},it=()=>{L=!A.getExtension("EXT_color_buffer_float")&&!A.getExtension("EXT_color_buffer_half_float"),je(),V=A.createVertexArray(),k=ze(A.NEAREST),S=ze(A.LINEAR_MIPMAP_LINEAR),A.bindTexture(A.TEXTURE_2D,S),A.texImage2D(A.TEXTURE_2D,0,A.RGBA8,1,1,0,A.RGBA,A.UNSIGNED_BYTE,new Uint8Array([0,0,0,255])),A.generateMipmap(A.TEXTURE_2D),z=O=0,Y=0,ot(),Ze(),Q&&rt()};function ot(){const e=t.getBoundingClientRect(),n=Math.min(globalThis.devicePixelRatio||1,v.maxPixelRatio),r=Math.max(1,Math.round(e.width*n)),i=Math.max(1,Math.round(e.height*n));if(r===z&&i===O&&n===W)return!1;const o=!z||Math.abs(r/i-z/O)>.01;return W=n,z=r,O=i,t.width=z,t.height=O,Y!==W&&Je(),(o||We()&&!B.length&&!N)&&$e(),he=!0,!0}let at=null,lt=[0,0,0],ut=[1,1,1],st=[0,0,0,0],ct=T(1,1,1,1,"cover"),ft=null,ht=0,dt=0,mt=0,vt=0;const gt=()=>{at!==v&&(at=v,lt=b(v.ink),ut=b("auto"===v.highlight?"#ffffff":v.highlight),st="transparent"===v.background?[0,0,0,0]:[...b(v.background),1]),ft===v&&ht===z&&dt===O&&mt===ee&&vt===te||(ft=v,ht=z,dt=O,mt=ee,vt=te,ct=T(z,O,ee||1,te||1,v.fit,v.focus));const e=v.fluid,t=Q&&ie?q.height/O*ie*ct.scale[1]:1,n=(()=>{const e=Math.ceil(z/q.width),t=Math.ceil(O/q.height);if(P&&P.columns===e&&P.rows===t)return P;Oe();const n=()=>{const n=ze(A.NEAREST);return A.texStorage2D(A.TEXTURE_2D,1,L?A.RGBA8:A.RGBA16F,e,t),n},r=n(),i=n(),o=A.createFramebuffer();return A.bindFramebuffer(A.FRAMEBUFFER,o),A.framebufferTexture2D(A.FRAMEBUFFER,A.COLOR_ATTACHMENT0,A.TEXTURE_2D,r,0),A.framebufferTexture2D(A.FRAMEBUFFER,A.COLOR_ATTACHMENT1,A.TEXTURE_2D,i,0),A.drawBuffers([A.COLOR_ATTACHMENT0,A.COLOR_ATTACHMENT1]),P={ink:r,under:i,buffer:o,columns:e,rows:t}})(),r=C.locations,i=D.locations,o=M(v),a=!Ue();A.bindFramebuffer(A.FRAMEBUFFER,n.buffer),A.viewport(0,0,n.columns,n.rows),A.useProgram(C.program),A.activeTexture(A.TEXTURE0),A.bindTexture(A.TEXTURE_2D,S),A.uniform1i(r.uSource,0),A.activeTexture(A.TEXTURE2),A.bindTexture(A.TEXTURE_2D,(N?X?.velocity:B[J])??S),A.uniform1i(r.uFluid,2),A.activeTexture(A.TEXTURE3),A.bindTexture(A.TEXTURE_2D,_e??S),A.uniform1i(r.uTrailMap,3),A.uniform1f(r.uFluidMode,N?1:0),A.uniform1i(r.uHoverMode,"fluid"===o?1:"lantern"===o?2:"trail"===o&&ke?3:0),A.uniform1f(r.uTrailAmount,v.trail.strength),A.uniform3f(r.uLight,Ae.at?.[0]??-1,Ae.at?.[1]??-1,"lantern"===o?Ae.strength:0),A.uniform3fv(r.uTrail,Fe),A.uniform1f(r.uLightRadius,v.lantern.radius),A.uniform1f(r.uLightGain,v.lantern.strength),A.uniform1f(r.uClock,performance.now()/1e3),A.uniform1f(r.uFlicker,a?1:0),A.uniform2f(r.uFluidTexel,1/Math.max(1,j),1/Math.max(1,K)),A.uniform2f(r.uResolution,z,O),A.uniform2f(r.uCell,q.width,q.height),A.uniform1f(r.uCount,$),A.uniform4f(r.uSourceMap,ct.scale[0],ct.scale[1],ct.offset[0],ct.offset[1]),A.uniform1i(r.uScene,Q?0:"flow"===v.scene?1:"orb"===v.scene?2:"drift"===v.scene?4:3),A.uniform1i(r.uMark,"dot"===v.mark?1:0),A.uniform1f(r.uScale,v.scale);const l=v.clear;A.uniform4f(r.uClear,l?l.x:0,l?l.y:0,l?l.width:0,l?l.height:0),A.uniform1f(r.uClearFeather,l?l.feather:.1),A.uniform1f(r.uClearShape,l&&"rect"===l.shape?1:0);const u=performance.now();Me.fill(0);for(let e=0;e<we.length;e++){const t=we[e],n=4*e;Me[n]=t.x,Me[n+1]=t.y,Me[n+2]=(u-t.born)/1e3,Me[n+3]=t.strength}A.uniform4fv(r.uPulse,Me),A.uniform3fv(r.uInk,lt),A.uniform3fv(r.uHighlight,ut),A.uniform1f(r.uHighlightAuto,"auto"===v.highlight?1:0),A.uniform4fv(r.uBackground,st),A.uniform1f(r.uBrightness,v.brightness),A.uniform1f(r.uContrast,v.contrast),A.uniform1f(r.uGamma,v.gamma),A.uniform1f(r.uInvert,v.invert?1:0),A.uniform1f(r.uVignette,v.vignette),A.uniform1f(r.uSourceColor,"source"===v.color?1:0),A.uniform1f(r.uRefraction,e?e.refraction*(a?1:.25):0),A.uniform1f(r.uGlow,e?e.glow:0),A.uniform1f(r.uTime,fe),A.uniform1f(r.uDrift,a?v.drift:0),A.uniform1f(r.uFluidOn,e&&"fluid"===o&&(B.length||N)?1:0),A.uniform1f(r.uLod,Math.max(0,Math.log2(Math.max(1,t))-.5)),A.uniform1f(r.uSaturation,v.saturation),A.uniform1f(r.uShade,v.shade),A.uniform1f(r.uBackdrop,Q?v.backdrop:0),A.uniform2f(r.uLevels,Q&&"auto"===v.levels?le[0]:0,Q&&"auto"===v.levels?le[1]:1),A.bindVertexArray(V),A.drawArrays(A.TRIANGLES,0,3),A.bindFramebuffer(A.FRAMEBUFFER,null),A.viewport(0,0,z,O),A.useProgram(D.program),A.activeTexture(A.TEXTURE1),A.bindTexture(A.TEXTURE_2D,k),A.uniform1i(i.uAtlas,1),A.activeTexture(A.TEXTURE4),A.bindTexture(A.TEXTURE_2D,n.ink),A.uniform1i(i.uCells,4),A.activeTexture(A.TEXTURE5),A.bindTexture(A.TEXTURE_2D,n.under),A.uniform1i(i.uUnder,5),A.uniform2f(i.uResolution,z,O),A.uniform2f(i.uCell,q.width,q.height),A.uniform1f(i.uColumns,q.columns),A.uniform1f(i.uDotScale,v.dotScale),A.uniform1i(i.uMark,"dot"===v.mark?1:0),A.drawArrays(A.TRIANGLES,0,3)},pt=()=>!de&&!me&&!ve&&(pe||Ie)&&!("undefined"!=typeof document&&document.hidden),xt=e=>{if(se=0,!pt())return;const t=(e-ce)/1e3,n=Math.min(1/30,Math.max(1/480,t));ce=e,Te=.9*Te+.1*Math.min(t,.25),ye=Te>1/24?ye+1:0;try{ye>45&&be<2&&(ye=0,be++,1===be&&W>1?(v={...v,maxPixelRatio:1},ot()):be=2);const t=He(),r=!(ge||be>=2&&!(Q instanceof HTMLVideoElement)||(!(Q instanceof HTMLVideoElement)||Q.paused||Q.ended)&&(Ue()||!(!Q&&"none"!==v.scene||v.drift>0)));r&&(fe+=n*v.speed),Q instanceof HTMLVideoElement&&Q.readyState>=2&&oe&&(oe=!("requestVideoFrameCallback"in Q),rt(),he=!0);const i=M(v);let o="fluid"===i&&(null!==Ee||e<Ce);if(o?((e,t)=>{const n=v.fluid;if(!n||!I.length&&!N)return;if(N?!X?.ready():!U||!Ge(U))return;const r=z/O,i=Ee;if(Ee=null,N&&X){const o=5200*n.force*(Ue()?.35:1);return i&&(Ce=t+1e3*Math.min(9,6/Math.max(.3,n.dissipation))),void X.step(i?{from:i.from,to:i.to,force:[(i.to[0]-i.from[0])*o,(i.to[1]-i.from[1])*o]}:null,{dt:Math.min(e,1/60),radius:1.1*n.radius,curl:n.curl,dissipation:n.dissipation,aspect:r})}let o=0,a=0;if(i){const e=(Ue()?1:3)*n.force;o=(i.to[0]-i.from[0])*r*e*4,a=(i.to[1]-i.from[1])*e*4,Ce=t+7/Math.min(n.dissipation,1.1*n.dissipation)*1e3}const l=U.locations;A.bindFramebuffer(A.FRAMEBUFFER,I[1-J]),A.viewport(0,0,j,K),A.useProgram(U.program),A.activeTexture(A.TEXTURE0),A.bindTexture(A.TEXTURE_2D,B[J]),A.uniform1i(l.uPrevious,0),A.uniform2f(l.uSize,j,K),A.uniform1f(l.uAspect,r),A.uniform1f(l.uDt,e),A.uniform1f(l.uDecay,n.dissipation),A.uniform1f(l.uEnergyDecay,1.1*n.dissipation),A.uniform1f(l.uRadius,n.radius),A.uniform1f(l.uSplat,i?1:0),A.uniform2f(l.uFrom,i?.from[0]??0,i?.from[1]??0),A.uniform2f(l.uTo,i?.to[0]??0,i?.to[1]??0),A.uniform2f(l.uForce,o,a),A.drawArrays(A.TRIANGLES,0,3),J=1-J})(n,e):Ee=null,"lantern"===i&&(o=((e,t)=>{const n=v.lantern,r=Ae.target;if(r){const i=Ae.at;if(i)if(Ue())i[0]=r[0],i[1]=r[1];else{const t=1-Math.exp(-n.follow*e);i[0]+=(r[0]-i[0])*t,i[1]+=(r[1]-i[1])*t}else Ae.at=[r[0],r[1]];const o=Ae.at,a=Ae.trail[Ae.trail.length-1];n.trail>0&&(!a||t-a.born>28&&Math.hypot(o[0]-a.x,o[1]-a.y)>.004)&&(Ae.trail.push({x:o[0],y:o[1],born:t}),Ae.trail.length>12&&Ae.trail.shift())}Ae.strength+=((r?1:0)-Ae.strength)*(1-Math.exp(-e*(r?9:5)));const i=Ae.trail;for(let e=i.length-1;e>=0;e--)n.trail>0&&t-i[e].born<4e3*n.trail||i.splice(e,1);Fe.fill(0);for(let e=0;e<i.length;e++){const r=i[e];Fe[3*e]=r.x,Fe[3*e+1]=r.y,Fe[3*e+2]=.55*Math.exp(-(t-r.born)/1e3/Math.max(.01,n.trail))}return Ae.strength>.003||i.length>0})(n,e)||o),"trail"===i){const e=!!Le?.active;o=(e=>{const t=Se();return!(!t||!t.active||(t.step(e),A.bindTexture(A.TEXTURE_2D,_e),A.texSubImage2D(A.TEXTURE_2D,0,0,0,t.width,t.height,A.RGBA,A.UNSIGNED_BYTE,t.pixels),ke=!0,0))})(n)||o,e&&!Le?.active&&(he=!0)}if(we.length){for(let t=we.length-1;t>=0;t--)e-we[t].born>=2400&&we.splice(t,1);o=!0,we.length||(he=!0)}t&&(r||o||he)&&(gt(),he=!1,!Ie||ne&&!Q||(Ie=!1,Pe())),(r||o||Ee||!t)&&(se=requestAnimationFrame(xt))}catch(e){ve=!0,Xe(e)}};function Et(){!se&&pt()&&(ce=performance.now(),se=requestAnimationFrame(xt))}const Tt=(e,n)=>{const r=t.getBoundingClientRect();if(!r.width||!r.height)return null;const i=(e-r.left)/r.width,o=(n-r.top)/r.height;return i<-.05||i>1.05||o<-.05||o>1.05?null:[i,1-o]},yt=e=>{const t=M(v);if("none"===t||ge)return xe=null,void(Ae.target=null);if("lantern"===t)return Ae.target=e?[e[0],1-e[1]]:null,xe=null,void Et();if("trail"===t){const t=Se();if(!t)return;return e?t.move(e[0],1-e[1],v.trail.radius,performance.now()):t.leave(),xe=null,void Et()}e?(xe&&(Ee=Ee?{from:Ee.from,to:e}:{from:xe,to:e}),xe=e,Et()):xe=null},bt=e=>yt(Tt(e.clientX,e.clientY)),wt=()=>{xe=null,Le?.leave(),Ae.target&&(Ae.target=null,Et())},Mt=f.interactionTarget??window;Mt.addEventListener("pointermove",bt,{passive:!0}),Mt.addEventListener("pointerdown",bt,{passive:!0}),document.addEventListener("pointerleave",wt),Mt!==window&&Mt.addEventListener("pointerleave",wt);const Rt="function"==typeof ResizeObserver?new ResizeObserver(()=>{const e=ot();Re(),(e||he)&&Et()}):null;Rt?.observe(t),m&&Rt?.observe(m.element);const At="function"==typeof IntersectionObserver?new IntersectionObserver(([e])=>{pe=e.isIntersecting,pe&&(he=!0,Et())},{rootMargin:"120px"}):null;At?.observe(t);const Ft=()=>{document.hidden||(he=!0,Et())};document.addEventListener("visibilitychange",Ft);const Lt=()=>{he=!0,Et()};De?.addEventListener?.("change",Lt),Qe?.addEventListener?.("loadingdone",tt);const _t=e=>{e.preventDefault(),me=!0,se&&cancelAnimationFrame(se),se=0},kt=()=>{me=!1,H.length=0,G=null,C=D=U=null,P=null,B=[],I=[],X=null,N=!1,_e=null,Le=null,ke=!1;try{it(),he=!0,Et()}catch(e){Xe(e)}};t.addEventListener("webglcontextlost",_t),t.addEventListener("webglcontextrestored",kt);const St=()=>{Z&&(Z.pause(),Z.removeAttribute("src"),Z.load(),Z=null),ae&&Q instanceof HTMLVideoElement&&"cancelVideoFrameCallback"in Q&&Q.cancelVideoFrameCallback(ae),ae=0},Ct=()=>{re?.close(),re=null},Dt=async e=>{const t=await createImageBitmap(e,{premultiplyAlpha:"none"}),n=[t.width,t.height],r=function(e,t,n,r,i,o,a=8){if(!(n>=16&&r>=16&&e>0&&t>0&&i>0))return 1;const l=a/i/("cover"===o?Math.min(e/n,t/r):Math.max(e/n,t/r));return l>.5?1:2**-Math.floor(Math.log2(1/l))}(t.width,t.height,z,O,q.height,v.fit);if(1===r)return{element:t,owned:!0,natural:n};try{return{element:await createImageBitmap(t,{resizeWidth:Math.max(1,Math.round(t.width*r)),resizeHeight:Math.max(1,Math.round(t.height*r)),resizeQuality:"medium",premultiplyAlpha:"none"}),owned:!0,natural:n}}finally{t.close()}},Ut=e=>new Promise((t,n)=>{const r=new Image;r.crossOrigin="anonymous",r.decoding="async",r.onload=()=>t({element:r,owned:!1}),r.onerror=()=>n(new Error("The glyph field could not load this image.")),r.src=e}),Pt=async e=>{const t=++ne;if(!e)return St(),Ct(),Q=null,ee=te=ie=0,he=!0,void Et();try{const n=await(async e=>{if("string"!=typeof e)return e instanceof HTMLImageElement&&!e.complete?e.decode().then(()=>({element:e,owned:!1})):e instanceof HTMLVideoElement&&e.readyState<2?new Promise((t,n)=>{e.addEventListener("loadeddata",()=>t({element:e,owned:!1}),{once:!0}),e.addEventListener("error",()=>n(new Error("The glyph field could not load this video.")),{once:!0})}):{element:e,owned:!1};if(F.test(e))return new Promise((t,n)=>{const r=document.createElement("video");r.crossOrigin="anonymous",r.muted=!0,r.loop=!0,r.playsInline=!0,r.preload="auto",r.addEventListener("loadeddata",()=>{r.play().catch(()=>{}),t({element:r,owned:!0})},{once:!0}),r.addEventListener("error",()=>n(new Error("The glyph field could not load this video.")),{once:!0}),r.src=e});if("function"!=typeof createImageBitmap||"function"!=typeof fetch)return Ut(e);let t;try{t=await(await fetch(e).then(e=>{if(!e.ok)throw new Error("The glyph field could not load this image.");return e})).blob()}catch(t){if(t instanceof Error&&t.message.startsWith("The glyph field"))throw t;return Ut(e)}if(!/svg/i.test(t.type))try{return await Dt(t)}catch{}const n=URL.createObjectURL(t);try{return await Ut(n)}finally{URL.revokeObjectURL(n)}})(e),{element:r,owned:i}=n;if(de||t!==ne)return void(i&&r instanceof HTMLVideoElement?(r.pause(),r.removeAttribute("src"),r.load()):i&&r.close());St(),Ct(),Z=i&&r instanceof HTMLVideoElement?r:null,!i||r instanceof HTMLVideoElement||(re=r),Q=r,ie=r instanceof HTMLVideoElement?r.videoHeight:r instanceof HTMLImageElement?r.naturalHeight:r.height,ee=n.natural?.[0]??(r instanceof HTMLVideoElement?r.videoWidth:r instanceof HTMLImageElement?r.naturalWidth:r.width),te=n.natural?.[1]??ie,oe=!0,r instanceof HTMLVideoElement&&(e=>{const t=e;if(!t.requestVideoFrameCallback)return;const n=()=>{de||Q!==e||(oe=!0,Et(),ae=t.requestVideoFrameCallback(n))};ae=t.requestVideoFrameCallback(n)})(r),me||rt(),he=!0,Et()}catch(e){Ie&&t===ne&&(Ie=!1,Pe()),Xe(e)}};it(),Re();const Bt=f.source??null;return Bt?(ne=1,Pt(Bt)):Et(),{canvas:t,ready:Be,get settings(){return v},update(e){const n=v,{element:r,rest:i}=d(e);"clear"in e&&(m&&m.element!==r?.element&&Rt?.unobserve(m.element),m=r,r&&Rt?.observe(r.element)),v=E(r?{...i,clear:v.clear}:i,v),Re(),e.label&&t.setAttribute("aria-label",e.label),v.maxPixelRatio!==n.maxPixelRatio&&ot(),v.charset===n.charset&&v.cellSize===n.cellSize&&v.cellAspect===n.cellAspect&&v.font===n.font&&v.fontWeight===n.fontWeight&&v.glyphScale===n.glyphScale||(Je(),Ze()),v.levels!==n.levels&&nt();((!!n.fluid&&"fluid"===M(n))!==We()||v.fluid&&n.fluid&&v.fluid.resolution!==n.fluid.resolution)&&$e(),"source"in e&&Pt(e.source??null),he=!0,Et()},setSource:Pt,pointer(e,n=0){if(null===e)return void yt(null);const r=t.getBoundingClientRect();yt(Tt(r.left+e,r.top+n))},redraw(){if(!(de||me||ve)&&z)try{He(!0)&&gt()}catch(e){ve=!0,Xe(e)}},pulse(e={}){if(de||Ue())return;const n=t.getBoundingClientRect();if(!n.width||!n.height)return;let r=e.x??n.width/2,i=e.y??n.height/2;if(e.element){const t=e.element.getBoundingClientRect();r=t.left+t.width/2-n.left,i=t.top+t.height/2-n.top}const o=Math.max(0,Math.min(2,e.strength??1));we=[...we,{x:r/n.width,y:i/n.height,born:performance.now(),strength:o}].slice(-4),Et()},play(){ge=!1,Q instanceof HTMLVideoElement&&Q.play().catch(()=>{}),he=!0,Et()},pause(){ge=!0,Q instanceof HTMLVideoElement&&Q.pause()},resize(){ot()&&Et()},destroy(){if(!de&&(de=!0,se&&cancelAnimationFrame(se),Mt.removeEventListener("pointermove",bt),Mt.removeEventListener("pointerdown",bt),document.removeEventListener("pointerleave",wt),Mt.removeEventListener("pointerleave",wt),document.removeEventListener("visibilitychange",Ft),De?.removeEventListener?.("change",Lt),Qe?.removeEventListener?.("loadingdone",tt),t.removeEventListener("webglcontextlost",_t),t.removeEventListener("webglcontextrestored",kt),Rt?.disconnect(),At?.disconnect(),St(),Ct(),Q=null,!me)){Ye(),Oe(),A.deleteTexture(k),A.deleteTexture(S);for(const e of[C,D,U])e&&A.deleteProgram(e.program);X?.dispose(),_e&&A.deleteTexture(_e),H.forEach(e=>A.deleteShader(e)),V&&A.deleteVertexArray(V)}}}}export{m as DEFAULT_FIELD_FLUID,h as DEFAULT_FIELD_LANTERN,v as DEFAULT_FIELD_SETTINGS,d as DEFAULT_FIELD_TRAIL,f as FIELD_HOVERS,M as activeFieldHover,y as fieldCellLayout,R as fieldClearFromRects,x as fieldGlyphs,w as fieldLevels,T as fieldSourceRect,_ as glyphFieldTier,k as isGlyphFieldSupported,S as mountGlyphField,E as normalizeFieldSettings};//# sourceMappingURL=field.js.map