@fusion.dev/helix 0.173.0 → 0.175.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 +257 -30
- package/dist/strands/palette.d.ts +1 -1
- package/dist/strands/path.d.ts +17 -6
- package/dist/strands/path.js +149 -43
- package/dist/strands/renderer.d.ts +14 -0
- package/dist/strands/renderer.js +14 -4
- package/dist/strands/root/strands-root.css.js +1 -1
- package/dist/strands/root/strands-root.d.ts +41 -10
- package/dist/strands/root/strands-root.js +32 -5
- package/dist/strands/shader.d.ts +3 -3
- package/dist/strands/shader.js +35 -12
- package/dist/strands/stop/strands-stop.d.ts +5 -5
- package/lint.json +1 -1
- package/package.json +6 -6
- package/types/jsx.d.ts +123 -47
- package/types/vue.d.ts +32 -11
package/dist/strands/path.js
CHANGED
|
@@ -1,37 +1,91 @@
|
|
|
1
1
|
export const MAX_POINTS = 96;
|
|
2
2
|
const NARROW = 760;
|
|
3
|
-
const GLOW = {
|
|
4
|
-
const WIDTH = {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GLOW = { bend: 1, ribbon: 0.36, waves: 0.7 };
|
|
4
|
+
const WIDTH = { bend: 0.12, ribbon: 0.24, waves: 0.06 };
|
|
5
|
+
const BEND_AT = 0.5;
|
|
6
|
+
const BEND_EXIT = 0.3;
|
|
7
|
+
export const TURN = 0.5;
|
|
8
|
+
const bend = (top, height, at, narrow, width, reach) => {
|
|
7
9
|
const x = narrow ? Math.max(at, 0.8) : at;
|
|
8
10
|
const y = top + height * 0.58;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const out = (down) => x + (down * down * height) / (2 * TURN) / width;
|
|
12
|
+
const points = [];
|
|
13
|
+
for (let step = -7; step * 0.15 <= reach + 1e-9; step++) {
|
|
14
|
+
const down = step * 0.15;
|
|
15
|
+
const upper = down < 0;
|
|
16
|
+
const wide = upper
|
|
17
|
+
? 0.02 + (WIDTH.bend - 0.02) * Math.min(1, -down / 0.6)
|
|
18
|
+
: 0.02 + (WIDTH.bend * 1.4 - 0.02) * (down / reach);
|
|
19
|
+
const glow = upper ? Math.max(0, 1 + down * 1.07) : 1 - down * 0.73;
|
|
20
|
+
points.push([out(down), y + down * height, wide, GLOW.bend * glow]);
|
|
21
|
+
}
|
|
22
|
+
return points;
|
|
17
23
|
};
|
|
18
|
-
const wave = (y, height, fromRight, narrow) => {
|
|
24
|
+
const wave = (y, height, fromRight, narrow, width, from) => {
|
|
19
25
|
const w = narrow ? WIDTH.waves * 2 : WIDTH.waves;
|
|
20
26
|
const glow = narrow ? GLOW.waves * 0.65 : GLOW.waves;
|
|
21
27
|
const side = (share) => (fromRight ? 1 - share : share);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (from !== undefined) {
|
|
29
|
+
const across = (share) => from + ((fromRight ? 0 : 1) - from) * share;
|
|
30
|
+
return [
|
|
31
|
+
[across(0.03), y - height * 0.62, WIDTH.ribbon * 0.7, GLOW.ribbon],
|
|
32
|
+
[across(0.12), y - height * 0.36, WIDTH.ribbon * 0.5, glow * 0.55],
|
|
33
|
+
[across(0.38), y - height * 0.1, w, glow],
|
|
34
|
+
[across(0.66), y + height * 0.04, w, glow],
|
|
35
|
+
[across(1), y + height * 0.14, w, glow * 0.6],
|
|
36
|
+
[across(1.5), y + height * 0.3, w, 0],
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
const beyond = height / width;
|
|
40
|
+
const rise = Math.min(18, width / 80);
|
|
41
|
+
const span = 1 + 2 * beyond;
|
|
42
|
+
const steps = Math.ceil(span / ((height * 0.45) / width));
|
|
43
|
+
const points = [];
|
|
44
|
+
for (let k = 0; k <= steps; k++) {
|
|
45
|
+
const share = -beyond + (k / steps) * span;
|
|
46
|
+
const out = share < 0 ? -share / beyond : share > 1 ? (share - 1) / beyond : 0;
|
|
47
|
+
const lift = share < 0 ? -out * out : out * out;
|
|
48
|
+
const ripple = share >= 0 && share <= 1 ? rise * Math.sin(2 * Math.PI * share) : 0;
|
|
49
|
+
points.push([
|
|
50
|
+
side(share),
|
|
51
|
+
y + lift * height * 0.35 + ripple,
|
|
52
|
+
w,
|
|
53
|
+
glow * Math.max(0, 1 - out * 1.6),
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
return points;
|
|
30
57
|
};
|
|
31
|
-
const
|
|
58
|
+
const swingOf = (height, width, narrow) => Math.min(narrow ? 0.26 : 0.3, (height * TURN) / width);
|
|
59
|
+
const RIBBON_STEP = 0.35;
|
|
60
|
+
const bridge = (from, to, first, last) => {
|
|
61
|
+
const length = to[1] - from[1];
|
|
62
|
+
const gaps = [];
|
|
63
|
+
let gap = first;
|
|
64
|
+
let sum = 0;
|
|
65
|
+
while (sum + Math.min(gap, last) * 1.5 < length) {
|
|
66
|
+
gaps.push(Math.min(gap, last));
|
|
67
|
+
sum += Math.min(gap, last);
|
|
68
|
+
gap *= 1.33;
|
|
69
|
+
}
|
|
70
|
+
const scale = length / (sum + Math.min(gap, last));
|
|
71
|
+
const out = [];
|
|
72
|
+
let at = 0;
|
|
73
|
+
for (const one of gaps) {
|
|
74
|
+
at += one * scale;
|
|
75
|
+
const f = at / length;
|
|
76
|
+
out.push([
|
|
77
|
+
from[0] + (to[0] - from[0]) * f,
|
|
78
|
+
from[1] + (to[1] - from[1]) * f,
|
|
79
|
+
from[2] + (to[2] - from[2]) * f,
|
|
80
|
+
from[3] + (to[3] - from[3]) * f,
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
};
|
|
85
|
+
const ribbon = (from, to, height, at, phase, narrow, width) => {
|
|
32
86
|
const points = [];
|
|
33
|
-
const step = height *
|
|
34
|
-
const swing =
|
|
87
|
+
const step = height * RIBBON_STEP;
|
|
88
|
+
const swing = swingOf(height, width, narrow);
|
|
35
89
|
const wide = narrow ? 0.45 : WIDTH.ribbon;
|
|
36
90
|
const glow = narrow ? GLOW.ribbon * 0.65 : GLOW.ribbon;
|
|
37
91
|
let a = phase;
|
|
@@ -41,17 +95,17 @@ const ribbon = (from, to, height, at, phase, narrow) => {
|
|
|
41
95
|
}
|
|
42
96
|
return { phase: a, points };
|
|
43
97
|
};
|
|
44
|
-
export const boxPath = (variant, at, height) => {
|
|
98
|
+
export const boxPath = (variant, at, height, width) => {
|
|
45
99
|
const narrow = false;
|
|
46
|
-
if (variant === '
|
|
47
|
-
return
|
|
100
|
+
if (variant === 'bend') {
|
|
101
|
+
return bend(0, height, at ?? BEND_AT, narrow, width, 0.6);
|
|
48
102
|
}
|
|
49
103
|
if (variant === 'waves') {
|
|
50
|
-
return wave(height * 0.5, height, false, narrow);
|
|
104
|
+
return wave(height * 0.5, height, false, narrow, width);
|
|
51
105
|
}
|
|
52
106
|
const start = -height * 0.4;
|
|
53
107
|
const lead = [at ?? 0.5, start - height * 0.4, WIDTH.ribbon, 0];
|
|
54
|
-
const body = ribbon(start, height * 1.4, height, at ?? 0.5, 0, narrow).points.map(([x, y, w]) => [x, y, w, narrow ? GLOW.ribbon * 0.65 * 1.4 : GLOW.ribbon * 1.4]);
|
|
108
|
+
const body = ribbon(start, height * 1.4, height, at ?? 0.5, 0, narrow, width).points.map(([x, y, w]) => [x, y, w, narrow ? GLOW.ribbon * 0.65 * 1.4 : GLOW.ribbon * 1.4]);
|
|
55
109
|
return [lead, ...body];
|
|
56
110
|
};
|
|
57
111
|
export const pagePath = (stops, fill, width, height, page) => {
|
|
@@ -63,42 +117,92 @@ export const pagePath = (stops, fill, width, height, page) => {
|
|
|
63
117
|
placed.push({ at: undefined, height, top: y, variant: 'waves' });
|
|
64
118
|
}
|
|
65
119
|
}
|
|
66
|
-
else if (fill === '
|
|
67
|
-
placed.push({ at: undefined, height, top: 0, variant: '
|
|
120
|
+
else if (fill === 'bend') {
|
|
121
|
+
placed.push({ at: undefined, height, top: 0, variant: 'bend' });
|
|
68
122
|
}
|
|
69
123
|
}
|
|
70
124
|
const points = [[0.6, -height * 0.9, WIDTH.ribbon, 0]];
|
|
125
|
+
const turn = (RIBBON_STEP / 1.7) * Math.PI;
|
|
71
126
|
let phase = 1.2;
|
|
72
127
|
let cursor = 0;
|
|
73
128
|
let fromRight = true;
|
|
74
129
|
let previous;
|
|
130
|
+
let joined;
|
|
75
131
|
const gap = (to, next) => {
|
|
76
132
|
if (to - cursor < height * 0.7) {
|
|
77
133
|
return;
|
|
78
134
|
}
|
|
79
|
-
if (previous === 'waves' && next === 'waves') {
|
|
135
|
+
if (previous === 'waves' && next === 'waves' && to - cursor < height * 1.5) {
|
|
80
136
|
return;
|
|
81
137
|
}
|
|
82
|
-
const
|
|
138
|
+
const end = next === 'waves' ? to - height * 0.95 : to - height * 0.45;
|
|
139
|
+
const stretch = ribbon(cursor + height * 0.2, end, height, 0.5, phase, narrow, width);
|
|
83
140
|
phase = stretch.phase;
|
|
84
|
-
|
|
85
|
-
|
|
141
|
+
let run = stretch.points;
|
|
142
|
+
let heading;
|
|
143
|
+
if (next === 'waves') {
|
|
144
|
+
for (let k = run.length - 2; k > 0; k--) {
|
|
145
|
+
const [one, two, three] = run.slice(k - 1, k + 2);
|
|
146
|
+
if ((two[0] - one[0]) * (three[0] - two[0]) <= 0) {
|
|
147
|
+
run = run.slice(0, k + 1);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (k === 1) {
|
|
151
|
+
heading = three[0] < two[0];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (previous === 'waves' && run.length > 1) {
|
|
156
|
+
run = run.map((point, k) => k < 3 ? [point[0], point[1], point[2], point[3] * Math.max(0, k - 1) * 0.5] : point);
|
|
157
|
+
}
|
|
158
|
+
points.push(...run);
|
|
159
|
+
const last = run.at(-1);
|
|
86
160
|
if (last) {
|
|
87
|
-
fromRight = last[0] > 0.5;
|
|
161
|
+
fromRight = heading ?? last[0] > 0.5;
|
|
162
|
+
joined = last[0];
|
|
88
163
|
}
|
|
89
164
|
};
|
|
90
165
|
for (const stop of placed) {
|
|
166
|
+
joined = undefined;
|
|
91
167
|
gap(stop.top, stop.variant);
|
|
92
|
-
if (stop.variant === '
|
|
168
|
+
if (stop.variant === 'bend') {
|
|
93
169
|
const span = Math.min(stop.height, height);
|
|
94
|
-
|
|
95
|
-
|
|
170
|
+
const at = stop.at ?? BEND_AT;
|
|
171
|
+
const slope = BEND_EXIT / TURN;
|
|
172
|
+
const swing = swingOf(height, width, narrow);
|
|
173
|
+
const steepest = (swing * width * Math.PI) / (1.7 * height);
|
|
174
|
+
const a = Math.acos(Math.min(1, slope / steepest));
|
|
175
|
+
const x = 0.5 + swing * Math.sin(a);
|
|
176
|
+
const bent = bend(stop.top, span, at, narrow, width, narrow ? 0.6 : BEND_EXIT);
|
|
177
|
+
const exit = bent.at(-1);
|
|
178
|
+
points.push(...bent);
|
|
179
|
+
if (!narrow && x > exit[0]) {
|
|
180
|
+
const drop = Math.max(height * 0.3, ((x - exit[0]) * width) / slope);
|
|
181
|
+
const start = [x, exit[1] + drop, WIDTH.ribbon, GLOW.ribbon];
|
|
182
|
+
points.push(...bridge(exit, start, span * 0.15, height * RIBBON_STEP));
|
|
183
|
+
cursor = start[1] - height * 0.2;
|
|
184
|
+
phase = a - turn;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
cursor = exit[1] + height * 0.1;
|
|
188
|
+
phase = Math.PI / 2 - turn;
|
|
189
|
+
}
|
|
96
190
|
fromRight = true;
|
|
97
191
|
}
|
|
98
192
|
else if (stop.variant === 'waves') {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
193
|
+
const crossing = wave(stop.top, height, fromRight, narrow, width, joined);
|
|
194
|
+
const end = points.at(-1);
|
|
195
|
+
const lead = crossing[0];
|
|
196
|
+
if (joined !== undefined && end && lead) {
|
|
197
|
+
points.push(...bridge(end, lead, height * RIBBON_STEP, height * 0.26));
|
|
198
|
+
}
|
|
199
|
+
points.push(...crossing);
|
|
200
|
+
phase =
|
|
201
|
+
joined === undefined
|
|
202
|
+
? (fromRight ? Math.PI * 1.5 : Math.PI / 2) - turn
|
|
203
|
+
: (fromRight ? Math.PI / 2 : Math.PI * 1.5) - turn;
|
|
204
|
+
fromRight = joined === undefined ? !fromRight : fromRight;
|
|
205
|
+
cursor = stop.top + height * (joined === undefined ? 0.35 : 0.5);
|
|
102
206
|
}
|
|
103
207
|
else {
|
|
104
208
|
const y = stop.top + Math.min(stop.height, height) / 2;
|
|
@@ -111,7 +215,9 @@ export const pagePath = (stops, fill, width, height, page) => {
|
|
|
111
215
|
gap(page + height * 0.4, undefined);
|
|
112
216
|
}
|
|
113
217
|
const last = points.at(-1) ?? [0.5, page, WIDTH.ribbon, 0];
|
|
114
|
-
points.
|
|
218
|
+
const before = points.at(-2) ?? last;
|
|
219
|
+
const on = [2 * last[0] - before[0], 2 * last[1] - before[1], last[2], 0];
|
|
220
|
+
points.push(on, [on[0], Math.max(on[1], page) + height, last[2], 0]);
|
|
115
221
|
return points.slice(0, MAX_POINTS);
|
|
116
222
|
};
|
|
117
223
|
export const catmull = (p0, p1, p2, p3, f) => {
|
|
@@ -3,10 +3,24 @@ import type { StrandsPalette } from './palette.js';
|
|
|
3
3
|
export declare const MAX_STRANDS = 340;
|
|
4
4
|
/** The fewest, at `density` 0. Fewer stops reading as a ribbon at all. */
|
|
5
5
|
export declare const MIN_STRANDS = 60;
|
|
6
|
+
/** Specks of dust around the strands at a `dust` of `1`. */
|
|
7
|
+
export declare const DUST = 600;
|
|
8
|
+
/** The most specks a frame draws, at a `dust` of `4`. */
|
|
9
|
+
export declare const MAX_DUST: number;
|
|
6
10
|
/** What one frame needs, all of it already resolved by the element. */
|
|
7
11
|
export interface StrandsFrame {
|
|
12
|
+
/** How much the glow around the strands adds, as a multiplier. */
|
|
13
|
+
bloom: number;
|
|
14
|
+
/** How far apart the brightest and dimmest strands are, from `0` to `1`. */
|
|
15
|
+
contrast: number;
|
|
8
16
|
/** How many of the path's points are in use. */
|
|
9
17
|
count: number;
|
|
18
|
+
/** How much nearness shows, from `0`, flat, to `1`. */
|
|
19
|
+
depth: number;
|
|
20
|
+
/** How many specks of dust drift around the strands. */
|
|
21
|
+
dust: number;
|
|
22
|
+
/** How differently each strand moves, from `0`, as one, to `1`. */
|
|
23
|
+
drift: number;
|
|
10
24
|
/** The device pixels per CSS pixel the buffer was sized at. */
|
|
11
25
|
dpr: number;
|
|
12
26
|
/** Overall brightness. */
|
package/dist/strands/renderer.js
CHANGED
|
@@ -2,7 +2,11 @@ import { BLUR_FRAGMENT, COMPOSITE_FRAGMENT, DOWN_FRAGMENT, DUST_FRAGMENT, DUST_V
|
|
|
2
2
|
export const MAX_STRANDS = 340;
|
|
3
3
|
export const MIN_STRANDS = 60;
|
|
4
4
|
const SEGMENTS = 320;
|
|
5
|
-
const DUST = 600;
|
|
5
|
+
export const DUST = 600;
|
|
6
|
+
export const MAX_DUST = DUST * 4;
|
|
7
|
+
const SPREAD = 1.35;
|
|
8
|
+
const BLOOM = 0.75;
|
|
9
|
+
const GAIN = 1.615;
|
|
6
10
|
export class StrandsRenderer {
|
|
7
11
|
constructor(gl, programs) {
|
|
8
12
|
this.targets = null;
|
|
@@ -82,6 +86,7 @@ export class StrandsRenderer {
|
|
|
82
86
|
this.vec3(this.line, 'uTint', palette.tint);
|
|
83
87
|
this.vec3(this.line, 'uPeak', palette.peak);
|
|
84
88
|
this.float(this.line, 'uSoftness', frame.softness);
|
|
89
|
+
this.float(this.line, 'uContrast', frame.contrast);
|
|
85
90
|
gl.bindVertexArray(this.ribbon);
|
|
86
91
|
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, (SEGMENTS + 1) * 2, frame.strands);
|
|
87
92
|
this.use(this.dust, frame);
|
|
@@ -89,7 +94,9 @@ export class StrandsRenderer {
|
|
|
89
94
|
this.vec3(this.dust, 'uPeak', palette.peak);
|
|
90
95
|
this.float(this.dust, 'uStrands', frame.strands);
|
|
91
96
|
gl.bindVertexArray(this.empty);
|
|
92
|
-
|
|
97
|
+
if (frame.dust > 0) {
|
|
98
|
+
gl.drawArrays(gl.POINTS, 0, frame.dust);
|
|
99
|
+
}
|
|
93
100
|
gl.disable(gl.BLEND);
|
|
94
101
|
}
|
|
95
102
|
const { far, farSwap, near, nearSwap } = targets;
|
|
@@ -108,7 +115,8 @@ export class StrandsRenderer {
|
|
|
108
115
|
this.vec3(this.composite, 'uGround', palette.ground);
|
|
109
116
|
this.vec3(this.composite, 'uInk', palette.peak);
|
|
110
117
|
this.float(this.composite, 'uLight', palette.light ? 1 : 0);
|
|
111
|
-
this.float(this.composite, 'uGain', (
|
|
118
|
+
this.float(this.composite, 'uGain', (GAIN * frame.gain) / this.scale);
|
|
119
|
+
this.float(this.composite, 'uBloom', BLOOM * frame.bloom);
|
|
112
120
|
gl.bindVertexArray(this.empty);
|
|
113
121
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
114
122
|
}
|
|
@@ -169,7 +177,9 @@ export class StrandsRenderer {
|
|
|
169
177
|
gl.uniform1f(program.uniforms.get('uScroll') ?? null, frame.scroll);
|
|
170
178
|
gl.uniform2f(program.uniforms.get('uT') ?? null, frame.range[0], frame.range[1]);
|
|
171
179
|
gl.uniform1f(program.uniforms.get('uTime') ?? null, frame.time);
|
|
172
|
-
gl.uniform1f(program.uniforms.get('uSpread') ?? null, frame.spread);
|
|
180
|
+
gl.uniform1f(program.uniforms.get('uSpread') ?? null, SPREAD * frame.spread);
|
|
181
|
+
gl.uniform1f(program.uniforms.get('uDepth') ?? null, frame.depth);
|
|
182
|
+
gl.uniform1f(program.uniforms.get('uDrift') ?? null, frame.drift);
|
|
173
183
|
gl.uniform1f(program.uniforms.get('uDpr') ?? null, frame.dpr);
|
|
174
184
|
gl.uniform1f(program.uniforms.get('uScale') ?? null, this.scale);
|
|
175
185
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
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-
|
|
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-accent));position: relative;display: block;inline-size: 100%;block-size: 100%;background-image: linear-gradient(
|
|
3
3
|
115deg,
|
|
4
4
|
var(--_strands-ground) 20%,
|
|
5
5
|
color-mix(in oklab, var(--_strands-glow) 22%, var(--_strands-ground)) 48%,
|
|
@@ -11,16 +11,16 @@ import { type StrandsVariant } from '../path.js';
|
|
|
11
11
|
* roll over one another, and light runs along them.
|
|
12
12
|
*
|
|
13
13
|
* Its palette comes from the tokens. The strands are `--helix-color-primary`,
|
|
14
|
-
* a few are `--helix-color-
|
|
14
|
+
* a few are `--helix-color-accent` for depth, and the page they light is
|
|
15
15
|
* `--helix-color-background`. On a light page the light becomes ink, because
|
|
16
16
|
* light added to near-white cannot be seen.
|
|
17
17
|
*
|
|
18
|
-
* **Three forms, set with `variant`.** `
|
|
18
|
+
* **Three forms, set with `variant`.** `bend` turns bright strands through one tight point, with its
|
|
19
19
|
* arms opening to the right. `ribbon`, the default, is a broad, soft ribbon in
|
|
20
20
|
* long curves. `waves` is sharp strands crossing the full width.
|
|
21
21
|
*
|
|
22
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
|
|
23
|
+
* way an image does, and `at` places the bend's apex or the ribbon's axis across
|
|
24
24
|
* it. Content placed inside sits over the ribbon, in the `content` part.
|
|
25
25
|
*
|
|
26
26
|
* **Down the whole page, with `travel`.** The element fixes itself behind the
|
|
@@ -28,8 +28,9 @@ import { type StrandsVariant } from '../path.js';
|
|
|
28
28
|
* bottom, so scrolling travels along it. Put `helix-strands-stop` elements
|
|
29
29
|
* inside to pin a form to a section: each names an element with `for` and says
|
|
30
30
|
* which form the ribbon takes there. Between stops the ribbon fills the gap in
|
|
31
|
-
* its own `variant`, and between two waves
|
|
32
|
-
* reads alone. Set `z-index` on the
|
|
31
|
+
* its own `variant`, and between two waves less than a screen and a half
|
|
32
|
+
* apart it turns off screen so each wave reads alone. Set `z-index` on the
|
|
33
|
+
* element to move it.
|
|
33
34
|
*
|
|
34
35
|
* The ribbon stops moving in these cases:
|
|
35
36
|
* - `prefers-reduced-motion` is set. The element shows one still frame, and in
|
|
@@ -61,16 +62,16 @@ import { type StrandsVariant } from '../path.js';
|
|
|
61
62
|
* @cssproperty --strands-glow - The color of most strands. Defaults to `--helix-color-primary`.
|
|
62
63
|
* @cssproperty --strands-ground - The page the light is added to. Defaults to `--helix-color-background`.
|
|
63
64
|
* @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-
|
|
65
|
+
* @cssproperty --strands-tint - The color of a few strands, for depth. Defaults to `--helix-color-accent`, which is the primary itself unless the theme relates an accent to the brand, as `accent: { from: 'brand', rotate: 40 }` does.
|
|
65
66
|
*
|
|
66
67
|
* @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
|
*/
|
|
68
69
|
export declare class HelixStrands extends HelixElement {
|
|
69
70
|
static styles: CSSResultGroup;
|
|
70
71
|
/**
|
|
71
|
-
* Where the form sits across the box, as a share of the width: the
|
|
72
|
-
*
|
|
73
|
-
* crosses the whole width and does not read it. Unset, a
|
|
72
|
+
* Where the form sits across the box, as a share of the width: the bend's
|
|
73
|
+
* apex or the ribbon's axis. `0` is the left edge and `1` the right. A wave
|
|
74
|
+
* crosses the whole width and does not read it. Unset, a bend's apex sits at `0.5`
|
|
74
75
|
* and a ribbon runs down the middle.
|
|
75
76
|
*
|
|
76
77
|
* **A share rather than a length**, so the ribbon keeps its place when the
|
|
@@ -78,17 +79,47 @@ export declare class HelixStrands extends HelixElement {
|
|
|
78
79
|
* own.
|
|
79
80
|
*/
|
|
80
81
|
at?: number;
|
|
82
|
+
/**
|
|
83
|
+
* How much the glow around the strands adds, as a multiplier. `0` draws the
|
|
84
|
+
* strands alone, crisp and dimmer, and `1` is the default glow.
|
|
85
|
+
*/
|
|
86
|
+
bloom: number;
|
|
87
|
+
/**
|
|
88
|
+
* How far apart the brightest and dimmest strands are, from `0`, most of
|
|
89
|
+
* them alike, to `1`, a few burning bright over many faint ones. Clamped to
|
|
90
|
+
* that range. The ribbon as a whole keeps its strength.
|
|
91
|
+
*/
|
|
92
|
+
contrast: number;
|
|
81
93
|
/**
|
|
82
94
|
* How many strands the ribbon has, from `0`, sixty, to `1`, three hundred and
|
|
83
95
|
* forty. Clamped to that range.
|
|
84
96
|
*/
|
|
85
97
|
density: number;
|
|
98
|
+
/**
|
|
99
|
+
* How much the ribbon's depth shows, from `0`, flat, to `1`. Clamped to that
|
|
100
|
+
* range. The strands at the front of the ribbon's roll are drawn thicker,
|
|
101
|
+
* brighter, and further from its axis, and those at the back thinner, dimmer,
|
|
102
|
+
* and out of focus.
|
|
103
|
+
*/
|
|
104
|
+
depth: number;
|
|
105
|
+
/**
|
|
106
|
+
* How differently each strand moves, from `0`, the whole ribbon rolling and
|
|
107
|
+
* waving as one, to `1`, each strand at its own pace and angle, crossing
|
|
108
|
+
* over the others. Clamped to that range.
|
|
109
|
+
*/
|
|
110
|
+
drift: number;
|
|
111
|
+
/**
|
|
112
|
+
* How many specks of dust drift around the strands, as a multiplier: `0`
|
|
113
|
+
* draws none, `1` six hundred, and `4`, the most, two thousand four
|
|
114
|
+
* hundred. They gather where the ribbon is brightest.
|
|
115
|
+
*/
|
|
116
|
+
dust: number;
|
|
86
117
|
/** Overall brightness, as a multiplier on the light. */
|
|
87
118
|
gain: number;
|
|
88
119
|
/**
|
|
89
120
|
* The share of strands drawn out of focus where the ribbon is wide, from `0`,
|
|
90
121
|
* every strand sharp, to `1`, every one soft. Clamped to that range. A tight
|
|
91
|
-
* form, such as a
|
|
122
|
+
* form, such as a bend's apex or a wave, stays sharp whatever this says.
|
|
92
123
|
*/
|
|
93
124
|
softness: number;
|
|
94
125
|
/** How fast the ribbon moves, as a multiplier on time. `0` holds it still. */
|
|
@@ -10,18 +10,24 @@ import { html } from 'lit';
|
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { paletteOf, RESTING_PALETTE } from '../palette.js';
|
|
12
12
|
import { boxPath, lookup, MAX_POINTS, pagePath, visibleRange, } from '../path.js';
|
|
13
|
-
import { MAX_STRANDS, MIN_STRANDS, StrandsRenderer } from '../renderer.js';
|
|
13
|
+
import { DUST, MAX_DUST, MAX_STRANDS, MIN_STRANDS, StrandsRenderer } from '../renderer.js';
|
|
14
14
|
import { styles } from './strands-root.css.js';
|
|
15
15
|
const MAX_CANVAS_EDGE = 4096;
|
|
16
16
|
const MAX_CANVAS_PIXELS = 4_194_304;
|
|
17
17
|
const OPENING_PATIENCE = 500;
|
|
18
18
|
const TRAVEL_EASE = 0.09;
|
|
19
|
+
const PACE = 0.25;
|
|
19
20
|
let HelixStrands = class HelixStrands extends HelixElement {
|
|
20
21
|
constructor() {
|
|
21
22
|
super(...arguments);
|
|
23
|
+
this.bloom = 1;
|
|
24
|
+
this.contrast = 1;
|
|
22
25
|
this.density = 0.8;
|
|
26
|
+
this.depth = 1;
|
|
27
|
+
this.drift = 1;
|
|
28
|
+
this.dust = 1;
|
|
23
29
|
this.gain = 1;
|
|
24
|
-
this.softness = 0
|
|
30
|
+
this.softness = 0;
|
|
25
31
|
this.speed = 1;
|
|
26
32
|
this.spread = 1;
|
|
27
33
|
this.travel = false;
|
|
@@ -303,7 +309,7 @@ let HelixStrands = class HelixStrands extends HelixElement {
|
|
|
303
309
|
this.table = lookup(path);
|
|
304
310
|
}
|
|
305
311
|
else {
|
|
306
|
-
path = boxPath(variant, this.at, this.box.height);
|
|
312
|
+
path = boxPath(variant, this.at, this.box.height, this.box.width);
|
|
307
313
|
}
|
|
308
314
|
this.points.fill(0);
|
|
309
315
|
path.forEach((point, i) => {
|
|
@@ -327,7 +333,7 @@ let HelixStrands = class HelixStrands extends HelixElement {
|
|
|
327
333
|
at: Number.isFinite(at) ? at : undefined,
|
|
328
334
|
height: rect.height,
|
|
329
335
|
top: rect.top + scroll,
|
|
330
|
-
variant: variant === '
|
|
336
|
+
variant: variant === 'bend' || variant === 'waves' ? variant : 'ribbon',
|
|
331
337
|
});
|
|
332
338
|
}
|
|
333
339
|
return placed;
|
|
@@ -364,9 +370,15 @@ let HelixStrands = class HelixStrands extends HelixElement {
|
|
|
364
370
|
? visibleRange(this.table, this.offset - view[1] * 0.4, this.offset + view[1] * 1.4)
|
|
365
371
|
: [0, 1];
|
|
366
372
|
const density = Math.min(1, Math.max(0, this.density));
|
|
373
|
+
const share = (value) => Math.min(1, Math.max(0, value));
|
|
367
374
|
renderer.render({
|
|
375
|
+
bloom: Math.max(0, this.bloom),
|
|
376
|
+
contrast: share(this.contrast),
|
|
368
377
|
count: this.count,
|
|
378
|
+
depth: share(this.depth),
|
|
369
379
|
dpr: this.dpr,
|
|
380
|
+
drift: share(this.drift),
|
|
381
|
+
dust: Math.round(Math.min(MAX_DUST, Math.max(0, this.dust * DUST))),
|
|
370
382
|
gain: Math.max(0, this.gain),
|
|
371
383
|
points: this.points,
|
|
372
384
|
range,
|
|
@@ -425,7 +437,7 @@ let HelixStrands = class HelixStrands extends HelixElement {
|
|
|
425
437
|
}
|
|
426
438
|
const step = Math.min(this.held, MAX_FRAME_STEP);
|
|
427
439
|
this.held = 0;
|
|
428
|
-
this.elapsed += step * Math.max(0, this.speed);
|
|
440
|
+
this.elapsed += step * PACE * Math.max(0, this.speed);
|
|
429
441
|
if (this.travel) {
|
|
430
442
|
const target = window.scrollY;
|
|
431
443
|
this.offset += (target - this.offset) * (1 - Math.exp(-step / TRAVEL_EASE));
|
|
@@ -439,9 +451,24 @@ let HelixStrands = class HelixStrands extends HelixElement {
|
|
|
439
451
|
__decorate([
|
|
440
452
|
property({ type: Number })
|
|
441
453
|
], HelixStrands.prototype, "at", void 0);
|
|
454
|
+
__decorate([
|
|
455
|
+
property({ type: Number })
|
|
456
|
+
], HelixStrands.prototype, "bloom", void 0);
|
|
457
|
+
__decorate([
|
|
458
|
+
property({ type: Number })
|
|
459
|
+
], HelixStrands.prototype, "contrast", void 0);
|
|
442
460
|
__decorate([
|
|
443
461
|
property({ type: Number })
|
|
444
462
|
], HelixStrands.prototype, "density", void 0);
|
|
463
|
+
__decorate([
|
|
464
|
+
property({ type: Number })
|
|
465
|
+
], HelixStrands.prototype, "depth", void 0);
|
|
466
|
+
__decorate([
|
|
467
|
+
property({ type: Number })
|
|
468
|
+
], HelixStrands.prototype, "drift", void 0);
|
|
469
|
+
__decorate([
|
|
470
|
+
property({ type: Number })
|
|
471
|
+
], HelixStrands.prototype, "dust", void 0);
|
|
445
472
|
__decorate([
|
|
446
473
|
property({ type: Number })
|
|
447
474
|
], HelixStrands.prototype, "gain", void 0);
|
package/dist/strands/shader.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
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.
|
|
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 float uContrast;\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;\nuniform float uDepth;\nuniform float uDrift;\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 r8 = h1(id + 13.7);\n float r9 = h1(id + 17.3);\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 pace = 1.0 + uDrift * (r8 - 0.5) * 1.2;\n float th = along * 0.002 * (1.0 + uDrift * (r9 - 0.5) * 0.3) + uTime * 0.25 * pace\n + r2 * (0.5 + uDrift * 2.6);\n float width = c.z * uSpread;\n float breadth = 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 ct = cos(th);\n float st = sin(th);\n float depth = clamp((z * 0.55 * ct - n * st) / 1.15, -1.0, 1.0);\n float lat = sway + breadth * (n * ct + z * 0.55 * st) * (1.0 + uDepth * 0.35 * depth);\n lat += sin(along * 0.006 * (1.0 + uDrift * (r9 - 0.5) * 0.3) + uTime * (0.6 + 0.5 * r1) * pace\n + r1 * 6.283) * 12.0 * (0.3 + r3) * (1.0 + uDrift * 0.5);\n info = vec3(max(c.w, 0.0), width, along);\n return vec3(p + nr * lat, depth);\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 /* Nearer is thicker, and the back of the ribbon falls out of focus: wider\n at the same energy, so it reads as haze behind the sharp strands. */\n float base = mix(0.35, 1.3, r7 * r7) * uDpr * (1.0 + (0.3 + 0.5 * uDepth) * a.z);\n float focus = uDepth * smoothstep(-0.1, -0.9, a.z) * 10.0 * uDpr;\n float w = max(base + soft * mix(8.0, 36.0, r6) * uDpr + focus, 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 /* Contrast spreads the strands' brightness: a few burn and most fall back.\n The mean is held, so the ribbon as a whole keeps its strength. */\n float spread = mix(1.4, 4.5, uContrast);\n float least = mix(0.25, 0.04, uContrast);\n float bright = mix(least, 1.0, pow(r5, spread)) * 0.5625 / (least + (1.0 - least) / (spread + 1.0));\n float energy = min(base, w) / w;\n float fall = 0.3 + 0.5 * uDepth;\n float depth = (1.0 - fall * (1.0 - a.z) * 0.5) / (1.0 - fall * 0.5);\n float rest = mix(0.35, 0.15, uContrast);\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 * (rest + (1.0 - rest) * shimmer) * bright * depth;\n}\n";
|
|
3
3
|
/** A soft profile across the ribbon, added into the buffer. */
|
|
4
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
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.
|
|
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;\nuniform float uDepth;\nuniform float uDrift;\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 r8 = h1(id + 13.7);\n float r9 = h1(id + 17.3);\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 pace = 1.0 + uDrift * (r8 - 0.5) * 1.2;\n float th = along * 0.002 * (1.0 + uDrift * (r9 - 0.5) * 0.3) + uTime * 0.25 * pace\n + r2 * (0.5 + uDrift * 2.6);\n float width = c.z * uSpread;\n float breadth = 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 ct = cos(th);\n float st = sin(th);\n float depth = clamp((z * 0.55 * ct - n * st) / 1.15, -1.0, 1.0);\n float lat = sway + breadth * (n * ct + z * 0.55 * st) * (1.0 + uDepth * 0.35 * depth);\n lat += sin(along * 0.006 * (1.0 + uDrift * (r9 - 0.5) * 0.3) + uTime * (0.6 + 0.5 * r1) * pace\n + r1 * 6.283) * 12.0 * (0.3 + r3) * (1.0 + uDrift * 0.5);\n info = vec3(max(c.w, 0.0), width, along);\n return vec3(p + nr * lat, depth);\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 /* By the root of the ribbon's brightness, so specks show along a dim ribbon\n as well as round a bright bend. */\n vColor = mix(uGlow, uPeak, 0.55) * tw * mix(0.2, 0.9, r) * sqrt(info.x) * 0.8;\n}\n";
|
|
7
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
8
|
/** One triangle over the whole target, from three vertex ids and no buffer. */
|
|
9
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";
|
|
@@ -19,4 +19,4 @@ export declare const BLUR_FRAGMENT = "#version 300 es\nprecision highp float;\ni
|
|
|
19
19
|
* the same energy darkens the ground toward the peak as ink. A step of noise
|
|
20
20
|
* under a code value hides the banding a slow gradient shows on eight bits.
|
|
21
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";
|
|
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;\nuniform float uBloom;\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) * uBloom;\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";
|