asciify-engine 4.25.0 → 4.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/field.cjs +1 -1
  3. package/dist/field.d.cts +132 -275
  4. package/dist/field.d.ts +132 -275
  5. package/dist/field.js +1 -1
  6. package/dist/glyph-field-C2fQIlnq.d.cts +292 -0
  7. package/dist/glyph-field-DRC-7uVf.d.ts +292 -0
  8. package/dist/kit/{chunk-KS3TL3S2.js → chunk-2FH6GGV6.js} +1 -1
  9. package/dist/kit/chunk-2GAV4OAK.js +1 -0
  10. package/dist/kit/{chunk-3F6FTBMN.js → chunk-2LBQ7QJT.js} +1 -1
  11. package/dist/kit/chunk-3LJ5C3U3.js +1 -0
  12. package/dist/kit/{chunk-SQGJ263V.js → chunk-3MHEDZLZ.js} +1 -1
  13. package/dist/kit/{chunk-KTYCGGRO.js → chunk-3OD5BMF5.js} +1 -1
  14. package/dist/kit/{chunk-OVCIHOOS.js → chunk-3VEMKDBW.js} +1 -1
  15. package/dist/kit/{chunk-H4DBJULW.js → chunk-42KMN727.js} +1 -1
  16. package/dist/kit/{chunk-PND73YEV.js → chunk-6G5MPND3.js} +1 -1
  17. package/dist/kit/{chunk-CA5J25SV.js → chunk-6WU247CB.js} +1 -1
  18. package/dist/kit/{chunk-6L2GAV5C.js → chunk-776H2BJ7.js} +1 -1
  19. package/dist/kit/{chunk-4SHQA3CO.js → chunk-7DXSG526.js} +1 -1
  20. package/dist/kit/{chunk-L3P7ZDSK.js → chunk-7M2FFUZ3.js} +1 -1
  21. package/dist/kit/{chunk-XYLLL2PQ.js → chunk-7NVKRDKE.js} +1 -1
  22. package/dist/kit/chunk-AIGAJMNN.js +1 -0
  23. package/dist/kit/{chunk-XXPDXJMQ.js → chunk-B3TY6UXJ.js} +1 -1
  24. package/dist/kit/chunk-BFBG3VHI.js +1 -0
  25. package/dist/kit/{chunk-6XNIZ52V.js → chunk-CG6SGQMY.js} +1 -1
  26. package/dist/kit/{chunk-KBXVIAWS.js → chunk-CGSWUO67.js} +1 -1
  27. package/dist/kit/{chunk-YDP6566E.js → chunk-DAJSBX6H.js} +1 -1
  28. package/dist/kit/{chunk-PFNFW4ZI.js → chunk-DCLJHSWM.js} +1 -1
  29. package/dist/kit/{chunk-QTVMD62E.js → chunk-DMRPBFLW.js} +1 -1
  30. package/dist/kit/{chunk-CR7QS3B3.js → chunk-E3FPFANJ.js} +1 -1
  31. package/dist/kit/{chunk-LUOIH2EM.js → chunk-EKYEA7Z4.js} +1 -1
  32. package/dist/kit/{chunk-QKZYCROD.js → chunk-F6F2C3RH.js} +1 -1
  33. package/dist/kit/chunk-FEAFQY7O.js +1 -0
  34. package/dist/kit/{chunk-A6EDU3UT.js → chunk-FHN3PPSI.js} +1 -1
  35. package/dist/kit/{chunk-FIWIUXNV.js → chunk-FPCCX4FM.js} +1 -1
  36. package/dist/kit/{chunk-EHYFSX5K.js → chunk-FWODCYYJ.js} +1 -1
  37. package/dist/kit/{chunk-PZAQLUAS.js → chunk-GBNV3GXG.js} +1 -1
  38. package/dist/kit/chunk-GF72XM4C.js +1 -0
  39. package/dist/kit/{chunk-MRDEO5HZ.js → chunk-GKR3ROYR.js} +1 -1
  40. package/dist/kit/{chunk-D4KCZSIP.js → chunk-H5KHXZWR.js} +1 -1
  41. package/dist/kit/{chunk-EJ3W4FN6.js → chunk-I5R2347P.js} +1 -1
  42. package/dist/kit/{chunk-ZYYLWSPY.js → chunk-IBNDCKEX.js} +1 -1
  43. package/dist/kit/{chunk-A73S2BCE.js → chunk-ILXVBO7M.js} +1 -1
  44. package/dist/kit/{chunk-QZHZWO7N.js → chunk-JELJEEJO.js} +1 -1
  45. package/dist/kit/{chunk-XDNCXCR7.js → chunk-K6JX64AS.js} +1 -1
  46. package/dist/kit/{chunk-2D2SG6FW.js → chunk-KCEYRIU4.js} +1 -1
  47. package/dist/kit/{chunk-SE6MQQLB.js → chunk-KDROJANO.js} +1 -1
  48. package/dist/kit/{chunk-ODMQERGZ.js → chunk-KEE55QSD.js} +1 -1
  49. package/dist/kit/{chunk-YBBYCUFP.js → chunk-KFXMLIVF.js} +1 -1
  50. package/dist/kit/{chunk-P6S6HSN3.js → chunk-KLRF3S7O.js} +1 -1
  51. package/dist/kit/{chunk-ANLBW5RI.js → chunk-KXI3T72F.js} +1 -1
  52. package/dist/kit/{chunk-PDNKLLBI.js → chunk-KY5KCIZE.js} +1 -1
  53. package/dist/kit/{chunk-PCGRRX5P.js → chunk-LE53AVKR.js} +1 -1
  54. package/dist/kit/{chunk-MIA6TUQC.js → chunk-LVCGC3SO.js} +1 -1
  55. package/dist/kit/chunk-M5VVLYI2.js +1 -0
  56. package/dist/kit/{chunk-KJJJORN6.js → chunk-MJJSZS23.js} +1 -1
  57. package/dist/kit/{chunk-EPVZIGEZ.js → chunk-MZFK765W.js} +1 -1
  58. package/dist/kit/{chunk-G4BQYQJ6.js → chunk-NF4WVWAC.js} +1 -1
  59. package/dist/kit/{chunk-JRSCTMFM.js → chunk-NW4U2YRE.js} +1 -1
  60. package/dist/kit/chunk-ORSI57T2.js +1 -0
  61. package/dist/kit/{chunk-GCIZKMJL.js → chunk-QSTDYL35.js} +1 -1
  62. package/dist/kit/{chunk-B6GZ645M.js → chunk-QVICL57J.js} +1 -1
  63. package/dist/kit/{chunk-JAM46FF6.js → chunk-RXOPJJC4.js} +1 -1
  64. package/dist/kit/{chunk-YL2AXWRA.js → chunk-SDSW7422.js} +1 -1
  65. package/dist/kit/{chunk-WT3XOWAI.js → chunk-TRDA73HN.js} +1 -1
  66. package/dist/kit/{chunk-VFV7HZ3G.js → chunk-UOYBFLGJ.js} +1 -1
  67. package/dist/kit/{chunk-EWPT56TR.js → chunk-UVLKCAKH.js} +1 -1
  68. package/dist/kit/{chunk-EHXSXMBE.js → chunk-VBWG7HQA.js} +1 -1
  69. package/dist/kit/{chunk-2TQW2PIG.js → chunk-VIUEAEBX.js} +1 -1
  70. package/dist/kit/{chunk-BBOSHBZK.js → chunk-VV2KP7ZW.js} +1 -1
  71. package/dist/kit/{chunk-4XT5IKM3.js → chunk-VX2AD3M7.js} +1 -1
  72. package/dist/kit/{chunk-2KFBIOVS.js → chunk-W4U5CK6L.js} +1 -1
  73. package/dist/kit/{chunk-6YVZZ3DO.js → chunk-WI2AF2KE.js} +1 -1
  74. package/dist/kit/{chunk-HMW5NRUV.js → chunk-XUCYFWC3.js} +1 -1
  75. package/dist/kit/{chunk-V6GEQ5Y5.js → chunk-Z2FRNFMB.js} +1 -1
  76. package/dist/kit/{chunk-NPJ77LDC.js → chunk-Z3KD6CLE.js} +1 -1
  77. package/dist/kit/{chunk-ONVONNUH.js → chunk-ZCXNGJGB.js} +1 -1
  78. package/dist/kit/{chunk-IUX46UDR.js → chunk-ZWZZMU5P.js} +1 -1
  79. package/dist/kit.cjs +1 -1
  80. package/dist/kit.d.cts +1 -1
  81. package/dist/kit.d.ts +1 -1
  82. package/dist/kit.js +1 -1
  83. package/dist/studio-worker.js +1 -0
  84. package/dist/studio.cjs +1 -1
  85. package/dist/studio.js +1 -1
  86. package/package.json +2 -1
  87. package/skills/asciify-engine/SKILL.md +2 -2
  88. package/skills/asciify-engine/references/performance.md +1 -1
  89. package/dist/kit/chunk-2JQ4MRIP.js +0 -1
  90. package/dist/kit/chunk-BJRPY7UB.js +0 -1
  91. package/dist/kit/chunk-EK4GIXIO.js +0 -1
  92. package/dist/kit/chunk-PFQBEWCF.js +0 -1
  93. package/dist/kit/chunk-SAQJAH2I.js +0 -1
  94. package/dist/kit/chunk-SVXRMHUJ.js +0 -1
  95. package/dist/kit/chunk-UO4U32P7.js +0 -1
  96. package/dist/kit/chunk-XVB3B3FV.js +0 -1
