@dorsk/tsumikit 0.78.0 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/components/atoms/Artwork.svelte +5 -3
  2. package/dist/components/atoms/Badge.svelte +2 -5
  3. package/dist/components/atoms/Button.svelte +327 -17
  4. package/dist/components/atoms/Button.svelte.d.ts +4 -1
  5. package/dist/components/atoms/Card.svelte +160 -12
  6. package/dist/components/atoms/Checkbox.svelte +181 -3
  7. package/dist/components/atoms/Gauge.svelte +2 -2
  8. package/dist/components/atoms/Input.svelte +41 -3
  9. package/dist/components/atoms/Kbd.svelte +1 -1
  10. package/dist/components/atoms/Link.svelte +61 -0
  11. package/dist/components/atoms/Progress.svelte +75 -2
  12. package/dist/components/atoms/Progress.svelte.d.ts +1 -0
  13. package/dist/components/atoms/SegmentedProgress.svelte +2 -2
  14. package/dist/components/atoms/Select.svelte +39 -3
  15. package/dist/components/atoms/Skeleton.svelte +54 -3
  16. package/dist/components/atoms/Skeleton.svelte.d.ts +1 -0
  17. package/dist/components/atoms/Slider.svelte +8 -6
  18. package/dist/components/atoms/Spinner.svelte +152 -3
  19. package/dist/components/atoms/Spinner.svelte.d.ts +1 -1
  20. package/dist/components/atoms/Swatch.svelte +1 -1
  21. package/dist/components/atoms/Switch.svelte +138 -2
  22. package/dist/components/atoms/Textarea.svelte +42 -3
  23. package/dist/components/atoms/Thinking.svelte +130 -0
  24. package/dist/components/atoms/Thinking.svelte.d.ts +15 -0
  25. package/dist/components/layouts/AppShell.svelte +6 -6
  26. package/dist/components/layouts/MasterDetail.svelte +3 -3
  27. package/dist/components/layouts/NavBar.svelte +4 -4
  28. package/dist/components/layouts/NavSection.svelte +1 -1
  29. package/dist/components/layouts/ResizablePanel.svelte +7 -7
  30. package/dist/components/layouts/TabBar.svelte +2 -2
  31. package/dist/components/molecules/Accordion.svelte +2 -2
  32. package/dist/components/molecules/AttachmentList.svelte +2 -2
  33. package/dist/components/molecules/CapBar.svelte +2 -2
  34. package/dist/components/molecules/CodeBlock.svelte +5 -5
  35. package/dist/components/molecules/Combobox.svelte +3 -3
  36. package/dist/components/molecules/CopyButton.svelte +146 -5
  37. package/dist/components/molecules/Drawer.svelte +8 -8
  38. package/dist/components/molecules/EmojiPicker.svelte +1 -1
  39. package/dist/components/molecules/Field.svelte +55 -1
  40. package/dist/components/molecules/FileButton.svelte +20 -6
  41. package/dist/components/molecules/FilterInput.svelte +4 -4
  42. package/dist/components/molecules/GitRef.svelte +2 -2
  43. package/dist/components/molecules/GlyphStack.svelte +21 -3
  44. package/dist/components/molecules/InputGroup.svelte +3 -3
  45. package/dist/components/molecules/LookPicker.svelte +87 -0
  46. package/dist/components/molecules/LookPicker.svelte.d.ts +17 -0
  47. package/dist/components/molecules/Modal.svelte +5 -5
  48. package/dist/components/molecules/MotionSettings.svelte +166 -0
  49. package/dist/components/molecules/MotionSettings.svelte.d.ts +18 -0
  50. package/dist/components/molecules/OptionButton.svelte +8 -3
  51. package/dist/components/molecules/Pagination.svelte +3 -1
  52. package/dist/components/molecules/Popover.svelte +18 -11
  53. package/dist/components/molecules/Prose.svelte +6 -6
  54. package/dist/components/molecules/RadialMenu.svelte +3 -3
  55. package/dist/components/molecules/RadioGroup.svelte +67 -4
  56. package/dist/components/molecules/RichSelect.svelte +6 -4
  57. package/dist/components/molecules/SectionHeader.svelte +1 -1
  58. package/dist/components/molecules/SeekSlider.svelte +6 -6
  59. package/dist/components/molecules/SegmentedControl.svelte +7 -7
  60. package/dist/components/molecules/SortableList.svelte +2 -2
  61. package/dist/components/molecules/SplitButton.svelte +1 -1
  62. package/dist/components/molecules/Tabs.svelte +160 -7
  63. package/dist/components/molecules/ThemePicker.svelte +1 -1
  64. package/dist/components/molecules/Timestamp.svelte +1 -1
  65. package/dist/components/molecules/Toggle.svelte +5 -3
  66. package/dist/components/molecules/Tooltip.svelte +2 -2
  67. package/dist/components/molecules/VolumeSlider.svelte +3 -3
  68. package/dist/components/organisms/ConversationFeed.svelte +1 -1
  69. package/dist/components/organisms/ConversationPanel.svelte +3 -3
  70. package/dist/components/organisms/DataTable.svelte +18 -16
  71. package/dist/fx.d.ts +149 -0
  72. package/dist/fx.js +111 -0
  73. package/dist/fx.md +58 -0
  74. package/dist/index.d.ts +8 -2
  75. package/dist/index.js +8 -2
  76. package/dist/looks/README.md +101 -0
  77. package/dist/looks/depth.css +939 -0
  78. package/dist/looks/editorial.css +545 -0
  79. package/dist/looks/tactile.css +871 -0
  80. package/dist/pointer.d.ts +3 -0
  81. package/dist/pointer.js +40 -0
  82. package/dist/stores/fx.svelte.d.ts +17 -0
  83. package/dist/stores/fx.svelte.js +64 -0
  84. package/dist/stores/theme.svelte.d.ts +8 -2
  85. package/dist/stores/theme.svelte.js +33 -7
  86. package/dist/styles/themes.css +249 -249
  87. package/dist/styles/tokens.css +37 -2
  88. package/dist/theme-mode.d.ts +31 -0
  89. package/dist/theme-mode.js +19 -0
  90. package/package.json +3 -2
