@fusion.dev/helix 0.142.1 → 0.143.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 (83) hide show
  1. package/README.md +9 -5
  2. package/custom-elements.json +19275 -1410
  3. package/dist/accordion/item/accordion-item.css.js +6 -1
  4. package/dist/accordion/item/accordion-item.d.ts +1 -0
  5. package/dist/announcement/root/announcement-root.d.ts +3 -3
  6. package/dist/app-header/root/app-header-root.d.ts +1 -3
  7. package/dist/aura/shader.d.ts +5 -1
  8. package/dist/aura/shader.js +88 -613
  9. package/dist/avatar/root/avatar-root.d.ts +3 -3
  10. package/dist/badge/root/badge-root.css.js +3 -2
  11. package/dist/badge/root/badge-root.d.ts +1 -0
  12. package/dist/bar-chart/root/bar-chart-root.css.js +10 -0
  13. package/dist/blockquote/root/blockquote-root.css.js +4 -0
  14. package/dist/card/root/card-root.css.js +1 -0
  15. package/dist/carousel/root/carousel-root.js +2 -1
  16. package/dist/checkbox/root/checkbox-root.css.js +3 -3
  17. package/dist/color-picker/root/color-picker-root.d.ts +0 -1
  18. package/dist/color-picker/root/color-picker-root.js +0 -1
  19. package/dist/combobox/root/combobox-root.css.js +5 -4
  20. package/dist/data-table/root/data-table-root.css.js +7 -0
  21. package/dist/date-picker/root/date-picker-root.css.js +15 -3
  22. package/dist/donut-chart/root/donut-chart-root.css.js +10 -0
  23. package/dist/drawer/root/drawer-root.css.js +25 -4
  24. package/dist/element-events.generated.js +41 -10
  25. package/dist/file-input/root/file-input-root.d.ts +0 -1
  26. package/dist/file-input/root/file-input-root.js +0 -1
  27. package/dist/filter-input/root/filter-input-root.d.ts +0 -1
  28. package/dist/filter-input/root/filter-input-root.js +0 -1
  29. package/dist/grid/root/grid-root.d.ts +15 -11
  30. package/dist/grid/root/grid-root.js +49 -15
  31. package/dist/icon-button/root/icon-button-root.css.js +43 -16
  32. package/dist/icon-button/root/icon-button-root.d.ts +4 -1
  33. package/dist/image/root/image-root.css.js +1 -1
  34. package/dist/input/root/input-root.d.ts +0 -1
  35. package/dist/input/root/input-root.js +0 -1
  36. package/dist/line-chart/root/line-chart-root.css.js +10 -0
  37. package/dist/list/item/list-item.css.js +7 -1
  38. package/dist/loader/root/loader-root.d.ts +7 -4
  39. package/dist/menu/item/menu-item.css.js +1 -1
  40. package/dist/menu/root/menu-root.d.ts +0 -3
  41. package/dist/meter/root/meter-root.css.js +4 -4
  42. package/dist/number-input/root/number-input-root.d.ts +3 -0
  43. package/dist/orb/root/orb-root.d.ts +11 -2
  44. package/dist/orb/root/orb-root.js +5 -1
  45. package/dist/qr-code/root/qr-code-root.css.js +2 -1
  46. package/dist/qr-code/root/qr-code-root.d.ts +2 -2
  47. package/dist/rating/root/rating-root.d.ts +3 -0
  48. package/dist/sandbox/index.d.ts +2 -2
  49. package/dist/sandbox/root/sandbox-root.d.ts +20 -1
  50. package/dist/sandbox/root/sandbox-root.js +27 -10
  51. package/dist/sandbox/source/sandbox-source.d.ts +6 -1
  52. package/dist/sandbox/source/sandbox-source.js +13 -3
  53. package/dist/sandbox/terminal/sandbox-terminal.d.ts +3 -1
  54. package/dist/sandbox/terminal/sandbox-terminal.js +3 -1
  55. package/dist/skeleton/root/skeleton-root.css.js +4 -0
  56. package/dist/skeleton/root/skeleton-root.d.ts +4 -1
  57. package/dist/table/column/table-column.css.js +5 -0
  58. package/dist/tabs/panel/tab-panel.css.js +1 -1
  59. package/dist/tabs/panel/tab-panel.d.ts +2 -0
  60. package/dist/tabs/root/tabs-root.css.js +13 -7
  61. package/dist/tabs/root/tabs-root.d.ts +5 -0
  62. package/dist/tabs/tab/tab.css.js +4 -4
  63. package/dist/tabs/tab/tab.d.ts +5 -0
  64. package/dist/tag/root/tag-root.d.ts +2 -3
  65. package/dist/text/root/text-root.css.js +58 -1
  66. package/dist/text/root/text-root.d.ts +11 -2
  67. package/dist/text/root/text-root.js +19 -1
  68. package/dist/time-input/root/time-input-root.d.ts +0 -1
  69. package/dist/time-input/root/time-input-root.js +0 -1
  70. package/dist/toast/root/toast-root.css.js +31 -7
  71. package/dist/toast/root/toast-root.d.ts +6 -9
  72. package/dist/toggle/group/toggle-group.d.ts +3 -0
  73. package/dist/tour/stop/tour-stop.css.js +5 -0
  74. package/dist/trend/root/trend-root.d.ts +0 -1
  75. package/dist/trend/root/trend-root.js +0 -1
  76. package/dist/video/root/video-root.d.ts +0 -1
  77. package/dist/video/root/video-root.js +0 -1
  78. package/dist/video-playlist/menu/video-playlist-menu.d.ts +0 -1
  79. package/dist/video-playlist/menu/video-playlist-menu.js +0 -1
  80. package/lint.json +1 -1
  81. package/package.json +6 -6
  82. package/types/jsx.d.ts +4973 -1964
  83. package/types/vue.d.ts +998 -212
@@ -37,29 +37,9 @@ export const FRAGMENT = `#version 300 es
37
37
  uniform float uBloom;
38
38
  uniform float uGain;
39
39
 
40
- /* How much the members of a population differ from one another, 1 being the
41
- resting amount and 0 a set that is uniform. Read by six of the eleven
42
- shapes, the ones made of many similar things, whether those are counted or
43
- continuous: streaks has lanes, particles has motes, rays has a fan, aurora
44
- has bundles and folds, caustics has three depths of fold and a pool over
45
- them, and fracture has territories and the borders between them. The five
46
- that do not read it refuse it. Braid, waves and flow have a single subject
47
- and nothing to vary. Threads and lattice are even on purpose, a scatter of
48
- strands and a regular structure each being what it is because its members do
49
- not differ. */
40
+ ${''}
50
41
  uniform float uVariance;
51
- /* Where the pointer has been, as x, y and age. Age runs 0 at the moment it
52
- was left to 1 when it has faded, so a spent slot costs a compare.
53
-
54
- **This is what a fluid gets from a framebuffer, bought for eight vec3s.** A
55
- real solver advects a dye texture and reads it back the next frame, which
56
- needs render targets, a ping-pong pair and about six passes. What a reader
57
- actually perceives from that is one thing: the cursor leaves a wake that
58
- outlives it. A short history of where it went reproduces that in the single
59
- pass this component already makes, at a fixed cost that does not grow with
60
- how long the page has been open. What it cannot do is conserve anything, so
61
- there is no diffusion and no pressure: the wake decays rather than
62
- spreading. Worth knowing before this is asked to be a simulation. */
42
+ ${''}
63
43
  uniform vec3 uTrail[9];
64
44
  uniform vec3 uGround;
65
45
  uniform vec3 uHaze;
@@ -73,10 +53,7 @@ export const FRAGMENT = `#version 300 es
73
53
  float noise(vec2 p) {
74
54
  vec2 i = floor(p);
75
55
  vec2 f = fract(p);
76
- /* Quintic rather than cubic. Cubic smoothstep leaves a second derivative
77
- discontinuity at every cell edge, which four octaves of lighting turn
78
- into a faint square lattice: visible on a large soft field and exactly
79
- the artifact that reads as cheap. */
56
+ ${''}
80
57
  vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
81
58
  return mix(
82
59
  mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),
@@ -85,9 +62,7 @@ export const FRAGMENT = `#version 300 es
85
62
  );
86
63
  }
87
64
 
88
- /* Each octave is rotated as well as scaled. Doubling alone stacks every
89
- octave on the same axes, so value noise keeps its grid however many are
90
- summed. An irrational angle means no two octaves ever line up again. */
65
+ ${''}
91
66
  const mat2 TURN = mat2(0.8, 0.6, -0.6, 0.8);
92
67
 
93
68
  float fbm(vec2 p) {
@@ -101,43 +76,22 @@ export const FRAGMENT = `#version 300 es
101
76
  return sum;
102
77
  }
