@juspay/svelte-ui-components 4.33.0 → 4.33.2

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.
@@ -61,20 +61,19 @@
61
61
  /*
62
62
  * `reducedMotion`/`onScreen`/`tabVisible` feed `shouldRun` below (a
63
63
  * `$derived`), so they must be real `$state` for it to react when they
64
- * change. `dark` is `$state` for the same reason `draw` below reads it
65
- * synchronously inside the main `$effect` -- writing it must retrigger
66
- * that effect, otherwise a theme flip while the loop is stopped (paused,
67
- * or reduced motion, which paints exactly once) would leave the ink ramp
68
- * on the wrong theme for as long as the orb stays still. `pointer` is a
69
- * plain `let`, not `$state`: a still orb repaints explicitly on pointer
70
- * events (see `repaintIfStill`), and a running orb's loop already reads
71
- * the latest pointer every tick, so retriggering the whole effect on every
72
- * pointer move would only tear down and restart the loop for nothing.
64
+ * change. `pointer` is a plain `let`, not `$state`: a still orb repaints
65
+ * explicitly on pointer events (see `repaintIfStill`), and a running orb's
66
+ * loop already reads the latest pointer every tick, so retriggering the
67
+ * whole effect on every pointer move would only tear down and restart the
68
+ * loop for nothing. A theme change works the same way: a still orb
69
+ * repaints explicitly (see `repaintIfColorChanged`) once its *resolved*
70
+ * colour actually moves, rather than through a theme-flavoured state write
71
+ * -- the paint never needs to know the theme itself, only the colour it
72
+ * comes out to.
73
73
  */
74
74
  let reducedMotion = $state(prefersReducedMotion());
75
75
  let onScreen = $state(true);
76
76
  let tabVisible = $state(true);
77
- let dark = $state(false);
78
77
  let announcedFirstFrame = false;
79
78
  let resolvedColorInput = '';
80
79
  let resolvedTint: Rgb = FALLBACK_TINT;
@@ -150,6 +149,22 @@
150
149
  draw(heldInstant());
151
150
  };
152
151
 
152
+ /** A still orb repaints only when its resolved colour actually changed --
153
+ * whatever mechanism the host used to flip theme (an attribute on `<html>`
154
+ * or `<body>`, or a CSS transition landing). An explicit `color` prop
155
+ * never depends on the theme, and a running orb already re-reads the
156
+ * colour every frame via `draw`, so both bail out here. `readColorInput`'s
157
+ * cached key is what `resolveTint` itself compares against, so this check
158
+ * and the eventual repaint can never disagree about whether anything moved. */
159
+ const repaintIfColorChanged = (): void => {
160
+ if (shouldRun || color || canvas === null) {
161
+ return;
162
+ }
163
+ if (readColorInput(canvas).key !== resolvedColorInput) {
164
+ draw(heldInstant());
165
+ }
166
+ };
167
+
153
168
  const trackPointer = (event: PointerEvent): void => {
154
169
  if (canvas === null || !gravity) {
155
170
  return;
@@ -196,7 +211,7 @@
196
211
  const frame = MODES[safeState](instant, { size: safeSize, density, dotScale });
197
212
  const gOpts = gravityOptions;
198
213
  const painted = gOpts === null ? frame : attract(frame, pointer, gOpts);
199
- paintFrame(context, painted, { dark, tint: resolveTint(context) });
214
+ paintFrame(context, painted, { tint: resolveTint(context) });
200
215
 
201
216
  if (!announcedFirstFrame) {
202
217
  announcedFirstFrame = true;
@@ -206,7 +221,7 @@
206
221
 
207
222
  /*
208
223
  * Re-runs whenever `shouldRun` or anything the leading `draw` reads changes
209
- * (`safeState`, `safeSize`, `density`, `dotScale`, `gravityOptions`, `dark`,
224
+ * (`safeState`, `safeSize`, `density`, `dotScale`, `gravityOptions`,
210
225
  * `color`, `reducedMotion`, ...) -- Svelte tracks every reactive read inside
211
226
  * this body, including transitively through that synchronous `draw` call,
212
227
  * so a prop change tears down the previous rAF (the cleanup below) and
@@ -266,23 +281,18 @@
266
281
  document.addEventListener('visibilitychange', onVisibilityChange);
267
282
 
268
283
  /*
269
- * This repo's `ThemeSwitcher` stamps `data-theme="dark"` on
270
- * `document.documentElement` -- one attribute on one element -- so a
271
- * `MutationObserver` there alone catches every theme change. It exists
272
- * only to keep `paintFrame`'s `dark` argument current: the tint colour
273
- * itself comes from CSS, resolved in `resolveTint` above.
284
+ * The orb never asks how a host marks its theme -- `theme`, `data-theme`,
285
+ * a class, an inline `style`, `color-scheme`, on `<html>` or `<body>`, or
286
+ * anything else -- it only cares whether the colour it actually resolves
287
+ * to (see `resolveTint`) moved. So this observes every attribute change
288
+ * on both elements, unfiltered, and lets `repaintIfColorChanged` decide
289
+ * whether anything worth a repaint happened.
274
290
  */
275
- const readTheme = (): void => {
276
- dark = document.documentElement.getAttribute('data-theme') === 'dark';
277
- };
278
- readTheme();
279
291
  let themeObserver: MutationObserver | null = null;
280
292
  if (typeof MutationObserver !== 'undefined') {
281
- themeObserver = new MutationObserver(readTheme);
282
- themeObserver.observe(document.documentElement, {
283
- attributes: true,
284
- attributeFilter: ['data-theme']
285
- });
293
+ themeObserver = new MutationObserver(repaintIfColorChanged);
294
+ themeObserver.observe(document.documentElement, { attributes: true });
295
+ themeObserver.observe(document.body, { attributes: true });
286
296
  }
287
297
 
288
298
  const onMotionChange = (event: MediaQueryListEvent): void => {
@@ -294,24 +304,26 @@
294
304
  motionQuery.addEventListener('change', onMotionChange);
295
305
  }
296
306
 
307
+ // A host that themes only through `@media (prefers-color-scheme)` changes
308
+ // the inherited colour when the OS flips theme, with no attribute change
309
+ // and no transition for the checks around it to catch.
310
+ let colorSchemeQuery: MediaQueryList | null = null;
311
+ if (typeof window.matchMedia === 'function') {
312
+ colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
313
+ colorSchemeQuery.addEventListener('change', repaintIfColorChanged);
314
+ }
315
+
297
316
  /*
298
- * A still orb only repaints on its own when `dark` flips. A host that
299
- * transitions its `color` (this repo's own layout fades `body` across a
300
- * theme switch) leaves a just-frozen frame at the outgoing colour, with
301
- * nothing else to redraw it. Re-read once the transition lands and
302
- * repaint the same frozen instant if the resolved colour actually moved.
303
- * A running orb re-reads every frame already and needs none of this.
317
+ * A host that transitions its `color` (this repo's own layout fades
318
+ * `body` across a theme switch) leaves a just-frozen frame at the
319
+ * outgoing colour, with nothing else to redraw it: no attribute changed,
320
+ * so the observer above never fires. Re-check once the transition lands.
304
321
  */
305
322
  const onColorTransitionEnd = (event: TransitionEvent): void => {
306
- if (shouldRun || color || canvas === null) {
307
- return;
308
- }
309
323
  if (event.propertyName !== 'color' && event.propertyName !== '--sui-thinking-orb-color') {
310
324
  return;
311
325
  }
312
- if (readColorInput(canvas).key !== resolvedColorInput) {
313
- draw(heldInstant());
314
- }
326
+ repaintIfColorChanged();
315
327
  };
316
328
  document.addEventListener('transitionend', onColorTransitionEnd);
317
329
 
@@ -321,6 +333,7 @@
321
333
  document.removeEventListener('transitionend', onColorTransitionEnd);
322
334
  themeObserver?.disconnect();
323
335
  motionQuery?.removeEventListener('change', onMotionChange);
336
+ colorSchemeQuery?.removeEventListener('change', repaintIfColorChanged);
324
337
  };
325
338
  });
326
339
  </script>
@@ -16,8 +16,11 @@ 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,13 @@ 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;
46
+ /** Dots along each thread at 64px; `countAlongPath` keeps their spacing as the canvas shrinks. */
47
+ const RIBBON_PER_THREAD_BASE = 36;
44
48
  const RIBBON_BASE_DOT_RADIUS = 2.2;