@@ -10,6 +10,9 @@
10
10
  // gradient — accent→teal fill (storage meter / library-health bars).
11
11
  // striped — diagonal stripes; animated while in-flight (unpacking).
12
12
  // indeterminate — explicit "scanning"/"importing" mode without a percentage.
13
+ // variant — `liquid`: two soft pulses flow through the track while
14
+ // indeterminate; `stripes`: a diagonal shimmer drifts over
15
+ // the fill.
13
16
  let {
14
17
  value,
15
18
  max = 100,
@@ -22,6 +25,7 @@
22
25
  shrink = true,
23
26
  block = false,
24
27
  indeterminate: indeterminateProp = false,
28
+ variant = 'default',
25
29
  markers = [],
26
30
  toneAt,
27
31
  color,
@@ -51,6 +55,7 @@
51
55
  striped?: boolean;
52
56
  // Force indeterminate mode even when a `value` is supplied.
53
57
  indeterminate?: boolean;
58
+ variant?: 'default' | 'liquid' | 'stripes';
54
59
  /** Thin vertical lines at `at / max` (caps, thresholds). */
55
60
  markers?: { at: number; label?: string; tone?: Tone }[];
56
61
  /** Percent thresholds that auto-pick warn/danger; an explicit `tone` other
@@ -80,7 +85,7 @@
80
85
  {#snippet track()}
81
86
  <div
82
87
  data-tsu="Progress"
83
- class="progress tone-{toneClass} size-{size} {klass}"
88
+ class="progress tone-{toneClass} size-{size} v-{variant} {klass}"
84
89
  class:grow={grow}
85
90
  class:no-shrink={!shrink}
86
91
  class:block={block}
@@ -146,7 +151,7 @@
146
151
  position: relative;
147
152
  width: 100%;
148
153
  height: 0.5rem;
149
- background: var(--bg-elevated-2);
154
+ background: var(--track-fill);
150
155
  border-radius: var(--r-pill);
151
156
  overflow: hidden;
152
157
  }
@@ -272,4 +277,72 @@
272
277
  opacity: 0.35;
273
278
  }
274
279
  }
280
+ .progress.v-liquid.indeterminate .bar {
281
+ animation: none;
282
+ opacity: 0;
283
+ }
284
+ .progress.v-liquid.indeterminate::before,
285
+ .progress.v-liquid.indeterminate::after {
286
+ content: '';
287
+ position: absolute;
288
+ inset: 0;
289
+ width: 55%;
290
+ border-radius: inherit;
291
+ background: linear-gradient(90deg, transparent, var(--fill, var(--accent)) 35% 65%, transparent);
292
+ transform: translateX(-100%);
293
+ animation: tsu-progress-liquid 1.7s var(--ease-drawer) infinite;
294
+ }
295
+ .progress.v-liquid.indeterminate::after {
296
+ width: 35%;
297
+ opacity: 0.55;
298
+ animation: tsu-progress-liquid-2 1.7s var(--ease-in-out) -0.7s infinite;
299
+ }
300
+ @keyframes tsu-progress-liquid {
301
+ to {
302
+ transform: translateX(282%);
303
+ }
304
+ }
305
+ @keyframes tsu-progress-liquid-2 {
306
+ to {
307
+ transform: translateX(386%);
308
+ }
309
+ }
310
+ .progress.v-stripes .bar {
311
+ position: relative;
312
+ overflow: hidden;
313
+ }
314
+ .progress.v-stripes .bar::after {
315
+ content: '';
316
+ position: absolute;
317
+ inset: 0 -1.5rem 0 0;
318
+ background: repeating-linear-gradient(
319
+ 135deg,
320
+ transparent 0 0.5rem,
321
+ color-mix(in srgb, var(--text-on-accent) 28%, transparent) 0.5rem 1rem
322
+ );
323
+ animation: tsu-progress-stripes 0.7s linear infinite;
324
+ }
325
+ @keyframes tsu-progress-stripes {
326
+ from {
327
+ transform: translateX(-1.4142rem);
328
+ }
329
+ to {
330
+ transform: translateX(0);
331
+ }
332
+ }
333
+ @media (prefers-reduced-motion: reduce) {
334
+ .progress.v-liquid.indeterminate {
335
+ background: color-mix(in srgb, var(--fill, var(--accent)) 35%, transparent);
336
+ }
337
+ .progress.v-liquid.indeterminate::before,
338
+ .progress.v-liquid.indeterminate::after {
339
+ display: none;
340
+ }
341
+ .progress.v-liquid.indeterminate .bar {
342
+ display: none;
343
+ }
344
+ .progress.v-stripes .bar::after {
345
+ animation: none;
346
+ }
347
+ }
275
348
  </style>
@@ -15,6 +15,7 @@ type $$ComponentProps = {
15
15
  gradient?: boolean;
16
16
  striped?: boolean;
17
17
  indeterminate?: boolean;
18
+ variant?: 'default' | 'liquid' | 'stripes';
18
19
  /** Thin vertical lines at `at / max` (caps, thresholds). */
19
20
  markers?: {
20
21
  at: number;
@@ -168,7 +168,7 @@
168
168
  flex: 0 1 0%;
169
169
  min-width: 3px;
170
170
  height: 100%;
171
- background: var(--bg-elevated-2);
171
+ background: var(--track-fill);
172
172
  overflow: hidden;
173
173
  }
174
174
  .segment:first-child {
@@ -206,7 +206,7 @@
206
206
 
207
207
  .stacked {
208
208
  overflow: hidden;
209
- background: var(--bg-elevated-2);
209
+ background: var(--track-fill);
210
210
  }
211
211
  .stacked .segment {
212
212
  min-width: 0;
@@ -217,9 +217,10 @@
217
217
  padding: var(--sp-2) var(--control-px-md);
218
218
  font-size: var(--control-fs-md);
219
219
  line-height: var(--lh-tight);
220
- background: var(--select-bg, var(--bg));
221
- border: 1px solid var(--select-border, var(--border-strong));
220
+ background: var(--select-bg, var(--control-fill));
221
+ border: var(--edge-width) solid var(--select-border, var(--control-edge));
222
222
  border-radius: var(--select-radius, var(--r-md));
223
+ box-shadow: var(--control-shadow);
223
224
  color: var(--select-fg, var(--text));
224
225
  transition: border-color var(--dur-fast) ease;
225
226
  }
@@ -261,11 +262,15 @@
261
262
  background: var(--select-bg, var(--bg-elevated-2));
262
263
  border: none;
263
264
  border-radius: var(--select-radius, var(--r-sm));
265
+ box-shadow: none;
264
266
  }
265
267
  .select:focus {
266
268
  outline: none;
267
269
  border-color: var(--accent);
268
270
  }
271
+ .select:focus-visible:not(.embedded, .ghost) {
272
+ box-shadow: var(--control-shadow), var(--focus-glow);
273
+ }
269
274
  .select:focus-visible {
270
275
  outline: var(--focus-ring);
271
276
  outline-offset: var(--focus-ring-offset);
@@ -274,6 +279,36 @@
274
279
  .select[aria-invalid='true']:focus {
275
280
  border-color: var(--danger);
276
281
  }
282
+ .select {
283
+ --focus-tint: var(--accent);
284
+ }
285
+ .select[aria-invalid='true'] {
286
+ --focus-tint: var(--danger);
287
+ }
288
+ /* Focus bloom: a solid ring whose soft halo swells, then settles to a quieter glow. */
289
+ .select:not(.ghost, .embedded):focus:not(:global([data-fx-focus='none']) *),
290
+ :global(:is([data-fx-focus='bloom'], [data-fx-focus='label'])) .select:not(.ghost, .embedded):focus:not(:global(:is([data-fx-focus='bloom'], [data-fx-focus='label']) [data-fx-focus='none']) *) {
291
+ outline: none;
292
+ box-shadow:
293
+ var(--control-shadow),
294
+ 0 0 0 var(--focus-ring-width) var(--focus-tint),
295
+ 0 0 16px 2px color-mix(in srgb, var(--focus-tint) 24%, transparent);
296
+ animation: focus-bloom var(--dur-fast) var(--ease-out);
297
+ }
298
+ @keyframes focus-bloom {
299
+ 0% {
300
+ box-shadow:
301
+ var(--control-shadow),
302
+ 0 0 0 0 transparent,
303
+ 0 0 0 0 transparent;
304
+ }
305
+ 45% {
306
+ box-shadow:
307
+ var(--control-shadow),
308
+ 0 0 0 calc(var(--focus-ring-width) + 1px) var(--focus-tint),
309
+ 0 0 20px 4px color-mix(in srgb, var(--focus-tint) 32%, transparent);
310
+ }
311
+ }
277
312
  .select:disabled {
278
313
  cursor: not-allowed;
279
314
  }
@@ -367,7 +402,7 @@
367
402
  }
368
403
  @media (hover: hover) and (pointer: fine) {
369
404
  .select:hover:not(:disabled, :focus, [aria-invalid='true'], .ghost, .embedded) {
370
- border-color: var(--text-faint);
405
+ border-color: var(--control-edge-hover);
371
406
  }
372
407
  }
373
408
  /* Transparent overlay: fills the positioned parent, the trigger shows through. */
@@ -380,6 +415,7 @@
380
415
  opacity: 0;
381
416
  border: none;
382
417
  background: none;
418
+ box-shadow: none;
383
419
  cursor: pointer;
384
420
  }