103
78
 
104
- /* Domain warping: the field is sampled at coordinates another field bends.
105
- This is the single largest difference between noise that reads as organic
106
- and noise that reads as television static, and it costs two more fbm calls
107
- rather than a different algorithm. */
79
+ ${''}
108
80
  float warped(vec2 p, float t) {
109
81
  vec2 q = vec2(fbm(p + vec2(0.0, t)), fbm(p + vec2(5.2, 1.3 - t)));
110
82
  vec2 r = vec2(fbm(p + 4.0 * q + vec2(1.7, 9.2)), fbm(p + 4.0 * q + vec2(8.3, 2.8)));
111
83
  return fbm(p + 4.0 * r);
112
84
  }
113
85
 
114
- /* An exponential falloff without the corner.
115
- *
116
- * Both abs and length have a vertex at zero and their derivative jumps
117
- * across it, so a falloff built on either acquires a hard line exactly along
118
- * the axis it is brightest on. That is a crease in something meant to be
119
- * light, and it is what put a chevron through the braid wherever the
120
- * pointer's height crossed a strand.
121
- *
122
- * Softening the distance rather than the exponent keeps the tails identical
123
- * and rounds only the point. The softening scales with the rate, so a tight
124
- * falloff is rounded tightly and a broad one broadly: at zero the result is
125
- * exp(-0.06) whatever the rate is, six per cent off the peak, which is not
126
- * visible as flattening.
127
- *
128
- * Takes the squared distance, because every caller already has one and a
129
- * square root taken here is the one that would have reintroduced the vertex.
130
- */
86
+ ${''}
131
87
  float falloff(float d2, float k) {
132
88
  float soft = 0.06 / k;
133
89
  return exp(-sqrt(d2 + soft * soft) * k);
134
90
  }
135
91
 
136
- /* Two strands half a turn apart, inside a wider volume. */
92
+ ${''}
137
93
  float braid(vec2 q, float t, vec2 qp, float reach) {
138
- /* The strands lean toward the pointer where it is alongside them, and are
139
- unmoved where it is not. A braid is a thing with a length, so what it can
140
- answer with is a bend at a height, not a bulge at a point. */
94
+ ${''}
141
95
  float alongside = falloff((q.y - qp.y) * (q.y - qp.y), 1.1) * reach;
142
96
  float lean = qp.x * alongside * 0.45;
143
97
  float lit = 0.0;
@@ -147,25 +101,15 @@ export const FRAGMENT = `#version 300 es
147
101
  lit += 0.8 * falloff(x * x, 13.0);
148
102
  }
149
103
  float volume = falloff(q.x * q.x, 2.6);
150
- /* Compressed hard across the shaft and stretched along it. The same octaves
151
- sampled square read as haze; this way they read as filaments. */
104
+ ${''}
152
105
  float streak = fbm(vec2(q.x * 9.0, q.y * 0.55 - t * 2.4));
153
106
  float haze = warped(vec2(q.x * 0.8 + 4.0, q.y * 0.45), t * 0.5);
154
107
  return lit * mix(0.35, 1.65, streak) + volume * 0.3 * mix(0.35, 1.05, haze);
155
108
  }
156
109
 
157
- /* A fan from a source above the frame, cut into filaments by angle. */
110
+ ${''}
158
111
  float rays(vec2 q, float t, vec2 qp, float reach) {
159
- /* The pointer occludes, rather than the source moving. Swinging the source
160
- re-aims the whole fan, which is a thing a light does and not a thing that
161
- happens when something passes in front of one, so the source is fixed and
162
- the pointer is a blocker.
163
-
164
- The shadow is angular, not radial: it is the wedge of directions the
165
- object covers as seen from the source, and it only exists further along
166
- than the object is. A fragment nearer the source than the blocker is in
167
- front of it and stays lit, which is the whole difference between an
168
- occluder and a dark patch that follows the cursor. */
112
+ ${''}
169
113
  vec2 o = vec2(0.0, 1.45);
170
114
  vec2 v = q - o;
171
115
  float dist = length(v);
@@ -174,117 +118,61 @@ export const FRAGMENT = `#version 300 es
174
118
  vec2 vp = qp - o;
175
119
  float distToBlocker = length(vp);
176
120
  float angToBlocker = atan(vp.x, -vp.y);
177
- /* Widened with distance, so the object reads as a fixed size rather than
178
- as a wedge that grows the further out it is carried. */
121
+ ${''}
179
122
  float halfAngle = 0.055 / max(distToBlocker, 0.35);
180
123
  float across = (ang - angToBlocker) / halfAngle;
181
- /* Cast from the near edge of the object rather than from somewhere past it.
182
- Ramping in over a fifth of a unit started the shadow below the thing
183
- supposedly casting it, which reads as two objects. */
124
+ ${''}
184
125
  float behind = smoothstep(-0.03, 0.05, dist - distToBlocker);
185
126
  float shadow = exp(-across * across) * behind * reach;
186
127
 
187
128
  float fan = fbm(vec2(ang * 8.0, dist * 0.5 - t * 1.8));
188
129
  float dust = warped(vec2(ang * 2.0, dist * 0.7), t * 0.4);
189
130
  float spread = smoothstep(0.85, 0.05, abs(ang));
190
- /* The fan is a population too, even though it is continuous rather than
191
- counted: how far apart its light and dark run is how much one ray differs
192
- from the next. Both ends travel toward the midpoint, so a variance of 0
193
- is an even wash and a variance of 1 is exactly the pair this always had. */
131
+ ${''}
194
132
  float rv = clamp(uVariance, 0.0, 1.0);
195
133
  float lit = spread * mix(mix(0.85, 0.2, rv), mix(0.85, 1.5, rv), fan) *
196
134
  mix(0.75, 1.15, dust) * falloff(dot(v, v), 0.8);
197
135
  return lit * (1.0 - shadow * 0.82);
198
136
  }
199
137
 
