@dorsk/tsumikit 0.73.2 → 0.75.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 (137) hide show
  1. package/README.md +38 -1
  2. package/dist/autoresize.d.ts +2 -0
  3. package/dist/autoresize.js +8 -1
  4. package/dist/components/atoms/Artwork.svelte +12 -5
  5. package/dist/components/atoms/Avatar.svelte +1 -1
  6. package/dist/components/atoms/Badge.svelte +59 -23
  7. package/dist/components/atoms/Button.svelte +130 -96
  8. package/dist/components/atoms/Button.svelte.d.ts +2 -0
  9. package/dist/components/atoms/Card.svelte +10 -4
  10. package/dist/components/atoms/Checkbox.svelte +36 -9
  11. package/dist/components/atoms/Checkbox.svelte.d.ts +2 -0
  12. package/dist/components/atoms/Gauge.svelte +20 -14
  13. package/dist/components/atoms/Heading.svelte +1 -1
  14. package/dist/components/atoms/Icon.svelte +1 -0
  15. package/dist/components/atoms/Input.svelte +67 -13
  16. package/dist/components/atoms/Kbd.svelte +3 -3
  17. package/dist/components/atoms/Link.svelte +7 -2
  18. package/dist/components/atoms/Progress.svelte +12 -6
  19. package/dist/components/atoms/Scrim.svelte +3 -8
  20. package/dist/components/atoms/SegmentedProgress.svelte +5 -4
  21. package/dist/components/atoms/Select.svelte +58 -27
  22. package/dist/components/atoms/Skeleton.svelte +14 -8
  23. package/dist/components/atoms/Slider.svelte +79 -22
  24. package/dist/components/atoms/Slider.svelte.d.ts +2 -0
  25. package/dist/components/atoms/Spinner.svelte +464 -5
  26. package/dist/components/atoms/Spinner.svelte.d.ts +2 -0
  27. package/dist/components/atoms/Swatch.svelte +19 -8
  28. package/dist/components/atoms/Switch.svelte +34 -21
  29. package/dist/components/atoms/Switch.svelte.d.ts +2 -0
  30. package/dist/components/atoms/Text.svelte +1 -1
  31. package/dist/components/atoms/Textarea.svelte +34 -12
  32. package/dist/components/atoms/TimeDisplay.svelte +4 -2
  33. package/dist/components/layouts/AppShell.svelte +26 -15
  34. package/dist/components/layouts/MasterDetail.svelte +23 -21
  35. package/dist/components/layouts/NavBar.svelte +11 -8
  36. package/dist/components/layouts/NavItem.svelte +15 -7
  37. package/dist/components/layouts/NavSection.svelte +1 -1
  38. package/dist/components/layouts/ResizablePanel.svelte +37 -16
  39. package/dist/components/layouts/TabBar.svelte +24 -14
  40. package/dist/components/layouts/TabBar.svelte.d.ts +2 -0
  41. package/dist/components/molecules/Accordion.svelte +4 -0
  42. package/dist/components/molecules/Accordion.svelte.d.ts +2 -0
  43. package/dist/components/molecules/AttachmentList.svelte +14 -11
  44. package/dist/components/molecules/Breadcrumb.svelte +13 -9
  45. package/dist/components/molecules/Callout.svelte +3 -3
  46. package/dist/components/molecules/CapBar.svelte +23 -22
  47. package/dist/components/molecules/Carousel.svelte +27 -17
  48. package/dist/components/molecules/ChatBubble.svelte +2 -2
  49. package/dist/components/molecules/CodeBlock.svelte +2 -2
  50. package/dist/components/molecules/Combobox.svelte +30 -1
  51. package/dist/components/molecules/Combobox.svelte.d.ts +4 -0
  52. package/dist/components/molecules/Composer.svelte +2 -2
  53. package/dist/components/molecules/ConfirmModal.svelte +4 -0
  54. package/dist/components/molecules/ConfirmModal.svelte.d.ts +2 -0
  55. package/dist/components/molecules/CopyButton.svelte +46 -2
  56. package/dist/components/molecules/CopyButton.svelte.d.ts +2 -0
  57. package/dist/components/molecules/Disclosure.svelte +23 -12
  58. package/dist/components/molecules/Disclosure.svelte.d.ts +2 -0
  59. package/dist/components/molecules/Drawer.svelte +43 -23
  60. package/dist/components/molecules/Drawer.svelte.d.ts +2 -0
  61. package/dist/components/molecules/Dropzone.svelte +24 -8
  62. package/dist/components/molecules/EmojiPicker.svelte +27 -8
  63. package/dist/components/molecules/EmojiPicker.svelte.d.ts +2 -0
  64. package/dist/components/molecules/EmptyState.svelte +5 -5
  65. package/dist/components/molecules/Fieldset.svelte +8 -13
  66. package/dist/components/molecules/FileButton.svelte +57 -31
  67. package/dist/components/molecules/FileButton.svelte.d.ts +2 -0
  68. package/dist/components/molecules/FilterInput.svelte +51 -11
  69. package/dist/components/molecules/FontScalePicker.svelte +20 -9
  70. package/dist/components/molecules/GitRef.svelte +9 -7
  71. package/dist/components/molecules/GlyphStack.svelte +38 -14
  72. package/dist/components/molecules/IconButton.svelte +6 -3
  73. package/dist/components/molecules/IconButton.svelte.d.ts +2 -0
  74. package/dist/components/molecules/InputGroup.svelte +13 -12
  75. package/dist/components/molecules/LoadMore.svelte +6 -1
  76. package/dist/components/molecules/LoadMore.svelte.d.ts +2 -0
  77. package/dist/components/molecules/Menu.svelte +33 -7
  78. package/dist/components/molecules/Menu.svelte.d.ts +2 -0
  79. package/dist/components/molecules/Metric.svelte +11 -9
  80. package/dist/components/molecules/Modal.svelte +76 -26
  81. package/dist/components/molecules/Modal.svelte.d.ts +2 -0
  82. package/dist/components/molecules/OptionButton.svelte +28 -10
  83. package/dist/components/molecules/OptionButton.svelte.d.ts +2 -0
  84. package/dist/components/molecules/Pagination.svelte +26 -9
  85. package/dist/components/molecules/Pagination.svelte.d.ts +2 -0
  86. package/dist/components/molecules/Popover.svelte +107 -51
  87. package/dist/components/molecules/Popover.svelte.d.ts +2 -0
  88. package/dist/components/molecules/Prose.svelte +1 -1
  89. package/dist/components/molecules/PullToRefresh.svelte +14 -10
  90. package/dist/components/molecules/PullToRefresh.svelte.d.ts +2 -0
  91. package/dist/components/molecules/RadialMenu.svelte +52 -19
  92. package/dist/components/molecules/RadialMenu.svelte.d.ts +2 -0
  93. package/dist/components/molecules/RadioGroup.svelte +38 -23
  94. package/dist/components/molecules/RadioGroup.svelte.d.ts +2 -0
  95. package/dist/components/molecules/SectionHeader.svelte +13 -8
  96. package/dist/components/molecules/SeekSlider.svelte +38 -27
  97. package/dist/components/molecules/SeekSlider.svelte.d.ts +2 -0
  98. package/dist/components/molecules/SegmentedControl.svelte +139 -86
  99. package/dist/components/molecules/SegmentedControl.svelte.d.ts +2 -0
  100. package/dist/components/molecules/SelectButton.svelte +21 -6
  101. package/dist/components/molecules/SelectButton.svelte.d.ts +2 -0
  102. package/dist/components/molecules/SortableList.svelte +10 -11
  103. package/dist/components/molecules/SoundSettings.svelte +161 -0
  104. package/dist/components/molecules/SoundSettings.svelte.d.ts +17 -0
  105. package/dist/components/molecules/SplitButton.svelte +30 -3
  106. package/dist/components/molecules/SplitButton.svelte.d.ts +2 -0
  107. package/dist/components/molecules/SwipeRow.svelte +18 -15
  108. package/dist/components/molecules/SwipeRow.svelte.d.ts +2 -0
  109. package/dist/components/molecules/Tabs.svelte +92 -10
  110. package/dist/components/molecules/Tabs.svelte.d.ts +2 -0
  111. package/dist/components/molecules/ThemePicker.svelte +25 -10
  112. package/dist/components/molecules/Timestamp.svelte +12 -7
  113. package/dist/components/molecules/Toaster.svelte +65 -14
  114. package/dist/components/molecules/Toaster.svelte.d.ts +2 -0
  115. package/dist/components/molecules/Toggle.svelte +36 -13
  116. package/dist/components/molecules/Toggle.svelte.d.ts +2 -0
  117. package/dist/components/molecules/Tooltip.svelte +41 -10
  118. package/dist/components/molecules/Tooltip.svelte.d.ts +1 -1
  119. package/dist/components/molecules/VolumeSlider.svelte +34 -14
  120. package/dist/components/molecules/VolumeSlider.svelte.d.ts +2 -0
  121. package/dist/components/molecules/WorkingDir.svelte +1 -0
  122. package/dist/components/organisms/ConversationFeed.svelte +4 -0
  123. package/dist/components/organisms/DataTable.svelte +37 -14
  124. package/dist/floating.js +26 -0
  125. package/dist/index.d.ts +4 -0
  126. package/dist/index.js +4 -1
  127. package/dist/sound-synth.d.ts +3 -0
  128. package/dist/sound-synth.js +91 -0
  129. package/dist/sound.d.ts +1 -0
  130. package/dist/sound.js +3 -0
  131. package/dist/stores/sound.svelte.d.ts +20 -0
  132. package/dist/stores/sound.svelte.js +150 -0
  133. package/dist/stores/toast.svelte.d.ts +9 -0
  134. package/dist/stores/toast.svelte.js +45 -9
  135. package/dist/styles/reset.css +19 -2
  136. package/dist/styles/tokens.css +89 -6
  137. package/package.json +1 -1