385
421
  </style>
@@ -2,8 +2,10 @@
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  // Shimmering placeholder bar(s) standing in for content that is still
4
4
  // loading. Decorative only — the surrounding region should carry aria-busy.
5
- // Bars are tinted from --text so they read on any surface; the sweep is
6
- // viewport-anchored so every bar on screen shimmers in sync.
5
+ // Bars are tinted from --text so they read on any surface. `shimmer` is a
6
+ // viewport-anchored sweep so every bar on screen shimmers in sync; `sheen`
7
+ // runs one light across each bar; `pulse` breathes in opacity, each bar a
8
+ // beat behind the previous one (`--skeleton-phase` offsets the whole set).
7
9
  type Own = {
8
10
  width?: string;
9
11
  height?: string;
@@ -12,6 +14,7 @@
12
14
  /** Round avatar placeholder — width follows height. */
13
15
  circle?: boolean;
14
16
  radius?: string;
17
+ variant?: 'shimmer' | 'sheen' | 'pulse';
15
18
  class?: string;
16
19
  };
17
20
  let {
@@ -20,6 +23,7 @@
20
23
  lines = 1,
21
24
  circle = false,
22
25
  radius = 'var(--r-sm)',
26
+ variant = 'shimmer',
23
27
  class: klass = '',
24
28
  ...rest
25
29
  }: Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own = $props();