45
- const RIBBON_THREADS = { 64: 7, 32: 5, 20: 4 };
46
- const RIBBON_HALF_WIDTH = 0.4;
49
+ /** Fewer, narrower threads than a full band: at 7 threads across 0.4 rad the 64px ribbon covered
50
+ * most of the sphere and read as a filled ball rather than a strip of cloth. */
51
+ const RIBBON_THREADS = { 64: 5, 32: 4, 20: 3 };
52
+ const RIBBON_HALF_WIDTH = 0.3;
47
53
  /** The ribbon's own great circle sits tilted off the spin axis by this much,
48
54
  * so spinning genuinely sweeps its width through the frame -- a band that
49
55
  * happened to lie flat on the equator would look identical at every spin
@@ -51,7 +57,9 @@ const RIBBON_HALF_WIDTH = 0.4;
51
57
  const RIBBON_AXIS_TILT = 0.68;
52
58
  const RIBBON_RIPPLE_WAVES = 3;
53
59
  const RIBBON_RIPPLE_SPEED = 1.15;
54
- const RIBBON_RIPPLE_AMPLITUDE = 0.5;
60
+ /** Kept under RIBBON_HALF_WIDTH: a ripple wider than the band itself swings the ribbon toward
61
+ * the pole and folds it over itself, which reads as a knot rather than cloth. */
62
+ const RIBBON_RIPPLE_AMPLITUDE = 0.18;
55
63
  const RIBBON_THREAD_JITTER = 0.1;
56
64
  const RIBBON_SPIN_SPEED = 0.48;
57
65
  const RIBBON_CAMERA_TILT = 0.45;