@@ -76,7 +76,7 @@
76
76
  color: var(--text);
77
77
  font: inherit;
78
78
  font-size: calc(var(--swatch-size) * 0.6);
79
- line-height: 1;
79
+ line-height: var(--lh-none);
80
80
  vertical-align: middle;
81
81
  box-sizing: border-box;
82
82
  }
@@ -90,7 +90,7 @@
90
90
  border-radius: var(--r-sm);
91
91
  }
92
92
  .sm.square {
93
- border-radius: 3px;
93
+ border-radius: var(--r-xs);
94
94
  }
95
95
  .selected {
96
96
  border-color: var(--text);
@@ -100,16 +100,27 @@
100
100
  }
101
101
  .interactive {
102
102
  cursor: pointer;
103
+ --swatch-scale-dur: var(--dur-fast);
103
104
  transition:
104
- transform 0.1s var(--ease),
105
- box-shadow 0.12s var(--ease);
105
+ scale var(--swatch-scale-dur) var(--ease-out),
106
+ border-color var(--dur-fast) ease,
107
+ box-shadow var(--dur-fast) ease;
106
108
  }
107
- .interactive:hover {
108
- transform: scale(1.15);
109
+ .interactive:active {
110
+ --swatch-scale-dur: var(--dur-press);
111
+ scale: var(--press-scale-sm);
109
112
  }
110
113
  .interactive:focus-visible {
111
- outline: 2px solid var(--accent);
112
- outline-offset: 3px;
114
+ outline: var(--focus-ring);
115
+ outline-offset: var(--focus-ring-offset);
116
+ }
117
+ @media (hover: hover) and (pointer: fine) {
118
+ .interactive:hover {
119
+ scale: 1.15;
120
+ }
121
+ .interactive:hover:active {
122
+ scale: calc(1.15 * var(--press-scale-sm));
123
+ }
113
124
  }
114
125
  @media (pointer: coarse) {
115
126
  .interactive {
@@ -5,6 +5,7 @@
5
5
  // `label` is the accessible name; `labelVisible` renders it beside the track.
6
6
  import type { HTMLButtonAttributes } from 'svelte/elements';
7
7
  import { getFieldContext } from '../../field-context';
8
+ import { playSound } from '../../sound';
8
9
 
9
10
  let {
10
11
  checked = $bindable(false),
@@ -12,6 +13,7 @@
12
13
  label,
13
14
  labelVisible = false,
14
15
  size = 'md',
16
+ sound = true,
15
17
  onclick,
16
18
  id,
17
19
  'aria-describedby': ariaDescribedby,
@@ -25,6 +27,8 @@
25
27
  /** Render `label` as visible text beside the track. */
26
28
  labelVisible?: boolean;
27
29
  size?: ControlSize;
30
+ /** Play UI sounds when enabled globally; false silences this instance. */
31
+ sound?: boolean;
28
32
  id?: string;
29
33
  'aria-describedby'?: string | null;
30
34
  'aria-invalid'?: HTMLButtonAttributes['aria-invalid'];
@@ -37,6 +41,7 @@
37
41
  onclick?.(e);
38
42
  if (e.defaultPrevented) return;
39
43
  checked = !checked;
44
+ playSound(checked ? 'toggle-on' : 'toggle-off', sound);
40
45
  }
41
46
  </script>
42
47
 
@@ -70,59 +75,67 @@
70
75
  {/if}
71
76
 
72
77
  <style>
78
+ /* Track and knob sit on the 4px grid: md 24 / sm 20 tall, knob = track minus
79
+ border and padding, travel = track width minus the knob and insets. */
73
80
  .switch {
81
+ --sw-h: var(--chip-md);
82
+ --sw-w: 2.75rem;
83
+ --sw-inset: calc(var(--sp-0-5) + 1px);
84
+ --sw-knob: calc(var(--sw-h) - 2 * var(--sw-inset));
74
85
  flex: none;
75
- width: 2.75rem;
76
- height: 1.6rem;
86
+ width: var(--sw-w);
87
+ height: var(--sw-h);
77
88
  border-radius: var(--r-pill);
78
89
  border: 1px solid var(--border-strong);
79
90
  background: var(--bg-elevated-2);
80
- padding: 2px;
91
+ padding: var(--sp-0-5);
81
92
  display: flex;
82
93
  align-items: center;
83
94
  cursor: pointer;
84
95
  transition:
85
- background 0.14s var(--ease),
86
- border-color 0.14s var(--ease);
96
+ background var(--dur-fast) var(--ease),
97
+ border-color var(--dur-fast) var(--ease);
87
98
  }
88
99
  .switch .knob {
89
- width: 1.25rem;
90
- height: 1.25rem;
91
- border-radius: 50%;
100
+ width: var(--sw-knob);
101
+ height: var(--sw-knob);
102
+ border-radius: var(--r-pill);
92
103
  background: var(--text-muted);
104
+ transform-origin: left center;
93
105
  transition:
94
- transform 0.14s var(--ease),
95
- background 0.14s var(--ease);
106
+ translate var(--dur-spring) var(--ease-spring),
107
+ scale var(--dur-fast) var(--ease-out),
108
+ background var(--dur-fast) var(--ease);
96
109
  }
97
110
  .switch.on {
98
111
  background: var(--accent);
99
112
  border-color: var(--accent);
100
113
  }
101
114
  .switch.on .knob {
102
- transform: translateX(1.15rem);
115
+ translate: calc(var(--sw-w) - var(--sw-knob) - 2 * var(--sw-inset)) 0;
103
116
  background: var(--text-on-accent);
117
+ transform-origin: right center;
118
+ }
119
+ /* 1.12 stretch toward the travel side; derived from the press tokens so the
120
+ reduced-motion root (press scales = 1) flattens it to 1. */
121
+ .switch:active:not(:disabled) .knob {
122
+ scale: calc(1 + (var(--press-scale) - var(--press-scale-sm)) * 2.4) 1;
123
+ transition-duration: var(--dur-press);
104
124
  }
105
125
  .switch:focus-visible {
106
126
  outline: var(--focus-ring);
107
127
  outline-offset: var(--focus-ring-offset);
108
128
  }
109
129
  .switch:disabled {
110
- opacity: 0.45;
130
+ opacity: var(--disabled-opacity);
111
131
  cursor: not-allowed;
112
132
  }
113
133
  .switch[aria-invalid='true']:not(.on) {
114
134
  border-color: var(--danger);
115
135
  }
116
136
  .switch-sm {
117
- width: 2rem;
118
- height: 1.2rem;
119
- }
120
- .switch-sm .knob {
121
- width: 0.9rem;
122
- height: 0.9rem;
123
- }
124
- .switch-sm.on .knob {
125
- transform: translateX(0.8rem);
137
+ --sw-h: var(--chip-sm);
138
+ --sw-w: 2rem;
126
139
  }
127
140
  @media (pointer: coarse) {
128
141
  .switch {
@@ -7,6 +7,8 @@ type $$ComponentProps = HTMLButtonAttributes & {
7
7
  /** Render `label` as visible text beside the track. */
8
8
  labelVisible?: boolean;
9
9
  size?: ControlSize;
10
+ /** Play UI sounds when enabled globally; false silences this instance. */
11
+ sound?: boolean;
10
12
  id?: string;
11
13
  'aria-describedby'?: string | null;
12
14
  'aria-invalid'?: HTMLButtonAttributes['aria-invalid'];
@@ -232,7 +232,7 @@
232
232
  line-height: var(--lh-normal);
233
233
  }
234
234
  .lh-none {
235
- line-height: 1;
235
+ line-height: var(--lh-none);
236
236
  }
237
237
  .grow {
238
238
  flex: 1 1 0;
@@ -266,23 +266,29 @@
266
266
  position: relative;
267
267
  display: flex;
268
268
  }
269
+ /* The wrap dims as one so the resize grip fades with the field; inside an
270
+ InputGroup the group dims instead. */
271
+ .textarea-wrap:has(.textarea:disabled):not(.grouped) {
272
+ opacity: var(--disabled-opacity);
273
+ }
269
274
  .textarea {
270
275
  width: 100%;
271
276
  /* Horizontal padding matches Input/Button; vertical padding is tighter
272
277
  (--sp-2) so a single line-box + border actually fits the 2.5rem floor —
273
278
  --sp-3 top+bottom would overshoot it and a `rows={1}` textarea would
274
279
  render taller than the buttons it sits beside. */
275
- padding: var(--sp-2) var(--sp-3);
280
+ padding: var(--sp-2) var(--control-px-md);
276
281
  background: var(--textarea-bg, var(--bg));
277
282
  border: 1px solid var(--textarea-border, var(--border-strong));
278
283
  border-radius: var(--textarea-radius, var(--r-md));
279
284
  color: var(--textarea-fg, var(--text));
280
- transition: border-color 0.12s var(--ease);
285
+ transition: border-color var(--dur-fast) ease;
281
286
  /* Custom handle replaces the native grip; never show the native one. */
282
287
  resize: none;
283
288
  /* Match the single-row height of Button/Input so a `rows={1}` textarea
284
289
  lines up with them; the native `rows` attribute grows it from here. */
285
- min-height: var(--textarea-size, 2.5rem);
290
+ min-height: var(--textarea-size, var(--control-height-default));
291
+ font-size: var(--control-fs-md);
286
292
  line-height: var(--lh-tight);
287
293
  font-family: inherit;
288
294
  }
@@ -294,24 +300,33 @@
294
300
  outline: var(--focus-ring);
295
301
  outline-offset: var(--focus-ring-offset);
296
302
  }
303
+ .textarea:disabled {
304
+ cursor: not-allowed;
305
+ }
297
306
  .textarea.capped {
298
307
  overflow-y: auto;
299
308
  }
300
309
  .textarea-sm {
301
- padding: var(--sp-1) var(--sp-2);
302
- font-size: var(--fs-sm);
303
- min-height: var(--textarea-size, 2rem);
310
+ padding: var(--sp-1) var(--control-px-sm);
311
+ font-size: var(--control-fs-sm);
312
+ min-height: var(--textarea-size, var(--control-height-compact));
304
313
  }
305
314
  .textarea-lg {
306
- padding: var(--sp-3) var(--sp-4);
307
- font-size: max(16px, var(--fs-md));
315
+ padding: var(--sp-3) var(--control-px-lg);
316
+ font-size: var(--control-fs-lg);
308
317
  min-height: var(--textarea-size, var(--control-height-large));
309
318
  }
310
319
  /* iOS auto-zooms any field under 16px on focus; on touch the size modifiers
311
320
  carry their difference through padding and height alone. */
312
321
  @media (pointer: coarse) {
322
+ .textarea {
323
+ font-size: max(16px, var(--control-fs-md));
324
+ }
313
325
  .textarea-sm {
314
- font-size: max(16px, var(--fs-sm));
326
+ font-size: max(16px, var(--control-fs-sm));
327
+ }
328
+ .textarea-lg {
329
+ font-size: max(16px, var(--control-fs-lg));
315
330
  }
316
331
  }
317
332
  .textarea[aria-invalid='true'],
@@ -378,9 +393,9 @@
378
393
  transform: translateX(-50%);
379
394
  width: 28px;
380
395
  height: 3px;
381
- border-radius: 999px;
396
+ border-radius: var(--r-pill);
382
397
  background: var(--border-strong);
383
- transition: background 0.12s var(--ease);
398
+ transition: background-color var(--dur-fast) ease;
384
399
  }
385
400
  .resize-bottom::after {
386
401
  bottom: 3px;
@@ -388,8 +403,15 @@
388
403
  .resize-top::after {
389
404
  top: 3px;
390
405
  }
391
- .resize-handle:hover::after,
392
406
  .dragging .resize-handle::after {
393
407
  background: var(--accent);
394
408
  }
409
+ @media (hover: hover) and (pointer: fine) {
410
+ .textarea:hover:not(:disabled, :focus, [aria-invalid='true']) {
411
+ border-color: var(--text-faint);
412
+ }
413
+ .resize-handle:hover::after {
414
+ background: var(--accent);
415
+ }
416
+ }
395
417
  </style>
@@ -84,8 +84,10 @@
84
84
  cursor: pointer;
85
85
  border-radius: var(--r-sm);
86
86
  }
87
- .td--button:hover {
88
- color: var(--text);
87
+ @media (hover: hover) and (pointer: fine) {
88
+ .td--button:hover {
89
+ color: var(--text);
90
+ }
89
91
  }
90
92
  .td--button:focus-visible {
91
93
  outline: var(--focus-ring);
@@ -326,8 +326,8 @@
326
326
  /* A query container named `sidebar` so nav items collapse to an icon rail
327
327
  based on the sidebar's own width (see NavItem). */
328
328
  container: sidebar / inline-size;
329
- transform: translateX(-100%);
330
- transition: transform 0.2s var(--ease);
329
+ translate: -100% 0;
330
+ transition: translate var(--dur-exit-slow) var(--ease-drawer);
331
331
  }
332
332
  .shell-sidebar[data-padding='sm'] {
333
333
  --shell-sidebar-pad: var(--sp-2);
@@ -336,8 +336,9 @@
336
336
  --shell-sidebar-pad: 0px;
337
337
  }
338
338
  .shell-sidebar.open {
339
- transform: translateX(0);
339
+ translate: 0 0;
340
340
  box-shadow: var(--shadow-lg);
341
+ transition-duration: var(--dur-sheet);
341
342
  }
342
343
  .shell-scrim {
343
344
  display: none;
@@ -347,9 +348,20 @@
347
348
  background: rgba(0, 0, 0, 0.5);
348
349
  border: 0;
349
350
  cursor: pointer;
351
+ opacity: 0;
352
+ transition:
353
+ opacity var(--dur-exit) var(--ease-out),
354
+ display var(--dur-exit) allow-discrete;
350
355
  }
351
356
  .shell-scrim.show {
352
357
  display: block;
358
+ opacity: 1;
359
+ transition-duration: var(--dur-base);
360
+ }
361
+ @starting-style {
362
+ .shell-scrim.show {
363
+ opacity: 0;
364
+ }
353
365
  }
354
366
 
355
367
  /* Resize handle lives only on the desktop persistent column. */
@@ -379,7 +391,8 @@
379
391
  .shell-sidebar {
380
392
  position: relative; /* anchor the absolute resize handle */
381
393
  grid-area: sidebar;
382
- transform: none;
394
+ translate: none;
395
+ transition: none;
383
396
  z-index: auto;
384
397
  box-shadow: none;
385
398
  border-right: 1px solid var(--border);
@@ -415,18 +428,22 @@
415
428
  transform: translateY(-50%);
416
429
  width: 3px;
417
430
  height: 28px;
418
- border-radius: 999px;
431
+ border-radius: var(--r-pill);
419
432
  background: var(--border-strong);
420
- transition: background 0.12s var(--ease);
433
+ transition: background var(--dur-fast) var(--ease);
421
434
  }
422
- .shell-sidebar-resize:hover::after,
423
435
  .shell-sidebar-resize:focus-visible::after,
424
436
  .shell.dragging .shell-sidebar-resize::after {
425
437
  background: var(--accent);
426
438
  }
427
439
  .shell-sidebar-resize:focus-visible {
428
- outline: 2px solid var(--accent);
429
- outline-offset: -2px;
440
+ outline: var(--focus-ring);
441
+ outline-offset: var(--focus-ring-offset-inset);
442
+ }
443
+ }
444
+ @media (min-width: 48rem) and (hover: hover) and (pointer: fine) {
445
+ .shell-sidebar-resize:hover::after {
446
+ background: var(--accent);
430
447
  }
431
448
  }
432
449
 
@@ -443,10 +460,4 @@
443
460
  display: none;
444
461
  }
445
462
  }
446
-
447
- @media (prefers-reduced-motion: reduce) {
448
- .shell-sidebar {
449
- transition: none;
450
- }
451
- }
452
463
  </style>
@@ -208,12 +208,14 @@
208
208
  cursor: pointer;
209
209
  flex-shrink: 0;
210
210
  }