@@ -29,13 +33,14 @@
29
33
  const count = $derived(Math.max(1, Math.floor(lines)));
30
34
  </script>
31
35
 
32
- <span class="skeleton {klass}" data-tsu="Skeleton" aria-hidden="true" style:width={w} {...rest}>
36
+ <span class="skeleton v-{variant} {klass}" data-tsu="Skeleton" aria-hidden="true" style:width={w} {...rest}>
33
37
  {#each { length: count } as _, i (i)}
34
38
  <span
35
39
  class="skeleton-bar"
36
40
  style:height
37
41
  style:border-radius={r}
38
42
  style:width={count > 1 && i === count - 1 ? '60%' : undefined}
43
+ style:--i={i}
39
44
  ></span>
40
45
  {/each}
41
46
  </span>
@@ -72,4 +77,50 @@
72
77
  background: var(--sk-base);
73
78
  }
74
79
  }
80
+ .v-sheen .skeleton-bar,
81
+ .v-pulse .skeleton-bar {
82
+ background: var(--sk-base);
83
+ }
84
+ .v-sheen .skeleton-bar {
85
+ position: relative;
86
+ overflow: hidden;
87
+ animation: none;
88
+ }
89
+ .v-sheen .skeleton-bar::after {
90
+ content: '';
91
+ position: absolute;
92
+ inset: 0;
93
+ width: 45%;
94
+ background: linear-gradient(100deg, transparent 20%, var(--sk-hi) 50%, transparent 80%);
95
+ transform: translateX(-100%);
96
+ animation: tsu-skeleton-sheen 2s var(--ease-in-out) infinite;
97
+ }
98
+ @keyframes tsu-skeleton-sheen {
99
+ 70%,
100
+ 100% {
101
+ transform: translateX(260%);
102
+ }
103
+ }
104
+ .v-pulse .skeleton-bar {
105
+ animation: tsu-skeleton-pulse 1.6s var(--ease-in-out) infinite;
106
+ animation-delay: calc(0.13s * (var(--i, 0) + var(--skeleton-phase, 0)));
107
+ }
108
+ @keyframes tsu-skeleton-pulse {
109
+ 0%,
110
+ 100% {
111
+ opacity: 1;
112
+ }
113
+ 50% {
114
+ opacity: 0.45;
115
+ }
116
+ }
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .v-pulse .skeleton-bar {
119
+ animation: none;
120
+ }
121
+ .v-sheen .skeleton-bar::after {
122
+ animation: none;
123
+ transform: translateX(60%);
124
+ }
125
+ }
75
126
  </style>