@@ -70,7 +78,7 @@ export const composing = (t, ctx) => {
70
78
  const worldRadius = ctx.size * FORM_RADIUS_RATIO;
71
79
  const originPx = ctx.size / 2;
72
80
  const threads = RIBBON_THREADS[ctx.size];
73
- const perThread = Math.max(1, Math.ceil(sizing.count(RIBBON_BASE_COUNT) / threads));
81
+ const perThread = sizing.countAlongPath(RIBBON_PER_THREAD_BASE);
74
82
  const total = threads * perThread;
75
83
  const dots = [];
76
84
  for (let index = 0; index < total; index += 1) {
@@ -102,7 +110,9 @@ export const composing = (t, ctx) => {
102
110
  return { dots, strokes: EMPTY_STROKES };
103
111
  };
104
112
  // --- breathing -----------------------------------------------------------
105
- const BREATHING_BASE_COUNT = 210;
113
+ /** Dots around the ring at 64px. A ring's length grows with `size`, not its area, so this goes
114
+ * through `countAlongPath`: 210 area-scaled dots sat 0.65px apart at 64px and fused into a line. */
115
+ const BREATHING_BASE_COUNT = 32;
106
116
  const BREATHING_BASE_DOT_RADIUS = 1.7;
107
117
  /** Sits below composing/shaping's ~0.4: at the peak of its breath this ring
108
118
  * already swells by its amplitude plus its shape wobble below, so a resting
@@ -125,7 +135,7 @@ export const breathing = (t, ctx) => {
125
135
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
126
136
  const worldRadius = ctx.size * BREATHING_WORLD_RADIUS_RATIO;
127
137
  const originPx = ctx.size / 2;
128
- const count = sizing.count(BREATHING_BASE_COUNT);
138
+ const count = sizing.countAlongPath(BREATHING_BASE_COUNT);
129
139
  const breathe = Math.sin(t * BREATHING_SPEED);
130
140
  const dots = [];
131
141
  for (let index = 0; index < count; index += 1) {
@@ -144,7 +154,8 @@ export const breathing = (t, ctx) => {
144
154
  return { dots, strokes: EMPTY_STROKES };
145
155
  };
146
156
  // --- shaping ---------------------------------------------------------------
147
- const SHAPING_BASE_COUNT = 210;
157
+ /** Dots around the outline at 64px, through `countAlongPath` for the same reason as breathing. */
158
+ const SHAPING_BASE_COUNT = 36;
148
159
  const SHAPING_BASE_DOT_RADIUS = 1.7;
149
160
  const SHAPING_CYCLE_SECONDS = 12;
150
161
  // One full turn per morph cycle, so the shape and its orientation both land
@@ -169,7 +180,7 @@ export const shaping = (t, ctx) => {
169
180
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
170
181
  const worldRadius = ctx.size * FORM_RADIUS_RATIO;
171
182
  const originPx = ctx.size / 2;
172
- const count = sizing.count(SHAPING_BASE_COUNT);
183
+ const count = sizing.countAlongPath(SHAPING_BASE_COUNT);
173
184
  const cyclePos = (((t / SHAPING_CYCLE_SECONDS) % 1) + 1) % 1;
174
185
  const phaseFloat = cyclePos * 3;
175
186
  const phaseIndex = Math.min(2, Math.floor(phaseFloat));
@@ -8,5 +8,5 @@
8
8
  import type { ModeFn } from '../types';
9
9
  /** A few nodes on a sphere; lines to near neighbours carry small travelling pulses. */
10
10
  export declare const connecting: ModeFn;
11
- /** Three strands wrapped around a sphere, crossing as they spiral from top to bottom. */
11
+ /** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
12
12
  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,75 @@ 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;
133
147
  const strandBaseAngle = (strand) => (strand / STRAND_COUNT) * Math.PI * 2;
134
148
  /**
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.
149
+ * The `pointIndex`-th of `pointsPerStrand` points around closed `strand`: a
150
+ * great circle (the first and last points meet) with a small wave in latitude
151
+ * travelling along it, inclined by `STRAND_INCLINATION`, turned to its own
152
+ * 120-degree slot and spun with the rest. Latitude and longitude keep every
153
+ * point exactly on the sphere, whatever the wave does.
141
154
  */
142
155
  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);
156
+ const u = (pointIndex / (pointsPerStrand - 1)) * Math.PI * 2;
157
+ const latitude = STRAND_WAVE_AMPLITUDE *
158
+ Math.sin(u * STRAND_WAVE_COUNT + t * STRAND_WAVE_SPEED + strandBaseAngle(strand));
159
+ const onCircle = {
160
+ x: sphereRadius * Math.cos(latitude) * Math.cos(u),
161
+ y: sphereRadius * Math.sin(latitude),
162
+ z: sphereRadius * Math.cos(latitude) * Math.sin(u)
163
+ };
164
+ const inclined = rotateX(onCircle, STRAND_INCLINATION);
165
+ const turned = rotateY(inclined, strandBaseAngle(strand) + t * STRAND_ROTATE_SPEED);
166
+ return rotateX(turned, STRAND_TILT);
152
167
  };
153
- /** Three strands wrapped around a sphere, crossing as they spiral from top to bottom. */
168
+ /** Three closed strands wrapped around a sphere, crossing over and under each other as they turn. */
154
169
  export const weaving = (t, ctx) => {
155
170
  const { sizing, originPx, sphereRadius } = sphereGeometry(ctx);
156
- const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.count(STRAND_POINTS_BASE_COUNT));
171
+ const pointsPerStrand = Math.max(MIN_STRAND_POINTS, sizing.countAlongPath(STRAND_POINTS_BASE_COUNT));
157
172
  const strands = Array.from({ length: STRAND_COUNT }, (_, strand) => Array.from({ length: pointsPerStrand }, (_, pointIndex) => project(strandPoint(strand, pointIndex, pointsPerStrand, sphereRadius, t), originPx, sphereRadius)));
158
173
  const dots = strands.flatMap((points, strand) => points.map((p) => ({
159
174
  x: p.x,
160
175
  y: p.y,
161
176
  depth: p.depth,
162
- radius: sizing.radius(STRAND_DOT_BASE_RADIUS),
163
- ink: clamp01(1 - p.depth * 0.8 + strand * 0.08),
177
+ radius: sizing.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[ctx.size],
178
+ ink: clamp01((1 - p.depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
164
179
  alpha: 0.55 + p.depth * 0.4
165
180
  })));
166
181
  const strokes = strands.flatMap((points, strand) => points.slice(1).map((p, i) => {
@@ -172,8 +187,8 @@ export const weaving = (t, ctx) => {
172
187
  x2: p.x,
173
188
  y2: p.y,
174
189
  depth,
175
- width: sizing.radius(STRAND_STROKE_BASE_WIDTH),
176
- ink: clamp01(1 - depth * 0.75 + strand * 0.06),
190
+ width: sizing.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[ctx.size],
191
+ ink: clamp01((1 - depth) * STRAND_DEPTH_INK + strand * STRAND_SHADE_STEP),
177
192
  alpha: 0.6 + depth * 0.35
178
193
  };
179
194
  }));
@@ -46,21 +46,17 @@ const toStroke = (from, to, width, ink, alpha) => ({
46
46
  * as noise once density thins the count out at 32/20px, while a handful of
47
47
  * particles held close together always reads as motion along a path. */
48
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 };
49
+ /** Keeps at least this many short arcs on screen at 64px (32/20px use WORKING_SMALL_LAYOUT). The
50
+ * area scaler alone already clears it at the default density; it only bites when density is low. */
51
+ const WORKING_MIN_TRAINS_64 = 3;
58
52
  const WORKING_TRAIN_LENGTH = 5;
59
53
  /** How far apart, in radians, a train's particles trail behind its lead --
60
54
  * wide enough to read as a short curved arc, short enough to stay a cluster
61
55
  * rather than smearing back into the full-orbit scatter this replaces. */
62
56
  const WORKING_TRAIN_ARC_SPAN = 0.5;
63
- const WORKING_BASE_RADIUS_PX = 1.6;
57
+ /** At 1.6, with a +/-25% jitter, 20px dots came out 0.6-1px across and the arcs blurred into a
58
+ * smudge; this and the narrower jitter below keep a train a visible stroke at every size. */
59
+ const WORKING_BASE_RADIUS_PX = 2.2;
64
60
  const WORKING_ORBIT_COUNT = 6;
65
61
  const WORKING_ANGULAR_SPEED = 1.1;
66
62
  /** How far an orbit's `weight` can push its dots' ink away from the shared
@@ -83,19 +79,58 @@ const workingOrbit = (orbit) => {
83
79
  weight: 0.35 + 0.65 * hashUnit(seed + 15)
84
80
  };
85
81
  };
82
+ /** At 32/20px six randomly tilted orbits of short arcs collapse into one busy cluster: each arc is
83
+ * only a few px long and the orbits overlap. These sizes instead spread a few orbits evenly around
84
+ * the view axis (like an atom symbol) and give each train a long arc, so every train draws a
85
+ * visible curve and the separate orbits stay readable. */
86
+ const WORKING_SMALL_LAYOUT = {
87
+ 32: { orbits: 4, trainLength: 6, arcSpan: 1 },
88
+ 20: { orbits: 3, trainLength: 7, arcSpan: 1.25 }
89
+ };
90
+ /** How far each small-size orbit tips out of edge-on. Its projection is an ellipse sin(tilt) as
91
+ * tall as it is wide; near 1 rad every orbit projects to nearly the same circle and rolling it
92
+ * about the view axis changes nothing, so the orbits merged into one ring. */
93
+ const WORKING_SMALL_ORBIT_TILT = 0.5;
94
+ /** At 32/20px each orbit's path is drawn as a faint thin ellipse under its trains: a 20px train
95
+ * alone is too short to show which ellipse it is travelling, so without the path the trains read
96
+ * as a pinwheel rather than as particles circling several tilted orbits. */
97
+ const WORKING_PATH_SEGMENTS = 24;
98
+ const WORKING_PATH_WIDTH_PX = 0.8;
99
+ const WORKING_PATH_INK = 0.5;
100
+ const WORKING_PATH_ALPHA = 0.45;
101
+ const WORKING_SMALL_ORBIT_RADIUS = 0.85;
102
+ const WORKING_SMALL_SPEEDS = [0.8, 1.15, 1.45, 1];
103
+ /** One orbit of the small-size layout: shared tilt, rolled evenly around the view axis. */
104
+ const workingSmallOrbit = (orbit, orbits) => ({
105
+ ...workingOrbit(orbit),
106
+ tilt: WORKING_SMALL_ORBIT_TILT,
107
+ roll: (orbit / orbits) * Math.PI,
108
+ orbitRadiusFactor: WORKING_SMALL_ORBIT_RADIUS,
109
+ speedFactor: WORKING_SMALL_SPEEDS[orbit % WORKING_SMALL_SPEEDS.length]
110
+ });
86
111
  export const working = (t, ctx) => {
87
112
  const sizing = modeSizing(ctx.size, ctx.density, ctx.dotScale);
88
113
  const origin = originOf(ctx);
89
114
  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);
115
+ const small = WORKING_SMALL_LAYOUT[ctx.size];
116
+ // Small sizes run two opposite trains per orbit (scaled by density); 64px keeps its own swarm.
117
+ const lapsPerOrbit = Math.max(1, Math.round(2 * ctx.density));
118
+ const trainCount = small
119
+ ? small.orbits * lapsPerOrbit
120
+ : Math.max(WORKING_MIN_TRAINS_64, sizing.count(WORKING_TRAIN_BASE_COUNT));
121
+ const trainLength = small ? small.trainLength : WORKING_TRAIN_LENGTH;
122
+ const arcStep = (small ? small.arcSpan : WORKING_TRAIN_ARC_SPAN) / (trainLength - 1);
92
123
  const dots = [];
93
124
  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;
125
+ const orbit = small
126
+ ? workingSmallOrbit(train % small.orbits, small.orbits)
127
+ : workingOrbit(train % WORKING_ORBIT_COUNT);
128
+ const leadPhase = small
129
+ ? (Math.floor(train / small.orbits) / lapsPerOrbit) * TWO_PI + (train % small.orbits) * 0.9
130
+ : hashUnit(train * 5 + 1) * TWO_PI;
96
131
  const leadAngle = leadPhase + t * WORKING_ANGULAR_SPEED * orbit.speedFactor;
97
132
  const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
98
- for (let slot = 0; slot < WORKING_TRAIN_LENGTH; slot += 1) {
133
+ for (let slot = 0; slot < trainLength; slot += 1) {
99
134
  const angle = leadAngle - slot * arcStep;
100
135
  const planar = {
101
136
  x: Math.cos(angle) * orbitRadius,
@@ -106,17 +141,35 @@ export const working = (t, ctx) => {
106
141
  const point = rotateZ(tilted, orbit.roll);
107
142
  const projected = project(point, origin, worldRadius);
108
143
  const seed = train * 13 + slot * 7 + 2;
109
- const trail = 1 - slot / (WORKING_TRAIN_LENGTH - 1);
144
+ const trail = 1 - slot / (trainLength - 1);
110
145
  const ink = shadeByDepth(projected.depth) +
111
146
  (hashUnit(seed) - 0.5) * 0.08 -
112
147
  (orbit.weight - 0.5) * WORKING_ORBIT_INK_SPREAD;
113
148
  const alpha = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * trail) *
114
149
  (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));
150
+ const radius = sizing.radius(WORKING_BASE_RADIUS_PX) * (0.9 + 0.25 * hashUnit(seed + 3));
116
151
  dots.push(toDot(projected, radius, ink, alpha));
117
152
  }
118
153
  }
119
- return { dots, strokes: [] };
154
+ const paths = small
155
+ ? Array.from({ length: small.orbits }, (_unused, orbitIndex) => {
156
+ const orbit = workingSmallOrbit(orbitIndex, small.orbits);
157
+ const orbitRadius = worldRadius * orbit.orbitRadiusFactor;
158
+ const points = Array.from({ length: WORKING_PATH_SEGMENTS + 1 }, (_unusedPoint, j) => {
159
+ const angle = (j / WORKING_PATH_SEGMENTS) * TWO_PI;
160
+ const planar = {
161
+ x: Math.cos(angle) * orbitRadius,
162
+ y: 0,
163
+ z: Math.sin(angle) * orbitRadius
164
+ };
165
+ return project(rotateZ(rotateX(planar, orbit.tilt), orbit.roll), origin, worldRadius);
166
+ });
167
+ return points
168
+ .slice(1)
169
+ .map((to, j) => toStroke(points[j], to, sizing.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
170
+ }).flat()
171
+ : [];
172
+ return { dots, strokes: paths };
120
173
  };
121
174
  // ---------------------------------------------------------------------------
122
175
  // searching -- a dotted globe with a meridian band sweeping around it
@@ -200,9 +253,23 @@ const SOLVING_TILT = -0.5;
200
253
  * cluster relative to its neighbours instead of only reshuffling texture
201
254
  * that looks the same before and after. */
202
255
  const SOLVING_LANDMARKS_PER_TIER = 3;
256
+ /** One phase for every tier, so in the solved pose each tier's landmarks line up into the same
257
+ * three columns and a turned tier visibly breaks out of them. A per-tier phase scattered them
258
+ * even when solved, leaving nothing for a turn to disturb. */
259
+ const SOLVING_LANDMARK_PHASE = hashUnit(5) * TWO_PI;
203
260
  const SOLVING_LANDMARK_RADIUS_PX = 3.1;
204
261
  const SOLVING_LANDMARK_INK_BOOST = 0.45;
205
262
  const SOLVING_LANDMARK_ALPHA = 0.9;
263
+ /** Each landmark also draws a short meridian stroke spanning its tier. Solved, the strokes of all
264
+ * tiers join into three unbroken vertical lines; a turned tier's strokes jump sideways and break
265
+ * the lines into a visible staircase. Held scrambled, loose landmark dots alone still formed tidy
266
+ * columns of their own and read as just another solved pose. */
267
+ const SOLVING_MERIDIAN_POINTS_PER_TIER = 3;
268
+ const SOLVING_MERIDIAN_WIDTH_PX = 1.6;
269
+ const SOLVING_MERIDIAN_INK_BOOST = 0.35;
270
+ const SOLVING_MERIDIAN_ALPHA = 0.85;
271
+ /** The meridians stop this far short of the poles, where all three would otherwise converge. */
272
+ const SOLVING_MERIDIAN_Y_LIMIT = 0.9;
206
273
  /** A static ring of short strokes at each cut between tiers, unaffected by
207
274
  * any turn, so the "cut into bands" structure reads even while the puzzle
208
275
  * sits solved and nothing is moving. */
@@ -270,8 +337,7 @@ export const solving = (t, ctx) => {
270
337
  const yUnit = solvingTierCenterYUnit(tier);
271
338
  const y = yUnit * worldRadius;
272
339
  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;
340
+ const longitude = SOLVING_LANDMARK_PHASE + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
275
341
  const base = {
276
342
  x: Math.cos(longitude) * ringRadius,
277
343
  y,
@@ -299,7 +365,31 @@ export const solving = (t, ctx) => {
299
365
  });
300
366
  return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (_unusedSegment, j) => toStroke(ringPoints[j], ringPoints[j + 1], seamWidth, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
301
367
  }).flat();
302
- return { dots: [...texture, ...landmarks], strokes: seams };
368
+ const meridianWidth = sizing.radius(SOLVING_MERIDIAN_WIDTH_PX);
369
+ const meridians = Array.from({ length: SOLVING_TIER_COUNT * SOLVING_LANDMARKS_PER_TIER }, (_unused, i) => {
370
+ const tier = Math.floor(i / SOLVING_LANDMARKS_PER_TIER);
371
+ const slot = i % SOLVING_LANDMARKS_PER_TIER;
372
+ const longitude = SOLVING_LANDMARK_PHASE + (slot / SOLVING_LANDMARKS_PER_TIER) * TWO_PI;
373
+ const topYUnit = Math.min(SOLVING_MERIDIAN_Y_LIMIT, 1 - (2 * tier) / SOLVING_TIER_COUNT);
374
+ const bottomYUnit = Math.max(-SOLVING_MERIDIAN_Y_LIMIT, 1 - (2 * (tier + 1)) / SOLVING_TIER_COUNT);
375
+ const points = Array.from({ length: SOLVING_MERIDIAN_POINTS_PER_TIER }, (_unusedPoint, j) => {
376
+ const yUnit = topYUnit + ((bottomYUnit - topYUnit) * j) / (SOLVING_MERIDIAN_POINTS_PER_TIER - 1);
377
+ const ringRadius = Math.sqrt(Math.max(0, 1 - yUnit * yUnit)) * worldRadius;
378
+ const base = {
379
+ x: Math.cos(longitude) * ringRadius,
380
+ y: yUnit * worldRadius,
381
+ z: Math.sin(longitude) * ringRadius
382
+ };
383
+ const turned = rotateY(base, solvingTurnAngle(tier, cyclePos));
384
+ return project(rotateX(turned, SOLVING_TILT), origin, worldRadius);
385
+ });
386
+ return points.slice(1).map((to, j) => {
387
+ const from = points[j];
388
+ const depth = (from.depth + to.depth) / 2;
389
+ return toStroke(from, to, meridianWidth, shadeByDepth(depth) - SOLVING_MERIDIAN_INK_BOOST, SOLVING_MERIDIAN_ALPHA);
390
+ });
391
+ }).flat();
392
+ return { dots: [...texture, ...landmarks], strokes: [...seams, ...meridians] };
303
393
  };
304
394
  // ---------------------------------------------------------------------------
305
395
  // listening -- stacked horizontal rings with a wave rolling through them
@@ -307,13 +397,17 @@ export const solving = (t, ctx) => {
307
397
  /** Fewer, fatter rings at the smaller presets read as distinct stacked bands;
308
398
  * six thin rings at 20px used to compress into a single smudge. */
309
399
  const LISTENING_RING_COUNT = { 64: 5, 32: 4, 20: 3 };
400
+ /** Dots per ring at 64px, through `countAlongPath` so 32px keeps a denser ring than 20px
401
+ * instead of both falling to the minimum below. */
310
402
  const LISTENING_DOTS_PER_RING_BASE = 26;
311
403
  /** A ring below this many dots reads as a loose cluster of points rather than
312
404
  * a line, especially once it is a small ellipse at 20px. */
313
405
  const LISTENING_MIN_DOTS_PER_RING = 10;
314
406
  const LISTENING_BASE_RADIUS_PX = 1.5;
315
407
  const LISTENING_WAVE_SPEED = 0.6;
316
- const LISTENING_SWELL = 0.55;
408
+ /** How much a ring's dots grow as the wave crests through it. At 0.55 the crest ate most of the
409
+ * gap between rings at 32/20px, where the rings sit only a few px apart. */
410
+ const LISTENING_SWELL = 0.3;
317
411
  const LISTENING_LIFT_INK = 0.45;
318
412
  const LISTENING_SPIN_SPEED = 0.12;
319
413
  /** A ring's own vertical thickness once tilted is (its radius) x sin(tilt); a
@@ -322,10 +416,10 @@ const LISTENING_SPIN_SPEED = 0.12;
322
416
  * neighbouring rings' ellipses swallow the empty space between them. Kept
323
417
  * shallow rather than flattened to zero so a ring still reads as an ellipse
324
418
  * seen slightly from above, not a flat line. */
325
- const LISTENING_TILT = -0.13;
419
+ const LISTENING_TILT = -0.1;
326
420
  /** Rings are stacked between +/- this fraction of the world radius, short of
327
421
  * the poles so the top and bottom rings stay full circles, not points. */
328
- const LISTENING_Y_SPAN = 0.82;
422
+ const LISTENING_Y_SPAN = 0.86;
329
423
  const listeningRing = (ring, ringCount, worldRadius) => {
330
424
  const yUnit = (-1 + (2 * ring + 1) / ringCount) * LISTENING_Y_SPAN;
331
425
  const y = yUnit * worldRadius;
@@ -339,8 +433,7 @@ export const listening = (t, ctx) => {
339
433
  const origin = originOf(ctx);
340
434
  const worldRadius = worldRadiusOf(ctx);
341
435
  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));
436
+ const dotsPerRing = Math.max(LISTENING_MIN_DOTS_PER_RING, sizing.countAlongPath(LISTENING_DOTS_PER_RING_BASE));
344
437
  const dots = Array.from({ length: ringCount }, (_unused, r) => {
345
438
  const ring = listeningRing(r, ringCount, worldRadius);
346
439
  const wave = ring.wave(t);
@@ -1,22 +1,27 @@
1
1
  /**
2
- * Turns one `Frame` into canvas calls. The only place that knows about theme
3
- * and colour -- a mode never sees `dark` or `tint`, only the 0..1 `ink` it
4
- * assigns each mark.
2
+ * Turns one `Frame` into canvas calls. The only place that knows about
3
+ * colour -- a mode never sees `tint`, only the 0..1 `ink` it assigns each
4
+ * mark.
5
5
  */
6
6
  import type { Rgb } from '../../types';
7
7
  import type { Frame } from './types';
8
- /** What `paintFrame` needs beyond the frame itself: the theme and the base colour. */
8
+ /** What `paintFrame` needs beyond the frame itself: the base colour. */
9
9
  export type PaintOptions = {
10
- dark: boolean;
11
10
  tint: Rgb;
12
11
  };
13
12
  /**
14
- * Maps `ink` (0 = full `tint`) to an exact, testable colour string: fading
15
- * toward white in light theme and toward black in dark theme, so a mark
16
- * further away can fade into either background without ever reading as the
17
- * wrong theme's ink.
13
+ * Every mark draws in the tint colour itself -- an exact, testable colour
14
+ * string that never changes with depth. Depth is expressed as transparency
15
+ * instead (see `inkAlpha`), so a mark reads correctly against any
16
+ * background rather than only a solid white or black one.
18
17
  */
19
- export declare const inkColor: (tint: Rgb, ink: number, dark: boolean) => string;
18
+ export declare const inkColor: (tint: Rgb) => string;
19
+ /**
20
+ * Maps `ink` (0 = fully opaque) to the opacity a mark paints at: it fades
21
+ * toward fully transparent as `ink` rises, multiplied with the mark's own
22
+ * `alpha` so the two fades compound rather than override each other.
23
+ */
24
+ export declare const inkAlpha: (ink: number, alpha: number) => number;
20
25
  /**
21
26
  * Clears the canvas and draws a `Frame`: every stroke, then every dot, each
22
27
  * group sorted far-to-near by `depth` so nearer marks are painted last and
@@ -1,23 +1,22 @@
1
1
  /**
2
- * Turns one `Frame` into canvas calls. The only place that knows about theme
3
- * and colour -- a mode never sees `dark` or `tint`, only the 0..1 `ink` it
4
- * assigns each mark.
2
+ * Turns one `Frame` into canvas calls. The only place that knows about
3
+ * colour -- a mode never sees `tint`, only the 0..1 `ink` it assigns each
4
+ * mark.
5
5
  */
6
6
  const clamp01 = (value) => Math.max(0, Math.min(1, value));
7
7
  /**
8
- * Maps `ink` (0 = full `tint`) to an exact, testable colour string: fading
9
- * toward white in light theme and toward black in dark theme, so a mark
10
- * further away can fade into either background without ever reading as the
11
- * wrong theme's ink.
8
+ * Every mark draws in the tint colour itself -- an exact, testable colour
9
+ * string that never changes with depth. Depth is expressed as transparency
10
+ * instead (see `inkAlpha`), so a mark reads correctly against any
11
+ * background rather than only a solid white or black one.
12
12
  */
13
- export const inkColor = (tint, ink, dark) => {
14
- const amount = clamp01(ink);
15
- const fadeTo = dark ? 0 : 255;
16
- const r = Math.round(tint.r + (fadeTo - tint.r) * amount);
17
- const g = Math.round(tint.g + (fadeTo - tint.g) * amount);
18
- const b = Math.round(tint.b + (fadeTo - tint.b) * amount);
19
- return `rgb(${r} ${g} ${b})`;
20
- };
13
+ export const inkColor = (tint) => `rgb(${tint.r} ${tint.g} ${tint.b})`;
14
+ /**
15
+ * Maps `ink` (0 = fully opaque) to the opacity a mark paints at: it fades
16
+ * toward fully transparent as `ink` rises, multiplied with the mark's own
17
+ * `alpha` so the two fades compound rather than override each other.
18
+ */
19
+ export const inkAlpha = (ink, alpha) => clamp01(1 - clamp01(ink)) * clamp01(alpha);
21
20
  /**
22
21
  * Clears the canvas and draws a `Frame`: every stroke, then every dot, each
23
22
  * group sorted far-to-near by `depth` so nearer marks are painted last and
@@ -25,10 +24,11 @@ export const inkColor = (tint, ink, dark) => {
25
24
  */
26
25
  export const paintFrame = (ctx, frame, options) => {
27
26
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
27
+ const color = inkColor(options.tint);
28
28
  const strokes = [...frame.strokes].sort((a, b) => a.depth - b.depth);
29
29
  for (const stroke of strokes) {
30
- ctx.strokeStyle = inkColor(options.tint, stroke.ink, options.dark);
31
- ctx.globalAlpha = clamp01(stroke.alpha);
30
+ ctx.strokeStyle = color;
31
+ ctx.globalAlpha = inkAlpha(stroke.ink, stroke.alpha);
32
32
  ctx.lineWidth = stroke.width;
33
33
  ctx.beginPath();
34
34
  ctx.moveTo(stroke.x1, stroke.y1);
@@ -37,8 +37,8 @@ export const paintFrame = (ctx, frame, options) => {
37
37
  }
38
38
  const dots = [...frame.dots].sort((a, b) => a.depth - b.depth);
39
39
  for (const dot of dots) {
40
- ctx.fillStyle = inkColor(options.tint, dot.ink, options.dark);
41
- ctx.globalAlpha = clamp01(dot.alpha);
40
+ ctx.fillStyle = color;
41
+ ctx.globalAlpha = inkAlpha(dot.ink, dot.alpha);
42
42
  ctx.beginPath();
43
43
  ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
44
44
  ctx.fill();
@@ -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 = {
@@ -24055,15 +24056,12 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24055
24056
  }, attract = (e, t, n) => t === null ? e : {
24056
24057
  dots: e.dots.map((e) => attractDot(e, t, n)),
24057
24058
  strokes: e.strokes.map((e) => attractStroke(e, t, n))
24058
- }, clamp01$3 = (e) => Math.max(0, Math.min(1, e)), inkColor = (e, t, n) => {
24059
- let i = clamp01$3(t), a = n ? 0 : 255;
24060
- return `rgb(${Math.round(e.r + (a - e.r) * i)} ${Math.round(e.g + (a - e.g) * i)} ${Math.round(e.b + (a - e.b) * i)})`;
24061
- }, paintFrame = (e, t, n) => {
24059
+ }, clamp01$3 = (e) => Math.max(0, Math.min(1, e)), inkColor = (e) => `rgb(${e.r} ${e.g} ${e.b})`, inkAlpha = (e, t) => clamp01$3(1 - clamp01$3(e)) * clamp01$3(t), paintFrame = (e, t, n) => {
24062
24060
  e.clearRect(0, 0, e.canvas.width, e.canvas.height);
24063
- let i = [...t.strokes].sort((e, t) => e.depth - t.depth);
24064
- for (let t of i) e.strokeStyle = inkColor(n.tint, t.ink, n.dark), e.globalAlpha = clamp01$3(t.alpha), e.lineWidth = t.width, e.beginPath(), e.moveTo(t.x1, t.y1), e.lineTo(t.x2, t.y2), e.stroke();
24065
- let a = [...t.dots].sort((e, t) => e.depth - t.depth);
24066
- for (let t of a) e.fillStyle = inkColor(n.tint, t.ink, n.dark), e.globalAlpha = clamp01$3(t.alpha), e.beginPath(), e.arc(t.x, t.y, t.radius, 0, Math.PI * 2), e.fill();
24061
+ let i = inkColor(n.tint), a = [...t.strokes].sort((e, t) => e.depth - t.depth);
24062
+ for (let t of a) e.strokeStyle = i, e.globalAlpha = inkAlpha(t.ink, t.alpha), e.lineWidth = t.width, e.beginPath(), e.moveTo(t.x1, t.y1), e.lineTo(t.x2, t.y2), e.stroke();
24063
+ let o = [...t.dots].sort((e, t) => e.depth - t.depth);
24064
+ for (let t of o) e.fillStyle = i, e.globalAlpha = inkAlpha(t.ink, t.alpha), e.beginPath(), e.arc(t.x, t.y, t.radius, 0, Math.PI * 2), e.fill();
24067
24065
  e.globalAlpha = 1;
24068
24066
  }, TWO_PI$1 = Math.PI * 2, WORLD_RADIUS_RATIO = .38, clamp01$2 = (e) => Math.max(0, Math.min(1, e)), clamp$1 = (e, t, n) => Math.max(t, Math.min(n, e)), originOf = (e) => e.size / 2, worldRadiusOf = (e) => e.size * WORLD_RADIUS_RATIO, INK_NEAR = .05, INK_FAR = .72, shadeByDepth = (e) => INK_FAR - (INK_FAR - INK_NEAR) * clamp01$2(e), toDot = (e, t, n, i) => ({
24069
24067
  x: e.x,
@@ -24081,11 +24079,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24081
24079
  width: n,
24082
24080
  ink: clamp01$2(i),
24083
24081
  alpha: clamp01$2(a)
24084
- }), WORKING_TRAIN_BASE_COUNT = 22, WORKING_MIN_TRAINS = {
24085
- 64: 3,
24086
- 32: 3,
24087
- 20: 12
24088
- }, 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 = 22, WORKING_MIN_TRAINS_64 = 3, WORKING_TRAIN_LENGTH = 5, WORKING_TRAIN_ARC_SPAN = .5, WORKING_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, workingOrbit = (e) => {
24089
24083
  let t = e * 31 + 7;
24090
24084
  return {
24091
24085
  tilt: (hashUnit(t) - .5) * Math.PI,
@@ -24094,22 +24088,54 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24094
24088
  speedFactor: .6 + 1.3 * hashUnit(t + 9),
24095
24089
  weight: .35 + .65 * hashUnit(t + 15)
24096
24090
  };
24097
- }, working = (e, t) => {
24098
- 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 = [];
24099
- for (let t = 0; t < o; t += 1) {
24100
- 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;
24101
- for (let e = 0; e < WORKING_TRAIN_LENGTH; e += 1) {
24102
- let f = l - e * s, p = project(rotateZ(rotateX({
24103
- x: Math.cos(f) * u,
24091
+ }, WORKING_SMALL_LAYOUT = {
24092
+ 32: {
24093
+ orbits: 4,
24094
+ trainLength: 6,
24095
+ arcSpan: 1
24096
+ },
24097
+ 20: {
24098
+ orbits: 3,
24099
+ trainLength: 7,
24100
+ arcSpan: 1.25
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 o = p - e * u, s = project(rotateZ(rotateX({
24119
+ x: Math.cos(o) * h,
24104
24120
  y: 0,
24105
- z: Math.sin(f) * u
24106
- }, 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));
24107
- c.push(toDot(p, R, C, k));
24121
+ z: Math.sin(o) * h
24122
+ }, c.tilt), c.roll), i, a), S = t * 13 + e * 7 + 2, C = 1 - e / (l - 1), k = shadeByDepth(s.depth) + (hashUnit(S) - .5) * .08 - (c.weight - .5) * WORKING_ORBIT_INK_SPREAD, R = (WORKING_TRAIL_ALPHA_FLOOR + (1 - WORKING_TRAIL_ALPHA_FLOOR) * C) * (WORKING_ORBIT_ALPHA_FLOOR + (1 - WORKING_ORBIT_ALPHA_FLOOR) * c.weight), te = n.radius(WORKING_BASE_RADIUS_PX) * (.9 + .25 * hashUnit(S + 3));
24123
+ f.push(toDot(s, te, k, R));
24108
24124
  }
24109
24125
  }
24110
24126
  return {
24111
- dots: c,
24112
- strokes: []
24127
+ dots: f,
24128
+ strokes: o ? Array.from({ length: o.orbits }, (e, t) => {
24129
+ let s = workingSmallOrbit(t, o.orbits), c = a * s.orbitRadiusFactor, l = Array.from({ length: 25 }, (e, t) => {
24130
+ let n = t / WORKING_PATH_SEGMENTS * TWO_PI$1;
24131
+ return project(rotateZ(rotateX({
24132
+ x: Math.cos(n) * c,
24133
+ y: 0,
24134
+ z: Math.sin(n) * c
24135
+ }, s.tilt), s.roll), i, a);
24136
+ });
24137
+ return l.slice(1).map((e, t) => toStroke(l[t], e, n.radius(WORKING_PATH_WIDTH_PX), WORKING_PATH_INK, WORKING_PATH_ALPHA));
24138
+ }).flat() : []
24113
24139
  };
24114
24140
  }, 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) => {
24115
24141
  let n = Math.abs(e - t) % TWO_PI$1;
@@ -24130,7 +24156,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24130
24156
  dots: c,
24131
24157
  strokes: Array.from({ length: SEARCHING_ARC_SEGMENTS }, (e, t) => toStroke(l[t], l[t + 1], u, SEARCHING_ARC_INK, SEARCHING_ARC_ALPHA))
24132
24158
  };
24133
- }, 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) => {
24159
+ }, 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, SOLVING_MERIDIAN_POINTS_PER_TIER = 3, SOLVING_MERIDIAN_WIDTH_PX = 1.6, SOLVING_MERIDIAN_INK_BOOST = .35, SOLVING_MERIDIAN_ALPHA = .85, SOLVING_MERIDIAN_Y_LIMIT = .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) => {
24134
24160
  let n = solvingTurnStart(e), i = solvingTurnEnd(e), a = solvingReturnStart(e), o = solvingReturnEnd(e);
24135
24161
  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;
24136
24162
  }, 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) => {
@@ -24138,7 +24164,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24138
24164
  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);
24139
24165
  return toDot(l, n.radius(SOLVING_BASE_RADIUS_PX), u, f);
24140
24166
  }), l = Array.from({ length: SOLVING_TIER_COUNT * SOLVING_LANDMARKS_PER_TIER }, (e, t) => {
24141
- 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({
24167
+ 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({
24142
24168
  x: Math.cos(p) * f,
24143
24169
  y: u,
24144
24170
  z: Math.sin(p) * f
@@ -24154,16 +24180,29 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24154
24180
  }, SOLVING_TILT), i, a);
24155
24181
  });
24156
24182
  return Array.from({ length: SOLVING_SEAM_SEGMENTS }, (e, t) => toStroke(c[t], c[t + 1], u, SOLVING_SEAM_INK, SOLVING_SEAM_ALPHA));
24183
+ }).flat(), p = n.radius(SOLVING_MERIDIAN_WIDTH_PX), h = Array.from({ length: SOLVING_TIER_COUNT * SOLVING_LANDMARKS_PER_TIER }, (e, t) => {
24184
+ let n = Math.floor(t / SOLVING_LANDMARKS_PER_TIER), o = SOLVING_LANDMARK_PHASE + t % SOLVING_LANDMARKS_PER_TIER / SOLVING_LANDMARKS_PER_TIER * TWO_PI$1, c = Math.min(SOLVING_MERIDIAN_Y_LIMIT, 1 - 2 * n / SOLVING_TIER_COUNT), l = Math.max(-.9, 1 - 2 * (n + 1) / SOLVING_TIER_COUNT), u = Array.from({ length: SOLVING_MERIDIAN_POINTS_PER_TIER }, (e, t) => {
24185
+ let u = c + (l - c) * t / (SOLVING_MERIDIAN_POINTS_PER_TIER - 1), f = Math.sqrt(Math.max(0, 1 - u * u)) * a;
24186
+ return project(rotateX(rotateY({
24187
+ x: Math.cos(o) * f,
24188
+ y: u * a,
24189
+ z: Math.sin(o) * f
24190
+ }, solvingTurnAngle(n, s)), SOLVING_TILT), i, a);
24191
+ });
24192
+ return u.slice(1).map((e, t) => {
24193
+ let n = u[t];
24194
+ return toStroke(n, e, p, shadeByDepth((n.depth + e.depth) / 2) - SOLVING_MERIDIAN_INK_BOOST, SOLVING_MERIDIAN_ALPHA);
24195
+ });
24157
24196
  }).flat();
24158
24197
  return {
24159
24198
  dots: [...c, ...l],
24160
- strokes: f
24199
+ strokes: [...f, ...h]
24161
24200
  };
24162
24201
  }, LISTENING_RING_COUNT = {
24163
24202
  64: 5,
24164
24203
  32: 4,
24165
24204
  20: 3
24166
- }, 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) => {
24205
+ }, 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) => {
24167
24206
  let i = (-1 + (2 * e + 1) / t) * LISTENING_Y_SPAN * n;
24168
24207
  return {
24169
24208
  y: i,
@@ -24172,15 +24211,15 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24172
24211
  wave: (n) => (Math.sin(TWO_PI$1 * (e / t - n * LISTENING_WAVE_SPEED)) + 1) / 2
24173
24212
  };
24174
24213
  }, listening = (e, t) => {
24175
- 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));
24214
+ 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));
24176
24215
  return {
24177
- dots: Array.from({ length: o }, (t, s) => {
24178
- let l = listeningRing(s, o, a), u = l.wave(e);
24179
- return Array.from({ length: c }, (t, o) => {
24180
- let s = o / c * TWO_PI$1 + l.phase, f = project(rotateY(rotateX({
24181
- x: Math.cos(s) * l.ringRadius,
24216
+ dots: Array.from({ length: o }, (t, c) => {
24217
+ let l = listeningRing(c, o, a), u = l.wave(e);
24218
+ return Array.from({ length: s }, (t, o) => {
24219
+ let c = o / s * TWO_PI$1 + l.phase, f = project(rotateY(rotateX({
24220
+ x: Math.cos(c) * l.ringRadius,
24182
24221
  y: l.y,
24183
- z: Math.sin(s) * l.ringRadius
24222
+ z: Math.sin(c) * l.ringRadius
24184
24223
  }, LISTENING_TILT), e * LISTENING_SPIN_SPEED), i, a), p = shadeByDepth(f.depth) - u * LISTENING_LIFT_INK, h = .6 + u * .3;
24185
24224
  return toDot(f, n.radius(LISTENING_BASE_RADIUS_PX) * (1 + u * LISTENING_SWELL), p, h);
24186
24225
  });
@@ -24194,7 +24233,7 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24194
24233
  }), squaredDistance = (e, t) => {
24195
24234
  let n = e.x - t.x, i = e.y - t.y, a = e.z - t.z;
24196
24235
  return n * n + i * i + a * a;
24197
- }, 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) => {
24236
+ }, 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) => {
24198
24237
  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]));
24199
24238
  return i.filter(([e, t], n) => i.findIndex(([n, i]) => n === e && i === t) === n);
24200
24239
  }, connecting = (e, t) => {
@@ -24232,51 +24271,59 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24232
24271
  dots: [...l, ...p],
24233
24272
  strokes: f
24234
24273
  };
24235
- }, 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) => {
24236
- 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;
24237
- return rotateX({
24238
- x: l * Math.cos(p),
24239
- y: c,
24240
- z: l * Math.sin(p)
24241
- }, STRAND_TILT);
24274
+ }, 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 = {
24275
+ 64: 1,
24276
+ 32: 1.2,
24277
+ 20: 1.5
24278
+ }, STRAND_DOT_SIZE_FACTOR = {
24279
+ 64: 1,
24280
+ 32: .85,
24281
+ 20: .7
24282
+ }, STRAND_DEPTH_INK = .45, STRAND_SHADE_STEP = .1, strandBaseAngle = (e) => e / STRAND_COUNT * Math.PI * 2, strandPoint = (e, t, n, i, a) => {
24283
+ let o = t / (n - 1) * Math.PI * 2, s = STRAND_WAVE_AMPLITUDE * Math.sin(o * STRAND_WAVE_COUNT + a * STRAND_WAVE_SPEED + strandBaseAngle(e));
24284
+ return rotateX(rotateY(rotateX({
24285
+ x: i * Math.cos(s) * Math.cos(o),
24286
+ y: i * Math.sin(s),
24287
+ z: i * Math.cos(s) * Math.sin(o)
24288
+ }, STRAND_INCLINATION), strandBaseAngle(e) + a * STRAND_ROTATE_SPEED), STRAND_TILT);
24242
24289
  }, weaving = (e, t) => {
24243
- 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)));
24290
+ 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)));
24244
24291
  return {
24245
- dots: s.flatMap((e, t) => e.map((e) => ({
24292
+ dots: s.flatMap((e, i) => e.map((e) => ({
24246
24293
  x: e.x,
24247
24294
  y: e.y,
24248
24295
  depth: e.depth,
24249
- radius: n.radius(STRAND_DOT_BASE_RADIUS),
24250
- ink: clamp01$1(1 - e.depth * .8 + t * .08),
24296
+ radius: n.radius(STRAND_DOT_BASE_RADIUS) * STRAND_DOT_SIZE_FACTOR[t.size],
24297
+ ink: clamp01$1((1 - e.depth) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
24251
24298
  alpha: .55 + e.depth * .4
24252
24299
  }))),
24253
- strokes: s.flatMap((e, t) => e.slice(1).map((i, a) => {
24254
- let o = e[a], s = (o.depth + i.depth) / 2;
24300
+ strokes: s.flatMap((e, i) => e.slice(1).map((a, o) => {
24301
+ let s = e[o], c = (s.depth + a.depth) / 2;
24255
24302
  return {
24256
- x1: o.x,
24257
- y1: o.y,
24258
- x2: i.x,
24259
- y2: i.y,
24260
- depth: s,
24261
- width: n.radius(STRAND_STROKE_BASE_WIDTH),
24262
- ink: clamp01$1(1 - s * .75 + t * .06),
24263
- alpha: .6 + s * .35
24303
+ x1: s.x,
24304
+ y1: s.y,
24305
+ x2: a.x,
24306
+ y2: a.y,
24307
+ depth: c,
24308
+ width: n.radius(STRAND_STROKE_BASE_WIDTH) * STRAND_STROKE_SIZE_FACTOR[t.size],
24309
+ ink: clamp01$1((1 - c) * STRAND_DEPTH_INK + i * STRAND_SHADE_STEP),
24310
+ alpha: .6 + c * .35
24264
24311
  };
24265
24312
  }))
24266
24313
  };
24267
- }, 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) => ({
24314
+ }, 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) => ({
24268
24315
  x: e.x,
24269
24316
  y: e.y,
24270
24317
  depth: e.depth,
24271
24318
  radius: n.radius(t) * depthToRadiusFactor(e.depth) * i,
24272
24319
  ink: depthToInk(e.depth),
24273
24320
  alpha: depthToAlpha(e.depth)
24274
- }), 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 = {
24275
- 64: 7,
24276
- 32: 5,
24277
- 20: 4
24278
- }, 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) => {
24279
- 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 = [];
24321
+ }), 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 = 2.2, RIBBON_THREADS = {
24322
+ 64: 5,
24323
+ 32: 4,
24324
+ 20: 3
24325
+ }, 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, composing = (e, t) => {
24326
+ 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 = [];
24280
24327
  for (let t = 0; t < c; t += 1) {
24281
24328
  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({
24282
24329
  x: Math.cos(S) * Math.cos(p) * i,
@@ -24293,8 +24340,8 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24293
24340
  dots: l,
24294
24341
  strokes: EMPTY_STROKES
24295
24342
  };
24296
- }, 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) => {
24297
- 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 = [];
24343
+ }, 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) => {
24344
+ 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 = [];
24298
24345
  for (let t = 0; t < o; t += 1) {
24299
24346
  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 = {
24300
24347
  x: Math.cos(l) * f * i,
@@ -24307,11 +24354,11 @@ var clamp01$5 = (e) => Math.max(0, Math.min(1, e)), rotateX = (e, t) => {
24307
24354
  dots: c,
24308
24355
  strokes: EMPTY_STROKES
24309
24356
  };
24310
- }, 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) => {
24357
+ }, 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) => {
24311
24358
  let n = TWO_PI / t, i = n / 2, a = (e % n + n) % n;
24312
24359
  return Math.cos(i) / Math.cos(a - i);
24313
24360
  }, lerp = (e, t, n) => e + (t - e) * n, shaping = (e, t) => {
24314
- 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 = [];
24361
+ 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 = [];
24315
24362
  for (let t = 0; t < o; t += 1) {
24316
24363
  let s = t / o * TWO_PI, f = polygonRadius(s, 3), p = polygonRadius(s, 4), h;
24317
24364
  h = c === 0 ? lerp(1, f, l) : c === 1 ? lerp(f, p, l) : lerp(p, 1, l);
@@ -24347,7 +24394,7 @@ function ThinkingOrb(e, t) {
24347
24394
  r: 128,
24348
24395
  g: 128,
24349
24396
  b: 128
24350
- }, R = 2.4, te = 2, ne = /* @__PURE__ */ user_derived(() => Object.hasOwn(MODES, n()) ? n() : "working"), re = /* @__PURE__ */ user_derived(() => C.includes(i()) ? i() : 64), G = /* @__PURE__ */ user_derived(() => Math.max(.1, c())), be = /* @__PURE__ */ user_derived(() => Math.max(.1, l())), Me = /* @__PURE__ */ user_derived(() => !u() || get(Ue) ? null : u() === !0 ? GRAVITY_DEFAULTS : u()), Re = /* @__PURE__ */ state(null), Ue = /* @__PURE__ */ state(proxy(prefersReducedMotion())), it = /* @__PURE__ */ state(!0), at = /* @__PURE__ */ state(!0), ot = /* @__PURE__ */ state(!1), ct = !1, lt = "", ut = k, dt = null, ft = 0, pt = 0, mt = /* @__PURE__ */ user_derived(() => !o() && !get(Ue) && get(it) && get(at)), ht = () => get(Ue) ? 2.4 : pt, gt = (e, t) => {
24397
+ }, R = 2.4, te = 2, ne = /* @__PURE__ */ user_derived(() => Object.hasOwn(MODES, n()) ? n() : "working"), re = /* @__PURE__ */ user_derived(() => C.includes(i()) ? i() : 64), G = /* @__PURE__ */ user_derived(() => Math.max(.1, c())), be = /* @__PURE__ */ user_derived(() => Math.max(.1, l())), Me = /* @__PURE__ */ user_derived(() => !u() || get(Ue) ? null : u() === !0 ? GRAVITY_DEFAULTS : u()), Re = /* @__PURE__ */ state(null), Ue = /* @__PURE__ */ state(proxy(prefersReducedMotion())), it = /* @__PURE__ */ state(!0), at = /* @__PURE__ */ state(!0), ot = !1, ct = "", lt = k, ut = null, dt = 0, ft = 0, pt = /* @__PURE__ */ user_derived(() => !o() && !get(Ue) && get(it) && get(at)), mt = () => get(Ue) ? 2.4 : ft, ht = (e, t) => {
24351
24398
  if (t.length === 0) return null;
24352
24399
  let n = e.fillStyle;
24353
24400
  e.fillStyle = "#000000", e.fillStyle = t;
@@ -24355,32 +24402,34 @@ function ThinkingOrb(e, t) {
24355
24402
  e.fillStyle = "#ffffff", e.fillStyle = t;
24356
24403
  let a = e.fillStyle;
24357
24404
  return e.fillStyle = n, typeof i == "string" && i === a ? i : null;
24358
- }, _t = (e) => {
24405
+ }, gt = (e) => {
24359
24406
  let t = getComputedStyle(e), n = t.getPropertyValue("--sui-thinking-orb-color").trim(), i = t.color.trim();
24360
24407
  return {
24361
24408
  declared: n,
24362
24409
  inherited: i,
24363
24410
  key: `${n}|${i}`
24364
24411
  };
24365
- }, vt = (e) => {
24412
+ }, _t = (e) => {
24366
24413
  if (s()) return s();
24367
- if (get(Re) === null) return ut;
24368
- let { declared: t, inherited: n, key: i } = _t(get(Re));
24369
- if (i === lt) return ut;
24370
- lt = i;
24371
- let a = gt(e, t) ?? gt(e, n);
24372
- return ut = (a === null ? null : parseCssColor(a)) ?? k, ut;
24414
+ if (get(Re) === null) return lt;
24415
+ let { declared: t, inherited: n, key: i } = gt(get(Re));
24416
+ if (i === ct) return lt;
24417
+ ct = i;
24418
+ let a = ht(e, t) ?? ht(e, n);
24419
+ return lt = (a === null ? null : parseCssColor(a)) ?? k, lt;
24420
+ }, vt = () => {
24421
+ get(pt) || get(Me) === null || Ct(mt());
24373
24422
  }, yt = () => {
24374
- get(mt) || get(Me) === null || Ct(ht());
24423
+ get(pt) || s() || get(Re) === null || gt(get(Re)).key !== ct && Ct(mt());
24375
24424
  }, bt = (e) => {
24376
24425
  if (get(Re) === null || !u()) return;
24377
24426
  let t = get(Re).getBoundingClientRect();
24378
- dt = {
24427
+ ut = {
24379
24428
  x: e.clientX - t.left,
24380
24429
  y: e.clientY - t.top
24381
- }, yt();
24430
+ }, vt();
24382
24431
  }, xt = () => {
24383
- dt = null, yt();
24432
+ ut = null, vt();
24384
24433
  }, St = () => typeof window > "u" ? 1 : Math.min(2, window.devicePixelRatio ?? 1), Ct = (e) => {
24385
24434
  if (get(Re) === null) return;
24386
24435
  let t = get(Re).getContext("2d");
@@ -24392,18 +24441,15 @@ function ThinkingOrb(e, t) {
24392
24441
  density: get(G),
24393
24442
  dotScale: get(be)
24394
24443
  }), o = get(Me);
24395
- paintFrame(t, o === null ? a : attract(a, dt, o), {
24396
- dark: get(ot),
24397
- tint: vt(t)
24398
- }), ct || (ct = !0, S()?.());
24444
+ paintFrame(t, o === null ? a : attract(a, ut, o), { tint: _t(t) }), ot || (ot = !0, S()?.());
24399
24445
  };
24400
24446
  user_effect(() => {
24401
- if (Ct(ht()), !get(mt)) return;
24447
+ if (Ct(mt()), !get(pt)) return;
24402
24448
  let e = null, t = (n) => {
24403
24449
  let i = e === null ? 0 : (n - e) / 1e3;
24404
- e = n, pt += i * a(), Ct(pt), ft = requestAnimationFrame(t);
24450
+ e = n, ft += i * a(), Ct(ft), dt = requestAnimationFrame(t);
24405
24451
  };
24406
- return ft = requestAnimationFrame(t), () => cancelAnimationFrame(ft);
24452
+ return dt = requestAnimationFrame(t), () => cancelAnimationFrame(dt);
24407
24453
  }), onMount(() => {
24408
24454
  if (get(Re) === null) return;
24409
24455
  let e = null;
@@ -24414,24 +24460,19 @@ function ThinkingOrb(e, t) {
24414
24460
  set(at, document.visibilityState !== "hidden");
24415
24461
  };
24416
24462
  document.addEventListener("visibilitychange", t);
24417
- let n = () => {
24418
- set(ot, document.documentElement.getAttribute("data-theme") === "dark");
24419
- };
24420
- n();
24421
- let i = null;
24422
- typeof MutationObserver < "u" && (i = new MutationObserver(n), i.observe(document.documentElement, {
24423
- attributes: !0,
24424
- attributeFilter: ["data-theme"]
24425
- }));
24426
- let a = (e) => {
24463
+ let n = null;
24464
+ typeof MutationObserver < "u" && (n = new MutationObserver(yt), n.observe(document.documentElement, { attributes: !0 }), n.observe(document.body, { attributes: !0 }));
24465
+ let i = (e) => {
24427
24466
  set(Ue, e.matches, !0);
24428
- }, o = null;
24429
- typeof window.matchMedia == "function" && (o = window.matchMedia("(prefers-reduced-motion: reduce)"), o.addEventListener("change", a));
24430
- let c = (e) => {
24431
- get(mt) || s() || get(Re) === null || e.propertyName !== "color" && e.propertyName !== "--sui-thinking-orb-color" || _t(get(Re)).key !== lt && Ct(ht());
24432
- };
24433
- return document.addEventListener("transitionend", c), () => {
24434
- e?.disconnect(), document.removeEventListener("visibilitychange", t), document.removeEventListener("transitionend", c), i?.disconnect(), o?.removeEventListener("change", a);
24467
+ }, a = null;
24468
+ typeof window.matchMedia == "function" && (a = window.matchMedia("(prefers-reduced-motion: reduce)"), a.addEventListener("change", i));
24469
+ let o = null;
24470
+ typeof window.matchMedia == "function" && (o = window.matchMedia("(prefers-color-scheme: dark)"), o.addEventListener("change", yt));
24471
+ let s = (e) => {
24472
+ e.propertyName !== "color" && e.propertyName !== "--sui-thinking-orb-color" || yt();
24473
+ };
24474
+ return document.addEventListener("transitionend", s), () => {
24475
+ e?.disconnect(), document.removeEventListener("visibilitychange", t), document.removeEventListener("transitionend", s), n?.disconnect(), a?.removeEventListener("change", i), o?.removeEventListener("change", yt);
24435
24476
  };
24436
24477
  });
24437
24478
  var wt = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "4.33.0",
3
+ "version": "4.33.2",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",