200
- /* Curtains on a drifting horizon. */
138
+ ${''}
201
139
  float aurora(vec2 q, float t, vec2 qp, float reach) {
202
- /* The curtain waves harder where the pointer is, rather than being lifted
203
- by it. An aurora is a sheet with an amplitude, and a swell only raised
204
- the whole thing locally, which is a bump in a line. Multiplying the
205
- amplitude bends it instead: the folds deepen and the ridge travels
206
- further, which is what an aurora does when it is disturbed. */
140
+ ${''}
207
141
  float agitate = falloff((q.x - qp.x) * (q.x - qp.x), 1.5) * reach;
208
142
 
209
- /* A curtain stands. It does not hang.
210
-
211
- A falloff symmetric about the ridge is a band with a wavy center line,
212
- and vertical noise on a band is an equalizer. An aurora has a sharp lower
213
- border where the field lines reach their lowest and light that thins
214
- upward from it, so the top and the bottom cannot be the same function:
215
- cut hard below, drawn long above.
216
-
217
- Two of them, offset and at different depths, because a single sheet reads
218
- as a decal and what makes the real thing look like drapery is one curtain
219
- seen through another. */
143
+ ${''}
220
144
  float lit = 0.0;
221
145
  for (int i = 0; i < 2; i++) {
222
146
  float f = float(i);
223
- /* Two octaves, not a warp. A warp folds the domain back on itself and
224
- puts high frequencies in the ridge, and against a hard top edge every
225
- wiggle becomes a spike: the first pass came out a mountain range. What
226
- an aurora has is a long undulation with a few folds in it, which is
227
- what a low fundamental and one overtone give. */
147
+ ${''}
228
148
  float ridge =
229
149
  ((noise(vec2(q.x * (0.55 + f * 0.2) + f * 4.0, t * 0.3)) - 0.5) * 1.7 +
230
150
  (noise(vec2(q.x * (1.3 + f * 0.4) + f * 9.0, t * 0.45)) - 0.5) * 0.55) *
231
151
  (1.0 + agitate * 2.0) +
232
152
  f * 0.22;
233
- /* **The sheet stands on the ridge rather than hanging from it, and that
234
- inversion is the whole difference between an aurora and a landscape.**
235
- A hard edge with mass under it is a mountain silhouette: one unbroken
236
- ridge line across the frame with a striped solid below it. A curtain's
237
- lower border is its sharpest, brightest line, and what rises from it
238
- thins and breaks into rays. */
153
+ ${''}
239
154
  float depth = q.y - ridge;
240
- /* Sharp, but not a cliff: the edge is where the light stops, and a step
241
- narrower than a pixel is an aliased line rather than an edge.
242
-
243
- **The border's sharpness varies along it.** A line of even weight reads
244
- as drawn rather than lit however it wanders. A real lower border is
245
- hard where the sheet faces us and diffuse where it turns away, so the
246
- crispness is drawn from the same slow noise the folds are. */
155
+ ${''}
247
156
  float crisp = mix(0.04, 0.3, noise(vec2(q.x * 0.8 + f * 5.0, t * 0.15)));
248
157
  float edge = smoothstep(-crisp, crisp * 0.5, depth);
249
- /* Steep, so the curtain thins instead of filling everything above the
250
- border. At 1.5 it still stood at a fifth a whole unit up, which with
251
- the bundles over it painted a solid wall of stripes from the border to
252
- the top of the frame. A curtain is mostly the bottom of a curtain. */
158
+ ${''}
253
159
  float rise = exp(-max(depth, 0.0) * (2.9 + f * 1.1));
254
- /* Stretched along the fall, so the striations are the vertical rays a
255
- curtain is actually made of rather than texture on a stripe. Not
256
- stretched harder than this: at 0.35 a ray held one value for its whole
257
- length, which is a picket fence however the bundles are shaped. */
160
+ ${''}
258
161
  float rays = fbm(vec2(q.x * (9.0 + f * 4.0), q.y * 0.9 - t * (2.2 + f)));
259
162
 
260
- /* A rim along the top edge. The brightest part of a curtain is the line
261
- where the field lines end, and without it the sheet fades in from
262
- nothing and reads as fog with stripes rather than as something with a
263
- boundary. Wide enough to see: at k = 26 this was two pixels and might
264
- as well not have been there. */
163
+ ${''}
265
164
  float rim = falloff(depth * depth, 7.0) * 0.45;
266
165
 
267
- /* **A curtain is not the width of the sky.** Running edge to edge is what
268
- made the ridge read as a horizon: a horizon is continuous and a curtain
269
- is not. Broken into lengths by a slow mask, with real gaps between
270
- them, it becomes some number of curtains rather than one long one. */
166
+ ${''}
271
167
  float lengths = smoothstep(0.2, 0.66, noise(vec2(q.x * 1.15 + f * 13.0, t * 0.12)));
272
168
 
273
- /* Rays clump. Straight fbm is evenly spaced striping, which is a picket
274
- fence whatever else is done to it, and the floor under it lit the sheet
275
- everywhere so the rays only ever read as texture on a solid band.
276
- Pushing the midtones apart leaves bright bundles with real gaps
277
- between them, and the low floor is what lets the gaps be gaps. */
169
+ ${''}
278
170
  float bundles = smoothstep(0.34, 0.86, rays);
279
171
 
280
- /* The curtain is a draped sheet, so it is bright where a fold turns edge
281
- on and nearly out where it faces us. A narrow range here is a band of
282
- even brightness, which is the other half of what made it read flat. */
172
+ ${''}
283
173
  float foldNoise = noise(vec2(q.x * 1.5 + f * 7.0, t * 0.25));
284
174
 
285
- /* Bundles and folds are what a curtain is made many of, so both answer
286
- variance about their own midpoint: at 0 an even sheet, at 1 the
287
- clumping and the deep folds this was tuned to. */
175
+ ${''}
288
176
  float av = clamp(uVariance, 0.0, 1.0);
289
177
  float bundled = mix(mix(0.85, 0.1, av), mix(0.85, 1.6, av), bundles);
290
178
  float folded = mix(mix(0.775, 0.15, av), mix(0.775, 1.4, av), foldNoise);
@@ -293,130 +181,40 @@ export const FRAGMENT = `#version 300 es
293
181
  return lit;
294
182
  }
295
183
 
296
- /* Light traveling in lanes, at speed.
297
- *
298
- * The one shape here that is about motion rather than about a form. Each lane
299
- * carries a trail with its own speed, length and phase, so the field reads as
300
- * something passing rather than something standing.
301
- *
302
- * The wrap is deliberately outside the frame. A trail restarting is a pop, so
303
- * the travel runs to plus and minus 1.8 while the visible field ends nearer
304
- * 1.2, and the reset happens where nobody is looking.
305
- *
306
- * The pointer is an obstacle: lanes bow around it and slow through it, the way
307
- * anything moving past a body does. Pushed outward in proportion to the offset
308
- * rather than by its sign, because a sign is a discontinuity and a
309
- * discontinuity in a displacement is a crease.
310
- */
184
+ ${''}
311
185
  float streaks(vec2 q, float t, vec2 qp, float reach) {
312
186
  float near = falloff(dot(q - qp, q - qp), 1.6) * reach;
313
187
  vec2 s = q + vec2((q.x - qp.x) * near * 1.1, 0.0);
314
188
 
315
- /* Three lanes are visited, not one, and that is the fix for a square edge.
316
-
317
- Each lane draws its speed, phase and length from a hash of its index, so
318
- every one of them is at a different point in its cycle. Sampled from the
319
- single lane a fragment falls in, the whole strip changes state at the
320
- boundary and the field acquires a hard vertical line at every lane edge:
321
- measured at 23 straight edges across the frame, and at a tilt of 90 the
322
- same count arrives horizontal, which is what says they belong to the
323
- shape's own axes rather than to the screen.
324
-
325
- Visiting the neighbors and weighting each by its own distance is the same
326
- answer particles already uses for the same reason. The weight reaches
327
- zero between centers, so nothing is left to step.
328
-
329
- **Few lanes, and not all of them carry anything.** A streak is an event,
330
- and a field of simultaneous events is a texture. A dead lane contributes
331
- nothing anywhere rather than being cut off somewhere, so it costs the
332
- three lane blend nothing. */
189
+ ${''}
333
190
  float laneF = s.x * 1.6;
334
191
  float base = floor(laneF);
335
192
 
336
193
  float lit = 0.0;
337
- /* Three lanes, which is what the widths support. The tail spreads to at
338
- most 0.15 and lanes sit 0.417 apart, so one lane out carries about a
339
- fiftieth and the lane that drops out of the visited set, two out, is
340
- around a ten millionth. That dropped lane is where a step could appear,
341
- and it is the whole reason this loop is not a single lane. */
194
+ ${''}
342
195
  for (int k = -1; k <= 1; k++) {
343
196
  float id = base + float(k);
344
197
  float within = laneF - (id + 0.5);
345
198
 
346
- /* Speed, length, width and spacing all scale from a common resting value,
347
- so a variance of 0 is a rank of equal streaks at one speed rather than
348
- a special case. Phase is never scaled: lanes sharing a phase march in
349
- lockstep, which is not a uniform population but a broken one.
350
-
351
- Clamped, and the upper half of that is not defensive tidying. These are
352
- mix() calls, so a value past 1 extrapolates rather than exaggerating:
353
- at 1.6 the length term reaches zero and beyond it goes negative, which
354
- makes exp(-behind / len) grow instead of decay and the lane becomes a
355
- full-height white bar. Measured at 2, the frame held 93 apparent lanes
356
- and six straight edges. There is nothing above 1 worth reaching, the
357
- resting spread already being fourfold in speed. */
199
+ ${''}
358
200
  float v = clamp(uVariance, 0.0, 1.0);
359
201
  float alive = step(0.42, hash(vec2(id, 97.0)));
360
202
  float speed = mix(1.3, 0.55 + hash(vec2(id, 11.0)) * 1.5, v);
361
203
  float phase = hash(vec2(id, 29.0));
362
- /* Long enough that the trail dominates the head, which is what the shape
363
- is named for, and no longer: a plume that stays bright for two and a
364
- half units is a searchlight, and the frame is two units tall. */
204
+ ${''}
365
205
  float len = mix(0.85, 0.5 + hash(vec2(id, 47.0)) * 0.8, v);
366
206
 
367
- /* **The lane is periodic, not a single streak that is put back when it
368
- runs out.** With fract() over a fixed span of 3.6 a head left the top
369
- at 1.8 and was moved to -1.8, taking whatever of its tail was still on
370
- screen with it. That span is only off screen while the frame is small
371
- enough to sit inside it, and the visible half-extent is
372
- (|sin tilt| * aspect + |cos tilt|) * scale, which reaches 1.97 at a
373
- tilt of 45 and 2.0 at a scale of 2. Past that the removal happens in
374
- plain view, which is the popping.
375
-
376
- Making the field periodic in y removes the event rather than moving it
377
- out of sight: heads sit at every integer of u and the whole rank slides
378
- through, so there is no frame in which one is created or destroyed and
379
- no scale or tilt at which that becomes visible.
380
-
381
- The gap is the distance between successive heads in a lane, and it is
382
- what density is made of. It varies too: lanes alike in spacing read as
383
- a grid however much else about them differs, and it has to exceed the
384
- tail length, or a streak's tail reaches the next one's nose and the
385
- lane reads as a dashed line rather than as separate things
386
- traveling. */
207
+ ${''}
387
208
  float gap = mix(4.2, 3.2 + hash(vec2(id, 59.0)) * 2.2, v);
388
209
  float travel = phase + t * speed * 1.8 / gap;
389
210
  float u = s.y / gap - travel;
390
211
  float w = fract(u);
391
- /* **A streak is one solid, not a brightness curve times a fixed width.**
392
- Drawn separably it is a dart, and that is what these were: along the
393
- lane the light stopped over a fifth of a unit, across it the lane kept
394
- its full width the whole way, and the level sets of such a product meet
395
- at a point. Straight edges and an apex, which is a paper dart.
396
-
397
- So each term carries its own width. The tail leaves the head narrow and
398
- spreads as it falls behind, and the front is a round cap rather than a
399
- full-width wall cut off across the lane. Together that is a teardrop,
400
- which is the silhouette the thing actually has. */
212
+ ${''}
401
213
  float across = within / 1.6;
402
- /* Narrow across the lane. Lanes sit 0.417 apart and a sigma of 0.15
403
- overlapped both neighbors, so the frame came out a blue wash with no
404
- streaks in it at all. Length against width is what makes a streak read
405
- as a streak: at a ratio near four these were stubby rods with rounded
406
- ends whichever way they tapered, and the fix for that is proportion
407
- rather than another change of profile. */
214
+ ${''}
408
215
  float wide = mix(0.045, 0.03 + hash(vec2(id, 71.0)) * 0.04, v);
409
216
 
410
- /* **A comet is a nucleus with a coma, and a plume behind it.** A round
411
- cap the width of the body with the body widening behind it draws a bulb
412
- on a shaft; tapering the front instead draws a hairline that reads as a
413
- scratch. The fault common to both is a head glow computed from the
414
- distance *in front of* a head only, which never puts a round bright
415
- thing at the head at all.
416
-
417
- The glow is centered on the head, measured to whichever head is nearer
418
- along the lane, so it is symmetric about the nucleus the way a coma
419
- is. */
217
+ ${''}
420
218
  float behind = (1.0 - w) * gap;
421
219
  float ahead = w * gap;
422
220
  float toHead = min(ahead, behind);
@@ -426,33 +224,15 @@ export const FRAGMENT = `#version 300 es
426
224
  float nucleus = exp(-near2 / (2.0 * nose * nose));
427
225
  float coma = exp(-near2 / (2.0 * (nose * 3.0) * (nose * 3.0))) * 0.42;
428
226
 
429
- /* The plume opens fast and fades fast, and is never as bright as the
430
- head. A tail that holds its brightness for its whole length is a
431
- streak of paint; what makes a comet read is that almost all of the
432
- light is in the first tenth of it.
433
-
434
- The onset is what keeps it continuous across a head. The plume exists
435
- only behind, so its value would step from nothing to something at the
436
- nucleus; ramping it in over a seventh of its own length puts that
437
- transition under the brightest part of the coma, where there is nothing
438
- left to see. */
227
+ ${''}
439
228
  float onset = 1.0 - exp(-behind / (0.15 * len));
440
229
 
441
- /* **The straight edge was a min().** Clamping the plume's width with
442
- min(nose * (1 + behind * 3), 0.16) saturates at a constant value of
443
- behind, and a constant behind is a straight line across the lane: above
444
- it the width was fixed and below it grew, and the kink between the two
445
- drew a hard diagonal through the frame. A cap on a quantity that varies
446
- along one axis is a cap along a line, which is the one shape this thing
447
- must not have.
448
-
449
- An exponential approach reaches the same limit without ever having a
450
- corner, so there is no line to see anywhere. */
230
+ ${''}
451
231
  float spread = nose + (0.11 - nose) * (1.0 - exp(-behind * 1.3));
452
232
  float tail = exp(-behind / len) * onset *
453
233
  exp(-(across * across) / (2.0 * spread * spread)) * 0.5;
454
234
 
455
- /* Slowed where the pointer is, which is the other half of an obstacle. */
235
+ ${''}
456
236
  float weight = mix(1.0, 0.5 + hash(vec2(id, 83.0)) * 0.95, v);
457
237
  lit += (nucleus + coma + tail) * alive * weight * mix(1.0, 0.45, near);
458
238
  }
@@ -460,51 +240,18 @@ export const FRAGMENT = `#version 300 es
460
240
  return lit * mix(0.55, 1.35, grain) * falloff(dot(q, q), 0.42);
461
241
  }
462
242
 
463
- /* Broad bands rolling under a warp.
464
- *
465
- * The softest shape here, and the only one with no filaments in it at all:
466
- * where braid and threads are made of lines, this is made of gradients. The
467
- * warp is what stops it sliding: bands moved by phase alone travel sideways
468
- * like a barcode, and bands moved through a warped domain roll.
469
- *
470
- * The pointer drags them. Bowing the phase toward it bends the crests without
471
- * changing how many there are, which is what a surface does when something
472
- * pulls across it.
473
- */
243
+ ${''}
474
244
  float waves(vec2 q, float t, vec2 qp, float reach) {
475
245
  float pull = falloff(dot(q - qp, q - qp), 1.2) * reach;
476
- /* **The warp was eating the waves.** At an amplitude of 2.4 against a band
477
- frequency of 1.7 it swung the sine's argument by about four radians,
478
- which is more than a whole period: the wavefronts were destroyed rather
479
- than bent and what came out was turbulence. It read as marbled paper, and
480
- at a low scale it filled the frame with near white.
481
-
482
- A tenth of that bends the fronts and leaves them fronts. */
246
+ ${''}
483
247
  float warp = warped(vec2(q.x * 0.5, q.y * 0.4), t * 0.3) - 0.5;
484
248
  vec2 w = q + warp * 0.32;
485
249
 
486
- /* Two systems crossing, lit where they agree, which is what the shape is
487
- named for.
488
-
489
- **And the frequency has to be high enough to be a wave at all.** At 2.3
490
- the period is 2.7 in shape units against a visible half height of 1, so
491
- less than one crest fitted across the frame: there was nothing periodic
492
- to see, and every version of this that looked like anything looked like
493
- it because the warp was making clouds. Around 8 puts five crests across
494
- the frame, which is a wave field rather than a single swell.
495
-
496
- The two are deliberately not harmonic. Close but unequal frequencies beat
497
- against each other, and the slow traveling interference that produces is
498
- the thing worth watching. */
250
+ ${''}
499
251
  float a = sin((w.y * 8.1 + w.x * 2.3) - t * 0.9 - pull * 1.4);
500
252
  float b = sin((w.y * 5.3 - w.x * 3.4) + t * 0.6 + pull * 1.0);
501
253
 
502
- /* **Added, these two make a lattice, not a wave field.** Equal amplitudes
503
- crossing at an angle give a regular grid of lobes, which came out as
504
- bubble wrap and got worse the more of it fitted on screen. Waves are
505
- lines. So one system carries the fronts and the other only varies their
506
- brightness along their length, which is what a real swell does: the
507
- crests stay crests and are taller in some places than others. */
254
+ ${''}
508
255
  float band = a * 0.5 + 0.5;
509
256
  band *= mix(0.45, 1.0, b * 0.5 + 0.5);
510
257
 
@@ -512,52 +259,23 @@ export const FRAGMENT = `#version 300 es
512
259
  return pow(band, 2.2) * mix(0.55, 1.2, grain) * falloff(dot(q, q), 0.4);
513
260
  }