package/dist/field.d.ts CHANGED
@@ -1,286 +1,143 @@
1
- import { H as HoverScope } from './hover-guide-DdFnAnfU.js';
1
+ export { D as DEFAULT_FIELD_FLUID, a as DEFAULT_FIELD_LANTERN, b as DEFAULT_FIELD_SETTINGS, c as DEFAULT_FIELD_TRAIL, F as FIELD_HOVERS, d as FIELD_MARKS, e as FIELD_SCENES, f as FieldClearElement, g as FieldClearZone, h as FieldColor, i as FieldFit, j as FieldFluidOptions, k as FieldHover, l as FieldLanternOptions, m as FieldMark, n as FieldScene, o as FieldSource, p as FieldTrailOptions, G as GlyphField, q as GlyphFieldInput, r as GlyphFieldOptions, s as GlyphFieldSettings, t as activeFieldHover, u as fieldCellLayout, v as fieldClearFromRects, w as fieldGlyphs, x as fieldLevels, y as fieldSourceRect, z as glyphFieldTier, A as isGlyphFieldSupported, B as mountGlyphField, C as normalizeFieldSettings, E as prewarmGlyphAtlas } from './glyph-field-DRC-7uVf.js';
2
+ export { H as FieldHoverScope } from './hover-guide-DdFnAnfU.js';
2
3
 
