@fusion.dev/helix 0.161.0 → 0.162.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.
@@ -0,0 +1,466 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var HelixStrands_1;
8
+ import { customElement, drawsInSoftware, elementById, HelixElement, HelixStrandsFallbackEvent, MAX_FRAME_STEP, PaintGateController, readColor, } from '@fusion.dev/helix-core';
9
+ import { html } from 'lit';
10
+ import { property } from 'lit/decorators.js';
11
+ import { paletteOf, RESTING_PALETTE } from '../palette.js';
12
+ import { boxPath, lookup, MAX_POINTS, pagePath, visibleRange, } from '../path.js';
13
+ import { MAX_STRANDS, MIN_STRANDS, StrandsRenderer } from '../renderer.js';
14
+ import { styles } from './strands-root.css.js';
15
+ const MAX_CANVAS_EDGE = 4096;
16
+ const MAX_CANVAS_PIXELS = 4_194_304;
17
+ const OPENING_PATIENCE = 500;
18
+ const TRAVEL_EASE = 0.09;
19
+ let HelixStrands = class HelixStrands extends HelixElement {
20
+ constructor() {
21
+ super(...arguments);
22
+ this.density = 0.8;
23
+ this.gain = 1;
24
+ this.softness = 0.55;
25
+ this.speed = 1;
26
+ this.spread = 1;
27
+ this.travel = false;
28
+ this.palette = { ...RESTING_PALETTE };
29
+ this.carried = null;
30
+ this.refused = 'unavailable';
31
+ this.canvas = null;
32
+ this.gl = null;
33
+ this.renderer = null;
34
+ this.release = 0;
35
+ this.elapsed = 0;
36
+ this.width = 0;
37
+ this.height = 0;
38
+ this.dpr = 1;
39
+ this.box = { height: 1, width: 1 };
40
+ this.points = new Float32Array(MAX_POINTS * 4);
41
+ this.count = 0;
42
+ this.table = new Float32Array(1);
43
+ this.offset = 0;
44
+ this.opened = false;
45
+ this.opening = 0;
46
+ this.openBy = 0;
47
+ this.laying = 0;
48
+ this.pageObserver = null;
49
+ this.stopObserver = null;
50
+ this.gate = new PaintGateController(this, {
51
+ canRun: () => !!this.gl && !this.gl.isContextLost() && this.width > 0 && this.opened,
52
+ onFrame: (delta) => {
53
+ this.advance(delta);
54
+ },
55
+ onMotion: (reduced) => {
56
+ if (reduced) {
57
+ this.paintOnce();
58
+ }
59
+ },
60
+ onResize: () => {
61
+ this.resized();
62
+ },
63
+ });
64
+ this.drop = () => {
65
+ this.release = 0;
66
+ this.renderer?.dispose();
67
+ this.gl?.getExtension('WEBGL_lose_context')?.loseContext();
68
+ this.renderer = null;
69
+ this.gl = null;
70
+ };
71
+ this.handleLost = (event) => {
72
+ event.preventDefault();
73
+ this.gate.stop();
74
+ if (this.opening) {
75
+ cancelAnimationFrame(this.opening);
76
+ this.opening = 0;
77
+ }
78
+ this.renderer = null;
79
+ this.canvas?.toggleAttribute('hidden', true);
80
+ this.fallBack('lost');
81
+ };
82
+ this.handleRestored = () => {
83
+ if (!this.prepare()) {
84
+ return;
85
+ }
86
+ this.width = 0;
87
+ this.height = 0;
88
+ this.measure();
89
+ if (this.opened) {
90
+ this.paint();
91
+ this.gate.play();
92
+ }
93
+ else if (this.width) {
94
+ this.open();
95
+ }
96
+ };
97
+ this.handleRestyle = () => {
98
+ this.paintOnce();
99
+ };
100
+ this.relayout = () => {
101
+ if (this.laying) {
102
+ return;
103
+ }
104
+ this.laying = requestAnimationFrame(() => {
105
+ this.laying = 0;
106
+ this.layout();
107
+ this.paintOnce();
108
+ });
109
+ };
110
+ this.handleScroll = () => {
111
+ if (!this.gate.running) {
112
+ this.offset = window.scrollY;
113
+ this.paintOnce();
114
+ }
115
+ };
116
+ this.held = 0;
117
+ }
118
+ static { HelixStrands_1 = this; }
119
+ static { this.styles = styles; }
120
+ static { this.CONTEXT = {
121
+ alpha: true,
122
+ antialias: false,
123
+ depth: false,
124
+ powerPreference: 'low-power',
125
+ stencil: false,
126
+ }; }
127
+ render() {
128
+ return html `<canvas part="canvas" aria-hidden="true"></canvas>
129
+ <div part="content"><slot></slot></div>`;
130
+ }
131
+ firstUpdated(changed) {
132
+ super.firstUpdated(changed);
133
+ this.start();
134
+ }
135
+ updated(changed) {
136
+ if (changed.has('travel') && this.hasUpdated && this.canvas) {
137
+ this.watchPage(this.travel);
138
+ }
139
+ if (changed.has('variant') || changed.has('at') || changed.has('travel')) {
140
+ this.layout();
141
+ }
142
+ this.paintOnce();
143
+ }
144
+ connectedCallback() {
145
+ super.connectedCallback();
146
+ if (this.release) {
147
+ clearTimeout(this.release);
148
+ this.release = 0;
149
+ }
150
+ if (this.hasUpdated) {
151
+ this.start();
152
+ }
153
+ }
154
+ disconnectedCallback() {
155
+ super.disconnectedCallback();
156
+ if (this.opening) {
157
+ cancelAnimationFrame(this.opening);
158
+ this.opening = 0;
159
+ this.openBy = 0;
160
+ }
161
+ if (this.laying) {
162
+ cancelAnimationFrame(this.laying);
163
+ this.laying = 0;
164
+ }
165
+ this.canvas?.removeEventListener('webglcontextlost', this.handleLost);
166
+ this.canvas?.removeEventListener('webglcontextrestored', this.handleRestored);
167
+ this.canvas?.removeEventListener('transitionend', this.handleRestyle);
168
+ this.watchPage(false);
169
+ this.release = window.setTimeout(this.drop, 0);
170
+ }
171
+ start() {
172
+ if (!this.isConnected) {
173
+ return;
174
+ }
175
+ if (!this.renderRoot.querySelector('canvas')) {
176
+ return;
177
+ }
178
+ if (this.renderer && this.gl?.isContextLost()) {
179
+ this.renderer = null;
180
+ this.gl = null;
181
+ }
182
+ if (!this.renderer && !this.gl?.isContextLost() && !this.build()) {
183
+ this.fallBack(this.refused);
184
+ return;
185
+ }
186
+ this.canvas?.addEventListener('webglcontextlost', this.handleLost);
187
+ this.canvas?.addEventListener('webglcontextrestored', this.handleRestored);
188
+ this.canvas?.addEventListener('transitionend', this.handleRestyle);
189
+ this.gate.watch();
190
+ this.watchPage(this.travel);
191
+ if (this.measure() || (this.width && !this.opened)) {
192
+ this.open();
193
+ }
194
+ }
195
+ build() {
196
+ let canvas = this.renderRoot.querySelector('canvas');
197
+ if (!canvas) {
198
+ return false;
199
+ }
200
+ if (drawsInSoftware(HelixStrands_1.CONTEXT)) {
201
+ this.refused = 'software';
202
+ return false;
203
+ }
204
+ this.refused = 'unavailable';
205
+ let gl = canvas.getContext('webgl2', HelixStrands_1.CONTEXT);
206
+ if (gl?.isContextLost()) {
207
+ const fresh = document.createElement('canvas');
208
+ fresh.setAttribute('part', 'canvas');
209
+ fresh.setAttribute('aria-hidden', 'true');
210
+ canvas.replaceWith(fresh);
211
+ canvas = fresh;
212
+ this.width = 0;
213
+ this.height = 0;
214
+ gl = canvas.getContext('webgl2', HelixStrands_1.CONTEXT);
215
+ }
216
+ this.canvas = canvas;
217
+ this.gl = gl;
218
+ if (!gl || gl.isContextLost()) {
219
+ return false;
220
+ }
221
+ return this.prepare();
222
+ }
223
+ prepare() {
224
+ const gl = this.gl;
225
+ if (!gl) {
226
+ return false;
227
+ }
228
+ this.renderer = StrandsRenderer.create(gl);
229
+ if (!this.renderer) {
230
+ return false;
231
+ }
232
+ this.canvas?.toggleAttribute('hidden', false);
233
+ this.fallback = undefined;
234
+ this.toggleState('fallback', false);
235
+ return true;
236
+ }
237
+ fallBack(reason) {
238
+ if (this.fallback !== undefined) {
239
+ return;
240
+ }
241
+ this.fallback = reason;
242
+ this.toggleState('fallback', true);
243
+ this.dispatchEvent(new HelixStrandsFallbackEvent({ reason }));
244
+ }
245
+ readPalette() {
246
+ const canvas = this.canvas;
247
+ if (!canvas) {
248
+ return;
249
+ }
250
+ if (this.carried?.canvas !== canvas) {
251
+ this.carried = { canvas, style: getComputedStyle(canvas), text: '' };
252
+ }
253
+ const { style } = this.carried;
254
+ const text = `${style.color} ${style.textDecorationColor} ${style.outlineColor} ${style.columnRuleColor} ${style.caretColor}`;
255
+ if (text !== this.carried.text) {
256
+ this.carried.text = text;
257
+ this.palette = paletteOf(style, readColor);
258
+ }
259
+ }
260
+ measure() {
261
+ const canvas = this.canvas;
262
+ const renderer = this.renderer;
263
+ if (!canvas || !renderer) {
264
+ return false;
265
+ }
266
+ const rect = this.getBoundingClientRect();
267
+ if (rect.width < 1 || rect.height < 1) {
268
+ return false;
269
+ }
270
+ this.box = { height: rect.height, width: rect.width };
271
+ let dpr = Math.min(window.devicePixelRatio || 1, 2) * 0.75;
272
+ const edge = Math.max(rect.width, rect.height) * dpr;
273
+ if (edge > MAX_CANVAS_EDGE) {
274
+ dpr *= MAX_CANVAS_EDGE / edge;
275
+ }
276
+ const pixels = rect.width * rect.height * dpr * dpr;
277
+ if (pixels > MAX_CANVAS_PIXELS) {
278
+ dpr *= Math.sqrt(MAX_CANVAS_PIXELS / pixels);
279
+ }
280
+ const width = Math.round(rect.width * dpr);
281
+ const height = Math.round(rect.height * dpr);
282
+ if (width === this.width && height === this.height) {
283
+ return false;
284
+ }
285
+ this.width = width;
286
+ this.height = height;
287
+ this.dpr = dpr;
288
+ canvas.width = width;
289
+ canvas.height = height;
290
+ if (!renderer.resize(width, height)) {
291
+ this.renderer = null;
292
+ this.fallBack('unavailable');
293
+ return false;
294
+ }
295
+ this.layout();
296
+ return true;
297
+ }
298
+ layout() {
299
+ let path;
300
+ const variant = this.variant ?? 'ribbon';
301
+ if (this.travel) {
302
+ path = pagePath(this.placedStops(), variant, this.box.width, this.box.height, document.documentElement.scrollHeight);
303
+ this.table = lookup(path);
304
+ }
305
+ else {
306
+ path = boxPath(variant, this.at, this.box.height);
307
+ }
308
+ this.points.fill(0);
309
+ path.forEach((point, i) => {
310
+ this.points.set(point, i * 4);
311
+ });
312
+ this.count = path.length;
313
+ }
314
+ placedStops() {
315
+ const placed = [];
316
+ const scroll = window.scrollY;
317
+ for (const stop of this.querySelectorAll(':scope > helix-strands-stop')) {
318
+ const name = stop.getAttribute('for');
319
+ const target = name ? elementById(stop, name) : null;
320
+ if (!target) {
321
+ continue;
322
+ }
323
+ const rect = target.getBoundingClientRect();
324
+ const at = Number.parseFloat(stop.getAttribute('at') ?? '');
325
+ const variant = stop.getAttribute('variant');
326
+ placed.push({
327
+ at: Number.isFinite(at) ? at : undefined,
328
+ height: rect.height,
329
+ top: rect.top + scroll,
330
+ variant: variant === 'fan' || variant === 'waves' ? variant : 'ribbon',
331
+ });
332
+ }
333
+ return placed;
334
+ }
335
+ watchPage(on) {
336
+ window.removeEventListener('scroll', this.handleScroll);
337
+ this.pageObserver?.disconnect();
338
+ this.stopObserver?.disconnect();
339
+ this.pageObserver = null;
340
+ this.stopObserver = null;
341
+ if (!on) {
342
+ return;
343
+ }
344
+ this.offset = window.scrollY;
345
+ window.addEventListener('scroll', this.handleScroll, { passive: true });
346
+ this.pageObserver = new ResizeObserver(this.relayout);
347
+ this.pageObserver.observe(document.documentElement);
348
+ this.stopObserver = new MutationObserver(this.relayout);
349
+ this.stopObserver.observe(this, {
350
+ attributeFilter: ['at', 'for', 'variant'],
351
+ attributes: true,
352
+ childList: true,
353
+ subtree: true,
354
+ });
355
+ }
356
+ paint() {
357
+ const { renderer } = this;
358
+ if (!renderer || !this.width) {
359
+ return;
360
+ }
361
+ this.readPalette();
362
+ const view = [this.box.width, this.box.height];
363
+ const range = this.travel
364
+ ? visibleRange(this.table, this.offset - view[1] * 0.4, this.offset + view[1] * 1.4)
365
+ : [0, 1];
366
+ const density = Math.min(1, Math.max(0, this.density));
367
+ renderer.render({
368
+ count: this.count,
369
+ dpr: this.dpr,
370
+ gain: Math.max(0, this.gain),
371
+ points: this.points,
372
+ range,
373
+ scroll: this.travel ? this.offset : 0,
374
+ softness: Math.min(1, Math.max(0, this.softness)),
375
+ spread: Math.max(0, this.spread),
376
+ strands: Math.round(MIN_STRANDS + (MAX_STRANDS - MIN_STRANDS) * density),
377
+ time: this.elapsed,
378
+ view,
379
+ }, this.palette);
380
+ }
381
+ resized() {
382
+ if (!this.measure()) {
383
+ this.paintOnce();
384
+ return;
385
+ }
386
+ if (this.opened) {
387
+ this.paint();
388
+ this.gate.play();
389
+ return;
390
+ }
391
+ this.open();
392
+ }
393
+ open() {
394
+ if (this.opening) {
395
+ cancelAnimationFrame(this.opening);
396
+ this.opening = 0;
397
+ }
398
+ if (this.openBy === 0) {
399
+ this.openBy = performance.now() + OPENING_PATIENCE;
400
+ }
401
+ const draw = () => {
402
+ this.opening = 0;
403
+ this.opened = true;
404
+ this.paint();
405
+ this.gate.play();
406
+ };
407
+ if (performance.now() >= this.openBy) {
408
+ draw();
409
+ return;
410
+ }
411
+ this.opening = requestAnimationFrame(draw);
412
+ }
413
+ paintOnce() {
414
+ if (this.gate.running || !this.opened || !this.isConnected) {
415
+ return;
416
+ }
417
+ this.paint();
418
+ }
419
+ static { this.CAPPED = 1 / 30; }
420
+ advance(delta) {
421
+ this.held += delta;
422
+ const traveling = this.travel && Math.abs(window.scrollY - this.offset) > 0.5;
423
+ if (!traveling && this.held < HelixStrands_1.CAPPED) {
424
+ return;
425
+ }
426
+ const step = Math.min(this.held, MAX_FRAME_STEP);
427
+ this.held = 0;
428
+ this.elapsed += step * Math.max(0, this.speed);
429
+ if (this.travel) {
430
+ const target = window.scrollY;
431
+ this.offset += (target - this.offset) * (1 - Math.exp(-step / TRAVEL_EASE));
432
+ if (Math.abs(target - this.offset) < 0.5) {
433
+ this.offset = target;
434
+ }
435
+ }
436
+ this.paint();
437
+ }
438
+ };
439
+ __decorate([
440
+ property({ type: Number })
441
+ ], HelixStrands.prototype, "at", void 0);
442
+ __decorate([
443
+ property({ type: Number })
444
+ ], HelixStrands.prototype, "density", void 0);
445
+ __decorate([
446
+ property({ type: Number })
447
+ ], HelixStrands.prototype, "gain", void 0);
448
+ __decorate([
449
+ property({ type: Number })
450
+ ], HelixStrands.prototype, "softness", void 0);
451
+ __decorate([
452
+ property({ type: Number })
453
+ ], HelixStrands.prototype, "speed", void 0);
454
+ __decorate([
455
+ property({ type: Number })
456
+ ], HelixStrands.prototype, "spread", void 0);
457
+ __decorate([
458
+ property({ reflect: true, type: Boolean })
459
+ ], HelixStrands.prototype, "travel", void 0);
460
+ __decorate([
461
+ property()
462
+ ], HelixStrands.prototype, "variant", void 0);
463
+ HelixStrands = HelixStrands_1 = __decorate([
464
+ customElement('helix-strands')
465
+ ], HelixStrands);
466
+ export { HelixStrands };
@@ -0,0 +1,22 @@
1
+ /** Each strand as a ribbon of quads, wide enough to anti-alias and to blur. */
2
+ export declare const LINE_VERTEX = "#version 300 es\nlayout(location = 0) in vec2 aVert;\nlayout(location = 1) in float aId;\nuniform float uDpr;\nuniform float uSoftness;\nuniform vec3 uGlow;\nuniform vec3 uTint;\nuniform vec3 uPeak;\nout float vSide;\nout vec3 vColor;\n\nuniform vec4 uPts[96];\nuniform int uCount;\nuniform vec2 uView;\nuniform float uScroll;\nuniform vec2 uT;\nuniform float uTime;\nuniform float uSpread;\n\nfloat h1(float n) { return fract(sin(n * 127.1 + 311.7) * 43758.5453); }\n\nvec4 pt(int i) { return uPts[clamp(i, 0, uCount - 1)]; }\n\nvec4 spline(float t) {\n float s = clamp(t, 0.0, 1.0) * float(uCount - 1);\n int i = min(int(floor(s)), uCount - 2);\n float f = s - float(i);\n vec4 p0 = pt(i - 1);\n vec4 p1 = pt(i);\n vec4 p2 = pt(i + 1);\n vec4 p3 = pt(i + 2);\n float f2 = f * f;\n float f3 = f2 * f;\n return 0.5 * ((2.0 * p1) + (-p0 + p2) * f + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * f2\n + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * f3);\n}\n\nvec2 place(vec4 c) { return vec2(c.x * uView.x, c.y - uScroll); }\n\nvec3 strandAt(float id, float t, out vec3 info) {\n vec4 c = spline(t);\n vec2 p = place(c);\n vec2 q = place(spline(t + 0.0004));\n vec2 tg = q - p;\n tg = length(tg) > 1e-4 ? normalize(tg) : vec2(0.0, 1.0);\n vec2 nr = vec2(-tg.y, tg.x);\n float r1 = h1(id + 0.13);\n float r2 = h1(id + 1.71);\n float r3 = h1(id + 3.37);\n float r4 = h1(id + 5.93);\n float n = (r1 + r2 + r3) / 1.5 - 1.0;\n float z = (r4 - 0.5) * 2.0;\n float along = c.y + c.x * uView.x;\n float th = along * 0.0033 + uTime * 0.25 + r2 * 0.5;\n float width = c.z * uSpread;\n float fan = width * uView.x * (1.0 + 0.25 * sin(uTime * 0.4 + along * 0.0045));\n float sway = sin(along * 0.006 + uTime * 0.23) * mix(6.0, 30.0, smoothstep(0.02, 0.2, width));\n float lat = sway + fan * (n * cos(th) + z * 0.55 * sin(th));\n lat += sin(along * 0.01 + uTime * (0.6 + 0.5 * r1) + r1 * 6.283) * 12.0 * (0.3 + r3);\n info = vec3(max(c.w, 0.0), width, along);\n return vec3(p + nr * lat, z * sin(th));\n}\n\nvoid main() {\n float id = aId;\n float r5 = h1(id + 7.77);\n float r6 = h1(id + 9.19);\n float r7 = h1(id + 11.3);\n float t = mix(uT.x, uT.y, aVert.x);\n vec3 info;\n vec3 unused;\n vec3 a = strandAt(id, t, info);\n vec3 b = strandAt(id, t + (uT.y - uT.x) / 700.0, unused);\n vec2 tg = b.xy - a.xy;\n tg = length(tg) > 1e-4 ? normalize(tg) : vec2(0.0, 1.0);\n vec2 nrm = vec2(-tg.y, tg.x);\n float soft = smoothstep(0.06, 0.28, info.y) * step(1.0 - uSoftness, r6);\n float base = mix(0.35, 1.3, r7 * r7) * uDpr * (1.0 + 0.3 * a.z);\n float w = max(base + soft * mix(8.0, 36.0, r6) * uDpr, 1.0);\n float drawW = w + 2.5 * uDpr;\n vec2 res = uView * uDpr;\n vec2 px = a.xy * uDpr + nrm * aVert.y * drawW * 0.5;\n gl_Position = vec4(px.x / res.x * 2.0 - 1.0, 1.0 - px.y / res.y * 2.0, 0.0, 1.0);\n vSide = aVert.y * drawW / w;\n float shimmer = 0.5 + 0.5 * sin(info.z * 0.044 - uTime * (2.0 + 2.0 * r5) + r6 * 6.283);\n shimmer *= shimmer;\n float bright = mix(0.25, 1.0, pow(r5, 1.4));\n float energy = min(base, w) / w;\n float depth = 0.7 + 0.3 * a.z;\n vec3 col = mix(uGlow, uTint, step(0.83, r6) * 0.9);\n col = mix(col, uPeak, step(0.93, r7) * 0.6);\n vColor = col * energy * info.x * (0.35 + 0.65 * shimmer) * bright * depth;\n}\n";
3
+ /** A soft profile across the ribbon, added into the buffer. */
4
+ export declare const LINE_FRAGMENT = "#version 300 es\nprecision highp float;\nin float vSide;\nin vec3 vColor;\nuniform float uScale;\nout vec4 outColor;\nvoid main() {\n outColor = vec4(vColor * exp(-vSide * vSide * 2.4) * uScale, 1.0);\n}\n";
5
+ /** Specks drifting along the strands, thickest where the ribbon is bright. */
6
+ export declare const DUST_VERTEX = "#version 300 es\nuniform float uDpr;\nuniform float uStrands;\nuniform vec3 uGlow;\nuniform vec3 uPeak;\nout vec3 vColor;\n\nuniform vec4 uPts[96];\nuniform int uCount;\nuniform vec2 uView;\nuniform float uScroll;\nuniform vec2 uT;\nuniform float uTime;\nuniform float uSpread;\n\nfloat h1(float n) { return fract(sin(n * 127.1 + 311.7) * 43758.5453); }\n\nvec4 pt(int i) { return uPts[clamp(i, 0, uCount - 1)]; }\n\nvec4 spline(float t) {\n float s = clamp(t, 0.0, 1.0) * float(uCount - 1);\n int i = min(int(floor(s)), uCount - 2);\n float f = s - float(i);\n vec4 p0 = pt(i - 1);\n vec4 p1 = pt(i);\n vec4 p2 = pt(i + 1);\n vec4 p3 = pt(i + 2);\n float f2 = f * f;\n float f3 = f2 * f;\n return 0.5 * ((2.0 * p1) + (-p0 + p2) * f + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * f2\n + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * f3);\n}\n\nvec2 place(vec4 c) { return vec2(c.x * uView.x, c.y - uScroll); }\n\nvec3 strandAt(float id, float t, out vec3 info) {\n vec4 c = spline(t);\n vec2 p = place(c);\n vec2 q = place(spline(t + 0.0004));\n vec2 tg = q - p;\n tg = length(tg) > 1e-4 ? normalize(tg) : vec2(0.0, 1.0);\n vec2 nr = vec2(-tg.y, tg.x);\n float r1 = h1(id + 0.13);\n float r2 = h1(id + 1.71);\n float r3 = h1(id + 3.37);\n float r4 = h1(id + 5.93);\n float n = (r1 + r2 + r3) / 1.5 - 1.0;\n float z = (r4 - 0.5) * 2.0;\n float along = c.y + c.x * uView.x;\n float th = along * 0.0033 + uTime * 0.25 + r2 * 0.5;\n float width = c.z * uSpread;\n float fan = width * uView.x * (1.0 + 0.25 * sin(uTime * 0.4 + along * 0.0045));\n float sway = sin(along * 0.006 + uTime * 0.23) * mix(6.0, 30.0, smoothstep(0.02, 0.2, width));\n float lat = sway + fan * (n * cos(th) + z * 0.55 * sin(th));\n lat += sin(along * 0.01 + uTime * (0.6 + 0.5 * r1) + r1 * 6.283) * 12.0 * (0.3 + r3);\n info = vec3(max(c.w, 0.0), width, along);\n return vec3(p + nr * lat, z * sin(th));\n}\n\nvoid main() {\n float id = float(gl_VertexID);\n float sid = floor(h1(id + 0.5) * uStrands);\n float r = h1(id + 2.3);\n float r2 = h1(id + 4.1);\n float t = mix(uT.x, uT.y, fract(r + uTime * 0.003 * (0.3 + r2)));\n vec3 info;\n vec3 a = strandAt(sid, t, info);\n vec2 off = (vec2(h1(id + 6.1), h1(id + 8.3)) - 0.5) * (24.0 + (150.0 + info.y * uView.x) * h1(id + 9.7));\n vec2 res = uView * uDpr;\n vec2 px = (a.xy + off) * uDpr;\n gl_Position = vec4(px.x / res.x * 2.0 - 1.0, 1.0 - px.y / res.y * 2.0, 0.0, 1.0);\n gl_PointSize = mix(1.2, 3.4, r2 * r2) * uDpr;\n float tw = 0.35 + 0.65 * (0.5 + 0.5 * sin(uTime * (0.5 + 1.8 * r) + r2 * 40.0));\n vColor = mix(uGlow, uPeak, 0.55) * tw * mix(0.2, 0.9, r) * info.x * 0.8;\n}\n";
7
+ export declare const DUST_FRAGMENT = "#version 300 es\nprecision highp float;\nin vec3 vColor;\nuniform float uScale;\nout vec4 outColor;\nvoid main() {\n vec2 c = gl_PointCoord - 0.5;\n outColor = vec4(vColor * exp(-dot(c, c) * 16.0) * uScale, 1.0);\n}\n";
8
+ /** One triangle over the whole target, from three vertex ids and no buffer. */
9
+ export declare const QUAD_VERTEX = "#version 300 es\nout vec2 vUv;\nvoid main() {\n vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\n vUv = p;\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\n";
10
+ /** Four bilinear taps, a box filter at half the step for the price of four reads. */
11
+ export declare const DOWN_FRAGMENT = "#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uSource;\nuniform vec2 uTexel;\nout vec4 outColor;\nvoid main() {\n vec3 c = texture(uSource, vUv + uTexel * vec2(-1.0, -1.0)).rgb\n + texture(uSource, vUv + uTexel * vec2(1.0, -1.0)).rgb\n + texture(uSource, vUv + uTexel * vec2(-1.0, 1.0)).rgb\n + texture(uSource, vUv + uTexel * vec2(1.0, 1.0)).rgb;\n outColor = vec4(c * 0.25, 1.0);\n}\n";
12
+ /** A nine-tap Gaussian in five reads, along one axis. */
13
+ export declare const BLUR_FRAGMENT = "#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uSource;\nuniform vec2 uStep;\nout vec4 outColor;\nvoid main() {\n vec3 c = texture(uSource, vUv).rgb * 0.2270;\n c += (texture(uSource, vUv + uStep * 1.3846).rgb + texture(uSource, vUv - uStep * 1.3846).rgb) * 0.3162;\n c += (texture(uSource, vUv + uStep * 3.2308).rgb + texture(uSource, vUv - uStep * 3.2308).rgb) * 0.0703;\n outColor = vec4(c, 1.0);\n}\n";
14
+ /**
15
+ * The light and its bloom over the page color.
16
+ *
17
+ * On a dark page the light is screened onto the ground, so it saturates to
18
+ * the peak rather than clipping. On a light page added light is invisible, so
19
+ * the same energy darkens the ground toward the peak as ink. A step of noise
20
+ * under a code value hides the banding a slow gradient shows on eight bits.
21
+ */
22
+ export declare const COMPOSITE_FRAGMENT = "#version 300 es\nprecision highp float;\nin vec2 vUv;\nuniform sampler2D uScene;\nuniform sampler2D uNear;\nuniform sampler2D uFar;\nuniform vec3 uGround;\nuniform vec3 uInk;\nuniform float uLight;\nuniform float uGain;\nout vec4 outColor;\nfloat hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }\nvoid main() {\n vec3 e = texture(uScene, vUv).rgb + texture(uNear, vUv).rgb * 1.1 + texture(uFar, vUv).rgb * 0.9;\n e *= uGain;\n vec3 dark = 1.0 - (1.0 - uGround) * exp(-e);\n float lum = dot(e, vec3(0.3, 0.55, 0.15));\n vec3 light = mix(uGround, uInk, 1.0 - exp(-lum * 1.4));\n vec3 c = mix(dark, light, uLight);\n c += (hash(gl_FragCoord.xy) - 0.5) / 255.0;\n outColor = vec4(c, 1.0);\n}\n";
@@ -0,0 +1,198 @@
1
+ import { MAX_POINTS } from './path.js';
2
+ const PATH = `
3
+ uniform vec4 uPts[${MAX_POINTS}];
4
+ uniform int uCount;
5
+ uniform vec2 uView;
6
+ uniform float uScroll;
7
+ uniform vec2 uT;
8
+ uniform float uTime;
9
+ uniform float uSpread;
10
+
11
+ float h1(float n) { return fract(sin(n * 127.1 + 311.7) * 43758.5453); }
12
+
13
+ vec4 pt(int i) { return uPts[clamp(i, 0, uCount - 1)]; }
14
+
15
+ vec4 spline(float t) {
16
+ float s = clamp(t, 0.0, 1.0) * float(uCount - 1);
17
+ int i = min(int(floor(s)), uCount - 2);
18
+ float f = s - float(i);
19
+ vec4 p0 = pt(i - 1);
20
+ vec4 p1 = pt(i);
21
+ vec4 p2 = pt(i + 1);
22
+ vec4 p3 = pt(i + 2);
23
+ float f2 = f * f;
24
+ float f3 = f2 * f;
25
+ return 0.5 * ((2.0 * p1) + (-p0 + p2) * f + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * f2
26
+ + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * f3);
27
+ }
28
+
29
+ vec2 place(vec4 c) { return vec2(c.x * uView.x, c.y - uScroll); }
30
+
31
+ vec3 strandAt(float id, float t, out vec3 info) {
32
+ vec4 c = spline(t);
33
+ vec2 p = place(c);
34
+ vec2 q = place(spline(t + 0.0004));
35
+ vec2 tg = q - p;
36
+ tg = length(tg) > 1e-4 ? normalize(tg) : vec2(0.0, 1.0);
37
+ vec2 nr = vec2(-tg.y, tg.x);
38
+ float r1 = h1(id + 0.13);
39
+ float r2 = h1(id + 1.71);
40
+ float r3 = h1(id + 3.37);
41
+ float r4 = h1(id + 5.93);
42
+ float n = (r1 + r2 + r3) / 1.5 - 1.0;
43
+ float z = (r4 - 0.5) * 2.0;
44
+ float along = c.y + c.x * uView.x;
45
+ float th = along * 0.0033 + uTime * 0.25 + r2 * 0.5;
46
+ float width = c.z * uSpread;
47
+ float fan = width * uView.x * (1.0 + 0.25 * sin(uTime * 0.4 + along * 0.0045));
48
+ float sway = sin(along * 0.006 + uTime * 0.23) * mix(6.0, 30.0, smoothstep(0.02, 0.2, width));
49
+ float lat = sway + fan * (n * cos(th) + z * 0.55 * sin(th));
50
+ lat += sin(along * 0.01 + uTime * (0.6 + 0.5 * r1) + r1 * 6.283) * 12.0 * (0.3 + r3);
51
+ info = vec3(max(c.w, 0.0), width, along);
52
+ return vec3(p + nr * lat, z * sin(th));
53
+ }
54
+ `;
55
+ export const LINE_VERTEX = `#version 300 es
56
+ layout(location = 0) in vec2 aVert;
57
+ layout(location = 1) in float aId;
58
+ uniform float uDpr;
59
+ uniform float uSoftness;
60
+ uniform vec3 uGlow;
61
+ uniform vec3 uTint;
62
+ uniform vec3 uPeak;
63
+ out float vSide;
64
+ out vec3 vColor;
65
+ ${PATH}
66
+ void main() {
67
+ float id = aId;
68
+ float r5 = h1(id + 7.77);
69
+ float r6 = h1(id + 9.19);
70
+ float r7 = h1(id + 11.3);
71
+ float t = mix(uT.x, uT.y, aVert.x);
72
+ vec3 info;
73
+ vec3 unused;
74
+ vec3 a = strandAt(id, t, info);
75
+ vec3 b = strandAt(id, t + (uT.y - uT.x) / 700.0, unused);
76
+ vec2 tg = b.xy - a.xy;
77
+ tg = length(tg) > 1e-4 ? normalize(tg) : vec2(0.0, 1.0);
78
+ vec2 nrm = vec2(-tg.y, tg.x);
79
+ float soft = smoothstep(0.06, 0.28, info.y) * step(1.0 - uSoftness, r6);
80
+ float base = mix(0.35, 1.3, r7 * r7) * uDpr * (1.0 + 0.3 * a.z);
81
+ float w = max(base + soft * mix(8.0, 36.0, r6) * uDpr, 1.0);
82
+ float drawW = w + 2.5 * uDpr;
83
+ vec2 res = uView * uDpr;
84
+ vec2 px = a.xy * uDpr + nrm * aVert.y * drawW * 0.5;
85
+ gl_Position = vec4(px.x / res.x * 2.0 - 1.0, 1.0 - px.y / res.y * 2.0, 0.0, 1.0);
86
+ vSide = aVert.y * drawW / w;
87
+ float shimmer = 0.5 + 0.5 * sin(info.z * 0.044 - uTime * (2.0 + 2.0 * r5) + r6 * 6.283);
88
+ shimmer *= shimmer;
89
+ float bright = mix(0.25, 1.0, pow(r5, 1.4));
90
+ float energy = min(base, w) / w;
91
+ float depth = 0.7 + 0.3 * a.z;
92
+ vec3 col = mix(uGlow, uTint, step(0.83, r6) * 0.9);
93
+ col = mix(col, uPeak, step(0.93, r7) * 0.6);
94
+ vColor = col * energy * info.x * (0.35 + 0.65 * shimmer) * bright * depth;
95
+ }
96
+ `;
97
+ export const LINE_FRAGMENT = `#version 300 es
98
+ precision highp float;
99
+ in float vSide;
100
+ in vec3 vColor;
101
+ uniform float uScale;
102
+ out vec4 outColor;
103
+ void main() {
104
+ outColor = vec4(vColor * exp(-vSide * vSide * 2.4) * uScale, 1.0);
105
+ }
106
+ `;
107
+ export const DUST_VERTEX = `#version 300 es
108
+ uniform float uDpr;
109
+ uniform float uStrands;
110
+ uniform vec3 uGlow;
111
+ uniform vec3 uPeak;
112
+ out vec3 vColor;
113
+ ${PATH}
114
+ void main() {
115
+ float id = float(gl_VertexID);
116
+ float sid = floor(h1(id + 0.5) * uStrands);
117
+ float r = h1(id + 2.3);
118
+ float r2 = h1(id + 4.1);
119
+ float t = mix(uT.x, uT.y, fract(r + uTime * 0.003 * (0.3 + r2)));
120
+ vec3 info;
121
+ vec3 a = strandAt(sid, t, info);
122
+ vec2 off = (vec2(h1(id + 6.1), h1(id + 8.3)) - 0.5) * (24.0 + (150.0 + info.y * uView.x) * h1(id + 9.7));
123
+ vec2 res = uView * uDpr;
124
+ vec2 px = (a.xy + off) * uDpr;
125
+ gl_Position = vec4(px.x / res.x * 2.0 - 1.0, 1.0 - px.y / res.y * 2.0, 0.0, 1.0);
126
+ gl_PointSize = mix(1.2, 3.4, r2 * r2) * uDpr;
127
+ float tw = 0.35 + 0.65 * (0.5 + 0.5 * sin(uTime * (0.5 + 1.8 * r) + r2 * 40.0));
128
+ vColor = mix(uGlow, uPeak, 0.55) * tw * mix(0.2, 0.9, r) * info.x * 0.8;
129
+ }
130
+ `;
131
+ export const DUST_FRAGMENT = `#version 300 es
132
+ precision highp float;
133
+ in vec3 vColor;
134
+ uniform float uScale;
135
+ out vec4 outColor;
136
+ void main() {
137
+ vec2 c = gl_PointCoord - 0.5;
138
+ outColor = vec4(vColor * exp(-dot(c, c) * 16.0) * uScale, 1.0);
139
+ }
140
+ `;
141
+ export const QUAD_VERTEX = `#version 300 es
142
+ out vec2 vUv;
143
+ void main() {
144
+ vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
145
+ vUv = p;
146
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
147
+ }
148
+ `;
149
+ export const DOWN_FRAGMENT = `#version 300 es
150
+ precision highp float;
151
+ in vec2 vUv;
152
+ uniform sampler2D uSource;
153
+ uniform vec2 uTexel;
154
+ out vec4 outColor;
155
+ void main() {
156
+ vec3 c = texture(uSource, vUv + uTexel * vec2(-1.0, -1.0)).rgb
157
+ + texture(uSource, vUv + uTexel * vec2(1.0, -1.0)).rgb
158
+ + texture(uSource, vUv + uTexel * vec2(-1.0, 1.0)).rgb
159
+ + texture(uSource, vUv + uTexel * vec2(1.0, 1.0)).rgb;
160
+ outColor = vec4(c * 0.25, 1.0);
161
+ }
162
+ `;
163
+ export const BLUR_FRAGMENT = `#version 300 es
164
+ precision highp float;
165
+ in vec2 vUv;
166
+ uniform sampler2D uSource;
167
+ uniform vec2 uStep;
168
+ out vec4 outColor;
169
+ void main() {
170
+ vec3 c = texture(uSource, vUv).rgb * 0.2270;
171
+ c += (texture(uSource, vUv + uStep * 1.3846).rgb + texture(uSource, vUv - uStep * 1.3846).rgb) * 0.3162;
172
+ c += (texture(uSource, vUv + uStep * 3.2308).rgb + texture(uSource, vUv - uStep * 3.2308).rgb) * 0.0703;
173
+ outColor = vec4(c, 1.0);
174
+ }
175
+ `;
176
+ export const COMPOSITE_FRAGMENT = `#version 300 es
177
+ precision highp float;
178
+ in vec2 vUv;
179
+ uniform sampler2D uScene;
180
+ uniform sampler2D uNear;
181
+ uniform sampler2D uFar;
182
+ uniform vec3 uGround;
183
+ uniform vec3 uInk;
184
+ uniform float uLight;
185
+ uniform float uGain;
186
+ out vec4 outColor;
187
+ float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
188
+ void main() {
189
+ vec3 e = texture(uScene, vUv).rgb + texture(uNear, vUv).rgb * 1.1 + texture(uFar, vUv).rgb * 0.9;
190
+ e *= uGain;
191
+ vec3 dark = 1.0 - (1.0 - uGround) * exp(-e);
192
+ float lum = dot(e, vec3(0.3, 0.55, 0.15));
193
+ vec3 light = mix(uGround, uInk, 1.0 - exp(-lum * 1.4));
194
+ vec3 c = mix(dark, light, uLight);
195
+ c += (hash(gl_FragCoord.xy) - 0.5) / 255.0;
196
+ outColor = vec4(c, 1.0);
197
+ }
198
+ `;
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;