211
- .md-back:hover {
212
- background: color-mix(in srgb, var(--accent) 12%, transparent);
211
+ @media (hover: hover) and (pointer: fine) {
212
+ .md-back:hover {
213
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
214
+ }
213
215
  }
214
216
  .md-back:focus-visible {
215
- outline: 2px solid var(--accent);
216
- outline-offset: 2px;
217
+ outline: var(--focus-ring);
218
+ outline-offset: var(--focus-ring-offset-inset);
217
219
  }
218
220
 
219
221
  .md.mobile {
@@ -228,7 +230,23 @@
228
230
  }
229
231
  .md.mobile .md-detail {
230
232
  width: 100%;
231
- animation: md-slide-in 0.2s var(--ease);
233
+ translate: 0 0;
234
+ opacity: 1;
235
+ transition:
236
+ translate var(--dur-base) var(--ease-out),
237
+ opacity var(--dur-base) var(--ease-out),
238
+ display var(--dur-base) allow-discrete;
239
+ }
240
+ @starting-style {
241
+ .md.mobile .md-detail {
242
+ translate: var(--shift-md) 0;
243
+ opacity: 0;
244
+ }
245
+ }
246
+ .md.mobile .md-detail[hidden] {
247
+ translate: var(--shift-md) 0;
248
+ opacity: 0;
249
+ transition-duration: var(--dur-exit);
232
250
  }
233
251
  .md.mobile .md-detail-header {
234
252
  padding-inline: max(var(--sp-3), var(--safe-left)) max(var(--sp-3), var(--safe-right));
@@ -237,20 +255,4 @@
237
255
  padding-inline: var(--safe-left) var(--safe-right);
238
256
  padding-bottom: var(--safe-bottom);
239
257
  }
240
-
241
- @keyframes md-slide-in {
242
- from {
243
- transform: translateX(8%);
244
- opacity: 0;
245
- }
246
- to {
247
- transform: none;
248
- opacity: 1;
249
- }
250
- }
251
- @media (prefers-reduced-motion: reduce) {
252
- .md.mobile .md-detail {
253
- animation: none;
254
- }
255
- }
256
258
  </style>
@@ -275,18 +275,21 @@
275
275
  text-decoration: none;
276
276
  cursor: pointer;
277
277
  transition:
278
- background 0.12s var(--ease),
279
- color 0.12s var(--ease);
278
+ background var(--dur-fast) var(--ease),
279
+ color var(--dur-fast) var(--ease);
280
280
  }
