@fusion.dev/helix 0.172.0 → 0.174.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/custom-elements.json +2140 -93
  2. package/dist/copy-button/root/copy-button-root.css.js +1 -1
  3. package/dist/element-events.generated.js +8 -1
  4. package/dist/graph/root/graph-root.css.js +2 -1
  5. package/dist/orb/root/orb-root.css.js +1 -1
  6. package/dist/orb/root/orb-root.d.ts +24 -5
  7. package/dist/orb/root/orb-root.js +24 -6
  8. package/dist/sandbox/index.d.ts +1 -0
  9. package/dist/sandbox/index.js +1 -0
  10. package/dist/sandbox/preview/sandbox-preview.d.ts +39 -2
  11. package/dist/sandbox/preview/sandbox-preview.js +37 -5
  12. package/dist/sandbox/root/sandbox-root.css.js +1 -1
  13. package/dist/sandbox/root/sandbox-root.d.ts +44 -4
  14. package/dist/sandbox/root/sandbox-root.js +92 -17
  15. package/dist/sandbox/source/editing.d.ts +111 -0
  16. package/dist/sandbox/source/editing.js +185 -0
  17. package/dist/sandbox/source/sandbox-source.css.js +2 -1
  18. package/dist/sandbox/source/sandbox-source.d.ts +131 -6
  19. package/dist/sandbox/source/sandbox-source.js +301 -17
  20. package/dist/strands/palette.d.ts +1 -1
  21. package/dist/strands/path.d.ts +17 -6
  22. package/dist/strands/path.js +149 -43
  23. package/dist/strands/renderer.d.ts +14 -0
  24. package/dist/strands/renderer.js +14 -4
  25. package/dist/strands/root/strands-root.css.js +1 -1
  26. package/dist/strands/root/strands-root.d.ts +41 -10
  27. package/dist/strands/root/strands-root.js +32 -5
  28. package/dist/strands/shader.d.ts +3 -3
  29. package/dist/strands/shader.js +35 -12
  30. package/dist/strands/stop/strands-stop.d.ts +5 -5
  31. package/dist/timeline/root/timeline-root.css.js +1 -1
  32. package/dist/virtual-list/reorder/index.d.ts +1 -0
  33. package/dist/virtual-list/reorder/index.js +5 -0
  34. package/dist/virtual-list/reorder/virtual-list-reorder.css.d.ts +1 -0
  35. package/dist/virtual-list/reorder/virtual-list-reorder.css.js +12 -0
  36. package/dist/virtual-list/root/virtual-list-root.d.ts +9 -0
  37. package/lint.json +1 -1
  38. package/package.json +6 -6
  39. package/types/jsx.d.ts +919 -79
  40. package/types/vue.d.ts +214 -15
  41. package/types/words.d.ts +27 -0
@@ -26,7 +26,7 @@ export declare const RESTING_PALETTE: StrandsPalette;
26
26
  * above the element, and the token otherwise:
27
27
  *
28
28
  * - `color`: `--strands-glow`, or `primary`.
29
- * - `text-decoration-color`: `--strands-tint`, or `info`.
29
+ * - `text-decoration-color`: `--strands-tint`, or `accent`.
30
30
  * - `outline-color`: `--strands-ground`, or the background.
31
31
  * - `caret-color` and `column-rule-color`: `--strands-peak`, or the dark and
32
32
  * the light page's own peak.
@@ -7,7 +7,7 @@
7
7
  * screen are all arithmetic.
8
8
  */
9
9
  /** The three forms the ribbon takes. */
10
- export type StrandsVariant = 'fan' | 'ribbon' | 'waves';
10
+ export type StrandsVariant = 'bend' | 'ribbon' | 'waves';
11
11
  /**
12
12
  * One point on the centerline: `x` as a share of the width, `y` in CSS pixels,
13
13
  * the ribbon's width as a share of the width, and its brightness from `0` to
@@ -38,6 +38,15 @@ export interface PlacedStop {
38
38
  * than forty screens long; a longer one keeps its first 96 points and fades.
39
39
  */
40
40
  export declare const MAX_POINTS = 96;
