@fusion.dev/helix 0.160.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.
- package/custom-elements.json +5183 -526
- package/dist/aura/root/aura-root.d.ts +0 -49
- package/dist/aura/root/aura-root.js +2 -16
- package/dist/chat/message/chat-message.css.js +1 -1
- package/dist/chat/root/chat-root.css.js +6 -1
- package/dist/code-block/root/code-block-root.css.js +10 -3
- package/dist/code-block/root/code-block-root.d.ts +4 -10
- package/dist/code-block/root/code-block-root.js +0 -8
- package/dist/element-events.generated.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/strands/index.d.ts +2 -0
- package/dist/strands/index.js +2 -0
- package/dist/strands/palette.d.ts +42 -0
- package/dist/strands/palette.js +24 -0
- package/dist/strands/path.d.ts +89 -0
- package/dist/strands/path.js +153 -0
- package/dist/strands/renderer.d.ts +84 -0
- package/dist/strands/renderer.js +255 -0
- package/dist/strands/root/strands-root.css.d.ts +1 -0
- package/dist/strands/root/strands-root.css.js +8 -0
- package/dist/strands/root/strands-root.d.ts +230 -0
- package/dist/strands/root/strands-root.js +466 -0
- package/dist/strands/shader.d.ts +22 -0
- package/dist/strands/shader.js +198 -0
- package/dist/strands/stop/strands-stop.css.d.ts +1 -0
- package/dist/strands/stop/strands-stop.css.js +2 -0
- package/dist/strands/stop/strands-stop.d.ts +44 -0
- package/dist/strands/stop/strands-stop.js +29 -0
- package/dist/table/root/table-root.css.js +2 -1
- package/dist/table/row/table-row.css.js +8 -2
- package/dist/table/row/table-row.d.ts +2 -0
- package/dist/timeline/item/timeline-item.css.js +5 -1
- package/dist/video-playlist/root/video-playlist-root.css.js +1 -1
- package/dist/video-playlist/root/video-playlist-root.d.ts +2 -0
- package/dist/virtual-list/root/virtual-list-root.css.js +1 -1
- package/dist/virtual-list/root/virtual-list-root.d.ts +1 -0
- package/lint.json +1 -1
- package/package.json +6 -6
- package/types/jsx.d.ts +1206 -66
- package/types/svelte.d.ts +2 -0
- package/types/vue.d.ts +314 -12
- package/types/words.d.ts +79 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { StrandsPalette } from './palette.js';
|
|
2
|
+
/** The most strands a ribbon draws, at `density` 1. */
|
|
3
|
+
export declare const MAX_STRANDS = 340;
|
|
4
|
+
/** The fewest, at `density` 0. Fewer stops reading as a ribbon at all. */
|
|
5
|
+
export declare const MIN_STRANDS = 60;
|
|
6
|
+
/** What one frame needs, all of it already resolved by the element. */
|
|
7
|
+
export interface StrandsFrame {
|
|
8
|
+
/** How many of the path's points are in use. */
|
|
9
|
+
count: number;
|
|
10
|
+
/** The device pixels per CSS pixel the buffer was sized at. */
|
|
11
|
+
dpr: number;
|
|
12
|
+
/** Overall brightness. */
|
|
13
|
+
gain: number;
|
|
14
|
+
/** The path, four floats a point, padded to the uniform array's length. */
|
|
15
|
+
points: Float32Array;
|
|
16
|
+
/** The stretch of the path on screen, as a range of its parameter. */
|
|
17
|
+
range: readonly [number, number];
|
|
18
|
+
/** How far the page is scrolled, in CSS pixels, or `0` inside a box. */
|
|
19
|
+
scroll: number;
|
|
20
|
+
/** The share of strands drawn soft, and how soft. */
|
|
21
|
+
softness: number;
|
|
22
|
+
/** A multiplier on the ribbon's width. */
|
|
23
|
+
spread: number;
|
|
24
|
+
/** How many strands to draw. */
|
|
25
|
+
strands: number;
|
|
26
|
+
/** Seconds of motion so far. */
|
|
27
|
+
time: number;
|
|
28
|
+
/** The box drawn into, in CSS pixels. */
|
|
29
|
+
view: readonly [number, number];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The pipeline, on one context: strands and dust added into a float buffer,
|
|
33
|
+
* two levels of bloom, and the composite over the page color.
|
|
34
|
+
*
|
|
35
|
+
* **Created whole or not at all.** `create` answers `null` when any program
|
|
36
|
+
* fails to link, so the element has one question to ask and one fallback to
|
|
37
|
+
* take, and never draws half a pipeline.
|
|
38
|
+
*
|
|
39
|
+
* **Float buffers where the GPU offers them, and eight bits where it does not.**
|
|
40
|
+
* Light is added strand by strand, and a hot bend sums far past `1`; an eight
|
|
41
|
+
* bit buffer clips it, so there the energy is scaled down on the way in and
|
|
42
|
+
* back up in the composite, which keeps the bend hot at the cost of some
|
|
43
|
+
* gradation in the dimmest strands.
|
|
44
|
+
*/
|
|
45
|
+
export declare class StrandsRenderer {
|
|
46
|
+
private readonly gl;
|
|
47
|
+
private readonly line;
|
|
48
|
+
private readonly dust;
|
|
49
|
+
private readonly down;
|
|
50
|
+
private readonly blur;
|
|
51
|
+
private readonly composite;
|
|
52
|
+
private readonly ribbon;
|
|
53
|
+
private readonly empty;
|
|
54
|
+
private readonly buffers;
|
|
55
|
+
private readonly half;
|
|
56
|
+
private readonly scale;
|
|
57
|
+
private targets;
|
|
58
|
+
private constructor();
|
|
59
|
+
/**
|
|
60
|
+
* The pipeline on `gl`, or `null` when a program does not link, which the
|
|
61
|
+
* element answers with its gradient.
|
|
62
|
+
*/
|
|
63
|
+
static create(gl: WebGL2RenderingContext): StrandsRenderer | null;
|
|
64
|
+
/**
|
|
65
|
+
* Sizes the buffers to the drawing buffer. The bloom runs at a quarter and a
|
|
66
|
+
* twelfth of it, which is where a glow's width lives and a twentieth of the
|
|
67
|
+
* fill.
|
|
68
|
+
*/
|
|
69
|
+
resize(width: number, height: number): boolean;
|
|
70
|
+
render(frame: StrandsFrame, palette: StrandsPalette): void;
|
|
71
|
+
/** Deletes everything made on the context, ahead of losing it. */
|
|
72
|
+
dispose(): void;
|
|
73
|
+
private release;
|
|
74
|
+
private target;
|
|
75
|
+
/** The uniforms every strand and speck reads. */
|
|
76
|
+
private use;
|
|
77
|
+
private shrink;
|
|
78
|
+
/** A separable blur of `target`, through `swap` and back. */
|
|
79
|
+
private smear;
|
|
80
|
+
private pass;
|
|
81
|
+
private texture;
|
|
82
|
+
private vec3;
|
|
83
|
+
private float;
|
|
84
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { BLUR_FRAGMENT, COMPOSITE_FRAGMENT, DOWN_FRAGMENT, DUST_FRAGMENT, DUST_VERTEX, LINE_FRAGMENT, LINE_VERTEX, QUAD_VERTEX, } from './shader.js';
|
|
2
|
+
export const MAX_STRANDS = 340;
|
|
3
|
+
export const MIN_STRANDS = 60;
|
|
4
|
+
const SEGMENTS = 320;
|
|
5
|
+
const DUST = 600;
|
|
6
|
+
export class StrandsRenderer {
|
|
7
|
+
constructor(gl, programs) {
|
|
8
|
+
this.targets = null;
|
|
9
|
+
this.gl = gl;
|
|
10
|
+
[this.line, this.dust, this.down, this.blur, this.composite] = programs;
|
|
11
|
+
this.half = !!gl.getExtension('EXT_color_buffer_float');
|
|
12
|
+
this.scale = this.half ? 1 : 0.2;
|
|
13
|
+
const vertices = new Float32Array((SEGMENTS + 1) * 4);
|
|
14
|
+
for (let s = 0; s <= SEGMENTS; s++) {
|
|
15
|
+
vertices.set([s / SEGMENTS, -1, s / SEGMENTS, 1], s * 4);
|
|
16
|
+
}
|
|
17
|
+
const ids = new Float32Array(MAX_STRANDS);
|
|
18
|
+
for (let i = 0; i < MAX_STRANDS; i++) {
|
|
19
|
+
ids[i] = i;
|
|
20
|
+
}
|
|
21
|
+
const vertexBuffer = gl.createBuffer();
|
|
22
|
+
const idBuffer = gl.createBuffer();
|
|
23
|
+
this.buffers = [vertexBuffer, idBuffer];
|
|
24
|
+
this.ribbon = gl.createVertexArray();
|
|
25
|
+
gl.bindVertexArray(this.ribbon);
|
|
26
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
27
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
28
|
+
gl.enableVertexAttribArray(0);
|
|
29
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
30
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, idBuffer);
|
|
31
|
+
gl.bufferData(gl.ARRAY_BUFFER, ids, gl.STATIC_DRAW);
|
|
32
|
+
gl.enableVertexAttribArray(1);
|
|
33
|
+
gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
|
|
34
|
+
gl.vertexAttribDivisor(1, 1);
|
|
35
|
+
this.empty = gl.createVertexArray();
|
|
36
|
+
gl.bindVertexArray(null);
|
|
37
|
+
}
|
|
38
|
+
static create(gl) {
|
|
39
|
+
const line = link(gl, LINE_VERTEX, LINE_FRAGMENT);
|
|
40
|
+
const dust = link(gl, DUST_VERTEX, DUST_FRAGMENT);
|
|
41
|
+
const down = link(gl, QUAD_VERTEX, DOWN_FRAGMENT);
|
|
42
|
+
const blur = link(gl, QUAD_VERTEX, BLUR_FRAGMENT);
|
|
43
|
+
const composite = link(gl, QUAD_VERTEX, COMPOSITE_FRAGMENT);
|
|
44
|
+
if (!line || !dust || !down || !blur || !composite) {
|
|
45
|
+
for (const one of [line, dust, down, blur, composite]) {
|
|
46
|
+
if (one) {
|
|
47
|
+
gl.deleteProgram(one.program);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return new StrandsRenderer(gl, [line, dust, down, blur, composite]);
|
|
53
|
+
}
|
|
54
|
+
resize(width, height) {
|
|
55
|
+
this.release();
|
|
56
|
+
const scene = this.target(width, height);
|
|
57
|
+
const near = this.target(Math.ceil(width / 4), Math.ceil(height / 4));
|
|
58
|
+
const nearSwap = this.target(Math.ceil(width / 4), Math.ceil(height / 4));
|
|
59
|
+
const far = this.target(Math.ceil(width / 12), Math.ceil(height / 12));
|
|
60
|
+
const farSwap = this.target(Math.ceil(width / 12), Math.ceil(height / 12));
|
|
61
|
+
if (!scene || !near || !nearSwap || !far || !farSwap) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
this.targets = { far, farSwap, near, nearSwap, scene };
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
render(frame, palette) {
|
|
68
|
+
const { gl, targets } = this;
|
|
69
|
+
if (!targets) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const { scene } = targets;
|
|
73
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, scene.buffer);
|
|
74
|
+
gl.viewport(0, 0, scene.width, scene.height);
|
|
75
|
+
gl.clearColor(0, 0, 0, 1);
|
|
76
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
77
|
+
if (frame.range[1] > frame.range[0] && frame.count >= 2) {
|
|
78
|
+
gl.enable(gl.BLEND);
|
|
79
|
+
gl.blendFunc(gl.ONE, gl.ONE);
|
|
80
|
+
this.use(this.line, frame);
|
|
81
|
+
this.vec3(this.line, 'uGlow', palette.glow);
|
|
82
|
+
this.vec3(this.line, 'uTint', palette.tint);
|
|
83
|
+
this.vec3(this.line, 'uPeak', palette.peak);
|
|
84
|
+
this.float(this.line, 'uSoftness', frame.softness);
|
|
85
|
+
gl.bindVertexArray(this.ribbon);
|
|
86
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, (SEGMENTS + 1) * 2, frame.strands);
|
|
87
|
+
this.use(this.dust, frame);
|
|
88
|
+
this.vec3(this.dust, 'uGlow', palette.glow);
|
|
89
|
+
this.vec3(this.dust, 'uPeak', palette.peak);
|
|
90
|
+
this.float(this.dust, 'uStrands', frame.strands);
|
|
91
|
+
gl.bindVertexArray(this.empty);
|
|
92
|
+
gl.drawArrays(gl.POINTS, 0, DUST);
|
|
93
|
+
gl.disable(gl.BLEND);
|
|
94
|
+
}
|
|
95
|
+
const { far, farSwap, near, nearSwap } = targets;
|
|
96
|
+
this.shrink(scene, near);
|
|
97
|
+
this.smear(near, nearSwap, 1);
|
|
98
|
+
this.smear(near, nearSwap, 2);
|
|
99
|
+
this.shrink(near, far);
|
|
100
|
+
this.smear(far, farSwap, 1);
|
|
101
|
+
this.smear(far, farSwap, 2);
|
|
102
|
+
gl.useProgram(this.composite.program);
|
|
103
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
104
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
105
|
+
this.texture(this.composite, 'uScene', 0, scene);
|
|
106
|
+
this.texture(this.composite, 'uNear', 1, near);
|
|
107
|
+
this.texture(this.composite, 'uFar', 2, far);
|
|
108
|
+
this.vec3(this.composite, 'uGround', palette.ground);
|
|
109
|
+
this.vec3(this.composite, 'uInk', palette.peak);
|
|
110
|
+
this.float(this.composite, 'uLight', palette.light ? 1 : 0);
|
|
111
|
+
this.float(this.composite, 'uGain', (1.7 * frame.gain) / this.scale);
|
|
112
|
+
gl.bindVertexArray(this.empty);
|
|
113
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
114
|
+
}
|
|
115
|
+
dispose() {
|
|
116
|
+
const { gl } = this;
|
|
117
|
+
this.release();
|
|
118
|
+
for (const one of [this.line, this.dust, this.down, this.blur, this.composite]) {
|
|
119
|
+
gl.deleteProgram(one.program);
|
|
120
|
+
}
|
|
121
|
+
for (const buffer of this.buffers) {
|
|
122
|
+
gl.deleteBuffer(buffer);
|
|
123
|
+
}
|
|
124
|
+
gl.deleteVertexArray(this.ribbon);
|
|
125
|
+
gl.deleteVertexArray(this.empty);
|
|
126
|
+
}
|
|
127
|
+
release() {
|
|
128
|
+
if (!this.targets) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const one of Object.values(this.targets)) {
|
|
132
|
+
this.gl.deleteTexture(one.texture);
|
|
133
|
+
this.gl.deleteFramebuffer(one.buffer);
|
|
134
|
+
}
|
|
135
|
+
this.targets = null;
|
|
136
|
+
}
|
|
137
|
+
target(width, height) {
|
|
138
|
+
const { gl } = this;
|
|
139
|
+
const texture = gl.createTexture();
|
|
140
|
+
const buffer = gl.createFramebuffer();
|
|
141
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
142
|
+
if (this.half) {
|
|
143
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.HALF_FLOAT, null);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
147
|
+
}
|
|
148
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
149
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
150
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
151
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
152
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, buffer);
|
|
153
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
154
|
+
const complete = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
155
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
156
|
+
if (!complete) {
|
|
157
|
+
gl.deleteTexture(texture);
|
|
158
|
+
gl.deleteFramebuffer(buffer);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return { buffer, height, texture, width };
|
|
162
|
+
}
|
|
163
|
+
use(program, frame) {
|
|
164
|
+
const { gl } = this;
|
|
165
|
+
gl.useProgram(program.program);
|
|
166
|
+
gl.uniform4fv(program.uniforms.get('uPts') ?? null, frame.points);
|
|
167
|
+
gl.uniform1i(program.uniforms.get('uCount') ?? null, frame.count);
|
|
168
|
+
gl.uniform2f(program.uniforms.get('uView') ?? null, frame.view[0], frame.view[1]);
|
|
169
|
+
gl.uniform1f(program.uniforms.get('uScroll') ?? null, frame.scroll);
|
|
170
|
+
gl.uniform2f(program.uniforms.get('uT') ?? null, frame.range[0], frame.range[1]);
|
|
171
|
+
gl.uniform1f(program.uniforms.get('uTime') ?? null, frame.time);
|
|
172
|
+
gl.uniform1f(program.uniforms.get('uSpread') ?? null, frame.spread);
|
|
173
|
+
gl.uniform1f(program.uniforms.get('uDpr') ?? null, frame.dpr);
|
|
174
|
+
gl.uniform1f(program.uniforms.get('uScale') ?? null, this.scale);
|
|
175
|
+
}
|
|
176
|
+
shrink(from, into) {
|
|
177
|
+
this.pass(this.down, into, () => {
|
|
178
|
+
this.texture(this.down, 'uSource', 0, from);
|
|
179
|
+
this.gl.uniform2f(this.down.uniforms.get('uTexel') ?? null, 1 / from.width, 1 / from.height);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
smear(target, swap, radius) {
|
|
183
|
+
this.pass(this.blur, swap, () => {
|
|
184
|
+
this.texture(this.blur, 'uSource', 0, target);
|
|
185
|
+
this.gl.uniform2f(this.blur.uniforms.get('uStep') ?? null, radius / target.width, 0);
|
|
186
|
+
});
|
|
187
|
+
this.pass(this.blur, target, () => {
|
|
188
|
+
this.texture(this.blur, 'uSource', 0, swap);
|
|
189
|
+
this.gl.uniform2f(this.blur.uniforms.get('uStep') ?? null, 0, radius / swap.height);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
pass(program, into, bind) {
|
|
193
|
+
const { gl } = this;
|
|
194
|
+
gl.useProgram(program.program);
|
|
195
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, into.buffer);
|
|
196
|
+
gl.viewport(0, 0, into.width, into.height);
|
|
197
|
+
bind();
|
|
198
|
+
gl.bindVertexArray(this.empty);
|
|
199
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
200
|
+
}
|
|
201
|
+
texture(program, name, unit, from) {
|
|
202
|
+
const { gl } = this;
|
|
203
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
204
|
+
gl.bindTexture(gl.TEXTURE_2D, from.texture);
|
|
205
|
+
gl.uniform1i(program.uniforms.get(name) ?? null, unit);
|
|
206
|
+
}
|
|
207
|
+
vec3(program, name, value) {
|
|
208
|
+
this.gl.uniform3f(program.uniforms.get(name) ?? null, value[0], value[1], value[2]);
|
|
209
|
+
}
|
|
210
|
+
float(program, name, value) {
|
|
211
|
+
this.gl.uniform1f(program.uniforms.get(name) ?? null, value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function link(gl, vertex, fragment) {
|
|
215
|
+
const compile = (type, source) => {
|
|
216
|
+
const shader = gl.createShader(type);
|
|
217
|
+
if (!shader) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
gl.shaderSource(shader, source);
|
|
221
|
+
gl.compileShader(shader);
|
|
222
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
223
|
+
console.error(gl.getShaderInfoLog(shader));
|
|
224
|
+
gl.deleteShader(shader);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return shader;
|
|
228
|
+
};
|
|
229
|
+
const vs = compile(gl.VERTEX_SHADER, vertex);
|
|
230
|
+
const fs = compile(gl.FRAGMENT_SHADER, fragment);
|
|
231
|
+
const program = vs && fs ? gl.createProgram() : null;
|
|
232
|
+
if (!vs || !fs || !program) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
gl.attachShader(program, vs);
|
|
236
|
+
gl.attachShader(program, fs);
|
|
237
|
+
gl.linkProgram(program);
|
|
238
|
+
gl.deleteShader(vs);
|
|
239
|
+
gl.deleteShader(fs);
|
|
240
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
241
|
+
console.error(gl.getProgramInfoLog(program));
|
|
242
|
+
gl.deleteProgram(program);
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const uniforms = new Map();
|
|
246
|
+
const count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
|
|
247
|
+
for (let i = 0; i < count; i++) {
|
|
248
|
+
const info = gl.getActiveUniform(program, i);
|
|
249
|
+
const at = info ? gl.getUniformLocation(program, info.name) : null;
|
|
250
|
+
if (info && at) {
|
|
251
|
+
uniforms.set(info.name.replace('[0]', ''), at);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { program, uniforms };
|
|
255
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createSheet } from '@fusion.dev/helix-core';
|
|
2
|
+
export const styles = createSheet(`:host {--_strands-glow: var(--strands-glow, var(--helix-color-primary));--_strands-ground: var(--strands-ground, var(--helix-color-background));--_strands-tint: var(--strands-tint, var(--helix-color-info));position: relative;display: block;inline-size: 100%;block-size: 100%;background-image: linear-gradient(
|
|
3
|
+
115deg,
|
|
4
|
+
var(--_strands-ground) 20%,
|
|
5
|
+
color-mix(in oklab, var(--_strands-glow) 22%, var(--_strands-ground)) 48%,
|
|
6
|
+
color-mix(in oklab, var(--_strands-tint) 12%, var(--_strands-ground)) 58%,
|
|
7
|
+
var(--_strands-ground) 82%
|
|
8
|
+
);pointer-events: none;}:host([travel]) {block-size: auto;inline-size: auto;inset: 0;position: fixed;z-index: var(--helix-layer-beneath);}[part="canvas"] {display: block;inline-size: 100%;block-size: 100%;caret-color: var(--strands-peak, var(--helix-color-primary-hover));color: var(--_strands-glow);column-rule-color: var(--strands-peak, var(--helix-color-primary-active));outline-color: var(--_strands-ground);text-decoration-color: var(--_strands-tint);transition-duration: var(--helix-duration-fast);transition-property: caret-color, color, column-rule-color, outline-color, text-decoration-color;}[part="content"] {inset: 0;pointer-events: none;position: absolute;}::slotted(*) {pointer-events: auto;}[part="canvas"][hidden] {display: none;}@media print {:host {display: none;}}`);
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
|
|
3
|
+
import { type StrandsVariant } from '../path.js';
|
|
4
|
+
/**
|
|
5
|
+
* @summary A ribbon of fine strands of light, drawn in your theme's colors, in its own box or down a whole page.
|
|
6
|
+
*
|
|
7
|
+
* @description
|
|
8
|
+
* Hundreds of thin strands are drawn as one ribbon, and the light they add
|
|
9
|
+
* together is bloomed, so where they converge the ribbon burns hot. The ribbon
|
|
10
|
+
* moves on its own all the time: it sways and breathes, its strands wave and
|
|
11
|
+
* roll over one another, and light runs along them.
|
|
12
|
+
*
|
|
13
|
+
* Its palette comes from the tokens. The strands are `--helix-color-primary`,
|
|
14
|
+
* a few are `--helix-color-info` for depth, and the page they light is
|
|
15
|
+
* `--helix-color-background`. On a light page the light becomes ink, because
|
|
16
|
+
* light added to near-white cannot be seen.
|
|
17
|
+
*
|
|
18
|
+
* **Three forms, set with `variant`.** `fan` is a tight, bright bend with its
|
|
19
|
+
* arms opening to the right. `ribbon`, the default, is a broad, soft ribbon in
|
|
20
|
+
* long curves. `waves` is sharp strands crossing the full width.
|
|
21
|
+
*
|
|
22
|
+
* **In its own box, by default.** The element fills the box it is given, the
|
|
23
|
+
* way an image does, and `at` places the fan's bend or the ribbon's axis across
|
|
24
|
+
* it. Content placed inside sits over the ribbon, in the `content` part.
|
|
25
|
+
*
|
|
26
|
+
* **Down the whole page, with `travel`.** The element fixes itself behind the
|
|
27
|
+
* page, on `--helix-layer-beneath`, and lays one ribbon from the top of the page to the
|
|
28
|
+
* bottom, so scrolling travels along it. Put `helix-strands-stop` elements
|
|
29
|
+
* inside to pin a form to a section: each names an element with `for` and says
|
|
30
|
+
* which form the ribbon takes there. Between stops the ribbon fills the gap in
|
|
31
|
+
* its own `variant`, and between two waves it turns off screen so each wave
|
|
32
|
+
* reads alone. Set `z-index` on the element to move it.
|
|
33
|
+
*
|
|
34
|
+
* The ribbon stops moving in these cases:
|
|
35
|
+
* - `prefers-reduced-motion` is set. The element shows one still frame, and in
|
|
36
|
+
* `travel` still follows the scroll, without easing.
|
|
37
|
+
* - The element is off screen or in a background tab.
|
|
38
|
+
*
|
|
39
|
+
* It shows a gradient from the same tokens in these cases, and matches
|
|
40
|
+
* `:state(fallback)` and sends `helix-strands-fallback` with the reason:
|
|
41
|
+
* - The renderer draws on the CPU, such as a headless browser or a machine with
|
|
42
|
+
* no usable GPU.
|
|
43
|
+
* - There is no WebGL, or the browser took the drawing context back.
|
|
44
|
+
*
|
|
45
|
+
* Strands is styled only and has behavior, as `Aura` does: its behavior is
|
|
46
|
+
* drawing to a canvas, so there is no structure for a headless version to hold.
|
|
47
|
+
*
|
|
48
|
+
* @since 0.147.0
|
|
49
|
+
* @status experimental
|
|
50
|
+
* @category media
|
|
51
|
+
* @aliases Ribbon, Filaments, Light strands, Fibers
|
|
52
|
+
* @documentation https://fusion.dev/helix/components/strands
|
|
53
|
+
*
|
|
54
|
+
* @slot - Content drawn over the ribbon, such as a heading, and the `helix-strands-stop` elements that pin its forms in `travel`.
|
|
55
|
+
*
|
|
56
|
+
* @event {HelixStrandsFallbackEvent} helix-strands-fallback - Fired each time the element stops drawing its ribbon and shows its gradient, with the reason.
|
|
57
|
+
*
|
|
58
|
+
* @csspart canvas - The surface the ribbon draws into.
|
|
59
|
+
* @csspart content - The box over the ribbon that holds the content. It covers the element.
|
|
60
|
+
*
|
|
61
|
+
* @cssproperty --strands-glow - The color of most strands. Defaults to `--helix-color-primary`.
|
|
62
|
+
* @cssproperty --strands-ground - The page the light is added to. Defaults to `--helix-color-background`.
|
|
63
|
+
* @cssproperty --strands-peak - The hottest strands, and on a light page the ink the light darkens toward. Defaults to `--helix-color-primary-hover` on a dark page and `--helix-color-primary-active` on a light one.
|
|
64
|
+
* @cssproperty --strands-tint - The color of a few strands, for depth. Defaults to `--helix-color-info`.
|
|
65
|
+
*
|
|
66
|
+
* @cssstate fallback - The element shows its gradient: the renderer draws on the CPU, there is no WebGL, or the browser took the context back. Cleared once the ribbon draws again.
|
|
67
|
+
*/
|
|
68
|
+
export declare class HelixStrands extends HelixElement {
|
|
69
|
+
static styles: CSSResultGroup;
|
|
70
|
+
/**
|
|
71
|
+
* Where the form sits across the box, as a share of the width: the fan's
|
|
72
|
+
* bend or the ribbon's axis. `0` is the left edge and `1` the right. A wave
|
|
73
|
+
* crosses the whole width and does not read it. Unset, a fan bends at `0.4`
|
|
74
|
+
* and a ribbon runs down the middle.
|
|
75
|
+
*
|
|
76
|
+
* **A share rather than a length**, so the ribbon keeps its place when the
|
|
77
|
+
* box changes shape. Read inside a box only; in `travel` each stop has its
|
|
78
|
+
* own.
|
|
79
|
+
*/
|
|
80
|
+
at?: number;
|
|
81
|
+
/**
|
|
82
|
+
* How many strands the ribbon has, from `0`, sixty, to `1`, three hundred and
|
|
83
|
+
* forty. Clamped to that range.
|
|
84
|
+
*/
|
|
85
|
+
density: number;
|
|
86
|
+
/** Overall brightness, as a multiplier on the light. */
|
|
87
|
+
gain: number;
|
|
88
|
+
/**
|
|
89
|
+
* The share of strands drawn out of focus where the ribbon is wide, from `0`,
|
|
90
|
+
* every strand sharp, to `1`, every one soft. Clamped to that range. A tight
|
|
91
|
+
* form, such as a fan's bend or a wave, stays sharp whatever this says.
|
|
92
|
+
*/
|
|
93
|
+
softness: number;
|
|
94
|
+
/** How fast the ribbon moves, as a multiplier on time. `0` holds it still. */
|
|
95
|
+
speed: number;
|
|
96
|
+
/** How wide the ribbon is, as a multiplier on each form's own width. */
|
|
97
|
+
spread: number;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the ribbon is laid down the whole page, so scrolling travels along
|
|
100
|
+
* it.
|
|
101
|
+
*
|
|
102
|
+
* Reflected because the stylesheet selects on it: in `travel` the host fixes
|
|
103
|
+
* itself to the viewport, behind the page.
|
|
104
|
+
*/
|
|
105
|
+
travel: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* The form the ribbon takes. When it is not set, the form is `ribbon`. In
|
|
108
|
+
* `travel`, the form of every stretch no stop claims.
|
|
109
|
+
*/
|
|
110
|
+
variant?: StrandsVariant;
|
|
111
|
+
/** The colors, resolved from what the canvas carries. Aura's approach, in `readPalette`. */
|
|
112
|
+
private palette;
|
|
113
|
+
/** The canvas's live computed style, and the five colors it last carried. */
|
|
114
|
+
private carried;
|
|
115
|
+
/** Why the gradient shows, while it does, or `undefined` while the ribbon draws. */
|
|
116
|
+
private fallback;
|
|
117
|
+
/** Why the last `build` drew nothing, for the fallback it leads to. */
|
|
118
|
+
private refused;
|
|
119
|
+
private canvas;
|
|
120
|
+
private gl;
|
|
121
|
+
private renderer;
|
|
122
|
+
/** The pending context release, or `0`. Canceled by a reconnection. */
|
|
123
|
+
private release;
|
|
124
|
+
/** What every context is asked for, the same as Aura's and the probe's. */
|
|
125
|
+
private static readonly CONTEXT;
|
|
126
|
+
/** Seconds of motion so far, accumulated so a paused tab resumes where it was. */
|
|
127
|
+
private elapsed;
|
|
128
|
+
/** The drawing buffer, in device pixels, and the ratio it was sized at. */
|
|
129
|
+
private width;
|
|
130
|
+
private height;
|
|
131
|
+
private dpr;
|
|
132
|
+
/** The element's box, in CSS pixels, from the last measurement. */
|
|
133
|
+
private box;
|
|
134
|
+
/** The path, padded to the shader's array, and how many points are in use. */
|
|
135
|
+
private readonly points;
|
|
136
|
+
private count;
|
|
137
|
+
/** In `travel`, the path's `y` at even steps of its parameter. */
|
|
138
|
+
private table;
|
|
139
|
+
/** In `travel`, the scroll position the ribbon is drawn at, easing toward the page's. */
|
|
140
|
+
private offset;
|
|
141
|
+
/** Whether the opening frame has been drawn. Aura's settle: see `open`. */
|
|
142
|
+
private opened;
|
|
143
|
+
private opening;
|
|
144
|
+
private openBy;
|
|
145
|
+
/** A layout booked for the next frame, so a burst of changes lays out once. */
|
|
146
|
+
private laying;
|
|
147
|
+
/** In `travel`, what tells the element that the page or its stops moved. */
|
|
148
|
+
private pageObserver;
|
|
149
|
+
private stopObserver;
|
|
150
|
+
private readonly gate;
|
|
151
|
+
protected render(): TemplateResult;
|
|
152
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
153
|
+
protected updated(changed: PropertyValues): void;
|
|
154
|
+
connectedCallback(): void;
|
|
155
|
+
disconnectedCallback(): void;
|
|
156
|
+
/** Everything the canvas needs, from either lifecycle. Aura's `start`, line for line in intent. */
|
|
157
|
+
private start;
|
|
158
|
+
/**
|
|
159
|
+
* The context and the pipeline, from whatever canvas can still give one. A
|
|
160
|
+
* spent canvas is replaced rather than re-asked, for the reason Aura's
|
|
161
|
+
* `build` records.
|
|
162
|
+
*/
|
|
163
|
+
private build;
|
|
164
|
+
/** The pipeline on the context already held, which a restored context comes back to alone. */
|
|
165
|
+
private prepare;
|
|
166
|
+
/** Gives the context back, once nothing has claimed the element again. Aura's `drop`. */
|
|
167
|
+
private readonly drop;
|
|
168
|
+
/** Stops for a context the browser took, hides the canvas, and asks for it back. */
|
|
169
|
+
private readonly handleLost;
|
|
170
|
+
/** Draws again on the context the browser gave back. */
|
|
171
|
+
private readonly handleRestored;
|
|
172
|
+
/** Shows the gradient, and says so once each time it starts to. */
|
|
173
|
+
private fallBack;
|
|
174
|
+
/** The colors, converted only when the text the canvas carries moved. */
|
|
175
|
+
private readPalette;
|
|
176
|
+
/** One frame for a stopped loop, once a color the canvas carries has moved. */
|
|
177
|
+
private readonly handleRestyle;
|
|
178
|
+
/**
|
|
179
|
+
* Re-reads the box and resizes the drawing buffer to match. Called from the
|
|
180
|
+
* gate's observer, never per frame, and an empty box is layout that has not
|
|
181
|
+
* happened: Aura's `measure` records both.
|
|
182
|
+
*
|
|
183
|
+
* **A ratio under Aura's**, 0.75 of the device's up to two, because a strand
|
|
184
|
+
* is a line rather than a field and the bloom hides the rest; the buffer's
|
|
185
|
+
* edge and pixel ceilings then apply as they do there.
|
|
186
|
+
*/
|
|
187
|
+
private measure;
|
|
188
|
+
/**
|
|
189
|
+
* Lays the path out again: inside the box, or down the page from the stops.
|
|
190
|
+
*
|
|
191
|
+
* Cheap, a few dozen points and a table of a few hundred numbers, so it runs
|
|
192
|
+
* on any change that could move the path rather than working out which did.
|
|
193
|
+
*/
|
|
194
|
+
private layout;
|
|
195
|
+
/** The stops inside, each with its target's place on the page. A stop naming nothing is skipped. */
|
|
196
|
+
private placedStops;
|
|
197
|
+
/**
|
|
198
|
+
* In `travel`, follows the page: the scroll it travels along, the page's
|
|
199
|
+
* height and its sections moving, and the stops being changed.
|
|
200
|
+
*/
|
|
201
|
+
private watchPage;
|
|
202
|
+
/** A layout on the next frame, once, however many changes asked for one. */
|
|
203
|
+
private readonly relayout;
|
|
204
|
+
/**
|
|
205
|
+
* The page scrolled. A running loop eases toward it on its own; a stopped
|
|
206
|
+
* one, for a reader who asked for no motion, jumps there and paints once.
|
|
207
|
+
*/
|
|
208
|
+
private readonly handleScroll;
|
|
209
|
+
private paint;
|
|
210
|
+
/** The box changed: measure, and repaint at once, because resizing cleared the buffer. */
|
|
211
|
+
private resized;
|
|
212
|
+
/** The opening frame, one animation frame after the box last changed. Aura's `open`. */
|
|
213
|
+
private open;
|
|
214
|
+
/** One frame, now, for a change that has to show while the loop is stopped. */
|
|
215
|
+
private paintOnce;
|
|
216
|
+
/**
|
|
217
|
+
* The shortest gap between two frames, in seconds, while nothing is being
|
|
218
|
+
* followed. Aura's cap, for Aura's reason: a drifting ribbon does not need
|
|
219
|
+
* the display's rate.
|
|
220
|
+
*/
|
|
221
|
+
private static readonly CAPPED;
|
|
222
|
+
/** What the skipped frames were worth, spent on the next one drawn. */
|
|
223
|
+
private held;
|
|
224
|
+
private advance;
|
|
225
|
+
}
|
|
226
|
+
declare global {
|
|
227
|
+
interface HTMLElementTagNameMap {
|
|
228
|
+
'helix-strands': HelixStrands;
|
|
229
|
+
}
|
|
230
|
+
}
|