3
- /** Pure option handling for the GPU glyph field, kept free of DOM and WebGL so it can be tested in Node. */
4
-
5
- type FieldColor = 'ink' | 'source';
6
- type FieldFit = 'cover' | 'contain';
7
- /**
8
- * Generated motion used when there is no media source. The later ones are the kit's background scenes: caustic light, drifting contour lines, aurora ribbons, water rings, a halo,
9
- * two interfering ring grids, a gradient mesh, falling streaks in three depths, and linked points.
10
- */
11
- declare const FIELD_SCENES: readonly ["none", "flow", "orb", "drift", "swell", "caustics", "contour", "aurora", "ripple", "halo", "interference", "mesh", "cascade", "constellation"];
12
- type FieldScene = typeof FIELD_SCENES[number];
13
- /**
14
- * What each cell draws. `glyph` draws characters from the atlas; `dot` draws a halftone: one circle per cell, its area
15
- * following the tone. The rest are the Studio's tile and print styles, drawn per cell from the cell's tone and colour:
16
- * tiles (`pixel`, `mosaic`, `lego`, `voxel`, `disco`, `hex`, `led`) fade with the tone in ink colour and stay solid in source
17
- * colour; `dither` lights whole cells through a 4x4 Bayer screen; `cmyk` separates the cell's colour into offset ink dots;
18
- * the print marks (`stipple`, `engraving`, `crosshatch`, `woodblock`, `risograph`, `pointillism`) carry the tone in their size.
19
- * Every mark but `dot` leaves empty the cells the charset would leave blank.
20
- */
21
- declare const FIELD_MARKS: readonly ["glyph", "dot", "pixel", "mosaic", "lego", "voxel", "disco", "dither", "hex", "led", "cmyk", "stipple", "engraving", "crosshatch", "woodblock", "risograph", "pointillism"];
22
- type FieldMark = typeof FIELD_MARKS[number];
23
- /**
24
- * An area the field keeps quiet, so content above it stays readable. Coordinates are fractions of the field,
25
- * from the top left; `feather` is the soft edge, as a fraction of the field's shorter side.
26
- */
27
- interface FieldClearZone {
28
- x: number;
29
- y: number;
30
- width: number;
31
- height: number;
32
- feather: number;
33
- shape: 'ellipse' | 'rect';
34
- }
35
4
  /**
36
- * How the field answers the pointer.
37
- * - `fluid`: an incompressible fluid that bends the image and swirls the characters. Bold, liquid.
38
- * - `lantern`: a soft light that glides after the pointer and lifts the characters it passes. Subtle, no distortion.
39
- * - `trail`: the Asciify Trail. The same ink field as the studio's Trail; the wake folds each character's tone, then settles.
40
- * - `loupe`: a glass loupe glides after the pointer and magnifies what it covers, easing through a soft rim. Uses `lantern` for radius, gain and follow.
41
- * - `reveal`: under a soft spotlight the characters take the source's own colours. Uses `lantern` settings.
42
- * - `none`: still under the pointer.
5
+ * What every glyph field on a page shares: one WebGL2 context drawing off screen, one frame loop, one set of page listeners
6
+ * and observers, and the programs and glyph atlases built in that context. Each field draws its frame into the shared
7
+ * context and copies it to its own canvas with a 2D blit, so a page with many fields holds one context: no per-field context
8
+ * creation or teardown, no browser context limit, and fields stay ordinary canvases in the page's own stacking and clipping.
43
9
  */