514
261
 
515
- /* Luminous threads, woven to a pinch.
516
- *
517
- * Five curves at different frequencies and speeds, each a thin bright line
518
- * rather than a band: what makes this read as woven is that they cross, and
519
- * what makes it read as one object rather than five is the pinch, where every
520
- * amplitude narrows at once and the whole weave gathers.
521
- *
522
- * The pointer is the pinch, which is the interaction this shape is for.
523
- * Carrying it along drags the gathering point with it and the threads open
524
- * behind and close ahead.
525
- */
262
+ ${''}
526
263
  float threads(vec2 q, float t, vec2 qp, float reach) {
527
264
  float pinchAt = mix(0.0, qp.x, reach);
528
- /* Narrowed, not collapsed. At 0.88 the amplitude and the spacing both fell
529
- to a tenth and all seven threads landed on the same line, which is a
530
- bright blob rather than a gathering: what a pinch has to preserve is that
531
- there are still threads in it. */
265
+ ${''}
532
266
  float gather = 1.0 - falloff((q.x - pinchAt) * (q.x - pinchAt), 1.1) * 0.6;
533
267
 
534
- /* Seven, and every one of them the same thread.
535
-
536
- What makes a weave read as a weave is that the strands are alike and only
537
- their phase differs, which is also what makes it a wave rather than a
538
- wobble. Given its own amplitude, height, thickness and brightness from
539
- hashes, each becomes one of seven unrelated squiggles that happen to
540
- share a frame.
541
-
542
- So one amplitude, one thickness, one weight, one frequency, and the
543
- phases fanned evenly across a turn. Alike, they cross at regular
544
- intervals instead of at coincidences. */
268
+ ${''}
545
269
  float lit = 0.0;
546
270
  for (int i = 0; i < 7; i++) {
547
271
  float f = float(i);
548
272
  float phase = f * (6.2831853 / 7.0);
549
273
 
550
- /* Evenly spaced, and the spacing collapses at the pinch along with the
551
- amplitude: threads that only narrow still run parallel through it,
552
- where threads that also converge actually gather. It closes less than
553
- the amplitude does, so they draw together without stacking. */
274
+ ${''}
554
275
  float lane = (f - 3.0) * 0.17 * mix(1.0, gather, 0.65);
555
276
  float amp = 0.34 * gather;
556
277
 
557
- /* A second harmonic at a third the frequency and a third the amplitude,
558
- which bends the long run of each thread without breaking it into
559
- wiggles. Low and quiet is the difference between a wave with character
560
- and a wave with noise on it. */
278
+ ${''}
561
279
  float y =
562
280
  lane +
563
281
  sin(q.x * 1.5 + t * 0.55 + phase) * amp +
@@ -565,23 +283,12 @@ export const FRAGMENT = `#version 300 es
565
283
 
566
284
  lit += falloff((q.y - y) * (q.y - y), 24.0);
567
285
  }
568
- /* Quiet grain. At the old depth it was texture on the threads rather than
569
- air around them, and a wave with grain on it is a wobble again. */
286
+ ${''}
570
287
  float grain = fbm(vec2(q.x * 2.0, q.y * 2.0 - t));
571
288
  return lit * mix(0.85, 1.12, grain) * falloff(dot(q, q), 0.4);
572
289
  }
573
290
 
574
- /* Points adrift, and pushed aside.
575
- *
576
- * One per cell of a coarse lattice, jittered off center so the lattice never
577
- * shows, and drifting on its own small orbit. Nine cells are visited because a
578
- * point near a boundary has to be drawn by its neighbors too, which is what
579
- * stops them popping in and out at the seams.
580
- *
581
- * The pointer repels. Pushing along the offset rather than by its sign keeps
582
- * the displacement smooth, and it is the one response here where the reader
583
- * is plainly moving things rather than lighting them.
584
- */
291
+ ${''}
585
292
  float particles(vec2 q, float t, vec2 qp, float reach) {
586
293
  float lit = 0.0;
587
294
  vec2 cell = floor(q * 2.6);
@@ -602,23 +309,10 @@ export const FRAGMENT = `#version 300 es
602
309
  at += away * push * 0.55;
603
310
 
604
311
  vec2 off = q - at;
605
- /* Size varies with the rest, or a field whose members already differed
606
- in brightness and in how fast they wandered is still made of one
607
- object repeated. Nine cells are visited here, so unlike a streak lane
608
- a mote may be broader as well as tighter without leaving an edge
609
- anywhere. */
312
+ ${''}
610
313
  float grade = mix(52.0, 52.0 * (0.62 + hash(c + 5.7) * 0.95), v);
611
314
 
612
- /* **Round, because a particle is round.** Stretching a mote along a
613
- direction drawn per mote is not motion, it is orientation, so a field
614
- of them reads as grains of rice pointing every which way rather than
615
- as particles at all. Motion, if it is ever wanted here, has to come
616
- from the direction each mote is actually traveling, which is the
617
- wander above rather than a hash.
618
-
619
- The halo stays. An order of magnitude wider and a tenth the strength,
620
- so a mote sits in the field rather than on top of it: a hard little
621
- disc on a dark ground is a pixel, and light has a neighborhood. */
315
+ ${''}
622
316
  float core = falloff(dot(off, off), grade);
623
317
  float halo = falloff(dot(off, off), grade * 0.16) * 0.1;
624
318
  lit += (core + halo) * mix(0.8, 0.35 + a * 0.9, v);
@@ -627,30 +321,7 @@ export const FRAGMENT = `#version 300 es
627
321
  return lit * falloff(dot(q, q), 0.4);
628
322
  }
629
323
 
630
- /* Ink in a viscous medium, drawn through by where the pointer went.
631
- *
632
- * **The wake is one stroke, not eight marks.** A vortex per trail point,
633
- * rotating the sample around its own center, draws eight discrete swirls fading
634
- * in place: a row of blobs rather than anything a hand had done. The marks are
635
- * a polyline. Distance is measured to the nearest *segment*, age is
636
- * interpolated along it, and what a reader sees is a continuous stroke however
637
- * far apart the samples fell.
638
- *
639
- * **And the displacement runs along the stroke, not around it.** Pushing the
640
- * sample down the tangent is advection: the pattern is carried the way the
641
- * hand went, which is what stirring looks like. Rotating it around a point is
642
- * a whirlpool, and eight whirlpools is a washing machine.
643
- *
644
- * The marks arrive oldest first, so consecutive indices are consecutive in
645
- * time. In the ring they are not, and a segment drawn across the wrap joined
646
- * the newest mark to the oldest with a bright bar through the middle of the
647
- * field.
648
- *
649
- * Nine, not eight: eight committed marks and the live head, which is held
650
- * outside the ring so that committing appends rather than overwrites. A
651
- * zero-length segment between the newest mark and the head is skipped below,
652
- * which is what makes the commit invisible.
653
- */
324
+ ${''}
654
325
  float flowWake(vec2 at, mat2 turn, vec2 shift, out vec2 push) {
655
326
  push = vec2(0.0);
656
327
  float wake = 0.0;
@@ -661,11 +332,7 @@ export const FRAGMENT = `#version 300 es
661
332
  if (a.z >= 1.0 || b.z >= 1.0) {
662
333
  continue;
663
334
  }
664
- /* The marks arrive in screen space, and the field is sampled after a
665
- rotation, a scale and a parallax shift. Compared without carrying them
666
- through the same three, the wake sits somewhere else entirely: at a
667
- tilt of 42 and a scale of 1.7 it landed most of a frame from the
668
- cursor, which is what read as offset rather than as lagging. */
335
+ ${''}
669
336
  vec2 pa = turn * (a.xy - shift) * uScale;
670
337
  vec2 pb = turn * (b.xy - shift) * uScale;
671
338
  vec2 ab = pb - pa;
@@ -676,39 +343,20 @@ export const FRAGMENT = `#version 300 es
676
343
  float t = clamp(dot(at - pa, ab) / len2, 0.0, 1.0);
677
344
  vec2 off = at - (pa + ab * t);
678
345
 
679
- /* Elongated along the stroke, not round about it.
680
-
681
- A circular falloff makes the wake a tube of even thickness, which is
682
- what a pipe looks like and not what a stirred medium does. Ink pulled
683
- through a liquid smears the way the hand went: it reaches further along
684
- the direction of travel than across it. Weighting the along component
685
- down stretches the ellipse into that direction, and costs one dot
686
- product. */
346
+ ${''}
687
347
  vec2 tangent = ab * inversesqrt(len2);
688
348
  float alongOff = dot(off, tangent);
689
349
  float acrossOff = dot(off, vec2(-tangent.y, tangent.x));
690
350
  float shaped = acrossOff * acrossOff + alongOff * alongOff * 0.3;
691
351
 
692
- /* Squared, so a wake thins as it dies rather than switching off. */
352
+ ${''}
693
353
  float life = 1.0 - mix(a.z, b.z, t);
694
354
  float w = life * life * falloff(shaped, 3.2);
695
355
  wake += w;
696
356
  push += (ab * inversesqrt(len2)) * w;
697
357
  }
698
358
 
699
- /* Limited, because an unbounded displacement folds and a fold is a crease.
700
-
701
- Where the map from sample to displaced sample stops being one to one,
702
- neighboring samples land on the same value and the pattern piles into a
703
- hard bright line: a caustic, which is what a sharp diagonal with a bright
704
- wedge beside it always is. The sum runs over eight segments of unit
705
- tangents, so near the stroke it reached several times the width of the
706
- falloff it was displacing across, and folded every time.
707
-
708
- The falloff is about a third of a unit wide, so the displacement has to
709
- stay under that for the map to stay monotone. Saturating rather than
710
- clamped: a clamp is a corner, and a corner in a displacement is the same
711
- crease by another route. */
359
+ ${''}
712
360
  float mag = length(push);
713
361
  push = mag > 0.0 ? push * (0.24 / (0.9 + mag)) : vec2(0.0);
714
362
  return wake;
@@ -717,88 +365,29 @@ export const FRAGMENT = `#version 300 es
717
365
  float flow(vec2 q, float t, mat2 turn, vec2 shift) {
718
366
  vec2 push;
719
367
  float wake = flowWake(q, turn, shift, push);
720
- /* Already limited inside, so it is applied whole. */
368
+ ${''}
721
369
  vec2 p = q + push;
722
370
 
723
- /* The field is warped by itself twice over, which is what gives a liquid
724
- its folded look: each sample is displaced by the field's own value, so
725
- structure gathers into sheets rather than staying a cloud.
726
-
727
- Big shapes first, detail on top of them. At twice these frequencies every
728
- octave landed small enough that the frame filled edge to edge with fine
729
- crumple at one density: marbled stone, and it looked the same at every
730
- scale, gain and bloom because there was no large form for any of them to
731
- act on.
732
-
733
- Isotropic noise cannot make a current, so anything wanting one has to
734
- carry a direction, and that is a bigger change than this shape wants. */
371
+ ${''}
735
372
  float ink = warped(p * 0.55, t * 0.55);
736
373
  float sheet = warped(p * 1.1 + vec2(3.1, 1.7), t * 0.35);
737
374
 
738
- /* Voids, not an even swatch. With a floor under each term the lowest the
739
- field can reach is a quarter lit, so every fragment lands in the same
740
- middle band and there is no structure to look at.
741
-
742
- And a slow large mass over the top, thresholded rather than merely
743
- varied: a gentle modulation over a field that is lit everywhere is still
744
- lit everywhere, and what a liquid needs is somewhere it simply is not.
745
- The empty parts of the frame are what give the full parts a shape. */
375
+ ${''}
746
376
  float mass = smoothstep(0.24, 0.72, noise(vec2(p.x * 0.45 - t * 0.1, p.y * 0.4 + t * 0.07)));
747
- /* The void floor sits well above zero. At a low scale the mass noise varies
748
- less across the frame than the frame is wide, so a reader can sit inside
749
- one void and see almost nothing: measured at scale 0.5, which came out
750
- near black. Empty has to mean faint, not absent, or the shape can
751
- disappear entirely at a setting the property explicitly allows. */
377
+ ${''}
752
378
  float body = mix(0.05, 1.4, ink) * mix(0.4, 1.2, sheet) * mix(0.22, 1.45, mass);
753
379
 
754
- /* The resting state matters more than the stirred one: most readers never
755
- move the pointer, and a shape that is dark until touched is a shape most
756
- people never see. With the voids in, halving an untouched field takes it
757
- to a dim murk that reads as wet rock, so the wake lifts from a resting
758
- level that is already worth looking at. */
380
+ ${''}
759
381
  return body * (0.85 + clamp(wake, 0.0, 1.0) * 0.5) * falloff(dot(q, q), 0.55);
760
382
  }
761
383
 
762
- /* Light through moving water, focused into a web.
763
- *
764
- * **A caustic is where a wavefront folds, not where it is bright.** Sampling
765
- * a noise field and lighting the high parts gives clouds; what makes the
766
- * pattern on a pool floor is that the surface bends light and neighboring
767
- * rays land on the same spot. The cheap stand-in for that is the *zero* of a
768
- * displaced wave sum rather than its peak: 1 - |sum| is thin and bright
769
- * exactly where the sum crosses, which is the fold.
770
- *
771
- * abs() puts a derivative discontinuity along each of those lines, and here
772
- * that is the drawing rather than a fault: a caustic edge is genuinely sharp,
773
- * which is the whole reason it reads as light rather than as fog.
774
- */
384
+ ${''}
775
385
  float caustics(vec2 q, float t, vec2 qp, float reach) {
776
- /* The pointer is a stone in the water: the surface it disturbs is a ring
777
- of extra phase, so the web tightens around it rather than brightening. */
386
+ ${''}
778
387
  float dropD = length(q - qp);
779
388
  float drop = falloff(dropD * dropD, 1.4) * reach;
780
389
 
781
- /* The layers are the population, and the pool says which of their cells are
782
- bright. Three depths of fold, each at its own frequency and carrying its
783
- own weight in the sum, are three sizes of the same thing, and the slow
784
- depth over the top is how far the lit part of the pool is from the dark
785
- part. Each travels toward the middle of its own spread, so a variance of
786
- 0 is one scale of web lit evenly from edge to edge and a variance of 1 is
787
- exactly the water this was tuned to.
788
-
789
- The total weight is the same at either end, three layers of 0.78 being
790
- the 1.0, 0.78 and 0.56 they spread into, so evening them out does not
791
- also dim the frame.
792
-
793
- The grain is left out of it. It is the texture on the light rather than a
794
- member of anything, which is the same reason threads keeps its.
795
-
796
- Clamped, and the upper half of that is not tidying: these are mix()
797
- calls, so a value past 1 extrapolates rather than exaggerating. The near
798
- layer's frequency runs 2.05 - 0.55v and the far layer's weight 0.78 -
799
- 0.22v, so at 2 the near web is drawn at 0.95, under half the scale it was
800
- tuned at, and past 3.5 the far layer's weight crosses zero and that layer
801
- lights everything the folds are not. */
390
+ ${''}
802
391
  float cv = clamp(uVariance, 0.0, 1.0);
803
392
 
804
393
  float lit = 0.0;
@@ -806,8 +395,7 @@ export const FRAGMENT = `#version 300 es
806
395
  float f = float(i);
807
396
  vec2 p = q * mix(2.05, 1.5 + f * 0.55, cv);
808
397
 
809
- /* Warped by the field's own value, so the wavefronts bend the way a
810
- surface does rather than sliding as a rigid pattern. */
398
+ ${''}
811
399
  vec2 w = p + vec2(
812
400
  noise(p * 1.25 + vec2(t * 0.42 + f * 4.0, 0.0)),
813
401
  noise(p * 1.1 - vec2(0.0, t * 0.36 + f * 7.0))
@@ -818,49 +406,23 @@ export const FRAGMENT = `#version 300 es
818
406
  lit += pow(fold, 7.0) * mix(0.78, 1.0 - f * 0.22, cv);
819
407
  }
820
408
 
821
- /* **The pool has a lit part and a dark part.** Three layers of folds at one
822
- density fill the frame edge to edge and every version of it looks the
823
- same: bright everywhere carries the same amount of information as dark
824
- everywhere. A slow depth over the top decides where the light is getting
825
- through, so the web has somewhere to be faint and the eye somewhere to
826
- start. */
409
+ ${''}
827
410
  float pool = smoothstep(0.2, 0.78, noise(vec2(q.x * 0.55 - t * 0.09, q.y * 0.5 + t * 0.06)));
828
411
 
829
- /* And the stone brightens what it disturbs as well as bending it, or the
830
- only evidence of a pointer is a phase shift nobody can see. */
412
+ ${''}
831
413
  float dim = warped(q * 0.7, t * 0.2);
832
414
  return lit * mix(0.55, 1.25, dim) *
833
415
  mix(mix(0.75, 0.25, cv), mix(0.75, 1.25, cv), pool) *
834
416
  (1.0 + drop * 0.9) * falloff(dot(q, q), 0.4);
835
417
  }
836
418
 
837
- /* A lattice in perspective, lit at its nodes.
838
- *
839
- * **The one shape here that is not weather.** Everything else in this set is
840
- * a fluid, a light or a sky, and a design system is none of those: it is a
841
- * structure, regular, with the interesting part at the joints. So this is
842
- * drawn rather than simulated, and it is the only one with straight lines in
843
- * it on purpose.
844
- *
845
- * Perspective is a divide, not a matrix. Scaling the grid by 1 / (1 + depth)
846
- * makes the cells shrink with distance and the whole thing recede, which is
847
- * the entire trick and costs one division.
848
- */
419
+ ${''}
849
420
  float lattice(vec2 q, float t, vec2 qp, float reach) {
850
- /* The pointer lifts the plane nearest it, so the lattice crowds where a
851
- reader is looking. A structure answers by changing its spacing, which is
852
- the only thing about a lattice that can move without breaking it. */
421
+ ${''}
853
422
  vec2 away = q - qp;
854
423
  float near = falloff(dot(away, away), 1.8) * reach;
855
424
 
856
- /* **A ground plane, not a mirrored one.** Dividing by abs(p.y) makes two
857
- floors meeting along y = 0, which reads as a bowtie with a crease down
858
- the middle rather than as anything receding. Dividing by the distance
859
- below a horizon is the actual projection: one plane, cells shrinking as
860
- they approach it, and nothing above it to fold back.
861
-
862
- It travels as well, so the structure arrives rather than sitting still.
863
- Perspective and motion together are what say the plane is a plane. */
425
+ ${''}
864
426
  float below = max(1.45 - q.y, 0.14);
865
427
  float depth = 1.0 / below;
866
428
  vec2 g = vec2(
@@ -869,61 +431,23 @@ export const FRAGMENT = `#version 300 es
869
431
  );
870
432
  vec2 cell = fract(g) - 0.5;
871
433
 
872
- /* The rails, thinner the further back they are, so the grid does not turn
873
- into a solid sheet at the horizon. */
434
+ ${''}
874
435
  float rail = min(abs(cell.x), abs(cell.y));
875
436
  float lines = falloff(rail * rail, 24.0);
876
437
 
877
- /* The joints, which is where a lattice is actually a lattice, and brighter
878
- than the rails between them: a grid of even weight is graph paper. */
438
+ ${''}
879
439
  float node = falloff(dot(cell, cell), 26.0) * 2.1 * (0.6 + near * 0.9);
880
440
 
881
- /* Faded toward the horizon, or the cells pile into a solid sheet exactly
882
- where they are smallest. */
441
+ ${''}
883
442
  float far = smoothstep(0.0, 0.55, below) * smoothstep(3.4, 1.3, below);
884
443
 
885
444
  float dust = warped(q * 0.9, t * 0.25);
886
445
  return (lines * 0.5 + node) * far * mix(0.7, 1.2, dust) * falloff(dot(q, q), 0.42);
887
446
  }
888
447
 
889
- /* Cells, lit along the seams between them.
890
- *
891
- * **Provenance as a picture.** A Voronoi field is a set of territories, each
892
- * one everything nearer to its own point than to any other, and the drawing
893
- * is not the cells but the boundaries: where two claims meet is exactly
894
- * what Trace is about. Nothing here is lit except the disagreements.
895
- *
896
- * The wall is the second nearest distance minus the nearest, which is zero
897
- * on the boundary and grows into the interior. It is a genuine distance, so
898
- * it needs no epsilon and produces no crease.
899
- */
448
+ ${''}
900
449
  float fracture(vec2 q, float t, vec2 qp, float reach) {
901
- /* The territories are the population, and how far each site wanders off its
902
- own cell center is the whole of what makes one differ from the next: the
903
- rates are already shared, so the spread is the amplitude and nothing
904
- else. At 0 every site sits at its center and every claim is the same
905
- size, which draws the lattice this is a Voronoi of: a grid. That is the
906
- answer rather than a collision with the shape above, which is a structure
907
- in perspective lit at its joints where this is a set of claims that
908
- happen to be equal.
909
-
910
- **The alternative is a shared phase, and it is the wrong uniform.**
911
- Keeping the swing and collapsing a and b would leave every site on the
912
- same orbit, which is not a set of equal territories but one territory
913
- repeated and the whole field sliding rigidly under it. The lanes in
914
- streaks say the same thing about a phase from the other side.
915
-
916
- At 0 the seams hold still, which is what a set of equal claims looks
917
- like and not a shape that has stopped: the grain still travels over them
918
- and the pointer still shoves the sites near it apart, so the one thing
919
- that moves a territory is something arriving.
920
-
921
- Clamped for the usual reason, that these are interpolations, and here
922
- there is a hard edge as well as an ugly one. Nine cells are visited, so
923
- the two nearest sites have to be inside them: the swing reaches
924
- 0.42 * v * sqrt(2) of a cell at its corner, which passes a whole cell at
925
- v = 1.68, and past that a site can be nearer than anything the loop
926
- looked at and the seam snaps along the lattice it was drawn to hide. */
450
+ ${''}
927
451
  float fv = clamp(uVariance, 0.0, 1.0);
928
452
 
929
453
  vec2 g = q * 2.1;
@@ -937,14 +461,12 @@ export const FRAGMENT = `#version 300 es
937
461
  vec2 o = vec2(float(i), float(j));
938
462
  vec2 id = base + o;
939
463
 
940
- /* Each site drifts on its own small orbit, so the seams breathe rather
941
- than the whole field sliding. */
464
+ ${''}
942
465
  float a = hash(id) * 6.2831853;
943
466
  float b = hash(id + 41.7) * 6.2831853;
944
467
  vec2 site = o + 0.5 + 0.42 * fv * vec2(sin(t * 0.5 + a), cos(t * 0.42 + b));
945
468
 
946
- /* Sites near the pointer are pushed away from it, which opens the
947
- cells there: a territory answers by giving ground. */
469
+ ${''}
948
470
  vec2 world = (id + 0.5) / 2.1;
949
471
  vec2 shove = world - qp;
950
472
  site += normalize(shove + 0.0001) * falloff(dot(shove, shove), 2.4) * reach * 0.5;
@@ -962,17 +484,7 @@ export const FRAGMENT = `#version 300 es
962
484
  float wall = second - first;
963
485
  float seam = falloff(wall * wall, 17.0);
964
486
 
965
- /* **Not every seam is lit the same.** At one brightness the field is a
966
- mosaic of equal cells, which is a texture; what makes a set of
967
- territories read is that some borders are contested and most are not. A
968
- slow field decides which, and the dark stretches give the lit ones a
969
- shape.
970
-
971
- Which is the second thing variance answers for here, from the other
972
- direction: how far apart the contested borders and the quiet ones are
973
- lit. Both ends travel toward the midpoint, so a variance of 0 is that
974
- mosaic, every border at one brightness, and the paragraph above is a
975
- description of what asking for no variance gets rather than a fault. */
487
+ ${''}
976
488
  float contested = smoothstep(0.24, 0.8, noise(vec2(q.x * 0.6 + t * 0.07, q.y * 0.55 - t * 0.05)));
977
489
 
978
490
  float grain = warped(q * 1.3, t * 0.3);
@@ -981,50 +493,27 @@ export const FRAGMENT = `#version 300 es
981
493
  falloff(dot(q, q), 0.42);
982
494
  }
983
495
 
984
- /* The sampling frame, the shape branch, and the ramp from field to color. */
496
+ ${''}
985
497
  void main() {
986
498
  vec2 p = uv * 2.0 - 1.0;
987
499
  float aspect = uResolution.x / max(uResolution.y, 1.0);
988
500
  p.x *= aspect;
989
501
 
990
- /* **No global response, parallax included.** Every named interaction is a
991
- promise about what the pointer does, and translating the whole field is
992
- not any of them: a consumer asking for a light asked for a light, not for
993
- a light and a lean. Small is not the test.
994
-
995
- What answers the pointer is each shape, in the terms it is made of, and
996
- the three generics for a consumer who asks for one. */
502
+ ${''}
997
503
  vec2 q = p;
998
504
 
999
- /* **The tilt is an angle on the screen, not in the sampling space.**
1000
-
1001
- p.x is stretched by the aspect before anything is rotated, so a
1002
- rotation applied here is a rotation in an anisotropic space, which is a
1003
- shear. Working it through: a feature along the shape's own x axis sits
1004
- where q.y = 0, which is p.y = -tan(a) * p.x, and since p.x is aspect
1005
- times the screen's x the slope a reader sees is aspect * tan(a). At a
1006
- widescreen 1.78 a tilt of 14 drew at 23.9 degrees, and the same number
1007
- drew differently in every box the component was put in.
1008
-
1009
- Solving aspect * tan(a) = tan(tilt) gives the angle to rotate by. The two
1010
- argument atan is what makes 90 work: tan blows up there, while
1011
- atan(1, 0) is exactly a quarter turn. */
505
+ ${''}
1012
506
  float want = radians(uTilt);
1013
507
  float a = atan(sin(want), cos(want) * aspect);
1014
508
  mat2 turn = mat2(cos(a), sin(a), -sin(a), cos(a));
1015
509
  q = turn * q * uScale;
1016
510
 
1017
- /* The pointer, carried into the same frame the shapes are drawn in, so each
1018
- one can answer it in its own terms. */
511
+ ${''}
1019
512
  vec2 qp = turn * uPointer * uScale;
1020
513
 
1021
514
  float t = uTime * 0.06;
1022
515
 
1023
- /* A shape answers for itself only when nothing else has been asked for.
1024
- Given a generic response, its own is switched off rather than layered
1025
- under: two answers to one gesture is how a shared zoom and a shared glow
1026
- got here in the first place, applied identically to every shape and
1027
- saying nothing about any of them. */
516
+ ${''}
1028
517
  float own = uInteraction == 0 ? uReach : 0.0;
1029
518
 
1030
519
  float d = 0.0;
@@ -1052,20 +541,10 @@ export const FRAGMENT = `#version 300 es
1052
541
  d = fracture(q, t, qp, own);
1053
542
  }
1054
543
 
1055
- /* Bloom as an exponent on the field rather than a term inside each shape.
1056
- Every shape is a density that falls off, so a power tightens or spreads
1057
- that falloff and one control means the same thing in all of them. */
544
+ ${''}
1058
545
  d = pow(max(d, 0.0), mix(2.0, 0.5, clamp(uBloom, 0.0, 1.0))) * uGain;
1059
546
 
1060
- /* The generic answers, for a consumer who has asked for one.
1061
-
1062
- All three are functions of distance from the pointer and nothing else,
1063
- which is exactly what makes them generic and exactly what makes them say
1064
- less. The light option lifts what is lit and puts a floor under itself so
1065
- there is something at the cursor over a dark field. The shadow option is
1066
- that gesture inverted, which suits a bright field where a lamp would be
1067
- invisible. The ripple option is the one with a clock in it, so it keeps
1068
- moving after the pointer has stopped. */
547
+ ${''}
1069
548
  if (uInteraction != 0 && uInteraction != 4) {
1070
549
  vec2 toCursor = p - uPointer;
1071
550
  float near = falloff(dot(toCursor, toCursor), 3.0) * uReach;
@@ -1081,18 +560,14 @@ export const FRAGMENT = `#version 300 es
1081
560
 
1082
561
  d *= mix(0.65, 1.35, smoothstep(-1.0, 1.0, q.y));
1083
562
 
1084
- /* Rolled off rather than clipped. A hard clamp flattens the core into a
1085
- plateau of one value, which is what makes a bright shader look like a
1086
- sticker; this compresses toward one and keeps the gradient inside it. */
563
+ ${''}
1087
564
  float lift = 1.0 - exp(-max(d, 0.0) * 1.15);
1088
565
 
1089
566
  vec3 rgb = mix(uGround, uHaze, smoothstep(0.0, 0.16, lift));
1090
567
  rgb = mix(rgb, uGlow, smoothstep(0.10, 0.46, lift));
1091
568
  rgb = mix(rgb, uPeak, smoothstep(0.40, 0.72, lift) * 0.6);
1092
569
 
1093
- /* Dithered before the buffer quantizes. A falloff this soft spans a few
1094
- hundred 8-bit steps across a screen, and without a sub-step of noise it
1095
- paints as visible rings. */
570
+ ${''}
1096
571
  float dither = (hash(gl_FragCoord.xy) - 0.5) / 255.0;
1097
572
  color = vec4(rgb + dither, 1.0);
1098
573
  }