@juspay/svelte-ui-components 4.33.1 → 4.33.3

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.
@@ -11,13 +11,16 @@
11
11
  * split keeps "the shape's own motion" and "the camera's fixed angle on it"
12
12
  * from tangling together in the per-dot math.
13
13
  */
14
- import { easeInOutCubic, hashUnit, modeSizing, project, rotateX, rotateY, rotateZ } from '../space';
14
+ import { easeInOutCubic, hashUnit, modeSizing, project, rotateX, rotateY, rotateZ, sampleSphere } from '../space';
15
15
  const TWO_PI = Math.PI * 2;
16
16
  /** The shared "reads as a form inside a square" contract: radius as a share of `size`. */
17
17
  const FORM_RADIUS_RATIO = 0.4;
18
18
  const clamp01 = (value) => Math.max(0, Math.min(1, value));
19
+ /** How far toward transparent the farthest dot's ink goes. `paintFrame` multiplies (1 - ink) with
20
+ * `alpha`, so ink reaching 1 would erase the far side of a ring or ribbon however high its alpha. */
21
+ const FAR_INK = 0.6;
19
22
  /** Nearer dots read as more saturated tint; `paintFrame` turns low ink into more of `tint`. */
20
- const depthToInk = (depth) => clamp01(1 - depth);
23
+ const depthToInk = (depth) => clamp01((1 - depth) * FAR_INK);
21
24
  /** Farther dots stay at partial opacity rather than fading to nothing, so no instant reads blank. */
22
25
  const depthToAlpha = (depth) => clamp01(0.5 + depth * 0.5);
23
26
  /** Nearer dots draw a touch larger, reinforcing the depth `paintFrame`'s draw order already implies. */
@@ -40,10 +43,17 @@ const place = (point, spinAngle, tiltAngle, originPx, worldRadius, baseRadius, s
40
43
  };
41
44
  const EMPTY_STROKES = [];
42
45
  // --- composing ---------------------------------------------------------
43
- const RIBBON_BASE_COUNT = 260;
44
- const RIBBON_BASE_DOT_RADIUS = 2.2;
45
- const RIBBON_THREADS = { 64: 7, 32: 5, 20: 4 };
46
- const RIBBON_HALF_WIDTH = 0.4;
46
+ /** Dots along each thread at 64px; `countAlongPath` keeps their spacing as the canvas shrinks. */
47
+ const RIBBON_PER_THREAD_BASE = 36;
48
+ /** Reference parity (BZ-6466 follow-up): upstream thinking-orbs' `ribbon` mode resolves to a
49
+ * ~0.9-1.1px base dot radius at 64px (its BASE_PROFILES rBase 1.1, x0.85 for this preset's `size`
50
+ * multiplier). This used to carry 2.2px, exactly the radius that fused composing's dots into a
51
+ * solid corrugated band instead of the reference's fine, individually-visible beads. */
52
+ const RIBBON_BASE_DOT_RADIUS = 1.2;
53
+ /** Fewer, narrower threads than a full band: at 7 threads across 0.4 rad the 64px ribbon covered
54
+ * most of the sphere and read as a filled ball rather than a strip of cloth. */
55
+ const RIBBON_THREADS = { 64: 5, 32: 4, 20: 3 };
56
+ const RIBBON_HALF_WIDTH = 0.3;
47
57
  /** The ribbon's own great circle sits tilted off the spin axis by this much,
48
58
  * so spinning genuinely sweeps its width through the frame -- a band that
49
59
  * happened to lie flat on the equator would look identical at every spin
@@ -51,7 +61,9 @@ const RIBBON_HALF_WIDTH = 0.4;
51
61
  const RIBBON_AXIS_TILT = 0.68;
52
62
  const RIBBON_RIPPLE_WAVES = 3;
53
63
  const RIBBON_RIPPLE_SPEED = 1.15;
54
- const RIBBON_RIPPLE_AMPLITUDE = 0.5;
64
+ /** Kept under RIBBON_HALF_WIDTH: a ripple wider than the band itself swings the ribbon toward
65
+ * the pole and folds it over itself, which reads as a knot rather than cloth. */
66
+ const RIBBON_RIPPLE_AMPLITUDE = 0.18;
55
67
  const RIBBON_THREAD_JITTER = 0.1;
56
68
  const RIBBON_SPIN_SPEED = 0.48;
57
69
  const RIBBON_CAMERA_TILT = 0.45;
@@ -60,6 +72,16 @@ const RIBBON_CAMERA_TILT = 0.45;
60
72
  * combined ink/alpha/radius fade would collapse an edge row to a speck. */
61
73
  const RIBBON_EDGE_ALPHA_FLOOR = 0.5;
62
74
  const RIBBON_EDGE_INK_LIFT = 0.22;
75
+ /** Reference parity (BZ-6466 follow-up): upstream's `ribbon` mode fills the sphere behind the band
76
+ * with a faint, fully static "ghost" texture (150 dots at 64px, this preset's `spin: 0` freezing
77
+ * even the shared camera precession that would otherwise turn it). Ours had no such backing layer,
78
+ * so the ribbon read as a thin strip floating in empty space rather than cloth wrapped on a solid
79
+ * form -- the ribbon's own turn was the only motion on screen, with nothing to anchor it against. */
80
+ const RIBBON_GHOST_BASE_COUNT = 110;
81
+ const RIBBON_GHOST_RADIUS_PX = 0.5;
82
+ const RIBBON_GHOST_INK = 0.3;
83
+ const RIBBON_GHOST_ALPHA_BASE = 0.16;
84
+ const RIBBON_GHOST_ALPHA_DEPTH = 0.22;
63
85
  /**
64
86
  * A ribbon of dots looped once around the sphere on its own tilted great
65
87
  * circle -- distinct from the spin axis -- rippling along its length like a
@@ -70,9 +92,22 @@ export const composing = (t, ctx) => {
70
92
  const worldRadius = ctx.size * FORM_RADIUS_RATIO;
71
93
  const originPx = ctx.size / 2;
72
94
  const threads = RIBBON_THREADS[ctx.size];
73
- const perThread = Math.max(1, Math.ceil(sizing.count(RIBBON_BASE_COUNT) / threads));
95
+ const perThread = sizing.countAlongPath(RIBBON_PER_THREAD_BASE);
74
96
  const total = threads * perThread;
75
- const dots = [];
97
+ const ghostCount = Math.max(20, sizing.count(RIBBON_GHOST_BASE_COUNT));
98
+ const dots = Array.from({ length: ghostCount }, (_unused, i) => {
99
+ const point = sampleSphere(i, ghostCount, worldRadius);
100
+ const tilted = rotateX(point, RIBBON_CAMERA_TILT);
101
+ const projected = project(tilted, originPx, worldRadius);
102
+ return {
103
+ x: projected.x,
104
+ y: projected.y,
105
+ depth: projected.depth,
106
+ radius: sizing.radius(RIBBON_GHOST_RADIUS_PX),
107
+ ink: RIBBON_GHOST_INK,
108
+ alpha: clamp01(RIBBON_GHOST_ALPHA_BASE + RIBBON_GHOST_ALPHA_DEPTH * projected.depth)
109
+ };
110
+ });
76
111
  for (let index = 0; index < total; index += 1) {
77
112
  const thread = index % threads;
78
113
  const step = Math.floor(index / threads);
@@ -102,7 +137,9 @@ export const composing = (t, ctx) => {
102
137
  return { dots, strokes: EMPTY_STROKES };
103
138
  };
104
139
  // --- breathing -----------------------------------------------------------
105
- const BREATHING_BASE_COUNT = 210;
140
+ /** Dots around the ring at 64px. A ring's length grows with `size`, not its area, so this goes
141
+ * through `countAlongPath`: 210 area-scaled dots sat 0.65px apart at 64px and fused into a line. */
142
+ const BREATHING_BASE_COUNT = 32;
106
143
  const BREATHING_BASE_DOT_RADIUS = 1.7;
107
144
  /** Sits below composing/shaping's ~0.4: at the peak of its breath this ring
108
145
  * already swells by its amplitude plus its shape wobble below, so a resting
@@ -125,7 +162,7 @@ export const breathing = (t, ctx) => {
125
162
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
126
163
  const worldRadius = ctx.size * BREATHING_WORLD_RADIUS_RATIO;
127
164
  const originPx = ctx.size / 2;
128
- const count = sizing.count(BREATHING_BASE_COUNT);
165
+ const count = sizing.countAlongPath(BREATHING_BASE_COUNT);
129
166
  const breathe = Math.sin(t * BREATHING_SPEED);
130
167
  const dots = [];
131
168
  for (let index = 0; index < count; index += 1) {
@@ -144,7 +181,8 @@ export const breathing = (t, ctx) => {
144
181
  return { dots, strokes: EMPTY_STROKES };
145
182
  };
146
183
  // --- shaping ---------------------------------------------------------------
147
- const SHAPING_BASE_COUNT = 210;
184
+ /** Dots around the outline at 64px, through `countAlongPath` for the same reason as breathing. */
185
+ const SHAPING_BASE_COUNT = 36;
148
186
  const SHAPING_BASE_DOT_RADIUS = 1.7;
149
187
  const SHAPING_CYCLE_SECONDS = 12;
150
188
  // One full turn per morph cycle, so the shape and its orientation both land