41
+ /**
42
+ * The tightest any form turns, as a share of the box's height: the radius of
43
+ * its sharpest curve.
44
+ *
45
+ * Half the height. A bend's apex a few dozen pixels round kinked the strands
46
+ * that ran along its inside, and read as a fold in the light where every form
47
+ * is meant to be one sweep. `path.pure.spec.ts` measures it on every form.
48
+ */
49
+ export declare const TURN = 0.5;
41
50
  /**
42
51
  * The ribbon drawn inside its own box, which is what an element without
43
52
  * `travel` shows.
@@ -45,18 +54,20 @@ export declare const MAX_POINTS = 96;
45
54
  * @param variant The form to draw.
46
55
  * @param at Where it sits across the box, as a share of the width. A wave
47
56
  * crosses the whole width and does not read it.
48
- * @param height The box's height, in CSS pixels. Its width needs no
49
- * argument: `x` is a share of it.
57
+ * @param height The box's height, in CSS pixels.
58
+ * @param width The box's width, in CSS pixels. `x` is a share of it, and a
59
+ * bend's apex is drawn in pixels so it keeps its radius at any shape.
50
60
  */
51
- export declare const boxPath: (variant: StrandsVariant, at: number | undefined, height: number) => PathPoint[];
61
+ export declare const boxPath: (variant: StrandsVariant, at: number | undefined, height: number, width: number) => PathPoint[];
52
62
  /**
53
63
  * The ribbon laid down a whole page, which is what an element with `travel`
54
64
  * shows.
55
65
  *
56
66
  * Each stop places its form at its target, in page order. Between two stops
57
67
  * the ribbon fills the gap with soft S-curves in `fill`'s place, except
58
- * between two waves, where it turns off screen so each wave reads alone.
59
- * Without stops, `fill` runs the length of the page: a fan opens the page and
68
+ * between two waves less than a screen and a half apart, where it turns off
69
+ * screen so each wave reads alone.
70
+ * Without stops, `fill` runs the length of the page: a bend opens the page and
60
71
  * a ribbon follows it, waves cross every screen and a third, and a ribbon
61
72
  * meanders the whole way. After a last wave the ribbon does not come back, so
62
73
  * the page ends on the wave.
@@ -1,37 +1,91 @@
1
1
  export const MAX_POINTS = 96;
2
2
  const NARROW = 760;
3
- const GLOW = { fan: 1, ribbon: 0.36, waves: 0.7 };
4
- const WIDTH = { fan: 0.07, ribbon: 0.24, waves: 0.06 };
5
- const FAN_AT = 0.4;
6
- const fan = (top, height, at, narrow) => {
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 arm = narrow ? 0.12 : 0.15;
10
- return [
11
- [x + 0.38, y - height * 0.73, WIDTH.fan, 0.2],
12
- [x + arm, y - height * 0.3, WIDTH.fan * 0.5, 0.9],
13
- [x, y, 0.005, GLOW.fan],
14
- [x + arm, y + height * 0.3, WIDTH.fan * 0.5, 0.9],
15
- [x + 0.45, y + height * 0.54, WIDTH.fan * 1.15, 0.7],
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
- return [
23
- [side(-0.15), y - height * 0.35, w, 0],
24
- [side(-0.05), y, w, glow * 0.6],
25
- [side(0.3), y + 18, w, glow],
26
- [side(0.7), y - 18, w, glow],
27
- [side(1.05), y, w, glow * 0.6],
28
- [side(1.15), y + height * 0.35, w, 0],
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 ribbon = (from, to, height, at, phase, narrow) => {
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 * 0.55;
34
- const swing = narrow ? 0.26 : 0.3;
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 === 'fan') {
47
- return fan(0, height, at ?? FAN_AT, narrow);
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 === 'fan') {
67
- placed.push({ at: undefined, height, top: 0, variant: 'fan' });
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 stretch = ribbon(cursor + height * 0.2, to - height * 0.45, height, 0.5, phase, narrow);
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
- points.push(...stretch.points);
85
- const last = stretch.points.at(-1);
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 === 'fan') {
168
+ if (stop.variant === 'bend') {
93
169
  const span = Math.min(stop.height, height);
94
- points.push(...fan(stop.top, span, stop.at ?? FAN_AT, narrow));
95
- cursor = stop.top + span * 1.12;
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
- points.push(...wave(stop.top, height, fromRight, narrow));
100
- fromRight = !fromRight;
101
- cursor = stop.top + height * 0.35;
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.push([last[0], Math.max(last[1], page) + height, last[2], 0]);
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. */
@@ -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
- gl.drawArrays(gl.POINTS, 0, DUST);
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', (1.7 * frame.gain) / this.scale);
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-info));position: relative;display: block;inline-size: 100%;block-size: 100%;background-image: linear-gradient(
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-info` for depth, and the page they light is
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`.** `fan` is a tight, bright bend with its
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 fan's bend or the ribbon's axis across
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 it turns off screen so each wave
32
- * reads alone. Set `z-index` on the element to move it.
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-info`.
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 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`
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 fan's bend or a wave, stays sharp whatever this says.
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.55;
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 === 'fan' || variant === 'waves' ? variant : 'ribbon',
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);