@@ -7,6 +7,7 @@ type Own = {
7
7
  /** Round avatar placeholder — width follows height. */
8
8
  circle?: boolean;
9
9
  radius?: string;
10
+ variant?: 'shimmer' | 'sheen' | 'pulse';
10
11
  class?: string;
11
12
  };
12
13
  type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, keyof Own> & Own;
@@ -210,13 +210,13 @@
210
210
  background: linear-gradient(
211
211
  to right,
212
212
  var(--slider-accent, var(--accent)) var(--pct),
213
- var(--bg-elevated-2) var(--pct)
213
+ var(--track-fill) var(--pct)
214
214
  );
215
215
  }
216
216
  input[type='range']::-moz-range-track {
217
217
  height: var(--track);
218
218
  border-radius: var(--r-pill);
219
- background: var(--bg-elevated-2);
219
+ background: var(--track-fill);
220
220
  }
221
221
  input[type='range']::-moz-range-progress {
222
222
  height: var(--track);
@@ -233,7 +233,7 @@
233
233
  border-radius: var(--r-pill);
234
234
  background: var(--slider-accent, var(--accent));
235
235
  border: 2px solid var(--bg);
236
- box-shadow: var(--shadow-sm);
236
+ box-shadow: var(--thumb-shadow);
237
237
  transition:
238
238
  scale var(--dur-press) var(--ease-out),
239
239
  box-shadow var(--dur-fast) var(--ease);
@@ -244,17 +244,17 @@
244
244
  border-radius: var(--r-pill);
245
245
  background: var(--slider-accent, var(--accent));
246
246
  border: 2px solid var(--bg);
247
- box-shadow: var(--shadow-sm);
247
+ box-shadow: var(--thumb-shadow);
248
248
  transition:
249
249
  scale var(--dur-press) var(--ease-out),
250
250
  box-shadow var(--dur-fast) var(--ease);
251
251
  }
252
252
  @media (hover: hover) and (pointer: fine) {
253
253
  input[type='range']:not(:disabled):hover::-webkit-slider-thumb {
254
- box-shadow: var(--shadow-sm), var(--halo);
254
+ box-shadow: var(--thumb-shadow), var(--halo);
255
255
  }
256
256
  input[type='range']:not(:disabled):hover::-moz-range-thumb {
257
- box-shadow: var(--shadow-sm), var(--halo);
257
+ box-shadow: var(--thumb-shadow), var(--halo);
258
258
  }
259
259
  }