@@ -169,7 +207,7 @@ export const shaping = (t, ctx) => {
169
207
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
170
208
  const worldRadius = ctx.size * FORM_RADIUS_RATIO;
171
209
  const originPx = ctx.size / 2;
172
- const count = sizing.count(SHAPING_BASE_COUNT);
210
+ const count = sizing.countAlongPath(SHAPING_BASE_COUNT);
173
211
  const cyclePos = (((t / SHAPING_CYCLE_SECONDS) % 1) + 1) % 1;
174
212
  const phaseFloat = cyclePos * 3;
175
213
  const phaseIndex = Math.min(2, Math.floor(phaseFloat));
@@ -6,7 +6,11 @@
6
6
  * projection setup below.
7
7
  */
8
8
  import type { ModeFn } from '../types';
9
+ import type { ModeSizing } from '../space';
9
10
  /** A few nodes on a sphere; lines to near neighbours carry small travelling pulses. */
10
11
  export declare const connecting: ModeFn;
11
- /** Three strands wrapped around a sphere, crossing as they spiral from top to bottom. */
12
+ /** Exported so tests that index `weaving`'s strand dots can reproduce and slice off this count,
13
+ * rather than duplicating the formula as a hardcoded literal. */
14
+ export declare const weavingGhostCount: (sizing: ModeSizing) => number;
15
+ /** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
12
16
  export declare const weaving: ModeFn;
@@ -25,11 +25,14 @@ const squaredDistance = (a, b) => {
25
25
  // connecting -- a few nodes on a sphere, linked to their nearest neighbours,
26
26
  // with small bright pulses running along the links.
27
27
  // ---------------------------------------------------------------------------
28
- const NODE_BASE_COUNT = 16;
28
+ /** A few nodes, not a molecule: at 16 nodes x 3 neighbours the 64px graph carried ~24 edges, each
29
+ * with its own pulse, and the pulses were lost among the nodes. */
30
+ const NODE_BASE_COUNT = 10;
29
31
  const MIN_NODE_COUNT = 6;
30
- const NEIGHBOR_COUNT = 3;
32
+ const NEIGHBOR_COUNT = 2;
31
33
  const NODE_BASE_RADIUS = 3.4;
32
- const PULSE_BASE_RADIUS = 2;
34
+ /** Well under a node's radius, so a pulse reads as something travelling a link, not another node. */
35
+ const PULSE_BASE_RADIUS = 1.4;
33
36
  const EDGE_BASE_WIDTH = 1.1;
34
37
  const NODE_ROTATE_SPEED = 0.35;
35
38
  const NODE_TILT = 0.45;
@@ -104,63 +107,90 @@ export const connecting = (t, ctx) => {
104
107
  return { dots: [...nodeDots, ...pulses], strokes };
105
108
  };
106
109
  // ---------------------------------------------------------------------------
107
- // weaving -- three strands spiralling from top to bottom around a sphere, offset
108
- // in phase so they interleave; the depth sort in `paintFrame` is what makes
109
- // them cross over and under each other.
110
+ // weaving -- three closed strands wrapped around a sphere like the windings on
111
+ // a ball of yarn; the depth sort in `paintFrame` is what makes them cross over
112
+ // and under each other.
110
113
  // ---------------------------------------------------------------------------
111
114
  const STRAND_COUNT = 3;
112
- const STRAND_POINTS_BASE_COUNT = 26;
113
- const MIN_STRAND_POINTS = 10;
114
- /** Lower than a bare spiral would use: the braid swing below is what supplies
115
- * the winding, so the longitude term only needs to carry the strands most of
116
- * the way around, not add its own extra turns on top. */
117
- const STRAND_WRAPS = 1.1;
115
+ /** Points around each closed strand at 64px, through `countAlongPath`. */
116
+ const STRAND_POINTS_BASE_COUNT = 40;
117
+ /** Enough points that a strand still draws as a curve at 20px; at 10 its segments were long
118
+ * enough to show as straight edges and the three strands drew an angular cage. */
119
+ const MIN_STRAND_POINTS = 16;
120
+ /** Each strand is a great circle tilted this far off the horizontal, and the three are turned
121
+ * 120 degrees apart about the vertical axis, so every pair crosses twice per turn. A pole-to-pole
122
+ * spiral kept projecting two strands onto nearly the same path. */
123
+ const STRAND_INCLINATION = 0.6;
124
+ /** A slow wave running along each strand, so the three read as threads being worked rather than
125
+ * three rigid hoops. Small, so the wave never folds a strand across its neighbour's path. */
126
+ const STRAND_WAVE_COUNT = 3;
127
+ const STRAND_WAVE_AMPLITUDE = 0.12;
128
+ const STRAND_WAVE_SPEED = 0.9;
118
129
  const STRAND_ROTATE_SPEED = 0.3;
119
- const STRAND_TILT = 0.5;
130
+ /** How far the camera looks down on the sphere: 1.1 rad, about 63 degrees. A ring turns edge-on,
131
+ * and draws as a straight bar, when its axis is square to the line of sight. Each ring's axis sits
132
+ * STRAND_INCLINATION (about 34 degrees) off vertical, and this tilt leaves the vertical about
133
+ * 90 - 63 = 27 degrees off the line of sight, so a ring's axis is never more than 34 + 27 = 61
134
+ * degrees off it. No spin angle, and so no paused frame, draws a ring flatter than about
135
+ * cos 61 = 48% of its width. */
136
+ const STRAND_TILT = 1.1;
120
137
  const STRAND_DOT_BASE_RADIUS = 1.6;
121
138
  const STRAND_STROKE_BASE_WIDTH = 1.4;
122
- /** How many times a strand swings past its neighbours along its own pole-to-
123
- * pole length -- enough that a still frame always lands inside more than one
124
- * crossing, not so many the braid blurs into texture. */
125
- const STRAND_BRAID_CYCLES = 2.5;
126
- /** A constant 120-degree offset alone keeps the three strands in the same
127
- * relative order the whole way, which reads as one thick spiral rather than
128
- * three distinct ones. Two strands only visibly swap places, i.e. actually
129
- * cross, once the swing below overtakes half their 120-degree separation;
130
- * this sits comfortably past that so every pair of strands crosses, not just
131
- * wobbles toward each other. */
132
- const STRAND_BRAID_SWING = 1.3;
139
+ /** At the small sizes the strand lines carry the motif and the beads only clutter it, so lines
140
+ * thicken and beads shrink as the canvas does. */
141
+ const STRAND_STROKE_SIZE_FACTOR = { 64: 1, 32: 1.2, 20: 1.5 };
142
+ const STRAND_DOT_SIZE_FACTOR = { 64: 1, 32: 0.85, 20: 0.7 };
143
+ /** Depth still dims a strand's far side, but only this far, so no stretch of it disappears. */
144
+ const STRAND_DEPTH_INK = 0.45;
145
+ /** A slight per-strand shade; the paths and their over/under crossings are what separate them. */
146
+ const STRAND_SHADE_STEP = 0.1;
147
+ /** Reference parity (BZ-6466 follow-up): upstream's `braid` mode fills the sphere behind the
148
+ * strands with a faint, static-in-object-space "ghost" texture (150 dots at 64px) that shares the
149
+ * strands' own turn, so the strands read as plaiting round a solid ball rather than floating loops
150
+ * in empty space. Ours had no such backing layer. Always appended after every strand's dots, so
151
+ * code and tests that index `dots` by strand can slice it off by a known, reproducible count. */
152
+ const STRAND_GHOST_BASE_COUNT = 110;
153
+ const STRAND_GHOST_RADIUS_PX = 0.5;
154
+ const STRAND_GHOST_INK = 0.3;
155
+ /** With STRAND_GHOST_INK 0.3, `inkAlpha` = (1-ink)*alpha = 0.7*alpha, so this needs to clear
156
+ * 0.1/0.7 for the "nothing paints below 10% opacity" contract even on a ghost dot's far side. */
157
+ const STRAND_GHOST_ALPHA_BASE = 0.16;
158
+ const STRAND_GHOST_ALPHA_DEPTH = 0.22;
159
+ /** Exported so tests that index `weaving`'s strand dots can reproduce and slice off this count,
160
+ * rather than duplicating the formula as a hardcoded literal. */
161
+ export const weavingGhostCount = (sizing) => Math.max(20, sizing.count(STRAND_GHOST_BASE_COUNT));
133
162
  const strandBaseAngle = (strand) => (strand / STRAND_COUNT) * Math.PI * 2;
134
163
  /**
135
- * The `pointIndex`-th of `pointsPerStrand` points of `strand`, spiralling
136
- * from the top of the sphere to the bottom. Each strand's longitude oscillates around its own
137
- * 120-degree slot (the braid swing) on top of the shared spiral term, so
138
- * strands actually change places rather than staying a fixed distance apart;
139
- * folding `t` into the shared longitude term spins the whole braid rigidly,
140
- * which is what keeps every point exactly on the sphere at every instant.
164
+ * The `pointIndex`-th of `pointsPerStrand` points around closed `strand`: a
165
+ * great circle (the first and last points meet) with a small wave in latitude
166
+ * travelling along it, inclined by `STRAND_INCLINATION`, turned to its own
167
+ * 120-degree slot and spun with the rest. Latitude and longitude keep every
168
+ * point exactly on the sphere, whatever the wave does.
141
169
  */
142
170
  const strandPoint = (strand, pointIndex, pointsPerStrand, sphereRadius, t) => {
143
- const u = pointIndex / (pointsPerStrand - 1);
144
- const phi = u * Math.PI - Math.PI / 2;
145
- const y = sphereRadius * Math.sin(phi);
146
- const ringRadius = sphereRadius * Math.cos(phi);
147
- const base = strandBaseAngle(strand);
148
- const swing = STRAND_BRAID_SWING * Math.sin(u * STRAND_BRAID_CYCLES * Math.PI * 2 + base);
149
- const theta = u * STRAND_WRAPS * Math.PI * 2 + base + swing + t * STRAND_ROTATE_SPEED;
150
- const spun = { x: ringRadius * Math.cos(theta), y, z: ringRadius * Math.sin(theta) };
151
- return rotateX(spun, STRAND_TILT);
171
+ const u = (pointIndex / (pointsPerStrand - 1)) * Math.PI * 2;
172
+ const latitude = STRAND_WAVE_AMPLITUDE *
173
+ Math.sin(u * STRAND_WAVE_COUNT + t * STRAND_WAVE_SPEED + strandBaseAngle(strand));
174
+ const onCircle = {
175
+ x: sphereRadius * Math.cos(latitude) * Math.cos(u),
176
+ y: sphereRadius * Math.sin(latitude),
177
+ z: sphereRadius * Math.cos(latitude) * Math.sin(u)
178
+ };
179
+ const inclined = rotateX(onCircle, STRAND_INCLINATION);
180
+ const turned = rotateY(inclined, strandBaseAngle(strand) + t * STRAND_ROTATE_SPEED);
181
+ return rotateX(turned, STRAND_TILT);
152
182
  };
153
- /** Three strands wrapped around a sphere, crossing as they spiral from top to bottom. */
183
+ /** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
154
184
  export const weaving = (t, ctx) => {
155
185
  const { sizing, originPx, sphereRadius } = sphereGeometry(ctx);
156
- const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.count(STRAND_POINTS_BASE_COUNT));
186
+ const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.countAlongPath(STRAND_POINTS_BASE_COUNT));
157
187
  const strands = Array.from({ length: STRAND_COUNT }, (_, strand) => Array.from({ length: pointsPerStrand }, (_, pointIndex) => project(strandPoint(strand, pointIndex, pointsPerStrand, sphereRadius, t), originPx, sphereRadius)));
158
188
  const dots = strands.flatMap((points, strand) => points.map((p) => ({
159
189
  x: p.x,
160
190
  y: p.y,
161
191
  depth: p.depth,
162
- radius: sizing.radius(STRAND_DOT_BASE_RADIUS),
163
- ink: clamp01(1 - p.depth * 0.8 + strand * 0.08),
192
+ radius: sizing.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[ctx.size],
193
+ ink: clamp01((1 - p.depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
164
194
  alpha: 0.55 + p.depth * 0.4
165
195
  })));
166
196
  const strokes = strands.flatMap((points, strand) => points.slice(1).map((p, i) => {
@@ -172,10 +202,27 @@ export const weaving = (t, ctx) => {
172
202
  x2: p.x,
173
203
  y2: p.y,
174
204
  depth,
175
- width: sizing.radius(STRAND_STROKE_BASE_WIDTH),
176
- ink: clamp01(1 - depth * 0.75 + strand * 0.06),
205
+ width: sizing.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[ctx.size],
206
+ ink: clamp01((1 - depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
177
207
  alpha: 0.6 + depth * 0.35
178
208
  };
179
209
  }));
180
- return { dots, strokes };
210
+ // Shares the strands' own spin+tilt (not their per-strand inclination/phase, which is strand
211
+ // geometry, not camera) so the backdrop turns together with what it is behind.
212
+ const ghostCount = weavingGhostCount(sizing);
213
+ const ghosts = Array.from({ length: ghostCount }, (_unused, i) => {
214
+ const point = sampleSphere(i, ghostCount, sphereRadius);
215
+ const spun = rotateY(point, t * STRAND_ROTATE_SPEED);
216
+ const tilted = rotateX(spun, STRAND_TILT);
217
+ const projected = project(tilted, originPx, sphereRadius);
218
+ return {
219
+ x: projected.x,
220
+ y: projected.y,
221
+ depth: projected.depth,
222
+ radius: sizing.radius(STRAND_GHOST_RADIUS_PX),
223
+ ink: STRAND_GHOST_INK,
224
+ alpha: clamp01(STRAND_GHOST_ALPHA_BASE + STRAND_GHOST_ALPHA_DEPTH * projected.depth)
225
+ };
226
+ });
227
+ return { dots: [...dots, ...ghosts], strokes };
181
228
  };
@@ -44,23 +44,33 @@ const toStroke = (from, to, width, ink, alpha) => ({
44
44
  /** Particles travel in short trailing arcs ("trains") rather than scattering
45
45
  * independently around their orbit -- a lone random phase per particle reads
46
46
  * as noise once density thins the count out at 32/20px, while a handful of
47
- * particles held close together always reads as motion along a path. */
48
- const WORKING_TRAIN_BASE_COUNT = 22;
49
- /** Keeps at least this many short arcs on screen at each size. At 32/64px the
50
- * area scaler alone already clears this floor, so it only bites at 20px. More
51
- * than one train per orbit (WORKING_ORBIT_COUNT = 6) matters because depth
52
- * shading fades whichever half of each orbit is on the far side of the
53
- * sphere at a given instant almost to the background -- one train per orbit
54
- * left whole quadrants with no train currently on the near side to fill
55
- * them; two independently-phased laps around the six orbits keeps at least
56
- * a couple of quadrants covered at every instant, not just on average. */
57
- const WORKING_MIN_TRAINS = { 64: 3, 32: 3, 20: 12 };
58
- const WORKING_TRAIN_LENGTH = 5;
47
+ * particles held close together always reads as motion along a path.
48
+ *
49
+ * Reference parity (BZ-6466 follow-up): the upstream thinking-orbs `orbits`
50
+ * mode this was ported from draws 12 orbits x 3 particles = 36 marks at
51
+ * 64px, radius ~1.2px, and reads as a sparse field of distinct particles.
52
+ * The previous tuning here (22 trains x 5-long, radius 2.2px = 110 marks)
53
+ * packed 3x the marks at ~1.7x the radius into the same arcs, so consecutive
54
+ * dots overlapped their own diameter and the whole thing fused into a
55
+ * blurred scribble instead of scattered points. */
56
+ const WORKING_TRAIN_BASE_COUNT = 12;
57
+ /** Keeps at least this many short arcs on screen at 64px (32/20px use WORKING_SMALL_LAYOUT). The
58
+ * area scaler alone already clears it at the default density; it only bites when density is low. */
59
+ const WORKING_MIN_TRAINS_64 = 3;
60
+ const WORKING_TRAIN_LENGTH = 3;
59
61
  /** How far apart, in radians, a train's particles trail behind its lead --
60
62
  * wide enough to read as a short curved arc, short enough to stay a cluster
61
63
  * rather than smearing back into the full-orbit scatter this replaces. */
62
64
  const WORKING_TRAIN_ARC_SPAN = 0.5;
63
- const WORKING_BASE_RADIUS_PX = 1.6;
65
+ /** Reference's `orbits` mode draws particles at ~1.2px on a 64px canvas but scales them back UP
66
+ * at smaller sizes (2.4x at 20px) rather than shrinking further, so they stay legible once there's
67
+ * less room for arc spacing to keep them apart. `sizing.radius`'s sub-linear shrink-with-a-floor
68
+ * goes the other way, so the two sizes need their own base rather than one shared constant. */
69
+ const WORKING_BASE_RADIUS_PX = 1.3;
70
+ /** 32/20px (WORKING_SMALL_LAYOUT): kept at this mode's original, pre-reference-parity radius --
71
+ * these sizes weren't the fused-blur regression 64px was, and shrinking to match 64px's new,
72
+ * smaller radius here would drop dots below a legible stroke width. */
73
+ const WORKING_SMALL_BASE_RADIUS_PX = 2.2;
64
74
  const WORKING_ORBIT_COUNT = 6;
65
75
  const WORKING_ANGULAR_SPEED = 1.1;
66
76
  /** How far an orbit's `weight` can push its dots' ink away from the shared
@@ -72,6 +82,17 @@ const WORKING_ORBIT_ALPHA_FLOOR = 0.65;
72
82
  /** A train's trailing-most particle never fades past this floor, so the arc's
73
83
  * tail stays legible instead of thinning away to nothing. */
74
84
  const WORKING_TRAIL_ALPHA_FLOOR = 0.35;
85
+ /** Reference parity (BZ-6466 follow-up): upstream's `orbits` mode traces each orbit's full circle
86
+ * as a faint, static "ghost" ring of many dots (40 per orbit at 64px) UNDER the few bright
87
+ * travelling particles -- about 13x as many ghost dots as particles, so the ring lattice anchors
88
+ * the silhouette and the particles read as a sparkle on top of it. Ours had no such layer: every
89
+ * visible dot was one of the sweeping particles, so with only 6-22 of them the whole silhouette
90
+ * reshaped every frame instead of holding a roughly stable shape -- the actual "fluidity" gap a
91
+ * static/moving frame comparison alone doesn't show. */
92
+ const WORKING_GHOST_DOTS_PER_ORBIT = 40;
93
+ const WORKING_GHOST_RADIUS_PX = 0.7;
94
+ const WORKING_GHOST_INK = 0.4;
95
+ const WORKING_GHOST_ALPHA = 0.5;
75
96
  /** Every particle's own orbital plane, radius, pace and shading weight, keyed by its orbit. */
76
97
  const workingOrbit = (orbit) => {
77
98
  const seed = orbit * 31 + 7;
@@ -83,19 +104,60 @@ const workingOrbit = (orbit) => {
83
104
  weight: 0.35 + 0.65 * hashUnit(seed + 15)
84
105
  };
85
106
  };
107
+ /** At 32/20px six randomly tilted orbits of short arcs collapse into one busy cluster: each arc is
108
+ * only a few px long and the orbits overlap. These sizes instead spread a few orbits evenly around
109
+ * the view axis (like an atom symbol) and give each train a long arc, so every train draws a
110
+ * visible curve and the separate orbits stay readable. */
111
+ const WORKING_SMALL_LAYOUT = {
112
+ // Shorter trains than before (was 6/7): at the smaller shared radius above, a longer train's
113
+ // dots sat closer together than their own diameter and fused the same way 64px did.
114
+ 32: { orbits: 4, trainLength: 4, arcSpan: 0.75 },
115
+ 20: { orbits: 3, trainLength: 4, arcSpan: 0.9 }
116
+ };
117
+ /** How far each small-size orbit tips out of edge-on. Its projection is an ellipse sin(tilt) as
118
+ * tall as it is wide; near 1 rad every orbit projects to nearly the same circle and rolling it
119
+ * about the view axis changes nothing, so the orbits merged into one ring. */
120
+ const WORKING_SMALL_ORBIT_TILT = 0.5;
121
+ /** At 32/20px each orbit's path is drawn as a faint thin ellipse under its trains: a 20px train
122
+ * alone is too short to show which ellipse it is travelling, so without the path the trains read
123
+ * as a pinwheel rather than as particles circling several tilted orbits. */
124
+ const WORKING_PATH_SEGMENTS = 24;
125
+ const WORKING_PATH_WIDTH_PX = 0.8;
126
+ const WORKING_PATH_INK = 0.5;
127
+ const WORKING_PATH_ALPHA = 0.45;
128
+ const WORKING_SMALL_ORBIT_RADIUS = 0.85;
129
+ const WORKING_SMALL_SPEEDS = [0.8, 1.15, 1.45, 1];
130
+ /** One orbit of the small-size layout: shared tilt, rolled evenly around the view axis. */
131
+ const workingSmallOrbit = (orbit, orbits) => ({
132
+ ...workingOrbit(orbit),
133
+ tilt: WORKING_SMALL_ORBIT_TILT,
134
+ roll: (orbit / orbits) * Math.PI,
135
+ orbitRadiusFactor: WORKING_SMALL_ORBIT_RADIUS,
136
+ speedFactor: WORKING_SMALL_SPEEDS[orbit % WORKING_SMALL_SPEEDS.length]
137
+ });
86
138
  export const working = (t, ctx) => {
87
139
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
88
140
  const origin = originOf(ctx);
89
141
  const worldRadius = worldRadiusOf(ctx);
90
- const trainCount = Math.max(WORKING_MIN_TRAINS[ctx.size], sizing.count(WORKING_TRAIN_BASE_COUNT));
91
- const arcStep = WORKING_TRAIN_ARC_SPAN / (WORKING_TRAIN_LENGTH - 1);
142
+ const small = WORKING_SMALL_LAYOUT[ctx.size];
143
+ // Small sizes run two opposite trains per orbit (scaled by density); 64px keeps its own swarm.
144
+ const lapsPerOrbit = Math.max(1, Math.round(2 * ctx.density));
145
+ const trainCount = small
146
+ ? small.orbits * lapsPerOrbit
147
+ : Math.max(WORKING_MIN_TRAINS_64, sizing.count(WORKING_TRAIN_BASE_COUNT));
148
+ const trainLength = small ? small.trainLength : WORKING_TRAIN_LENGTH;
149
+ const arcStep = (small ? small.arcSpan : WORKING_TRAIN_ARC_SPAN) / (trainLength - 1);
92
150
  const dots = [];
93
151
  for (let train = 0; train < trainCount; train += 1) {
94
- const orbit = workingOrbit(train % WORKING_ORBIT_COUNT);
95
- const leadPhase = hashUnit(train * 5 + 1) * TWO_PI;
152
+ const orbit = small
153
+ ? workingSmallOrbit(train % small.orbits, small.orbits)
154
+ : workingOrbit(train % WORKING_ORBIT_COUNT);
155
+ const leadPhase = small
156
+ ? (Math.floor(train / small.orbits) / lapsPerOrbit) * TWO_PI + (train % small.orbits) * 0.9
157
+ : hashUnit(train * 5 + 1) * TWO_PI;
96
158
  const leadAngle = leadPhase + t * WORKING_ANGULAR_SPEED * orbit.speedFactor;
97
159
  const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
98
- for (let slot = 0; slot < WORKING_TRAIN_LENGTH; slot += 1) {
160
+ for (let slot = 0; slot < trainLength; slot += 1) {
99
161
  const angle = leadAngle - slot * arcStep;
100
162
  const planar = {
101
163
  x: Math.cos(angle) * orbitRadius,
@@ -106,17 +168,57 @@ export const working = (t, ctx) => {
106
168
  const point = rotateZ(tilted, orbit.roll);
107
169
  const projected = project(point, origin, worldRadius);
108
170
  const seed = train * 13 + slot * 7 + 2;
109
- const trail = 1 - slot / (WORKING_TRAIN_LENGTH - 1);
171
+ const trail = 1 - slot / (trainLength - 1);
110
172
  const ink = shadeByDepth(projected.depth) +
111
173
  (hashUnit(seed) - 0.5) * 0.08 -
112
174
  (orbit.weight - 0.5) * WORKING_ORBIT_INK_SPREAD;
113
175
  const alpha = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * trail) *
114
176
  (WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * orbit.weight);
115
- const radius = sizing.radius(WORKING_BASE_RADIUS_PX) * (0.75 + 0.5 * hashUnit(seed + 3));
177
+ const radiusBase = small ? WORKING_SMALL_BASE_RADIUS_PX : WORKING_BASE_RADIUS_PX;
178
+ const radius = sizing.radius(radiusBase) * (0.9 + 0.25 * hashUnit(seed + 3));
116
179
  dots.push(toDot(projected, radius, ink, alpha));
117
180
  }
118
181
  }
119
- return { dots, strokes: [] };
182
+ // 64px only: 32/20px already anchor their orbits with the faint stroke path below, drawn for
183
+ // every size in WORKING_SMALL_LAYOUT.
184
+ if (!small) {
185
+ const ghostDotsPerOrbit = Math.max(8, sizing.countAlongPath(WORKING_GHOST_DOTS_PER_ORBIT));
186
+ for (let orbitIndex = 0; orbitIndex < WORKING_ORBIT_COUNT; orbitIndex += 1) {
187
+ const orbit = workingOrbit(orbitIndex);
188
+ const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
189
+ for (let k = 0; k < ghostDotsPerOrbit; k += 1) {
190
+ const angle = (k / ghostDotsPerOrbit) * TWO_PI;
191
+ const planar = {
192
+ x: Math.cos(angle) * orbitRadius,
193
+ y: 0,
194
+ z: Math.sin(angle) * orbitRadius
195
+ };
196
+ const tilted = rotateX(planar, orbit.tilt);
197
+ const point = rotateZ(tilted, orbit.roll);
198
+ const projected = project(point, origin, worldRadius);
199
+ dots.push(toDot(projected, sizing.radius(WORKING_GHOST_RADIUS_PX), WORKING_GHOST_INK, WORKING_GHOST_ALPHA * (0.4 + 0.6 * projected.depth)));
200
+ }
201
+ }
202
+ }
203
+ const paths = small
204
+ ? Array.from({ length: small.orbits }, (_unused, orbitIndex) => {
205
+ const orbit = workingSmallOrbit(orbitIndex, small.orbits);
206
+ const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
207
+ const points = Array.from({ length: WORKING_PATH_SEGMENTS + 1 }, (_unusedPoint, j) => {
208
+ const angle = (j / WORKING_PATH_SEGMENTS) * TWO_PI;
209
+ const planar = {
210
+ x: Math.cos(angle) * orbitRadius,
211
+ y: 0,
212
+ z: Math.sin(angle) * orbitRadius
213
+ };
214
+ return project(rotateZ(rotateX(planar, orbit.tilt), orbit.roll), origin, worldRadius);
215
+ });
216
+ return points
217
+ .slice(1)
218
+ .map((to, j) => toStroke(points[j], to, sizing.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
219
+ }).flat()
220
+ : [];
221
+ return { dots, strokes: paths };
120
222
  };
121
223
  // ---------------------------------------------------------------------------
122
224
  // searching -- a dotted globe with a meridian band sweeping around it
@@ -183,10 +285,16 @@ export const searching = (t, ctx) => {
183
285
  };
184
286
  // ---------------------------------------------------------------------------
185
287
  // solving -- tiers that snap a quarter turn out of alignment, hold scrambled,
186
- // then resolve back, on a loop. Landmark dots and fixed seam rings make the
187
- // misalignment legible: an otherwise-uniform, evenly-sampled dot texture
188
- // looks identical under any rotation about the spin axis, so turning a
189
- // "band" of it invisibly wouldn't read as scrambling at all.
288
+ // then resolve back, on a loop. Each tier's own texture dots rotate with it
289
+ // (reference parity, BZ-6466 follow-up: matching upstream thinking-orbs'
290
+ // `rubik`, which reads solved-vs-scrambled off this same per-band rotation
291
+ // alone), so the misalignment shows as a visible discontinuity in the dot
292
+ // pattern itself; oversized landmark dots add a brighter set of accent
293
+ // points per tier. An earlier tuning also drew fixed seam rings and
294
+ // per-landmark meridian strokes to make the misalignment legible, but next
295
+ // to the reference that wireframe read as a busy skeleton the upstream
296
+ // version doesn't have, so it's gone -- the texture's own rotation already
297
+ // carries the same information.
190
298
  // ---------------------------------------------------------------------------
191
299
  const SOLVING_BASE_COUNT = 130;
192
300
  const SOLVING_BASE_RADIUS_PX = 1.5;
@@ -200,16 +308,13 @@ const SOLVING_TILT = -0.5;
200
308
  * cluster relative to its neighbours instead of only reshuffling texture
201
309
  * that looks the same before and after. */
202
310
  const SOLVING_LANDMARKS_PER_TIER = 3;
311
+ /** One phase for every tier, so in the solved pose each tier's landmarks line up into the same
312
+ * three columns and a turned tier visibly breaks out of them. A per-tier phase scattered them
313
+ * even when solved, leaving nothing for a turn to disturb. */
314
+ const SOLVING_LANDMARK_PHASE = hashUnit(5) * TWO_PI;
203
315
  const SOLVING_LANDMARK_RADIUS_PX = 3.1;
204
316
  const SOLVING_LANDMARK_INK_BOOST = 0.45;
205
317
  const SOLVING_LANDMARK_ALPHA = 0.9;
206
- /** A static ring of short strokes at each cut between tiers, unaffected by
207
- * any turn, so the "cut into bands" structure reads even while the puzzle
208
- * sits solved and nothing is moving. */
209
- const SOLVING_SEAM_SEGMENTS = 12;
210
- const SOLVING_SEAM_STROKE_RADIUS = 1.05;
211
- const SOLVING_SEAM_INK = 0.15;
212
- const SOLVING_SEAM_ALPHA = 0.5;
213
318
  const solvingTurnStart = (tier) => tier * SOLVING_TURN_GAP;
214
319
  const solvingTurnEnd = (tier) => solvingTurnStart(tier) + SOLVING_TURN_DURATION;
215
320
  const SOLVING_HOLD_START = solvingTurnEnd(SOLVING_TIER_COUNT - 1) + SOLVING_HOLD_GAP;
@@ -243,8 +348,6 @@ const solvingTierOf = (yUnit) => clamp(Math.floor(((1 - yUnit) / 2) * SOLVING_TI
243
348
  const solvingTierDirection = (tier) => (hashUnit(tier * 53 + 21) < 0.5 ? -1 : 1);
244
349
  /** The y-unit (-1..1) at the centre of a tier's latitude band. */
245
350
  const solvingTierCenterYUnit = (tier) => 1 - (2 * tier + 1) / SOLVING_TIER_COUNT;
246
- /** The y-unit (-1..1) of the fixed cut between one tier and the next. */
247
- const solvingSeamYUnit = (seam) => 1 - (2 * (seam + 1)) / SOLVING_TIER_COUNT;
248
351
  const solvingTurnAngle = (tier, cyclePos) => solvingTierTurn(tier, cyclePos) * solvingTierDirection(tier) * (Math.PI / 2);
249
352
  export const solving = (t, ctx) => {
250
353
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
@@ -270,8 +373,7 @@ export const solving = (t, ctx) => {
270
373
  const yUnit = solvingTierCenterYUnit(tier);
271
374
  const y = yUnit * worldRadius;
272
375
  const ringRadius = Math.sqrt(Math.max(0, 1 - yUnit * yUnit)) * worldRadius;
273
- const phase = hashUnit(tier * 29 + 5) * TWO_PI;
274
- const longitude = phase + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
376
+ const longitude = SOLVING_LANDMARK_PHASE + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
275
377
  const base = {
276
378
  x: Math.cos(longitude) * ringRadius,
277
379
  y,
@@ -283,23 +385,7 @@ export const solving = (t, ctx) => {
283
385
  const ink = shadeByDepth(projected.depth) - SOLVING_LANDMARK_INK_BOOST;
284
386
  return toDot(projected, sizing.radius(SOLVING_LANDMARK_RADIUS_PX), ink, SOLVING_LANDMARK_ALPHA);
285
387
  });
286
- const seamWidth = sizing.radius(SOLVING_SEAM_STROKE_RADIUS);
287
- const seams = Array.from({ length: SOLVING_TIER_COUNT - 1 }, (_unused, seam) => {
288
- const yUnit = solvingSeamYUnit(seam);
289
- const y = yUnit * worldRadius;
290
- const ringRadius = Math.sqrt(Math.max(0, 1 - yUnit * yUnit)) * worldRadius;
291
- const ringPoints = Array.from({ length: SOLVING_SEAM_SEGMENTS + 1 }, (_unusedPoint, j) => {
292
- const angle = (j / SOLVING_SEAM_SEGMENTS) * TWO_PI;
293
- const point = {
294
- x: Math.cos(angle) * ringRadius,
295
- y,
296
- z: Math.sin(angle) * ringRadius
297
- };
298
- return project(rotateX(point, SOLVING_TILT), origin, worldRadius);
299
- });
300
- return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (_unusedSegment, j) => toStroke(ringPoints[j], ringPoints[j + 1], seamWidth, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
301
- }).flat();
302
- return { dots: [...texture, ...landmarks], strokes: seams };
388
+ return { dots: [...texture, ...landmarks], strokes: [] };
303
389
  };
304
390
  // ---------------------------------------------------------------------------
305
391
  // listening -- stacked horizontal rings with a wave rolling through them
@@ -307,13 +393,17 @@ export const solving = (t, ctx) => {
307
393
  /** Fewer, fatter rings at the smaller presets read as distinct stacked bands;
308
394
  * six thin rings at 20px used to compress into a single smudge. */
309
395
  const LISTENING_RING_COUNT = { 64: 5, 32: 4, 20: 3 };
396
+ /** Dots per ring at 64px, through `countAlongPath` so 32px keeps a denser ring than 20px
397
+ * instead of both falling to the minimum below. */
310
398
  const LISTENING_DOTS_PER_RING_BASE = 26;
311
399
  /** A ring below this many dots reads as a loose cluster of points rather than
312
400
  * a line, especially once it is a small ellipse at 20px. */
313
401
  const LISTENING_MIN_DOTS_PER_RING = 10;
314
402
  const LISTENING_BASE_RADIUS_PX = 1.5;
315
403
  const LISTENING_WAVE_SPEED = 0.6;
316
- const LISTENING_SWELL = 0.55;
404
+ /** How much a ring's dots grow as the wave crests through it. At 0.55 the crest ate most of the
405
+ * gap between rings at 32/20px, where the rings sit only a few px apart. */
406
+ const LISTENING_SWELL = 0.3;
317
407
  const LISTENING_LIFT_INK = 0.45;
318
408
  const LISTENING_SPIN_SPEED = 0.12;
319
409
  /** A ring's own vertical thickness once tilted is (its radius) x sin(tilt); a
@@ -322,10 +412,10 @@ const LISTENING_SPIN_SPEED = 0.12;
322
412
  * neighbouring rings' ellipses swallow the empty space between them. Kept
323
413
  * shallow rather than flattened to zero so a ring still reads as an ellipse
324
414
  * seen slightly from above, not a flat line. */
325
- const LISTENING_TILT = -0.13;
415
+ const LISTENING_TILT = -0.1;
326
416
  /** Rings are stacked between +/- this fraction of the world radius, short of
327
417
  * the poles so the top and bottom rings stay full circles, not points. */
328
- const LISTENING_Y_SPAN = 0.82;
418
+ const LISTENING_Y_SPAN = 0.86;
329
419
  const listeningRing = (ring, ringCount, worldRadius) => {
330
420
  const yUnit = (-1 + (2 * ring + 1) / ringCount) * LISTENING_Y_SPAN;
331
421
  const y = yUnit * worldRadius;
@@ -339,8 +429,7 @@ export const listening = (t, ctx) => {
339
429
  const origin = originOf(ctx);
340
430
  const worldRadius = worldRadiusOf(ctx);
341
431
  const ringCount = LISTENING_RING_COUNT[ctx.size];
342
- const totalBase = ringCount * LISTENING_DOTS_PER_RING_BASE;
343
- const dotsPerRing = Math.max(LISTENING_MIN_DOTS_PER_RING, Math.round(sizing.count(totalBase) / ringCount));
432
+ const dotsPerRing = Math.max(LISTENING_MIN_DOTS_PER_RING, sizing.countAlongPath(LISTENING_DOTS_PER_RING_BASE));
344
433
  const dots = Array.from({ length: ringCount }, (_unused, r) => {
345
434
  const ring = listeningRing(r, ringCount, worldRadius);
346
435
  const wave = ring.wave(t);
@@ -18,9 +18,13 @@ export type Projected = {
18
18
  depth: number;
19
19
  scale: number;
20
20
  };
21
- /** The two scalers `modeSizing` hands back: multiply a mode's own base counts and radii by them. */
21
+ /** The scalers `modeSizing` hands back: multiply a mode's own base counts and radii by them. */
22
22
  export type ModeSizing = {
23
+ /** For marks spread over an area (a swarm, a globe's surface): scales with `size` squared. */
23
24
  count: (base: number) => number;
25
+ /** For marks spaced along a line (a ring, a thread): scales with `size`, so the gap between
26
+ * neighbours keeps the same share of the canvas at every size instead of closing up. */
27
+ countAlongPath: (base: number) => number;
24
28
  radius: (base: number) => number;
25
29
  };
26
30
  /** Rotates a point about the X axis (mixes y and z). */
@@ -57,7 +61,8 @@ export declare const hashUnit: (seed: number) => number;
57
61
  /**
58
62
  * Turns `(size, density, dotScale)` into a count scaler and a radius scaler,
59
63
  * so every mode derives its dot counts and radii the same way instead of
60
- * keeping its own per-size table. Counts scale with area (`size` squared);
64
+ * keeping its own per-size table. Counts scale with area (`size` squared) for
65
+ * marks spread over a surface and with `size` for marks spaced along a path;
61
66
  * radii grow sub-linearly, with a floor that keeps dots visible at 20px.
62
67
  */
63
68
  export declare const modeSizing: (size: ModeSize, density: number, dotScale: number) => ModeSizing;
@@ -100,7 +100,8 @@ const MIN_DOT_RADIUS_PX = 0.6;
100
100
  /**
101
101
  * Turns `(size, density, dotScale)` into a count scaler and a radius scaler,
102
102
  * so every mode derives its dot counts and radii the same way instead of
103
- * keeping its own per-size table. Counts scale with area (`size` squared);
103
+ * keeping its own per-size table. Counts scale with area (`size` squared) for
104
+ * marks spread over a surface and with `size` for marks spaced along a path;
104
105
  * radii grow sub-linearly, with a floor that keeps dots visible at 20px.
105
106
  */
106
107
  export const modeSizing = (size, density, dotScale) => {
@@ -110,6 +111,7 @@ export const modeSizing = (size, density, dotScale) => {
110
111
  const safeDotScale = dotScale > 0 ? dotScale : 0.1;
111
112
  return {
112
113
  count: (base) => Math.max(1, Math.round(base * areaRatio * safeDensity)),
114
+ countAlongPath: (base) => Math.max(1, Math.round(base * sizeRatio * safeDensity)),
113
115
  radius: (base) => Math.max(MIN_DOT_RADIUS_PX, base * sizeRatio ** RADIUS_GROWTH_EXPONENT * safeDotScale)
114
116
  };
115
117
  };
package/dist-wc/index.js CHANGED
@@ -24017,6 +24017,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24017
24017
  let i = e / REFERENCE_SIZE, a = i * i, o = t > 0 ? t : .1, s = n > 0 ? n : .1;
24018
24018
  return {
24019
24019
  count: (e) => Math.max(1, Math.round(e * a * o)),
24020
+ countAlongPath: (e) => Math.max(1, Math.round(e * i * o)),
24020
24021
  radius: (e) => Math.max(MIN_DOT_RADIUS_PX, e * i ** RADIUS_GROWTH_EXPONENT * s)
24021
24022
  };
24022
24023
  }, GRAVITY_DEFAULTS = {
@@ -24078,11 +24079,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24078
24079
  width: n,
24079
24080
  ink: clamp01$2(i),
24080
24081
  alpha: clamp01$2(a)
24081
- }), WORKING_TRAIN_BASE_COUNT = 22, WORKING_MIN_TRAINS = {
24082
- 64: 3,
24083
- 32: 3,
24084
- 20: 12
24085
- }, WORKING_TRAIN_LENGTH = 5, WORKING_TRAIN_ARC_SPAN = .5, WORKING_BASE_RADIUS_PX = 1.6, WORKING_ORBIT_COUNT = 6, WORKING_ANGULAR_SPEED = 1.1, WORKING_ORBIT_INK_SPREAD = .3, WORKING_ORBIT_ALPHA_FLOOR = .65, WORKING_TRAIL_ALPHA_FLOOR = .35, workingOrbit = (e) => {
24082
+ }), WORKING_TRAIN_BASE_COUNT = 12, WORKING_MIN_TRAINS_64 = 3, WORKING_TRAIN_LENGTH = 3, WORKING_TRAIN_ARC_SPAN = .5, WORKING_BASE_RADIUS_PX = 1.3, WORKING_SMALL_BASE_RADIUS_PX = 2.2, WORKING_ORBIT_COUNT = 6, WORKING_ANGULAR_SPEED = 1.1, WORKING_ORBIT_INK_SPREAD = .3, WORKING_ORBIT_ALPHA_FLOOR = .65, WORKING_TRAIL_ALPHA_FLOOR = .35, WORKING_GHOST_DOTS_PER_ORBIT = 40, WORKING_GHOST_RADIUS_PX = .7, WORKING_GHOST_INK = .4, WORKING_GHOST_ALPHA = .5, workingOrbit = (e) => {
24086
24083
  let t = e * 31 + 7;
24087
24084
  return {
24088
24085
  tilt: (hashUnit(t) - .5) * Math.PI,
@@ -24091,22 +24088,68 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24091
24088
  speedFactor: .6 + 1.3 * hashUnit(t + 9),
24092
24089
  weight: .35 + .65 * hashUnit(t + 15)
24093
24090
  };
24094
- }, working = (e, t) => {
24095
- let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = Math.max(WORKING_MIN_TRAINS[t.size], n.count(WORKING_TRAIN_BASE_COUNT)), s = WORKING_TRAIN_ARC_SPAN / (WORKING_TRAIN_LENGTH - 1), c = [];
24096
- for (let t = 0; t < o; t += 1) {
24097
- let o = workingOrbit(t % WORKING_ORBIT_COUNT), l = hashUnit(t * 5 + 1) * TWO_PI$1 + e * WORKING_ANGULAR_SPEED * o.speedFactor, u = a * o.orbitRadiusFactor;
24098
- for (let e = 0; e < WORKING_TRAIN_LENGTH; e += 1) {
24099
- let f = l - e * s, p = project(rotateZ(rotateX({
24100
- x: Math.cos(f) * u,
24091
+ }, WORKING_SMALL_LAYOUT = {
24092
+ 32: {
24093
+ orbits: 4,
24094
+ trainLength: 4,
24095
+ arcSpan: .75
24096
+ },
24097
+ 20: {
24098
+ orbits: 3,
24099
+ trainLength: 4,
24100
+ arcSpan: .9
24101
+ }
24102
+ }, WORKING_SMALL_ORBIT_TILT = .5, WORKING_PATH_SEGMENTS = 24, WORKING_PATH_WIDTH_PX = .8, WORKING_PATH_INK = .5, WORKING_PATH_ALPHA = .45, WORKING_SMALL_ORBIT_RADIUS = .85, WORKING_SMALL_SPEEDS = [
24103
+ .8,
24104
+ 1.15,
24105
+ 1.45,
24106
+ 1
24107
+ ], workingSmallOrbit = (e, t) => ({
24108
+ ...workingOrbit(e),
24109
+ tilt: WORKING_SMALL_ORBIT_TILT,
24110
+ roll: e / t * Math.PI,
24111
+ orbitRadiusFactor: WORKING_SMALL_ORBIT_RADIUS,
24112
+ speedFactor: WORKING_SMALL_SPEEDS[e % WORKING_SMALL_SPEEDS.length]
24113
+ }), working = (e, t) => {
24114
+ let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = WORKING_SMALL_LAYOUT[t.size], s = Math.max(1, Math.round(2 * t.density)), c = o ? o.orbits * s : Math.max(WORKING_MIN_TRAINS_64, n.count(WORKING_TRAIN_BASE_COUNT)), l = o ? o.trainLength : WORKING_TRAIN_LENGTH, u = (o ? o.arcSpan : WORKING_TRAIN_ARC_SPAN) / (l - 1), f = [];
24115
+ for (let t = 0; t < c; t += 1) {
24116
+ let c = o ? workingSmallOrbit(t % o.orbits, o.orbits) : workingOrbit(t % WORKING_ORBIT_COUNT), p = (o ? Math.floor(t / o.orbits) / s * TWO_PI$1 + t % o.orbits * .9 : hashUnit(t * 5 + 1) * TWO_PI$1) + e * WORKING_ANGULAR_SPEED * c.speedFactor, h = a * c.orbitRadiusFactor;
24117
+ for (let e = 0; e < l; e += 1) {
24118
+ let s = p - e * u, S = project(rotateZ(rotateX({
24119
+ x: Math.cos(s) * h,
24101
24120
  y: 0,
24102
- z: Math.sin(f) * u
24103
- }, o.tilt), o.roll), i, a), h = t * 13 + e * 7 + 2, S = 1 - e / (WORKING_TRAIN_LENGTH - 1), C = shadeByDepth(p.depth) + (hashUnit(h) - .5) * .08 - (o.weight - .5) * WORKING_ORBIT_INK_SPREAD, k = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * S) * (WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * o.weight), R = n.radius(WORKING_BASE_RADIUS_PX) * (.75 + .5 * hashUnit(h + 3));
24104
- c.push(toDot(p, R, C, k));
24121
+ z: Math.sin(s) * h
24122
+ }, c.tilt), c.roll), i, a), C = t * 13 + e * 7 + 2, k = 1 - e / (l - 1), R = shadeByDepth(S.depth) + (hashUnit(C) - .5) * .08 - (c.weight - .5) * WORKING_ORBIT_INK_SPREAD, te = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * k) * (WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * c.weight), ne = o ? WORKING_SMALL_BASE_RADIUS_PX : WORKING_BASE_RADIUS_PX, re = n.radius(ne) * (.9 + .25 * hashUnit(C + 3));
24123
+ f.push(toDot(S, re, R, te));
24124
+ }
24125
+ }
24126
+ if (!o) {
24127
+ let e = Math.max(8, n.countAlongPath(WORKING_GHOST_DOTS_PER_ORBIT));
24128
+ for (let t = 0; t < WORKING_ORBIT_COUNT; t += 1) {
24129
+ let o = workingOrbit(t), s = a * o.orbitRadiusFactor;
24130
+ for (let t = 0; t < e; t += 1) {
24131
+ let c = t / e * TWO_PI$1, l = project(rotateZ(rotateX({
24132
+ x: Math.cos(c) * s,
24133
+ y: 0,
24134
+ z: Math.sin(c) * s
24135
+ }, o.tilt), o.roll), i, a);
24136
+ f.push(toDot(l, n.radius(WORKING_GHOST_RADIUS_PX), WORKING_GHOST_INK, WORKING_GHOST_ALPHA * (.4 + .6 * l.depth)));
24137
+ }
24105
24138
  }
24106
24139
  }
24107
24140
  return {
24108
- dots: c,
24109
- strokes: []
24141
+ dots: f,
24142
+ strokes: o ? Array.from({ length: o.orbits }, (e, t) => {
24143
+ let s = workingSmallOrbit(t, o.orbits), c = a * s.orbitRadiusFactor, l = Array.from({ length: 25 }, (e, t) => {
24144
+ let n = t / WORKING_PATH_SEGMENTS * TWO_PI$1;
24145
+ return project(rotateZ(rotateX({
24146
+ x: Math.cos(n) * c,
24147
+ y: 0,
24148
+ z: Math.sin(n) * c
24149
+ }, s.tilt), s.roll), i, a);
24150
+ });
24151
+ return l.slice(1).map((e, t) => toStroke(l[t], e, n.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
24152
+ }).flat() : []
24110
24153
  };
24111
24154
  }, SEARCHING_BASE_COUNT = 150, SEARCHING_BASE_RADIUS_PX = 1.5, SEARCHING_BAND_HALF_WIDTH = .42, SEARCHING_SWEEP_SPEED = 1.15, SEARCHING_SPIN_SPEED = .18, SEARCHING_TILT = -.55, SEARCHING_ARC_SEGMENTS = 10, SEARCHING_ARC_LATITUDE_SPAN = .92, SEARCHING_BAND_INK_BOOST = .75, SEARCHING_BAND_ALPHA_BOOST = .35, SEARCHING_BAND_RADIUS_BOOST = 1.3, SEARCHING_ARC_STROKE_RADIUS = 2.2, SEARCHING_ARC_INK = 0, SEARCHING_ARC_ALPHA = .7, angleDistance = (e, t) => {
24112
24155
  let n = Math.abs(e - t) % TWO_PI$1;
@@ -24127,40 +24170,30 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24127
24170
  dots: c,
24128
24171
  strokes: Array.from({ length: SEARCHING_ARC_SEGMENTS }, (e, t) => toStroke(l[t], l[t + 1], u, SEARCHING_ARC_INK, SEARCHING_ARC_ALPHA))
24129
24172
  };
24130
- }, SOLVING_BASE_COUNT = 130, SOLVING_BASE_RADIUS_PX = 1.5, SOLVING_TIER_COUNT = 4, SOLVING_TURN_DURATION = .16, SOLVING_TURN_GAP = .3, SOLVING_HOLD_GAP = .55, SOLVING_TILT = -.5, SOLVING_LANDMARKS_PER_TIER = 3, SOLVING_LANDMARK_RADIUS_PX = 3.1, SOLVING_LANDMARK_INK_BOOST = .45, SOLVING_LANDMARK_ALPHA = .9, SOLVING_SEAM_SEGMENTS = 12, SOLVING_SEAM_STROKE_RADIUS = 1.05, SOLVING_SEAM_INK = .15, SOLVING_SEAM_ALPHA = .5, solvingTurnStart = (e) => e * SOLVING_TURN_GAP, solvingTurnEnd = (e) => solvingTurnStart(e) + SOLVING_TURN_DURATION, SOLVING_HOLD_START = solvingTurnEnd(SOLVING_TIER_COUNT - 1) + SOLVING_HOLD_GAP, solvingReturnStart = (e) => SOLVING_HOLD_START + (SOLVING_TIER_COUNT - 1 - e) * SOLVING_TURN_GAP, solvingReturnEnd = (e) => solvingReturnStart(e) + SOLVING_TURN_DURATION, SOLVING_CYCLE = solvingReturnEnd(0) + SOLVING_HOLD_GAP, solvingTierTurn = (e, t) => {
24173
+ }, SOLVING_BASE_COUNT = 130, SOLVING_BASE_RADIUS_PX = 1.5, SOLVING_TIER_COUNT = 4, SOLVING_TURN_DURATION = .16, SOLVING_TURN_GAP = .3, SOLVING_HOLD_GAP = .55, SOLVING_TILT = -.5, SOLVING_LANDMARKS_PER_TIER = 3, SOLVING_LANDMARK_PHASE = hashUnit(5) * TWO_PI$1, SOLVING_LANDMARK_RADIUS_PX = 3.1, SOLVING_LANDMARK_INK_BOOST = .45, SOLVING_LANDMARK_ALPHA = .9, solvingTurnStart = (e) => e * SOLVING_TURN_GAP, solvingTurnEnd = (e) => solvingTurnStart(e) + SOLVING_TURN_DURATION, SOLVING_HOLD_START = solvingTurnEnd(SOLVING_TIER_COUNT - 1) + SOLVING_HOLD_GAP, solvingReturnStart = (e) => SOLVING_HOLD_START + (SOLVING_TIER_COUNT - 1 - e) * SOLVING_TURN_GAP, solvingReturnEnd = (e) => solvingReturnStart(e) + SOLVING_TURN_DURATION, SOLVING_CYCLE = solvingReturnEnd(0) + SOLVING_HOLD_GAP, solvingTierTurn = (e, t) => {
24131
24174
  let n = solvingTurnStart(e), i = solvingTurnEnd(e), a = solvingReturnStart(e), o = solvingReturnEnd(e);
24132
24175
  return t <= n ? 0 : t < i ? easeOutCubic((t - n) / SOLVING_TURN_DURATION) : t < a ? 1 : t < o ? 1 - easeInOutCubic((t - a) / SOLVING_TURN_DURATION) : 0;
24133
- }, solvingTierOf = (e) => clamp$1(Math.floor((1 - e) / 2 * SOLVING_TIER_COUNT), 0, SOLVING_TIER_COUNT - 1), solvingTierDirection = (e) => hashUnit(e * 53 + 21) < .5 ? -1 : 1, solvingTierCenterYUnit = (e) => 1 - (2 * e + 1) / SOLVING_TIER_COUNT, solvingSeamYUnit = (e) => 1 - 2 * (e + 1) / SOLVING_TIER_COUNT, solvingTurnAngle = (e, t) => solvingTierTurn(e, t) * solvingTierDirection(e) * (Math.PI / 2), solving = (e, t) => {
24176
+ }, solvingTierOf = (e) => clamp$1(Math.floor((1 - e) / 2 * SOLVING_TIER_COUNT), 0, SOLVING_TIER_COUNT - 1), solvingTierDirection = (e) => hashUnit(e * 53 + 21) < .5 ? -1 : 1, solvingTierCenterYUnit = (e) => 1 - (2 * e + 1) / SOLVING_TIER_COUNT, solvingTurnAngle = (e, t) => solvingTierTurn(e, t) * solvingTierDirection(e) * (Math.PI / 2), solving = (e, t) => {
24134
24177
  let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = n.count(SOLVING_BASE_COUNT), s = e - SOLVING_CYCLE * Math.floor(e / SOLVING_CYCLE), c = Array.from({ length: o }, (e, t) => {
24135
24178
  let c = sampleSphere(t, o, a), l = project(rotateX(rotateY(c, solvingTurnAngle(solvingTierOf(a > 0 ? c.y / a : 0), s)), SOLVING_TILT), i, a), u = shadeByDepth(l.depth), f = .55 + .3 * hashUnit(t * 3 + 9);
24136
24179
  return toDot(l, n.radius(SOLVING_BASE_RADIUS_PX), u, f);
24137
24180
  }), l = Array.from({ length: SOLVING_TIER_COUNT * SOLVING_LANDMARKS_PER_TIER }, (e, t) => {
24138
- let o = Math.floor(t / SOLVING_LANDMARKS_PER_TIER), c = t % SOLVING_LANDMARKS_PER_TIER, l = solvingTierCenterYUnit(o), u = l * a, f = Math.sqrt(Math.max(0, 1 - l * l)) * a, p = hashUnit(o * 29 + 5) * TWO_PI$1 + c / SOLVING_LANDMARKS_PER_TIER * TWO_PI$1, h = project(rotateX(rotateY({
24181
+ let o = Math.floor(t / SOLVING_LANDMARKS_PER_TIER), c = t % SOLVING_LANDMARKS_PER_TIER, l = solvingTierCenterYUnit(o), u = l * a, f = Math.sqrt(Math.max(0, 1 - l * l)) * a, p = SOLVING_LANDMARK_PHASE + c / SOLVING_LANDMARKS_PER_TIER * TWO_PI$1, h = project(rotateX(rotateY({
24139
24182
  x: Math.cos(p) * f,
24140
24183
  y: u,
24141
24184
  z: Math.sin(p) * f
24142
24185
  }, solvingTurnAngle(o, s)), SOLVING_TILT), i, a), S = shadeByDepth(h.depth) - SOLVING_LANDMARK_INK_BOOST;
24143
24186
  return toDot(h, n.radius(SOLVING_LANDMARK_RADIUS_PX), S, SOLVING_LANDMARK_ALPHA);
24144
- }), u = n.radius(SOLVING_SEAM_STROKE_RADIUS), f = Array.from({ length: SOLVING_TIER_COUNT - 1 }, (e, t) => {
24145
- let n = solvingSeamYUnit(t), o = n * a, s = Math.sqrt(Math.max(0, 1 - n * n)) * a, c = Array.from({ length: 13 }, (e, t) => {
24146
- let n = t / SOLVING_SEAM_SEGMENTS * TWO_PI$1;
24147
- return project(rotateX({
24148
- x: Math.cos(n) * s,
24149
- y: o,
24150
- z: Math.sin(n) * s
24151
- }, SOLVING_TILT), i, a);
24152
- });
24153
- return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (e, t) => toStroke(c[t], c[t + 1], u, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
24154
- }).flat();
24187
+ });
24155
24188
  return {
24156
24189
  dots: [...c, ...l],
24157
- strokes: f
24190
+ strokes: []
24158
24191
  };
24159
24192
  }, LISTENING_RING_COUNT = {
24160
24193
  64: 5,
24161
24194
  32: 4,
24162
24195
  20: 3
24163
- }, LISTENING_DOTS_PER_RING_BASE = 26, LISTENING_MIN_DOTS_PER_RING = 10, LISTENING_BASE_RADIUS_PX = 1.5, LISTENING_WAVE_SPEED = .6, LISTENING_SWELL = .55, LISTENING_LIFT_INK = .45, LISTENING_SPIN_SPEED = .12, LISTENING_TILT = -.13, LISTENING_Y_SPAN = .82, listeningRing = (e, t, n) => {
24196
+ }, LISTENING_DOTS_PER_RING_BASE = 26, LISTENING_MIN_DOTS_PER_RING = 10, LISTENING_BASE_RADIUS_PX = 1.5, LISTENING_WAVE_SPEED = .6, LISTENING_SWELL = .3, LISTENING_LIFT_INK = .45, LISTENING_SPIN_SPEED = .12, LISTENING_TILT = -.1, LISTENING_Y_SPAN = .86, listeningRing = (e, t, n) => {
24164
24197
  let i = (-1 + (2 * e + 1) / t) * LISTENING_Y_SPAN * n;
24165
24198
  return {
24166
24199
  y: i,
@@ -24169,15 +24202,15 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24169
24202
  wave: (n) => (Math.sin(TWO_PI$1 * (e / t - n * LISTENING_WAVE_SPEED)) + 1) / 2
24170
24203
  };
24171
24204
  }, listening = (e, t) => {
24172
- let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = LISTENING_RING_COUNT[t.size], s = o * LISTENING_DOTS_PER_RING_BASE, c = Math.max(LISTENING_MIN_DOTS_PER_RING, Math.round(n.count(s) / o));
24205
+ let n = modeSizing(t.size, t.density, t.dotScale), i = originOf(t), a = worldRadiusOf(t), o = LISTENING_RING_COUNT[t.size], s = Math.max(LISTENING_MIN_DOTS_PER_RING, n.countAlongPath(LISTENING_DOTS_PER_RING_BASE));
24173
24206
  return {
24174
- dots: Array.from({ length: o }, (t, s) => {
24175
- let l = listeningRing(s, o, a), u = l.wave(e);
24176
- return Array.from({ length: c }, (t, o) => {
24177
- let s = o / c * TWO_PI$1 + l.phase, f = project(rotateY(rotateX({
24178
- x: Math.cos(s) * l.ringRadius,
24207
+ dots: Array.from({ length: o }, (t, c) => {
24208
+ let l = listeningRing(c, o, a), u = l.wave(e);
24209
+ return Array.from({ length: s }, (t, o) => {
24210
+ let c = o / s * TWO_PI$1 + l.phase, f = project(rotateY(rotateX({
24211
+ x: Math.cos(c) * l.ringRadius,
24179
24212
  y: l.y,
24180
- z: Math.sin(s) * l.ringRadius
24213
+ z: Math.sin(c) * l.ringRadius
24181
24214
  }, LISTENING_TILT), e * LISTENING_SPIN_SPEED), i, a), p = shadeByDepth(f.depth) - u * LISTENING_LIFT_INK, h = .6 + u * .3;
24182
24215
  return toDot(f, n.radius(LISTENING_BASE_RADIUS_PX) * (1 + u * LISTENING_SWELL), p, h);
24183
24216
  });
@@ -24191,7 +24224,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24191
24224
  }), squaredDistance = (e, t) => {
24192
24225
  let n = e.x - t.x, i = e.y - t.y, a = e.z - t.z;
24193
24226
  return n * n + i * i + a * a;
24194
- }, NODE_BASE_COUNT = 16, MIN_NODE_COUNT = 6, NEIGHBOR_COUNT = 3, NODE_BASE_RADIUS = 3.4, PULSE_BASE_RADIUS = 2, EDGE_BASE_WIDTH = 1.1, NODE_ROTATE_SPEED = .35, NODE_TILT = .45, EDGE_FLICKER_SPEED = .5, EDGE_MIN_ALPHA = .25, PULSE_SPEED = .6, nearestNeighborEdges = (e, t) => {
24227
+ }, NODE_BASE_COUNT = 10, MIN_NODE_COUNT = 6, NEIGHBOR_COUNT = 2, NODE_BASE_RADIUS = 3.4, PULSE_BASE_RADIUS = 1.4, EDGE_BASE_WIDTH = 1.1, NODE_ROTATE_SPEED = .35, NODE_TILT = .45, EDGE_FLICKER_SPEED = .5, EDGE_MIN_ALPHA = .25, PULSE_SPEED = .6, nearestNeighborEdges = (e, t) => {
24195
24228
  let n = e.map((e, t) => t), i = n.flatMap((i) => n.filter((e) => e !== i).sort((t, n) => squaredDistance(e[i], e[t]) - squaredDistance(e[i], e[n])).slice(0, t).map((e) => i < e ? [i, e] : [e, i]));
24196
24229
  return i.filter(([e, t], n) => i.findIndex(([n, i]) => n === e && i === t) === n);
24197
24230
  }, connecting = (e, t) => {
@@ -24229,69 +24262,97 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24229
24262
  dots: [...l, ...p],
24230
24263
  strokes: f
24231
24264
  };
24232
- }, STRAND_COUNT = 3, STRAND_POINTS_BASE_COUNT = 26, MIN_STRAND_POINTS = 10, STRAND_WRAPS = 1.1, STRAND_ROTATE_SPEED = .3, STRAND_TILT = .5, STRAND_DOT_BASE_RADIUS = 1.6, STRAND_STROKE_BASE_WIDTH = 1.4, STRAND_BRAID_CYCLES = 2.5, STRAND_BRAID_SWING = 1.3, strandBaseAngle = (e) => e / STRAND_COUNT * Math.PI * 2, strandPoint = (e, t, n, i, a) => {
24233
- let o = t / (n - 1), s = o * Math.PI - Math.PI / 2, c = i * Math.sin(s), l = i * Math.cos(s), u = strandBaseAngle(e), f = STRAND_BRAID_SWING * Math.sin(o * STRAND_BRAID_CYCLES * Math.PI * 2 + u), p = o * STRAND_WRAPS * Math.PI * 2 + u + f + a * STRAND_ROTATE_SPEED;
24234
- return rotateX({
24235
- x: l * Math.cos(p),
24236
- y: c,
24237
- z: l * Math.sin(p)
24238
- }, STRAND_TILT);
24265
+ }, STRAND_COUNT = 3, STRAND_POINTS_BASE_COUNT = 40, MIN_STRAND_POINTS = 16, STRAND_INCLINATION = .6, STRAND_WAVE_COUNT = 3, STRAND_WAVE_AMPLITUDE = .12, STRAND_WAVE_SPEED = .9, STRAND_ROTATE_SPEED = .3, STRAND_TILT = 1.1, STRAND_DOT_BASE_RADIUS = 1.6, STRAND_STROKE_BASE_WIDTH = 1.4, STRAND_STROKE_SIZE_FACTOR = {
24266
+ 64: 1,
24267
+ 32: 1.2,
24268
+ 20: 1.5
24269
+ }, STRAND_DOT_SIZE_FACTOR = {
24270
+ 64: 1,
24271
+ 32: .85,
24272
+ 20: .7
24273
+ }, STRAND_DEPTH_INK = .45, STRAND_SHADE_STEP = .1, STRAND_GHOST_BASE_COUNT = 110, STRAND_GHOST_RADIUS_PX = .5, STRAND_GHOST_INK = .3, STRAND_GHOST_ALPHA_BASE = .16, STRAND_GHOST_ALPHA_DEPTH = .22, weavingGhostCount = (e) => Math.max(20, e.count(STRAND_GHOST_BASE_COUNT)), strandBaseAngle = (e) => e / STRAND_COUNT * Math.PI * 2, strandPoint = (e, t, n, i, a) => {
24274
+ let o = t / (n - 1) * Math.PI * 2, s = STRAND_WAVE_AMPLITUDE * Math.sin(o * STRAND_WAVE_COUNT + a * STRAND_WAVE_SPEED + strandBaseAngle(e));
24275
+ return rotateX(rotateY(rotateX({
24276
+ x: i * Math.cos(s) * Math.cos(o),
24277
+ y: i * Math.sin(s),
24278
+ z: i * Math.cos(s) * Math.sin(o)
24279
+ }, STRAND_INCLINATION), strandBaseAngle(e) + a * STRAND_ROTATE_SPEED), STRAND_TILT);
24239
24280
  }, weaving = (e, t) => {
24240
- let { sizing: n, originPx: i, sphereRadius: a } = sphereGeometry(t), o = Math.max(MIN_STRAND_POINTS, n.count(STRAND_POINTS_BASE_COUNT)), s = Array.from({ length: STRAND_COUNT }, (t, n) => Array.from({ length: o }, (t, s) => project(strandPoint(n, s, o, a, e), i, a)));
24281
+ let { sizing: n, originPx: i, sphereRadius: a } = sphereGeometry(t), o = Math.max(MIN_STRAND_POINTS, n.countAlongPath(STRAND_POINTS_BASE_COUNT)), s = Array.from({ length: STRAND_COUNT }, (t, n) => Array.from({ length: o }, (t, s) => project(strandPoint(n, s, o, a, e), i, a))), c = s.flatMap((e, i) => e.map((e) => ({
24282
+ x: e.x,
24283
+ y: e.y,
24284
+ depth: e.depth,
24285
+ radius: n.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[t.size],
24286
+ ink: clamp01$1((1 - e.depth) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
24287
+ alpha: .55 + e.depth * .4
24288
+ }))), l = s.flatMap((e, i) => e.slice(1).map((a, o) => {
24289
+ let s = e[o], c = (s.depth + a.depth) / 2;
24290
+ return {
24291
+ x1: s.x,
24292
+ y1: s.y,
24293
+ x2: a.x,
24294
+ y2: a.y,
24295
+ depth: c,
24296
+ width: n.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[t.size],
24297
+ ink: clamp01$1((1 - c) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
24298
+ alpha: .6 + c * .35
24299
+ };
24300
+ })), u = weavingGhostCount(n), f = Array.from({ length: u }, (t, o) => {
24301
+ let s = project(rotateX(rotateY(sampleSphere(o, u, a), e * STRAND_ROTATE_SPEED), STRAND_TILT), i, a);
24302
+ return {
24303
+ x: s.x,
24304
+ y: s.y,
24305
+ depth: s.depth,
24306
+ radius: n.radius(STRAND_GHOST_RADIUS_PX),
24307
+ ink: STRAND_GHOST_INK,
24308
+ alpha: clamp01$1(STRAND_GHOST_ALPHA_BASE + STRAND_GHOST_ALPHA_DEPTH * s.depth)
24309
+ };
24310
+ });
24241
24311
  return {
24242
- dots: s.flatMap((e, t) => e.map((e) => ({
24243
- x: e.x,
24244
- y: e.y,
24245
- depth: e.depth,
24246
- radius: n.radius(STRAND_DOT_BASE_RADIUS),
24247
- ink: clamp01$1(1 - e.depth * .8 + t * .08),
24248
- alpha: .55 + e.depth * .4
24249
- }))),
24250
- strokes: s.flatMap((e, t) => e.slice(1).map((i, a) => {
24251
- let o = e[a], s = (o.depth + i.depth) / 2;
24252
- return {
24253
- x1: o.x,
24254
- y1: o.y,
24255
- x2: i.x,
24256
- y2: i.y,
24257
- depth: s,
24258
- width: n.radius(STRAND_STROKE_BASE_WIDTH),
24259
- ink: clamp01$1(1 - s * .75 + t * .06),
24260
- alpha: .6 + s * .35
24261
- };
24262
- }))
24312
+ dots: [...c, ...f],
24313
+ strokes: l
24263
24314
  };
24264
- }, TWO_PI = Math.PI * 2, FORM_RADIUS_RATIO = .4, clamp01 = (e) => Math.max(0, Math.min(1, e)), depthToInk = (e) => clamp01(1 - e), depthToAlpha = (e) => clamp01(.5 + e * .5), depthToRadiusFactor = (e) => .7 + e * .3, dotFrom = (e, t, n, i) => ({
24315
+ }, TWO_PI = Math.PI * 2, FORM_RADIUS_RATIO = .4, clamp01 = (e) => Math.max(0, Math.min(1, e)), FAR_INK = .6, depthToInk = (e) => clamp01((1 - e) * FAR_INK), depthToAlpha = (e) => clamp01(.5 + e * .5), depthToRadiusFactor = (e) => .7 + e * .3, dotFrom = (e, t, n, i) => ({
24265
24316
  x: e.x,
24266
24317
  y: e.y,
24267
24318
  depth: e.depth,
24268
24319
  radius: n.radius(t) * depthToRadiusFactor(e.depth) * i,
24269
24320
  ink: depthToInk(e.depth),
24270
24321
  alpha: depthToAlpha(e.depth)
24271
- }), place = (e, t, n, i, a, o, s, c) => dotFrom(project(rotateX(rotateY(e, t), n), i, a), o, s, c), EMPTY_STROKES = [], RIBBON_BASE_COUNT = 260, RIBBON_BASE_DOT_RADIUS = 2.2, RIBBON_THREADS = {
24272
- 64: 7,
24273
- 32: 5,
24274
- 20: 4
24275
- }, RIBBON_HALF_WIDTH = .4, RIBBON_AXIS_TILT = .68, RIBBON_RIPPLE_WAVES = 3, RIBBON_RIPPLE_SPEED = 1.15, RIBBON_RIPPLE_AMPLITUDE = .5, RIBBON_THREAD_JITTER = .1, RIBBON_SPIN_SPEED = .48, RIBBON_CAMERA_TILT = .45, RIBBON_EDGE_ALPHA_FLOOR = .5, RIBBON_EDGE_INK_LIFT = .22, composing = (e, t) => {
24276
- let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = RIBBON_THREADS[t.size], s = Math.max(1, Math.ceil(n.count(RIBBON_BASE_COUNT) / o)), c = o * s, l = [];
24322
+ }), place = (e, t, n, i, a, o, s, c) => dotFrom(project(rotateX(rotateY(e, t), n), i, a), o, s, c), EMPTY_STROKES = [], RIBBON_PER_THREAD_BASE = 36, RIBBON_BASE_DOT_RADIUS = 1.2, RIBBON_THREADS = {
24323
+ 64: 5,
24324
+ 32: 4,
24325
+ 20: 3
24326
+ }, RIBBON_HALF_WIDTH = .3, RIBBON_AXIS_TILT = .68, RIBBON_RIPPLE_WAVES = 3, RIBBON_RIPPLE_SPEED = 1.15, RIBBON_RIPPLE_AMPLITUDE = .18, RIBBON_THREAD_JITTER = .1, RIBBON_SPIN_SPEED = .48, RIBBON_CAMERA_TILT = .45, RIBBON_EDGE_ALPHA_FLOOR = .5, RIBBON_EDGE_INK_LIFT = .22, RIBBON_GHOST_BASE_COUNT = 110, RIBBON_GHOST_RADIUS_PX = .5, RIBBON_GHOST_INK = .3, RIBBON_GHOST_ALPHA_BASE = .16, RIBBON_GHOST_ALPHA_DEPTH = .22, composing = (e, t) => {
24327
+ let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = RIBBON_THREADS[t.size], s = n.countAlongPath(RIBBON_PER_THREAD_BASE), c = o * s, l = Math.max(20, n.count(RIBBON_GHOST_BASE_COUNT)), u = Array.from({ length: l }, (e, t) => {
24328
+ let o = project(rotateX(sampleSphere(t, l, i), RIBBON_CAMERA_TILT), a, i);
24329
+ return {
24330
+ x: o.x,
24331
+ y: o.y,
24332
+ depth: o.depth,
24333
+ radius: n.radius(RIBBON_GHOST_RADIUS_PX),
24334
+ ink: RIBBON_GHOST_INK,
24335
+ alpha: clamp01(RIBBON_GHOST_ALPHA_BASE + RIBBON_GHOST_ALPHA_DEPTH * o.depth)
24336
+ };
24337
+ });
24277
24338
  for (let t = 0; t < c; t += 1) {
24278
- let c = t % o, u = Math.floor(t / o), f = o > 1 ? c / (o - 1) * 2 - 1 : 0, p = u / s * TWO_PI, h = Math.sin(p * RIBBON_RIPPLE_WAVES + e * RIBBON_RIPPLE_SPEED) * RIBBON_RIPPLE_AMPLITUDE, S = f * RIBBON_HALF_WIDTH + h, C = rotateZ({
24339
+ let c = t % o, l = Math.floor(t / o), f = o > 1 ? c / (o - 1) * 2 - 1 : 0, p = l / s * TWO_PI, h = Math.sin(p * RIBBON_RIPPLE_WAVES + e * RIBBON_RIPPLE_SPEED) * RIBBON_RIPPLE_AMPLITUDE, S = f * RIBBON_HALF_WIDTH + h, C = rotateZ({
24279
24340
  x: Math.cos(S) * Math.cos(p) * i,
24280
24341
  y: Math.sin(S) * i,
24281
24342
  z: Math.cos(S) * Math.sin(p) * i
24282
24343
  }, RIBBON_AXIS_TILT), k = 1 - RIBBON_THREAD_JITTER / 2 + hashUnit(t * 7 + 1) * RIBBON_THREAD_JITTER, R = place(C, e * RIBBON_SPIN_SPEED, RIBBON_CAMERA_TILT, a, i, RIBBON_BASE_DOT_RADIUS, n, k), te = Math.abs(f);
24283
- l.push({
24344
+ u.push({
24284
24345
  ...R,
24285
24346
  ink: clamp01(R.ink + te * RIBBON_EDGE_INK_LIFT),
24286
24347
  alpha: clamp01(R.alpha * (1 - te * (1 - RIBBON_EDGE_ALPHA_FLOOR)))
24287
24348
  });
24288
24349
  }
24289
24350
  return {
24290
- dots: l,
24351
+ dots: u,
24291
24352
  strokes: EMPTY_STROKES
24292
24353
  };
24293
- }, BREATHING_BASE_COUNT = 210, BREATHING_BASE_DOT_RADIUS = 1.7, BREATHING_WORLD_RADIUS_RATIO = .34, BREATHING_AMPLITUDE = .09, BREATHING_SPEED = .55, BREATHING_SHAPE_HARMONIC = 5, BREATHING_SHAPE_SPEED = .22, BREATHING_SHAPE_AMPLITUDE = .035, BREATHING_SPIN_SPEED = .12, BREATHING_TILT = .62, breathing = (e, t) => {
24294
- let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * BREATHING_WORLD_RADIUS_RATIO, a = t.size / 2, o = n.count(BREATHING_BASE_COUNT), s = Math.sin(e * BREATHING_SPEED), c = [];
24354
+ }, BREATHING_BASE_COUNT = 32, BREATHING_BASE_DOT_RADIUS = 1.7, BREATHING_WORLD_RADIUS_RATIO = .34, BREATHING_AMPLITUDE = .09, BREATHING_SPEED = .55, BREATHING_SHAPE_HARMONIC = 5, BREATHING_SHAPE_SPEED = .22, BREATHING_SHAPE_AMPLITUDE = .035, BREATHING_SPIN_SPEED = .12, BREATHING_TILT = .62, breathing = (e, t) => {
24355
+ let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * BREATHING_WORLD_RADIUS_RATIO, a = t.size / 2, o = n.countAlongPath(BREATHING_BASE_COUNT), s = Math.sin(e * BREATHING_SPEED), c = [];
24295
24356
  for (let t = 0; t < o; t += 1) {
24296
24357
  let l = t / o * TWO_PI, u = Math.sin(l * BREATHING_SHAPE_HARMONIC + e * BREATHING_SHAPE_SPEED) * BREATHING_SHAPE_AMPLITUDE, f = 1 + s * BREATHING_AMPLITUDE + u, p = {
24297
24358
  x: Math.cos(l) * f * i,
@@ -24304,11 +24365,11 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24304
24365
  dots: c,
24305
24366
  strokes: EMPTY_STROKES
24306
24367
  };
24307
- }, SHAPING_BASE_COUNT = 210, SHAPING_BASE_DOT_RADIUS = 1.7, SHAPING_CYCLE_SECONDS = 12, SHAPING_SPIN_SPEED = 2 * Math.PI / SHAPING_CYCLE_SECONDS, SHAPING_TILT = .6, polygonRadius = (e, t) => {
24368
+ }, SHAPING_BASE_COUNT = 36, SHAPING_BASE_DOT_RADIUS = 1.7, SHAPING_CYCLE_SECONDS = 12, SHAPING_SPIN_SPEED = 2 * Math.PI / SHAPING_CYCLE_SECONDS, SHAPING_TILT = .6, polygonRadius = (e, t) => {
24308
24369
  let n = TWO_PI / t, i = n / 2, a = (e % n + n) % n;
24309
24370
  return Math.cos(i) / Math.cos(a - i);
24310
24371
  }, lerp = (e, t, n) => e + (t - e) * n, shaping = (e, t) => {
24311
- let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = n.count(SHAPING_BASE_COUNT), s = (e / SHAPING_CYCLE_SECONDS % 1 + 1) % 1 * 3, c = Math.min(2, Math.floor(s)), l = easeInOutCubic(s - c), u = [];
24372
+ let n = modeSizing(t.size, t.density, t.dotScale), i = t.size * FORM_RADIUS_RATIO, a = t.size / 2, o = n.countAlongPath(SHAPING_BASE_COUNT), s = (e / SHAPING_CYCLE_SECONDS % 1 + 1) % 1 * 3, c = Math.min(2, Math.floor(s)), l = easeInOutCubic(s - c), u = [];
24312
24373
  for (let t = 0; t < o; t += 1) {
24313
24374
  let s = t / o * TWO_PI, f = polygonRadius(s, 3), p = polygonRadius(s, 4), h;
24314
24375
  h = c === 0 ? lerp(1, f, l) : c === 1 ? lerp(f, p, l) : lerp(p, 1, l);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "4.33.1",
3
+ "version": "4.33.3",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",