281
281
  .stacked .navbar-link {
282
282
  flex-direction: column;
283
- gap: 2px;
283
+ gap: var(--sp-0-5);
284
284
  font-size: var(--fs-xs);
285
285
  }
286
- .navbar-link:hover {
287
- background: var(--bg-elevated-2);
288
- color: var(--text);
289
- text-decoration: none;
286
+ @media (hover: hover) and (pointer: fine) {
287
+ .navbar-link:hover {
288
+ background: var(--bg-elevated-2);
289
+ color: var(--text);
290
+ text-decoration: none;
291
+ transition-duration: var(--dur-instant);
292
+ }
290
293
  }
291
294
  .navbar-link.current {
292
295
  color: var(--accent);
@@ -300,7 +303,7 @@
300
303
  }
301
304
  .navbar-emoji {
302
305
  font-size: 1.15em;
303
- line-height: 1;
306
+ line-height: var(--lh-none);
304
307
  }
305
308
  .navbar-label {
306
309
  min-width: 0;
@@ -115,13 +115,21 @@
115
115
  text-decoration: none;
116
116
  white-space: nowrap;
117
117
  transition:
118
- background 0.12s var(--ease),
119
- color 0.12s var(--ease);
118
+ background var(--dur-fast) var(--ease),
119
+ color var(--dur-fast) var(--ease);
120
120
  }
121
- .nav-item:hover {
122
- background: var(--bg-elevated-2);
123
- color: var(--text);
124
- text-decoration: none;
121
+ @media (hover: hover) and (pointer: fine) {
122
+ .nav-item:hover {
123
+ background: var(--bg-elevated-2);
124
+ color: var(--text);
125
+ text-decoration: none;
126
+ transition-duration: var(--dur-instant);
127
+ }
128
+ }
129
+ /* Nav items live in scrolling sidebars and lists; the ring stays inside. */
130
+ .nav-item:focus-visible {
131
+ outline: var(--focus-ring);
132
+ outline-offset: var(--focus-ring-offset-inset);
125
133
  }
126
134
  .nav-item.active {
127
135
  background: color-mix(in srgb, var(--accent) 14%, transparent);
@@ -158,7 +166,7 @@
158
166
  .nav-item.vertical {
159
167
  flex-direction: column;
160
168
  justify-content: center;
161
- gap: 2px;
169
+ gap: var(--sp-0-5);
162
170
  padding: var(--sp-1) var(--sp-2);
163
171
  font-size: var(--fs-xs);
164
172
  text-align: center;
@@ -62,7 +62,7 @@
62
62
  .nav-section-items {
63
63
  display: flex;
64
64
  flex-direction: column;
65
- gap: 2px;
65
+ gap: var(--sp-0-5);
66
66
  }
67
67
 
68
68
  /* Icon-rail: drop the textual label and use a hairline divider to keep groups
@@ -364,7 +364,7 @@
364
364
  style="transform: translateY({stickyShift}px)"
365
365
  onclick={toggle}
366
366
  >
367
- <Icon name={toggleIcon} size={14} />
367
+ <Icon name={toggleIcon} />
368
368
  </button>
369
369
  {/if}
370
370
  </svelte:element>
@@ -403,7 +403,12 @@
403
403
  width: var(--panel-current-width);
404
404
  background: var(--bg-elevated);
405
405
  border-right: 1px solid var(--border);
406
- transition: width 0.18s var(--ease), box-shadow 0.18s var(--ease);
406
+ transition:
407
+ width var(--dur-base) var(--ease),
408
+ box-shadow var(--dur-base) var(--ease);
409
+ }
410
+ .resizing .panel {
411
+ transition: none;
407
412
  }
408
413
  .right .panel {
409
414
  grid-column: 2;
@@ -442,14 +447,19 @@
442
447
  padding: 0;
443
448
  color: var(--text-faint);
444
449
  font: inherit;
450
+ font-size: var(--icon-sm);
445
451
  background: transparent;
446
452
  border: 1px solid transparent;
447
453
  border-radius: var(--r-sm);
448
454
  cursor: pointer;
449
455
  transition:
450
- color 0.12s var(--ease),
451
- background 0.12s var(--ease),
452
- border-color 0.12s var(--ease);
456
+ color var(--dur-fast) var(--ease),
457
+ background var(--dur-fast) var(--ease),
458
+ border-color var(--dur-fast) var(--ease),
459
+ scale var(--dur-press) var(--ease-out);
460
+ }
461
+ .collapse-control:active {
462
+ scale: var(--press-scale-sm);
453
463
  }
454
464
  .collapse-control.top {
455
465
  top: var(--sp-2);
@@ -461,15 +471,17 @@
461
471
  left: auto;
462
472
  right: var(--sp-1);
463
473
  }
464
- .collapse-control:hover {
465
- color: var(--text);
466
- background: var(--bg-elevated-2);
467
- border-color: var(--border);
474
+ @media (hover: hover) and (pointer: fine) {
475
+ .collapse-control:hover {
476
+ color: var(--text);
477
+ background: var(--bg-elevated-2);
478
+ border-color: var(--border);
479
+ }
468
480
  }
469
481
  .collapse-control:focus-visible,
470
482
  .resize-handle:focus-visible {
471
- outline: 2px solid var(--accent);
472
- outline-offset: -2px;
483
+ outline: var(--focus-ring);
484
+ outline-offset: var(--focus-ring-offset-inset);
473
485
  }
474
486
  /* The handle overhangs the panel edge by 6px, but never past the host
475
487
  container: when the panel fills it (edge at the window border), the
@@ -503,12 +515,19 @@
503
515
  border-radius: var(--r-pill);
504
516
  background: var(--border-strong);
505
517
  transform: translate(-50%, -50%);
506
- transition: background 0.12s var(--ease), height 0.12s var(--ease);
518
+ transition:
519
+ background var(--dur-fast) var(--ease),
520
+ scale var(--dur-fast) var(--ease);
521
+ }
522
+ @media (hover: hover) and (pointer: fine) {
523
+ .resize-handle:hover::after {
524
+ scale: 1 1.5;
525
+ background: var(--accent);
526
+ }
507
527
  }
508
- .resize-handle:hover::after,
509
528
  .resize-handle:focus-visible::after,
510
529
  .resizing .resize-handle::after {
511
- height: var(--sp-12);
530
+ scale: 1 1.5;
512
531
  background: var(--accent);
513
532
  }
514
533
  .resizing {
@@ -564,7 +583,7 @@
564
583
  width: min(var(--panel-current-width), 100%);
565
584
  box-shadow: var(--shadow-md);
566
585
  outline: none;
567
- animation: panel-slide-left 0.18s var(--ease);
586
+ animation: panel-slide-left var(--dur-base) var(--ease);
568
587
  }
569
588
  .overlay.right .panel {
570
589
  right: 0;
@@ -597,6 +616,9 @@
597
616
 
598
617
  /* Coarse pointers: an invisible slab grows the chevron towards --touch-target. */
599
618
  @media (pointer: coarse) {
619
+ .collapse-control {
620
+ min-inline-size: var(--sp-6);
621
+ }
600
622
  .collapse-control::before {
601
623
  content: '';
602
624
  position: absolute;
@@ -607,7 +629,6 @@
607
629
 
608
630
  @media (prefers-reduced-motion: reduce) {
609
631
  .panel {
610
- transition: none;
611
632
  animation: none;
612
633
  }
613
634
  .overlay .panel,