260
260
  input[type='range']:not(:disabled):active::-webkit-slider-thumb {
@@ -269,10 +269,12 @@
269
269
  input[type='range']:focus-visible::-webkit-slider-thumb {
270
270
  outline: var(--focus-ring);
271
271
  outline-offset: var(--focus-ring-offset);
272
+ box-shadow: var(--thumb-shadow), var(--focus-glow);
272
273
  }
273
274
  input[type='range']:focus-visible::-moz-range-thumb {
274
275
  outline: var(--focus-ring);
275
276
  outline-offset: var(--focus-ring-offset);
277
+ box-shadow: var(--thumb-shadow), var(--focus-glow);
276
278
  }
277
279
  @media (pointer: coarse) {
278
280
  input[type='range'] {
@@ -8,7 +8,11 @@
8
8
  | 'bars'
9
9
  | 'grid'
10
10
  | 'clock'
11
- | 'bounce';
11
+ | 'bounce'
12
+ | 'comet'
13
+ | 'breath'
14
+ | 'morph'
15
+ | 'sweep';
12
16
 
13
17
  // Inner spans per variant; `.in` wraps the flex/grid ones in an absolute
14
18
  // layer so the root keeps the same inline baseline as the others.
@@ -20,7 +24,11 @@
20
24
  bars: { count: 4, wrap: true },
21
25
  grid: { count: 9, wrap: true },
22
26
  clock: { count: 0, wrap: false },
23
- bounce: { count: 2, wrap: false }
27
+ bounce: { count: 2, wrap: false },
28
+ comet: { count: 1, wrap: false },
29
+ breath: { count: 5, wrap: true },
30
+ morph: { count: 0, wrap: false },
31
+ sweep: { count: 0, wrap: false }
24
32
  };
25
33
  </script>
26
34
 
@@ -29,7 +37,7 @@
29
37
  // other variants are pure-CSS pictograms drawn in currentColor inside a 1em
30
38
  // box. Every variant's base style is a meaningful static pose, which is what
31
39
  // shows under prefers-reduced-motion. `--spinner-speed` scales the tempo.
32
- // `clock` and `bounce` read best at 20px and up.
40
+ // `clock` and `bounce` read best at 20px and up; `breath` is 2.5em wide.
33
41
  import Icon from './Icon.svelte';
34
42
 
35
43
  let {
@@ -82,6 +90,11 @@
82
90
  inherits: false;
83
91
  initial-value: 100deg;
84
92
  }
93
+ @property --tsu-spin-sweep {
94
+ syntax: '<angle>';
95
+ inherits: false;
96
+ initial-value: 0deg;
97
+ }
85
98
 
86
99
  .spinner {
87
100
  --sw: max(1.5px, 0.11em);
@@ -472,6 +485,142 @@
472
485
  }
473
486
  }
474
487
 
488
+ .v-comet::before,
489
+ .v-comet > span,
490
+ .v-sweep::before {
491
+ content: '';
492
+ position: absolute;
493
+ inset: 0;
494
+ border-radius: 50%;
495
+ -webkit-mask: radial-gradient(
496
+ farthest-side,
497
+ transparent calc(100% - var(--sw)),
498
+ #000 calc(100% - var(--sw) + 0.5px)
499
+ );
500
+ mask: radial-gradient(
501
+ farthest-side,
502
+ transparent calc(100% - var(--sw)),
503
+ #000 calc(100% - var(--sw) + 0.5px)
504
+ );
505
+ }
506
+ .v-comet::before {
507
+ background: currentColor;
508
+ opacity: 0.1;
509
+ }
510
+ .v-comet > span {
511
+ background: conic-gradient(from 0deg, transparent 0 35%, currentColor 100%);
512
+ animation: tsu-spin-comet calc(var(--t) * 1.1) linear infinite;
513
+ }
514
+ .v-comet > span::after {
515
+ content: '';
516
+ position: absolute;
517
+ top: 0;
518
+ left: 50%;
519
+ width: var(--sw);
520
+ height: var(--sw);
521
+ border-radius: 50%;
522
+ background: currentColor;
523
+ box-shadow: 0 0 0.25em 0.04em currentColor;
524
+ transform: translateX(-50%);
525
+ }
526
+ @keyframes tsu-spin-comet {
527
+ to {
528
+ transform: rotate(360deg);
529
+ }
530
+ }
531
+
532
+ .v-sweep::before {
533
+ background: conic-gradient(
534
+ from var(--tsu-spin-sweep),
535
+ color-mix(in srgb, currentColor 18%, transparent),
536
+ currentColor 50%,
537
+ color-mix(in srgb, currentColor 18%, transparent)
538
+ );
539
+ animation: tsu-spin-sweep calc(var(--t) * 1.4) linear infinite;
540
+ }
541
+ @keyframes tsu-spin-sweep {
542
+ to {
543
+ --tsu-spin-sweep: 360deg;
544
+ }
545
+ }
546
+
547
+ .v-morph::before {
548
+ content: '';
549
+ position: absolute;
550
+ inset: 0.08em;
551
+ border-radius: 50%;
552
+ background: currentColor;
553
+ animation: tsu-spin-morph calc(var(--t) * 2.4) cubic-bezier(0.77, 0, 0.175, 1) infinite;
554
+ }
555
+ @keyframes tsu-spin-morph {
556
+ 0% {
557
+ border-radius: 50%;
558
+ transform: rotate(0deg) scale(1);
559
+ }
560
+ 25% {
561
+ border-radius: 24%;
562
+ transform: rotate(90deg) scale(0.92);
563
+ }
564
+ 50% {
565
+ border-radius: 50%;
566
+ transform: rotate(180deg) scale(1);
567
+ }
568
+ 75% {
569
+ border-radius: 24%;
570
+ transform: rotate(270deg) scale(0.92);
571
+ }
572
+ 100% {
573
+ border-radius: 50%;
574
+ transform: rotate(360deg) scale(1);
575
+ }
576
+ }
577
+
578
+ .v-breath {
579
+ width: 2.5em;
580
+ }
581
+ .v-breath .in {
582
+ display: flex;
583
+ align-items: center;
584
+ justify-content: space-between;
585
+ }
586
+ .v-breath .in span {
587
+ width: 0.34em;
588
+ height: 0.34em;
589
+ border-radius: 50%;
590
+ background: currentColor;
591
+ animation: tsu-spin-breath calc(var(--t) * 1.3) cubic-bezier(0.77, 0, 0.175, 1) infinite;
592
+ }
593
+ .v-breath .in span:nth-child(1) {
594
+ opacity: 1;
595
+ animation-delay: calc(var(--t) * -0.52);
596
+ }
597
+ .v-breath .in span:nth-child(2) {
598
+ opacity: 0.75;
599
+ animation-delay: calc(var(--t) * -0.39);
600
+ }
601
+ .v-breath .in span:nth-child(3) {
602
+ opacity: 0.55;
603
+ animation-delay: calc(var(--t) * -0.26);
604
+ }
605
+ .v-breath .in span:nth-child(4) {
606
+ opacity: 0.4;
607
+ animation-delay: calc(var(--t) * -0.13);
608
+ }
609
+ .v-breath .in span:nth-child(5) {
610
+ opacity: 0.28;
611
+ }
612
+ @keyframes tsu-spin-breath {
613
+ 0%,
614
+ 100% {
615
+ opacity: 0.3;
616
+ transform: scale(0.6);
617
+ }
618
+ 40% {
619
+ opacity: 1;
620
+ transform: scale(1);
621
+ }
622
+ }
623
+
475
624
  @media (prefers-reduced-motion: reduce) {
476
625
  .spinner.spinner,
477
626
  .spinner.spinner span,
@@ -1,4 +1,4 @@
1
- export type SpinnerVariant = 'icon' | 'arc' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'grid' | 'clock' | 'bounce';
1
+ export type SpinnerVariant = 'icon' | 'arc' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'grid' | 'clock' | 'bounce' | 'comet' | 'breath' | 'morph' | 'sweep';
2
2
  type $$ComponentProps = {
3
3
  variant?: SpinnerVariant;
4
4
  /** Explicit pixel size. Omit to track the surrounding text (1em). */
@@ -70,7 +70,7 @@
70
70
  height: var(--swatch-size);
71
71
  padding: 0;
72
72
  margin: 0;
73
- border: 1px solid var(--border);
73
+ border: var(--edge-width) solid var(--border);
74
74
  border-radius: var(--r-pill);
75
75
  background: var(--swatch-color);
76
76
  color: var(--text);