44
- type FieldHover = 'fluid' | 'lantern' | 'trail' | 'loupe' | 'reveal' | 'none';
45
- declare const FIELD_HOVERS: readonly {
46
- value: FieldHover;
47
- label: string;
48
- description: string;
49
- }[];
50
- interface FieldLanternOptions {
51
- /** Light radius as a fraction of the field height. */
52
- radius: number;
53
- /** How far the light lifts characters toward denser glyphs. */
54
- strength: number;
55
- /** Seconds the light's tail takes to fade. 0 is no tail. */
56
- trail: number;
57
- /** How quickly the light catches the pointer; higher follows more tightly. */
58
- follow: number;
10
+ type Locations = Record<string, WebGLUniformLocation | null>;
11
+ interface SharedProgram {
12
+ program: WebGLProgram;
13
+ locations: Locations | null;
14
+ fragment: WebGLShader;
59
15
  }
60
- declare const DEFAULT_FIELD_LANTERN: FieldLanternOptions;
61
- /** Matches the studio's Trail hover settings, so a look moves between renderers unchanged. */
62
- interface FieldTrailOptions {
63
- /** 0–1. How far the wake folds tone. */
64
- strength: number;
65
- /** 0.1–1. Wake width. */
66
- radius: number;
16
+ interface SharedAtlas {
17
+ key: string;
18
+ texture: WebGLTexture;
19
+ refs: number;
20
+ final: boolean;
21
+ users: Set<() => void>;
22
+ bytes: number;
67
23
  }
68
- declare const DEFAULT_FIELD_TRAIL: FieldTrailOptions;
69
- interface FieldFluidOptions {
70
- /** Brush radius as a fraction of the field height. */
71
- radius: number;
72
- /** How strongly pointer speed pushes the fluid. */
73
- force: number;
74
- /** Velocity decay per second; higher settles faster. */
75
- dissipation: number;
76
- /** How far the fluid bends the sampled image, in field heights per unit of velocity. */
77
- refraction: number;
78
- /** How much a wake lifts characters toward denser glyphs and the highlight color. */
79
- glow: number;
80
- /** Longest side of the simulation grid in cells. */
81
- resolution: number;
82
- /** Vorticity: how strongly the wake curls into eddies. 0 is a straight smear. */
83
- curl: number;
84
- }
85
- interface GlyphFieldSettings {
86
- /** Ordered from empty to dense. Any graphemes, including words such as " .:-+ASCIIFY". */
87
- charset: string;
88
- /** Cell height in CSS pixels. */
89
- cellSize: number;
90
- /** Cell width divided by height. 1 is a square grid. */
91
- cellAspect: number;
92
- /**
93
- * `pixel` (default) snaps each cell to whole device pixels. `fill` fits a whole number of cells of at least `cellSize` to the
94
- * canvas and stretches them to fill it exactly, as asciify-engine's core renderer lays out a frame, so rows and columns keep their
95
- * place however fractional the cell; each glyph sits centred in its cell, texel for texel.
96
- */
97
- cellFit: 'pixel' | 'fill';
98
- font: string;
99
- fontWeight: number;
100
- /** Glyph height relative to the cell. */
101
- glyphScale: number;
102
- /** See `FIELD_MARKS`. Only `glyph` reads the atlas; the others keep the charset for which cells stay empty. */
103
- mark: FieldMark;
104
- /** Largest dot as a fraction of the cell, for `mark: 'dot'`. */
105
- dotScale: number;
106
- color: FieldColor;
107
- ink: string;
108
- /** Color the wake lifts characters toward. 'auto' brightens each character's own color, so the wake never turns white. */
109
- highlight: string;
110
- /** Any CSS hex color, or "transparent". */
111
- background: string;
112
- brightness: number;
113
- contrast: number;
114
- gamma: number;
115
- invert: boolean;
116
- /** Stretch the source's tonal range so the whole charset is used. */
117
- levels: 'auto' | 'none';
118
- /**
119
- * Shades between two characters, drawn by mixing them across neighbouring cells in a fixed 4x4 pattern, as print
120
- * halftones do. 'auto' turns it on for photo and video sources drawn in nine or fewer characters, whose steps are coarse;
121
- * drawn interface shapes stay crisp, and ramps of ten and more are left as they are. 'coarse' does the same for any
122
- * source, for a canvas that paints photos. The pattern is locked to the grid, so it never shimmers.
123
- */
124
- blend: 'auto' | 'coarse' | 'on' | 'off';
125
- /** Glyph color saturation in source color mode. */
126
- saturation: number;
127
- /** How far darker tones dim the ink, so same-density glyphs such as words still carry the image. 0 is one flat ink. */
128
- shade: number;
129
- /** Opacity of a soft, blurred copy of the source behind the characters. 0 draws characters only. */
130
- backdrop: number;
131
- fit: FieldFit;
132
- /** Crop focus for cover, 0–1 on each axis. */
133
- focus: {
134
- x: number;
135
- y: number;
136
- };
137
- vignette: number;
138
- /** Slow ambient sampling drift that keeps a still image alive. 0 is still. */
139
- drift: number;
140
- scene: FieldScene;
141
- /** Size of generated scenes' features; higher is finer. */
142
- scale: number;
143
- clear: FieldClearZone | false;
144
- /** Speed of generated scenes and drift. */
145
- speed: number;
146
- /** Which pointer response runs. */
147
- hover: FieldHover;
148
- /** Settings for the fluid hover. `false` turns it off. */
149
- fluid: FieldFluidOptions | false;
150
- /** Settings for the lantern hover. */
151
- lantern: FieldLanternOptions;
152
- /** Settings for the trail hover. */
153
- trail: FieldTrailOptions;
154
- /**
155
- * Where the pointer response acts: `all` (default) everywhere, `subject` only on the source's subject, `outside` only on
156
- * the ground around it. The subject is the source's alpha, or for an opaque source on a plain, even ground, its distance
157
- * from that ground; feathered at the edge. A busy photo and generated scenes have no subject, so every scope acts as `all`.
158
- */
159
- scope: HoverScope;
160
- maxPixelRatio: number;
161
- /** Stop ambient motion and soften the wake when the visitor prefers reduced motion. */
162
- respectReducedMotion: boolean;
24
+ type Task = (now: number) => void;
25
+ /** How many times longer ambient motion waits between frames right now: 1, or 2 to 3 while frames are expensive. */
26
+ declare const ambientFactor: () => number;
27
+ /** Runs `task` in the next shared frame. A task asked for twice in one frame runs once. */
28
+ declare function requestFieldFrame(task: Task): void;
29
+ declare function cancelFieldFrame(task: Task): void;
30
+ interface PointerClient {
31
+ /** Where to read the pointer: the window, or an element whose events (bubbling from its content) count. */
32
+ target: HTMLElement | Window;
33
+ move(event: PointerEvent): void;
34
+ /** The pointer left the target, or the page. */
35
+ leave(): void;
36
+ /** False while the field cannot be under the pointer (scrolled away), so moves elsewhere skip it. */
37
+ near?(): boolean;
38
+ inside?: boolean;
163
39
  }
164
- declare const DEFAULT_FIELD_FLUID: FieldFluidOptions;
165
- declare const DEFAULT_FIELD_SETTINGS: GlyphFieldSettings;
166
- type GlyphFieldInput = Partial<Omit<GlyphFieldSettings, 'fluid' | 'focus' | 'lantern' | 'trail' | 'clear'>> & {
167
- clear?: Partial<FieldClearZone> | false;
168
- trail?: Partial<FieldTrailOptions>;
169
- fluid?: Partial<FieldFluidOptions> | boolean;
170
- lantern?: Partial<FieldLanternOptions>;
171
- focus?: Partial<GlyphFieldSettings['focus']>;
172
- };
173
- /** Splits a charset into graphemes so emoji, block elements and accented letters each take one cell. */
174
- declare function fieldGlyphs(charset: string): string[];
175
- /** Mount options may carry an element-based clear zone; normalizing ignores it, since only a mounted field can measure it. */
176
- type ClearElementLike = {
177
- element: unknown;
178
- } | {
179
- nodeType: number;
180
- };
181
- declare function normalizeFieldSettings(input?: Omit<GlyphFieldInput, 'clear'> & {
182
- clear?: GlyphFieldInput['clear'] | ClearElementLike;
183
- }, base?: GlyphFieldSettings): GlyphFieldSettings;
184
- /** Maps field UV (0–1, top left) to source UV as `source = uv * scale + offset`. */
185
- declare function fieldSourceRect(fieldWidth: number, fieldHeight: number, sourceWidth: number, sourceHeight: number, fit: FieldFit, focus?: {
186
- x: number;
187
- y: number;
188
- }): {
189
- scale: [number, number];
190
- offset: [number, number];
191
- };
192
- /** Device-pixel cell and atlas layout. Cells are whole pixels so glyphs are sampled texel for texel. */
193
- declare function fieldCellLayout(cellSize: number, cellAspect: number, pixelRatio: number, glyphCount: number): {
194
- width: number;
195
- height: number;
196
- columns: number;
197
- rows: number;
198
- atlasWidth: number;
199
- atlasHeight: number;
200
- };
201
- /** 2nd and 98th luminance percentiles of RGBA pixels, used to stretch a source across the charset. */
202
- declare function fieldLevels(pixels: Uint8ClampedArray | Uint8Array): [number, number];
203
- /** The hover that actually runs: fluid needs its settings, and anything is off without a pointer response. */
204
- declare const activeFieldHover: (settings: GlyphFieldSettings) => FieldHover;
205
- /** The clear zone around an element, in field fractions; `padding` is CSS pixels added on every side. */
206
- declare function fieldClearFromRects(field: {
207
- left: number;
208
- top: number;
209
- width: number;
210
- height: number;
211
- }, element: {
212
- left: number;
213
- top: number;
214
- width: number;
215
- height: number;
216
- }, padding?: number, feather?: number, shape?: FieldClearZone['shape']): FieldClearZone | false;
217
-
218
- type FieldSource = string | HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | ImageBitmap;
219
- interface GlyphFieldOptions extends Omit<GlyphFieldInput, 'clear'> {
220
- /**
221
- * Keep the field quiet around content: an element (followed through layout changes), an element with
222
- * `padding` (CSS px), `feather` (fraction of the shorter side) and `shape`, a zone in field fractions, or `false`.
223
- */
224
- clear?: GlyphFieldInput['clear'] | HTMLElement | FieldClearElement;
225
- /** Image or video URL, or a decoded element. Without one, `scene` draws generated motion. */
226
- source?: FieldSource | null;
227
- /** Where pointer movement is read. Defaults to the window, so overlaid text never blocks the wake. */
228
- interactionTarget?: HTMLElement | Window | null;
229
- /** Accessible name for the canvas. */
230
- label?: string;
231
- onError?: (error: Error) => void;
232
- }
233
- interface FieldClearElement {
234
- element: HTMLElement;
235
- padding?: number;
236
- feather?: number;
237
- shape?: 'ellipse' | 'rect';
238
- }
239
- interface GlyphField {
240
- readonly canvas: HTMLCanvasElement;
241
- /** Resolves once the first frame with the current source has been drawn. */
242
- readonly ready: Promise<void>;
243
- /** Merges settings; only the parts that changed are rebuilt. */
244
- update(options: GlyphFieldOptions): void;
245
- setSource(source: FieldSource | null): Promise<void>;
246
- /** Moves the wake programmatically, in CSS pixels relative to the canvas. `null` lifts the pointer. */
247
- pointer(x: number | null, y?: number): void;
248
- /**
249
- * Draws the current frame now, so the canvas can be read with drawImage in the same task. Before `ready`, while the shaders
250
- * are still compiling in parallel, it leaves the first frame to the field's own loop rather than block on the compiler.
251
- */
252
- redraw(): void;
40
+ /**
41
+ * Pointer moves over the target, through the page's one listener. A target in a shadow root, or in another document, is
42
+ * listened to directly: the page's listener can't see into either. Returns the unsubscribe.
43
+ */
44
+ declare function watchPointer(client: PointerClient): () => void;
45
+ /** The tab shown or hidden. */
46
+ declare const watchVisibility: (fn: () => void) => () => void;
47
+ /** `prefers-reduced-motion` changed. */
48
+ declare const watchMotionPreference: (fn: () => void) => () => void;
49
+ /** The colour scheme, or a class or attribute theme on the root, changed. */
50
+ declare const watchTheme: (fn: () => void) => () => void;
51
+ type GlCanvas = OffscreenCanvas | HTMLCanvasElement;
52
+ declare class FieldContext {
53
+ gl: WebGL2RenderingContext;
54
+ canvas: GlCanvas;
55
+ lost: boolean;
56
+ /** Index into SCALES; 0 is full resolution. */
57
+ level: number;
58
+ private programs;
59
+ private atlases;
60
+ private vertex;
61
+ private vao;
62
+ private clients;
63
+ private parallel;
64
+ private timer;
65
+ private queries;
66
+ private open;
67
+ private drew;
68
+ private gpuMs;
69
+ private cpuMs;
70
+ private changedAt;
71
+ private lostTimer;
72
+ renderer: string;
73
+ /** At the lowest render scale and still over budget: ambient motion slows down too. */
74
+ overloaded: boolean;
75
+ constructor();
76
+ private create;
77
+ private onLost;
78
+ private onRestored;
79
+ private restored;
80
+ private reset;
81
+ private idleTimer;
82
+ join(client: FieldClient): void;
83
+ leave(client: FieldClient): void;
84
+ private dispose;
85
+ /** The program for a fragment shader, linked once per context. Linking finishes in the browser's compiler. */
86
+ program(fragment: string): SharedProgram;
87
+ /** True once linked; collects uniform locations then. Throws if linking failed. */
88
+ linked(entry: {
89
+ program: WebGLProgram;
90
+ locations: Locations | null;
91
+ fragment?: WebGLShader;
92
+ }, wait?: boolean): boolean;
93
+ get canCompileInParallel(): boolean;
94
+ vertexArray(): WebGLVertexArrayObject;
253
95
  /**
254
- * Sends a ring through the field, for interface events such as a save or a message.
255
- * `x` and `y` are CSS pixels relative to the canvas, or pass an element to start from its centre. Ignored under reduced motion.
96
+ * The glyph atlas for `key`, painted once and shared by every field drawing the same glyphs at the same size. `final` is
97
+ * false when the font was still loading; the first field that sees it arrive repaints it for all.
256
98
  */
257
- pulse(options?: {
258
- x?: number;
259
- y?: number;
260
- element?: Element;
261
- strength?: number;
99
+ atlas(key: string, paint: () => {
100
+ image: TexImageSource;
101
+ final: boolean;
102
+ }, user: () => void): SharedAtlas;
103
+ /** True when the atlas for `key` exists, in use or spare. */
104
+ hasAtlas(key: string): boolean;
105
+ paintAtlas(entry: SharedAtlas, paint: () => {
106
+ image: TexImageSource;
107
+ final: boolean;
262
108
  }): void;
263
- play(): void;
264
- pause(): void;
265
- resize(): void;
266
- destroy(): void;
267
- readonly settings: GlyphFieldSettings;
109
+ releaseAtlas(entry: SharedAtlas | null, user: () => void): void;
110
+ /** Deletes unused atlases past the spare count, oldest first, and all of them while over the memory budget. */
111
+ private evictAtlases;
112
+ /** Shrinks the shared drawing buffer once the largest field it was grown for has gone. */
113
+ private fit;
114
+ /** GPU memory the page's fields hold now, in bytes (see FIELD_MEMORY_BUDGET). */
115
+ get bytes(): number;
116
+ get overBudget(): boolean;
117
+ /** Room for another field: a margin under the budget, so fields at its edge don't mount and release in turn. */
118
+ get roomy(): boolean;
119
+ /** Readies the shared drawing buffer for a frame of this size; it only grows, in steps, so fields rarely resize it. */
120
+ begin(width: number, height: number): void;
121
+ /** Copies the frame just drawn (the bottom-left `width` x `height` of the shared buffer) to a field's canvas. */
122
+ present(target: CanvasRenderingContext2D, width: number, height: number): void;
123
+ /** After each shared frame: collects GPU time where the browser reports it, else main-thread draw time, and sets the scale. */
124
+ endFrame(now: number): void;
125
+ private adapt;
126
+ get scale(): number;
127
+ get atMinimum(): boolean;
268
128
  }
269
- /** 'gpu', 'software' (WebGL2 emulated on the CPU, such as SwiftShader or llvmpipe) or 'none'. Probed once per page. */
270
- declare function glyphFieldTier(): 'gpu' | 'software' | 'none';
271
- /**
272
- * True when WebGL2 runs on a GPU. Software WebGL draws every pixel on the CPU and would starve the rest of the page,
273
- * so it reports false unless `allowSoftware` is set. Mounting can still fail on a blocked GPU, so handle errors.
274
- */
275
- declare function isGlyphFieldSupported(options?: {
276
- allowSoftware?: boolean;
277
- }): boolean;
278
- /**
279
- * A GPU character field: every cell samples the source once and draws its glyph texel for texel,
280
- * while a small advected fluid bends the sampling and lifts characters under the cursor.
281
- * Idle fields stop drawing until the pointer, a video frame or generated motion needs a new frame.
282
- * 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.
283
- */
284
- declare function mountGlyphField(canvas: HTMLCanvasElement, options?: GlyphFieldOptions): GlyphField;
129
+ interface FieldClient {
130
+ lost(): void;
131
+ restored(): void;
132
+ rescale(): void;
133
+ /** The field's frame in device pixels. */
134
+ size(): [number, number];
135
+ /** GPU memory the field holds of its own, in bytes. */
136
+ bytes(): number;
137
+ }
138
+ /** The page's shared context if one exists, without making one. */
139
+ declare const currentFieldContext: () => FieldContext | null;
140
+ /** True for a field canvas drawn through the shared context: it holds no WebGL context of its own. */
141
+ declare const isSharedFieldCanvas: (canvas: object) => boolean;
285
142
 
286
- export { DEFAULT_FIELD_FLUID, DEFAULT_FIELD_LANTERN, DEFAULT_FIELD_SETTINGS, DEFAULT_FIELD_TRAIL, FIELD_HOVERS, FIELD_MARKS, FIELD_SCENES, type FieldClearElement, type FieldClearZone, type FieldColor, type FieldFit, type FieldFluidOptions, type FieldHover, HoverScope as FieldHoverScope, 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 };
143
+ export { ambientFactor, cancelFieldFrame, currentFieldContext, isSharedFieldCanvas, requestFieldFrame, watchMotionPreference, watchPointer, watchTheme, watchVisibility };