@teamofsilicons/silicon-ui 0.2.0 → 0.3.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 (112) hide show
  1. package/README.md +4 -3
  2. package/check.mjs +14 -5
  3. package/components/activity-heatmap/activity-heatmap.module.css +4 -4
  4. package/components/alert/alert.module.css +1 -1
  5. package/components/announcement-bar/announcement-bar.module.css +6 -6
  6. package/components/avatar/avatar.d.ts +2 -2
  7. package/components/avatar/avatar.js +1 -1
  8. package/components/avatar/avatar.module.css +5 -2
  9. package/components/avatar-group/avatar-group.module.css +4 -4
  10. package/components/badge/badge.module.css +4 -4
  11. package/components/bar-chart/bar-chart.module.css +4 -4
  12. package/components/billing-toggle/billing-toggle.module.css +10 -10
  13. package/components/bottom-sheet/bottom-sheet.module.css +4 -5
  14. package/components/breadcrumb/breadcrumb.d.ts +8 -2
  15. package/components/breadcrumb/breadcrumb.js +21 -9
  16. package/components/breadcrumb/breadcrumb.module.css +7 -3
  17. package/components/brush-chart/brush-chart.module.css +9 -9
  18. package/components/button/button.d.ts +7 -2
  19. package/components/button/button.js +3 -1
  20. package/components/button/button.module.css +10 -1
  21. package/components/button-group/button-group.module.css +6 -6
  22. package/components/calendar/calendar.module.css +7 -7
  23. package/components/card/card.module.css +2 -2
  24. package/components/card-stack/card-stack.module.css +2 -3
  25. package/components/carousel/carousel.module.css +2 -2
  26. package/components/chat-thread/chat-thread.module.css +41 -41
  27. package/components/checkbox/checkbox.module.css +1 -1
  28. package/components/chip-group/chip-group.module.css +2 -2
  29. package/components/code-block/code-block.module.css +7 -14
  30. package/components/color-picker/color-picker.module.css +20 -21
  31. package/components/combobox/combobox.module.css +2 -2
  32. package/components/command-palette/command-palette.module.css +18 -18
  33. package/components/comment-thread/comment-thread.module.css +35 -35
  34. package/components/confirm-morph/confirm-morph.module.css +9 -9
  35. package/components/context-menu/context-menu.module.css +3 -3
  36. package/components/countdown/countdown.module.css +4 -4
  37. package/components/date-picker/date-picker.module.css +6 -6
  38. package/components/date-range-picker/date-range-picker.module.css +21 -21
  39. package/components/dialog/dialog.module.css +2 -2
  40. package/components/donut-chart/donut-chart.module.css +4 -4
  41. package/components/drawer/drawer.module.css +5 -5
  42. package/components/dropdown-menu/dropdown-menu.d.ts +71 -1
  43. package/components/dropdown-menu/dropdown-menu.js +100 -6
  44. package/components/dropdown-menu/dropdown-menu.module.css +15 -5
  45. package/components/elastic-slider/elastic-slider.module.css +4 -4
  46. package/components/expandable-card/expandable-card.module.css +1 -1
  47. package/components/expanding-button-group/expanding-button-group.module.css +1 -1
  48. package/components/expanding-search/expanding-search.module.css +9 -9
  49. package/components/file-dropzone/file-dropzone.module.css +21 -21
  50. package/components/file-upload/file-upload.module.css +7 -7
  51. package/components/filter-toolbar/filter-toolbar.module.css +9 -9
  52. package/components/floating-button-group/floating-button-group.module.css +6 -6
  53. package/components/gauge/gauge.module.css +2 -2
  54. package/components/hover-card/hover-card.module.css +2 -2
  55. package/components/image-compare/image-compare.module.css +4 -4
  56. package/components/inline-edit/inline-edit.module.css +12 -12
  57. package/components/json-viewer/json-viewer.module.css +12 -12
  58. package/components/line-chart/line-chart.module.css +8 -8
  59. package/components/mention-input/mention-input.module.css +8 -8
  60. package/components/metric-card/metric-card.module.css +1 -1
  61. package/components/money-input/money-input.module.css +9 -9
  62. package/components/morph-select/morph-select.module.css +13 -13
  63. package/components/multi-select/multi-select.module.css +6 -6
  64. package/components/nav-list/nav-list.d.ts +64 -0
  65. package/components/nav-list/nav-list.js +103 -0
  66. package/components/nav-list/nav-list.module.css +25 -0
  67. package/components/notification-center/notification-center.module.css +27 -27
  68. package/components/number-field/number-field.module.css +2 -2
  69. package/components/otp-input/otp-input.module.css +4 -4
  70. package/components/password-field/password-field.module.css +1 -1
  71. package/components/password-strength/password-strength.module.css +7 -7
  72. package/components/phone-input/phone-input.module.css +19 -19
  73. package/components/popover/popover.module.css +2 -2
  74. package/components/progress/progress.module.css +1 -1
  75. package/components/radio-cards/radio-cards.module.css +8 -8
  76. package/components/radio-group/radio-group.module.css +1 -1
  77. package/components/resizable-panels/resizable-panels.module.css +5 -5
  78. package/components/rich-text-editor/rich-text-editor.module.css +12 -12
  79. package/components/ridgeline/ridgeline.module.css +7 -7
  80. package/components/scroll-area/scroll-area.module.css +8 -8
  81. package/components/segmented-control/segmented-control.d.ts +3 -1
  82. package/components/segmented-control/segmented-control.js +2 -2
  83. package/components/segmented-control/segmented-control.module.css +7 -4
  84. package/components/select/select.module.css +5 -5
  85. package/components/shortcut-recorder/shortcut-recorder.module.css +15 -15
  86. package/components/signature-pad/signature-pad.module.css +11 -11
  87. package/components/slider/slider.module.css +4 -4
  88. package/components/slope-chart/slope-chart.module.css +7 -7
  89. package/components/sortable-data-table/sortable-data-table.module.css +4 -4
  90. package/components/split-button/split-button.module.css +5 -5
  91. package/components/stepper/stepper.module.css +3 -3
  92. package/components/streamgraph/streamgraph.module.css +7 -7
  93. package/components/swipe-actions/swipe-actions.module.css +5 -5
  94. package/components/switch/switch.module.css +1 -1
  95. package/components/tabs/tabs.d.ts +9 -2
  96. package/components/tabs/tabs.js +4 -2
  97. package/components/tabs/tabs.module.css +7 -2
  98. package/components/tag-input/tag-input.module.css +4 -4
  99. package/components/time-picker/time-picker.module.css +3 -3
  100. package/components/timeline/timeline.module.css +3 -3
  101. package/components/toast/toast.module.css +5 -5
  102. package/components/toast-stack/toast-stack.module.css +5 -5
  103. package/components/tooltip/tooltip.module.css +1 -1
  104. package/components/tree-view/tree-view.module.css +7 -7
  105. package/components/treemap/treemap.module.css +13 -13
  106. package/components/usage-meter/usage-meter.module.css +5 -5
  107. package/components/user-menu/user-menu.module.css +22 -22
  108. package/components/waffle-chart/waffle-chart.module.css +7 -7
  109. package/components.json +416 -1
  110. package/eslint.js +35 -3
  111. package/package.json +1 -1
  112. package/styles.css +44 -4
@@ -15,20 +15,29 @@
15
15
  .secondary { color: var(--foreground); background: var(--surface); border-color: var(--border); }
16
16
  .ghost { color: var(--text-secondary); background: transparent; border-color: transparent; }
17
17
  .danger { color: var(--danger); background: var(--surface); border-color: var(--border); }
18
+ /* Filled and borderless, for controls that live in a bar (a search trigger, a toolbar, a switcher). Place it on --background or
19
+ --surface; on a --surface-muted panel it has no fill to show. */
20
+ .soft { color: var(--foreground); background: var(--surface-muted); border-color: transparent; }
18
21
  /* A disabled outlined button keeps its shape and fades only its label, so it still reads as a button that is off, not as plain text. */
19
22
  .secondary:disabled, .danger:disabled { opacity: 1; color: var(--text-muted); background: var(--surface-muted); border-color: var(--border); }
20
23
  .danger:disabled { color: color-mix(in oklab, var(--danger) 55%, var(--surface-muted)); }
21
- .ghost:disabled { opacity: 1; color: var(--text-muted); }
24
+ .ghost:disabled, .soft:disabled { opacity: 1; color: var(--text-muted); }
22
25
  @media (hover: hover) and (pointer: fine) {
23
26
  .primary:hover:not(:disabled) { opacity: .91; box-shadow: var(--shadow-resting); }
24
27
  .secondary:hover:not(:disabled) { background: var(--surface-muted); box-shadow: var(--shadow-resting); }
25
28
  .ghost:hover:not(:disabled) { color: var(--foreground); background: var(--surface-muted); }
26
29
  .danger:hover:not(:disabled) { border-color: var(--danger); background: var(--surface-muted); }
30
+ .soft:hover:not(:disabled) { background: var(--surface-pressed); }
27
31
  }
28
32
  /* Color feedback on press, so touch and menu triggers (which never scale) still answer the finger. */
29
33
  .primary:active:not(:disabled) { opacity: .84; }
30
34
  .secondary:active:not(:disabled), .ghost:active:not(:disabled) { color: var(--foreground); background: var(--surface-muted); }
31
35
  .danger:active:not(:disabled) { border-color: var(--danger); background: var(--surface-muted); }
36
+ .soft:active:not(:disabled) { background: var(--surface-pressed); }
37
+ .xs { min-height: var(--control-height-xs); gap: var(--space-tight); padding-inline: var(--space-related); font-size: var(--text-xs); }
38
+ /* A compact icon-only button is a square, with the icon at its usual 16px. */
39
+ .xs[data-icon-only] { min-width: var(--control-height-xs); padding-inline: 0; }
40
+ .xs svg { width: 16px; height: 16px; }
32
41
  .sm { min-height: var(--control-height-sm); padding-inline: var(--space-group); font-size: var(--text-sm); }
33
42
  .md { min-height: var(--control-height-md); }
34
43
  .lg { min-height: var(--control-height-lg); padding-inline: var(--space-comfort); font-size: var(--text-sm); }
@@ -12,7 +12,7 @@
12
12
 
13
13
  /* The travelling highlight. Motion sets its box to the active segment; the visible shape sits inset so the border stays crisp. */
14
14
  .highlight { position: absolute; z-index: 0; top: 0; left: 0; pointer-events: none; }
15
- .highlight::before { position: absolute; inset: var(--bg-inset); border-radius: calc(var(--bg-radius) - var(--bg-inset) - 1px); corner-shape: var(--corner-shape); background: var(--bg-highlight); content: ""; transition: background-color var(--duration-fast) var(--ease-standard); }
15
+ .highlight::before { position: absolute; inset: var(--bg-inset); border-radius: calc(var(--bg-radius) - var(--bg-inset) - var(--space-hair)); corner-shape: var(--corner-shape); background: var(--bg-highlight); content: ""; transition: background-color var(--duration-fast) var(--ease-standard); }
16
16
  .highlight[data-pressed]::before { background: var(--bg-highlight-pressed); transition-duration: var(--duration-instant); }
17
17
 
18
18
  .segment { position: relative; z-index: 1; display: inline-flex; flex: none; min-width: calc(var(--bg-h) - 2px); height: calc(var(--bg-h) - 2px); align-items: center; justify-content: center; margin: 0; padding: 0 var(--bg-pad-x); border: 0; border-radius: 0; corner-shape: round; background: transparent; color: inherit; font: inherit; font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-body); line-height: 1; white-space: nowrap; text-decoration: none; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; }
@@ -29,8 +29,8 @@
29
29
  .compact .label[data-collapsible] { display: none; }
30
30
 
31
31
  /* Hairline dividers sit between segments and fade where the highlight arrives, so it never sits beside a line. */
32
- .segment + .segment::before { position: absolute; top: var(--bg-divider-inset); bottom: var(--bg-divider-inset); left: -.5px; width: 1px; background: var(--bg-divider); content: ""; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
33
- .vertical .segment + .segment::before { top: -.5px; right: var(--bg-divider-inset); bottom: auto; left: var(--bg-divider-inset); width: auto; height: 1px; }
32
+ .segment + .segment::before { position: absolute; top: var(--bg-divider-inset); bottom: var(--bg-divider-inset); left: calc(var(--space-hair) * -1); width: 1px; background: var(--bg-divider); content: ""; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
33
+ .vertical .segment + .segment::before { top: calc(var(--space-hair) * -1); right: var(--bg-divider-inset); bottom: auto; left: var(--bg-divider-inset); width: auto; height: 1px; }
34
34
  .segment[data-quiet]::before { opacity: 0; }
35
35
 
36
36
  /* The press answers inside the segment: content dips and springs back, the group and its neighbours stay still. */
@@ -51,7 +51,7 @@
51
51
  .phase { display: inline-flex; align-items: center; gap: var(--space-related); white-space: nowrap; }
52
52
  .sizer { visibility: hidden; pointer-events: none; }
53
53
  .iconBox { display: inline-block; width: 16px; height: 16px; flex: none; }
54
- .sm .phase { gap: 6px; }
54
+ .sm .phase { gap: var(--space-related); }
55
55
  .icon { display: inline-flex; flex: none; }
56
56
  .icon svg { width: 16px; height: 16px; stroke-width: 1.75; }
57
57
  .value { display: inline-flex; align-items: center; font-variant-numeric: tabular-nums; }
@@ -60,7 +60,7 @@
60
60
 
61
61
  /* The menu grows from the group's edge: offset toward it, scale from the Radix origin, and leave faster than it arrives.
62
62
  Transitions instead of keyframes, so a close that interrupts the open reverses from where the menu is. */
63
- .menu { --menu-x: 0px; --menu-y: -5px; z-index: 60; min-width: min(13rem, var(--radix-dropdown-menu-content-available-width)); max-width: var(--radix-dropdown-menu-content-available-width); padding: 5px; border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring); }
63
+ .menu { --menu-x: 0px; --menu-y: -5px; z-index: var(--layer-dropdown); min-width: min(13rem, var(--radix-dropdown-menu-content-available-width)); max-width: var(--radix-dropdown-menu-content-available-width); padding: var(--space-tight); border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring); }
64
64
  .menu[data-side="top"] { --menu-y: 5px; }
65
65
  .menu[data-side="left"] { --menu-x: 5px; --menu-y: 0px; }
66
66
  .menu[data-side="right"] { --menu-x: -5px; --menu-y: 0px; }
@@ -70,7 +70,7 @@
70
70
  }
71
71
  /* Radix unmounts when the exit animation ends, so a no-op keyframe times the exit while the transition does the visual work. */
72
72
  .menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity 130ms var(--ease-standard), transform 130ms var(--ease-standard); animation: menu-exit 130ms linear both; pointer-events: none; }
73
- .item { display: flex; min-height: 36px; align-items: center; gap: 10px; padding: 0 11px; border-radius: calc(var(--radius-container) - 6px); corner-shape: var(--corner-shape); color: inherit; font-size: var(--text-sm); text-decoration: none; cursor: pointer; outline: none; transition: opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms); }
73
+ .item { display: flex; min-height: 36px; align-items: center; gap: var(--space-group); padding: 0 var(--space-group); border-radius: calc(var(--radius-container) - var(--space-related)); corner-shape: var(--corner-shape); color: inherit; font-size: var(--text-sm); text-decoration: none; cursor: pointer; outline: none; transition: opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms); }
74
74
  .item[data-highlighted] { background: var(--surface-muted); }
75
75
  .item[data-disabled] { cursor: default; opacity: .45; }
76
76
  .itemIcon { display: inline-flex; flex: none; color: var(--text-secondary); }
@@ -1,6 +1,6 @@
1
1
  .calendar { width: min(100%, 328px); min-width: 0; color: var(--foreground); }
2
2
  /* The header is its own size container: below 260px the title takes a full row and the controls sit under it, decided by width alone so a long month name never reflows it. */
3
- .header { container-type: inline-size; display: grid; min-height: 34px; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px 12px; margin-bottom: 14px; }
3
+ .header { container-type: inline-size; display: grid; min-height: 34px; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-tight) var(--space-group); margin-bottom: var(--space-inset); }
4
4
  @container (max-width: 259px) { .heading, .navigation { grid-column: 1 / -1; } .navigation { justify-self: end; } }
5
5
  .heading { min-width: 0; margin: 0; font-family: var(--font-body); font-size: var(--text-base); font-weight: 500; line-height: var(--leading-body); letter-spacing: var(--tracking-body); }
6
6
  /* The global heading rule forces a tight line-height on h2, so the title row sets its own to center with the controls. */
@@ -9,22 +9,22 @@
9
9
  .title > .titleRow { grid-area: 1 / 1; justify-self: start; }
10
10
  .titleRow { display: inline-block; white-space: nowrap; font-variant-numeric: tabular-nums; }
11
11
  .srOnly { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
12
- .navigation { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 2px; margin-right: -6px; }
12
+ .navigation { display: inline-flex; flex: 0 0 auto; align-items: center; gap: var(--space-hair); margin-right: calc(var(--space-related) * -1); }
13
13
  /* Quiet header controls: a fill on hover, a short press, and a spring release. */
14
14
  .navButton, .todayButton { display: grid; height: 32px; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); font: inherit; cursor: pointer; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), scale var(--duration-spring) var(--ease-spring); }
15
15
  .navButton { width: 32px; }
16
- .todayButton { padding: 0 11px; font-size: var(--text-sm); font-weight: 500; line-height: 1; }
16
+ .todayButton { padding: 0 var(--space-group); font-size: var(--text-sm); font-weight: 500; line-height: 1; }
17
17
  @media (hover: hover) and (pointer: fine) { .navButton:hover:not([aria-disabled]), .todayButton:hover:not([aria-disabled]) { background: var(--surface-muted); color: var(--foreground); } }
18
18
  .navButton:active:not([aria-disabled]), .todayButton:active:not([aria-disabled]) { scale: .96; transition-duration: var(--duration-fast), var(--duration-fast), var(--duration-fast), 100ms; transition-timing-function: var(--ease-standard); }
19
19
  .navButton[aria-disabled], .todayButton[aria-disabled] { cursor: default; opacity: .36; }
20
- .weekdays, .week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); column-gap: 4px; }
21
- .weekdays { margin-bottom: 6px; color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; line-height: 24px; text-align: center; }
20
+ .weekdays, .week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); column-gap: var(--space-tight); }
21
+ .weekdays { margin-bottom: var(--space-related); color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; line-height: 24px; text-align: center; }
22
22
  .weekdays span { min-width: 0; overflow: hidden; }
23
23
  /* Months sit on one strip inside a clipped window. Every pane shares one grid cell and has six weeks, so the height never changes. */
24
24
  .monthViewport { position: relative; display: grid; overflow: hidden; contain: paint; }
25
25
  .monthBody { position: relative; grid-area: 1 / 1; min-width: 0; will-change: transform; }
26
26
  .monthBody[inert] { pointer-events: none; }
27
- .grid { display: grid; row-gap: 4px; }
27
+ .grid { display: grid; row-gap: var(--space-tight); }
28
28
  .placeholderDay { aspect-ratio: 1; }
29
29
  .day { position: relative; display: grid; width: 100%; aspect-ratio: 1; min-width: 0; place-items: center; border: 0; border-radius: 50%; corner-shape: round; padding: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); font-weight: 400; line-height: 1; font-variant-numeric: tabular-nums; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard) var(--number-delay, 0ms); }
30
30
  /* The inner circle carries hover and press; the grid cell itself never moves, so the hit area stays put. */
@@ -39,7 +39,7 @@
39
39
  .day.outside { color: var(--text-muted); }
40
40
  .day.outside:not(.selected) .dayNumber { opacity: .8; }
41
41
  /* Today is a small dot under the number. It takes the number's color, so it flips with it when the disc arrives. */
42
- .day.today .dayNumber::after { content: ""; position: absolute; top: calc(100% + 4px); left: 50%; width: 4px; height: 4px; margin-left: -2px; border-radius: 50%; corner-shape: round; background: currentColor; }
42
+ .day.today .dayNumber::after { content: ""; position: absolute; top: calc(100% + var(--space-tight)); left: 50%; width: 4px; height: 4px; margin-left: calc(var(--space-hair) * -1); border-radius: 50%; corner-shape: round; background: currentColor; }
43
43
  .day.selected, .day.today { font-weight: 500; }
44
44
  .day.selected { color: var(--accent-foreground); }
45
45
  .day:disabled { cursor: default; color: var(--text-muted); }
@@ -29,9 +29,9 @@
29
29
  .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
30
30
  .action { position: relative; z-index: 1; display: flex; flex: none; flex-wrap: wrap; gap: var(--space-group); }
31
31
 
32
- .overlay { position: fixed; inset: 0; z-index: 50; background: oklch(10% 0 0 / .46); backdrop-filter: blur(7px); }
32
+ .overlay { position: fixed; inset: 0; z-index: var(--layer-overlay); background: var(--scrim); backdrop-filter: blur(7px); }
33
33
  /* Centered with auto margins, so transform stays free for the morph. */
34
- .panel { position: fixed; inset: 0; z-index: 51; width: min(calc(100vw - var(--space-block)), 30rem); height: fit-content; max-height: calc(100dvh - var(--space-block)); margin: auto; overflow: hidden auto; overscroll-behavior: contain; border: 1px solid var(--border); border-radius: var(--radius-overlay); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transition: background-color var(--duration-standard) var(--ease-standard), box-shadow var(--duration-standard) var(--ease-standard); }
34
+ .panel { position: fixed; inset: 0; z-index: var(--layer-dialog); width: min(calc(100vw - var(--space-block)), 30rem); height: fit-content; max-height: calc(100dvh - var(--space-block)); margin: auto; overflow: hidden auto; overscroll-behavior: contain; border: 1px solid var(--border); border-radius: var(--radius-overlay); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transition: background-color var(--duration-standard) var(--ease-standard), box-shadow var(--duration-standard) var(--ease-standard); }
35
35
  .panel:focus { outline: none; }
36
36
  /* Closing: the panel takes the card's box and look, so it lands exactly where the card sits and hands back without a seam. */
37
37
  .panel[data-returning] { inset: auto; top: var(--landing-top); left: var(--landing-left); width: var(--landing-width); height: var(--landing-height); max-height: none; margin: 0; overflow: hidden; border-color: var(--border); background: var(--surface); box-shadow: none; pointer-events: none; }
@@ -12,10 +12,9 @@
12
12
  .card::before { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; box-shadow: var(--shadow-floating); content: ""; opacity: 0; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
13
13
  .card[data-dragging]::before, .card[data-flying]::before { opacity: 1; }
14
14
  .face { position: relative; height: 100%; overflow: hidden; border: 1px solid var(--border); border-radius: inherit; corner-shape: inherit; background: var(--surface); color: var(--foreground); }
15
- .shade { position: absolute; inset: 0; background: oklch(0% 0 0 / .035); pointer-events: none; }
16
- :global(:root[data-theme="dark"]) .shade { background: oklch(0% 0 0 / .22); }
15
+ .shade { position: absolute; inset: 0; background: var(--shade); pointer-events: none; }
17
16
 
18
- .stamp { position: absolute; top: var(--space-inset); display: inline-flex; height: 2rem; align-items: center; gap: 6px; padding: 0 var(--space-group) 0 10px; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; pointer-events: none; }
17
+ .stamp { position: absolute; top: var(--space-inset); display: inline-flex; height: 2rem; align-items: center; gap: var(--space-related); padding: 0 var(--space-group) 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; pointer-events: none; }
19
18
  .stamp[data-side="right"] { left: var(--space-inset); border-color: color-mix(in oklab, var(--success) 32%, var(--border)); background: color-mix(in oklab, var(--success) 9%, var(--surface-raised)); }
20
19
  .stamp[data-side="right"] svg { color: var(--success); }
21
20
  .stamp[data-side="left"] { right: var(--space-inset); }
@@ -13,7 +13,7 @@
13
13
  /* Controls line up with the edges of the centered slide. */
14
14
  .controls { display: flex; min-width: 0; align-items: center; gap: var(--space-related); padding-inline: calc((100cqw - var(--carousel-slide)) / 2); }
15
15
  .tabs { display: flex; min-width: 0; align-items: center; }
16
- .tab { display: grid; height: var(--control-height-sm); flex: none; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; padding: 0 5px; background: transparent; cursor: pointer; -webkit-tap-highlight-color: transparent; }
16
+ .tab { display: grid; height: var(--control-height-sm); flex: none; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; padding: 0 var(--space-tight); background: transparent; cursor: pointer; -webkit-tap-highlight-color: transparent; }
17
17
  /* Resting dots use the strong border color; the fill fades in as the pill forms, and the drain covers it from the right while rotation runs. */
18
18
  .dot { position: relative; display: block; width: 6px; height: 6px; overflow: hidden; border-radius: var(--radius-full); corner-shape: round; background: var(--border-strong); transition: background-color var(--duration-fast) var(--ease-standard); }
19
19
  .dotFill, .dotDrain { position: absolute; inset: 0; }
@@ -33,7 +33,7 @@
33
33
  @container (max-width: 340px) {
34
34
  .controls { gap: var(--space-tight); padding-inline: max(0px, calc((100cqw - var(--carousel-slide)) / 2 - var(--space-related))); }
35
35
  .arrows { gap: var(--space-tight); }
36
- .tab { padding: 0 4px; }
36
+ .tab { padding: 0 var(--space-tight); }
37
37
  }
38
38
  .visuallyHidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
39
39
 
@@ -14,32 +14,32 @@
14
14
  .scroller { min-height: 0; flex: 1; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-gutter: stable; }
15
15
  /* The content springs up from where it was when the thread grows; the clip keeps that travel out of the scroll range. */
16
16
  .track { overflow: clip; }
17
- .content { padding: 12px 16px 14px; will-change: transform; }
17
+ .content { padding: var(--space-group) var(--space-inset) var(--space-inset); will-change: transform; }
18
18
 
19
19
  .list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
20
20
 
21
- .day { display: flex; align-items: center; gap: 12px; padding: 20px 4px 4px; color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; }
21
+ .day { display: flex; align-items: center; gap: var(--space-group); padding: var(--space-comfort) var(--space-tight) var(--space-tight); color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; }
22
22
  .day::before, .day::after { height: 1px; flex: 1; background: var(--border-subtle); content: ""; }
23
- .day:first-child { padding-top: 4px; }
23
+ .day:first-child { padding-top: var(--space-tight); }
24
24
  .day > span:empty::before { content: "\00a0"; }
25
25
 
26
- .row { position: relative; display: flex; align-items: flex-end; gap: 8px; margin-top: 2px; }
27
- .row[data-first] { margin-top: 14px; }
28
- .day + .row { margin-top: 10px; }
26
+ .row { position: relative; display: flex; align-items: flex-end; gap: var(--space-related); margin-top: var(--space-hair); }
27
+ .row[data-first] { margin-top: var(--space-inset); }
28
+ .day + .row { margin-top: var(--space-group); }
29
29
  .row[data-mine] { justify-content: flex-end; }
30
30
 
31
31
  .gutter { width: 28px; flex: none; align-self: flex-end; }
32
- .stack { display: flex; min-width: 0; max-width: min(78%, 34rem); flex-direction: column; align-items: flex-start; gap: 4px; }
32
+ .stack { display: flex; min-width: 0; max-width: min(78%, 34rem); flex-direction: column; align-items: flex-start; gap: var(--space-tight); }
33
33
  .row[data-mine] .stack { align-items: flex-end; }
34
34
 
35
- .meta { display: flex; align-items: baseline; gap: 6px; margin: 0 0 2px; padding: 0 12px; color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
35
+ .meta { display: flex; align-items: baseline; gap: var(--space-related); margin: 0 0 var(--space-hair); padding: 0 var(--space-group); color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
36
36
  .author { color: var(--text-secondary); font-weight: 500; }
37
37
 
38
- .line { position: relative; display: flex; max-width: 100%; align-items: center; gap: 6px; }
38
+ .line { position: relative; display: flex; max-width: 100%; align-items: center; gap: var(--space-related); }
39
39
  /* The author's face sits in the gutter, level with the bottom of their last bubble rather than the reactions under it. */
40
- .face { position: absolute; bottom: 0; left: -36px; display: flex; }
40
+ .face { position: absolute; bottom: 0; left: calc(var(--space-block) * -1); display: flex; }
41
41
  .row[data-mine] .line { flex-direction: row-reverse; }
42
- .parts { display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap: 4px; }
42
+ .parts { display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap: var(--space-tight); }
43
43
  .row[data-mine] .parts { align-items: flex-end; }
44
44
 
45
45
  /* Bubbles in a group share a soft edge on the author's side, so a run of messages reads as one voice. */
@@ -47,7 +47,7 @@
47
47
  --r: 18px;
48
48
  --tight: 6px;
49
49
  max-width: 100%;
50
- padding: 8px 13px 9px;
50
+ padding: var(--space-related) var(--space-group) var(--space-related);
51
51
  border-radius: var(--r);
52
52
  background: var(--surface-muted);
53
53
  color: var(--foreground);
@@ -63,7 +63,7 @@
63
63
  .row[data-mine]:not([data-last]) .bubble { border-bottom-right-radius: var(--tight); }
64
64
  .row[data-failed] .bubble { opacity: .6; }
65
65
 
66
- .gallery { display: grid; width: min(240px, 100%); gap: 3px; grid-template-columns: 1fr; }
66
+ .gallery { display: grid; width: min(240px, 100%); gap: var(--space-tight); grid-template-columns: 1fr; }
67
67
  .gallery[data-count="2"],
68
68
  .gallery[data-count="3"],
69
69
  .gallery[data-count="4"] { grid-template-columns: 1fr 1fr; }
@@ -73,10 +73,10 @@
73
73
  .gallery:not([data-count="1"]) .image { border-radius: var(--radius-detail); corner-shape: var(--corner-shape); }
74
74
  .gallery:not([data-count="1"]) { overflow: hidden; border-radius: var(--radius-control); corner-shape: var(--corner-shape); }
75
75
  .image img { display: block; width: 100%; height: 100%; object-fit: cover; }
76
- .more { position: absolute; inset: 0; display: grid; place-items: center; background: oklch(0% 0 0 / .45); color: #fff; font-size: var(--text-lg); font-weight: 500; }
76
+ .more { position: absolute; inset: 0; display: grid; place-items: center; background: var(--media-scrim); color: var(--on-media); font-size: var(--text-lg); font-weight: 500; }
77
77
 
78
78
  .file {
79
- display: flex; max-width: 100%; align-items: center; gap: 10px; padding: 9px 14px 9px 11px;
79
+ display: flex; max-width: 100%; align-items: center; gap: var(--space-group); padding: var(--space-related) var(--space-inset) var(--space-related) var(--space-group);
80
80
  border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); color: var(--foreground); text-decoration: none;
81
81
  transition: background-color var(--duration-fast) var(--ease-standard);
82
82
  }
@@ -103,17 +103,17 @@
103
103
  @media (hover: none) { .reactButton { opacity: .7; } }
104
104
 
105
105
  .picker {
106
- position: absolute; z-index: 5; bottom: calc(100% + 6px); right: -4px;
107
- display: flex; gap: 2px; padding: 4px;
106
+ position: absolute; z-index: 5; bottom: calc(100% + var(--space-related)); right: calc(var(--space-tight) * -1);
107
+ display: flex; gap: var(--space-hair); padding: var(--space-tight);
108
108
  border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); box-shadow: var(--shadow-floating);
109
109
  transform-origin: calc(100% - 18px) 100%;
110
110
  }
111
- .picker[data-mine] { right: auto; left: -4px; transform-origin: 18px 100%; }
112
- .pickerWrap[data-below] .picker { top: calc(100% + 6px); bottom: auto; transform-origin: calc(100% - 18px) 0; }
111
+ .picker[data-mine] { right: auto; left: calc(var(--space-tight) * -1); transform-origin: 18px 100%; }
112
+ .pickerWrap[data-below] .picker { top: calc(100% + var(--space-related)); bottom: auto; transform-origin: calc(100% - 18px) 0; }
113
113
  .pickerWrap[data-below] .picker[data-mine] { transform-origin: 18px 0; }
114
114
  .emoji {
115
115
  display: grid; width: 34px; height: 34px; place-items: center; padding: 0;
116
- border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: pointer; font-size: 19px; line-height: 1;
116
+ border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: pointer; font-size: var(--text-lg); line-height: 1;
117
117
  -webkit-tap-highlight-color: transparent;
118
118
  transition: background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
119
119
  }
@@ -121,31 +121,31 @@
121
121
  .emoji:active { transform: scale(.9); }
122
122
  @media (hover: hover) and (pointer: fine) { .emoji:hover { background: var(--surface-muted); transform: scale(1.12); } }
123
123
 
124
- .reactionClip { overflow: hidden; margin: -2px -4px; padding: 2px 4px 2px 8px; }
125
- .row[data-mine] .reactionClip { padding: 2px 8px 2px 4px; }
126
- .reactions { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none; }
124
+ .reactionClip { overflow: hidden; margin: calc(var(--space-hair) * -1) calc(var(--space-tight) * -1); padding: var(--space-hair) var(--space-tight) var(--space-hair) var(--space-related); }
125
+ .row[data-mine] .reactionClip { padding: var(--space-hair) var(--space-related) var(--space-hair) var(--space-tight); }
126
+ .reactions { display: flex; flex-wrap: wrap; gap: var(--space-tight); margin: 0; padding: 0; list-style: none; }
127
127
  .row[data-mine] .reactions { justify-content: flex-end; }
128
128
  .reaction {
129
- display: inline-flex; height: 26px; align-items: center; gap: 5px; padding: 0 9px 0 7px;
129
+ display: inline-flex; height: 26px; align-items: center; gap: var(--space-tight); padding: 0 var(--space-related) 0 var(--space-related);
130
130
  border: 1px solid var(--border-subtle); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); color: var(--text-secondary); cursor: pointer;
131
131
  font: inherit; font-size: var(--text-xs); font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums;
132
132
  -webkit-tap-highlight-color: transparent;
133
133
  transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
134
134
  }
135
- .reaction > span:first-child { font-size: 14px; }
135
+ .reaction > span:first-child { font-size: var(--text-sm); }
136
136
  .reaction[aria-pressed="true"] { border-color: color-mix(in oklab, var(--accent) 28%, transparent); background: color-mix(in oklab, var(--accent) 8%, var(--surface)); color: var(--foreground); }
137
137
  @media (hover: hover) and (pointer: fine) { .reaction:hover { border-color: var(--border-strong); } }
138
138
  .reaction:active { transform: scale(.95); }
139
139
  .countRoll { display: inline-grid; overflow: hidden; font-variant-numeric: tabular-nums; }
140
140
  .countRoll > span { grid-area: 1 / 1; }
141
141
 
142
- .receipts { display: flex; min-height: 16px; align-items: center; gap: 6px; padding: 1px 6px 0; color: var(--text-muted); font-size: var(--text-xs); }
142
+ .receipts { display: flex; min-height: 16px; align-items: center; gap: var(--space-related); padding: 1px var(--space-related) 0; color: var(--text-muted); font-size: var(--text-xs); }
143
143
  .readers { display: inline-flex; }
144
144
  .reader { display: inline-flex; border-radius: 50%; corner-shape: round; box-shadow: 0 0 0 2px var(--surface); }
145
- .reader + .reader { margin-left: -4px; }
146
- .status[data-status="failed"] { display: inline-flex; align-items: center; gap: 8px; color: var(--danger); }
145
+ .reader + .reader { margin-left: calc(var(--space-tight) * -1); }
146
+ .status[data-status="failed"] { display: inline-flex; align-items: center; gap: var(--space-related); color: var(--danger); }
147
147
  .retry {
148
- display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
148
+ display: inline-flex; align-items: center; gap: var(--space-tight); padding: var(--space-hair) var(--space-related);
149
149
  border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); color: var(--foreground); cursor: pointer;
150
150
  font: inherit; font-weight: 500; line-height: 16px;
151
151
  -webkit-tap-highlight-color: transparent;
@@ -157,8 +157,8 @@
157
157
  .avatar { display: inline-grid; flex: none; overflow: hidden; place-items: center; border-radius: 50%; corner-shape: round; background: var(--surface-muted); color: var(--text-secondary); font-weight: 500; }
158
158
  .avatar img { width: 100%; height: 100%; object-fit: cover; }
159
159
 
160
- .typing { display: grid; height: 36px; box-sizing: border-box; place-items: center; padding: 0 14px; }
161
- .dots { display: inline-flex; gap: 4px; }
160
+ .typing { display: grid; height: 36px; box-sizing: border-box; place-items: center; padding: 0 var(--space-inset); }
161
+ .dots { display: inline-flex; gap: var(--space-tight); }
162
162
  .dots i { width: 6px; height: 6px; border-radius: 50%; corner-shape: round; background: var(--text-muted); animation: breathe 1.2s var(--ease-in-out) infinite; }
163
163
  .dots i:nth-child(2) { animation-delay: .16s; }
164
164
  .dots i:nth-child(3) { animation-delay: .32s; }
@@ -169,8 +169,8 @@
169
169
  @media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: .6; } }
170
170
 
171
171
  .pill {
172
- position: absolute; z-index: 6; bottom: 12px; left: 50%; translate: -50% 0;
173
- display: inline-flex; height: 32px; align-items: center; gap: 6px; padding: 0 14px 0 11px;
172
+ position: absolute; z-index: 6; bottom: var(--space-group); left: 50%; translate: -50% 0;
173
+ display: inline-flex; height: 32px; align-items: center; gap: var(--space-related); padding: 0 var(--space-inset) 0 var(--space-group);
174
174
  border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); box-shadow: var(--shadow-floating); color: var(--foreground); cursor: pointer;
175
175
  font: inherit; font-size: var(--text-xs); font-weight: 500; white-space: nowrap;
176
176
  -webkit-tap-highlight-color: transparent;
@@ -180,22 +180,22 @@
180
180
  .pill:active { scale: .97; }
181
181
 
182
182
  /* Composer. */
183
- .composer { display: grid; padding: 10px 12px 12px; border-top: 1px solid var(--border-subtle); transition: background-color var(--duration-fast) var(--ease-standard); }
183
+ .composer { display: grid; padding: var(--space-group) var(--space-group) var(--space-group); border-top: 1px solid var(--border-subtle); transition: background-color var(--duration-fast) var(--ease-standard); }
184
184
  .composer[data-dragging] { background: var(--accent-subtle); }
185
185
  .trayClip { overflow: hidden; }
186
- .tray { display: flex; gap: 8px; margin: 0; padding: 8px 8px 10px 4px; overflow-x: auto; list-style: none; scrollbar-width: none; }
186
+ .tray { display: flex; gap: var(--space-related); margin: 0; padding: var(--space-related) var(--space-related) var(--space-group) var(--space-tight); overflow-x: auto; list-style: none; scrollbar-width: none; }
187
187
  .pending { position: relative; flex: none; }
188
188
  .pendingImage { display: block; width: 56px; height: 56px; border-radius: var(--radius-control); corner-shape: var(--corner-shape); object-fit: cover; }
189
- .pendingFile { display: grid; width: 168px; height: 56px; box-sizing: border-box; align-content: center; column-gap: 8px; padding: 0 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); grid-template-columns: auto minmax(0, 1fr); }
189
+ .pendingFile { display: grid; width: 168px; height: 56px; box-sizing: border-box; align-content: center; column-gap: var(--space-related); padding: 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); grid-template-columns: auto minmax(0, 1fr); }
190
190
  .pendingFile > svg { grid-row: span 2; align-self: center; color: var(--text-secondary); }
191
191
  .pendingName { overflow: hidden; font-size: var(--text-xs); font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
192
192
  .pendingSize { color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
193
193
  .remove {
194
- position: absolute; top: -6px; right: -6px; display: grid; width: 20px; height: 20px; place-items: center; padding: 0;
194
+ position: absolute; top: calc(var(--space-related) * -1); right: calc(var(--space-related) * -1); display: grid; width: 20px; height: 20px; place-items: center; padding: 0;
195
195
  border: 2px solid var(--surface); border-radius: 50%; corner-shape: round; background: var(--foreground); color: var(--background); cursor: pointer;
196
196
  }
197
197
 
198
- .inputRow { display: flex; align-items: flex-end; gap: 6px; }
198
+ .inputRow { display: flex; align-items: flex-end; gap: var(--space-related); }
199
199
  .attach {
200
200
  display: grid; width: 40px; height: 40px; flex: none; place-items: center; margin-bottom: 1px; padding: 0;
201
201
  border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer;
@@ -216,7 +216,7 @@
216
216
  .field { position: relative; min-width: 0; flex: 1; overflow: hidden; border-radius: var(--radius-control); corner-shape: var(--corner-shape); }
217
217
  .mirror,
218
218
  .textarea {
219
- box-sizing: border-box; width: 100%; margin: 0; padding: 9px 6px 9px 16px; border: 0;
219
+ box-sizing: border-box; width: 100%; margin: 0; padding: var(--space-related) var(--space-related) var(--space-related) var(--space-inset); border: 0;
220
220
  font: inherit; font-size: var(--text-base); letter-spacing: var(--tracking-body); line-height: 22px;
221
221
  overflow-wrap: break-word; white-space: pre-wrap; word-break: break-word;
222
222
  }
@@ -225,7 +225,7 @@
225
225
  .textarea::placeholder { color: var(--text-muted); }
226
226
 
227
227
  .send {
228
- position: relative; display: grid; width: 32px; height: 32px; flex: none; place-items: center; margin: 0 4px 4px 0; overflow: hidden; padding: 0;
228
+ position: relative; display: grid; width: 32px; height: 32px; flex: none; place-items: center; margin: 0 var(--space-tight) var(--space-tight) 0; overflow: hidden; padding: 0;
229
229
  border: 0; border-radius: 50%; corner-shape: round; background: transparent; color: var(--text-muted); cursor: default;
230
230
  -webkit-tap-highlight-color: transparent;
231
231
  transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
@@ -237,6 +237,6 @@
237
237
  .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
238
238
 
239
239
  @media (max-width: 420px) {
240
- .content { padding-inline: 12px; }
240
+ .content { padding-inline: var(--space-group); }
241
241
  .stack { max-width: 84%; }
242
242
  }
@@ -9,7 +9,7 @@
9
9
  .box:active:not(:disabled) .visual { transform: scale(.95); transition: border-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
10
10
  .box:disabled { opacity: .5; cursor: not-allowed; }
11
11
  /* The label's first line centers on the square: (44px box - 19.6px line) / 2. */
12
- .copy { display: grid; gap: 2px; min-width: 0; margin-left: 1px; padding-top: calc((var(--control-height-md) - var(--text-sm) * 1.4) / 2); }
12
+ .copy { display: grid; gap: var(--space-hair); min-width: 0; margin-left: 1px; padding-top: calc((var(--control-height-md) - var(--text-sm) * 1.4) / 2); }
13
13
  .copy label { color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); cursor: pointer; }
14
14
  .copy span { color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); }
15
15
  @media (prefers-reduced-motion: reduce) { .visual, .box:active:not(:disabled) .visual { transition: none; transform: none; } }
@@ -6,11 +6,11 @@
6
6
  /* The button is only the hit area and the layout box. What you see is the surface, which trails the box on a spring while the width morphs. */
7
7
  .chip { position: relative; display: inline-flex; max-width: 100%; padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round; background: none; color: var(--text-secondary); font: inherit; font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-body); line-height: 20px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard); }
8
8
  /* Press answers on the body with the individual scale property, so it never fights the layout transform on the button. */
9
- .body { position: relative; isolation: isolate; display: inline-flex; min-width: 0; height: var(--control-height-sm); align-items: center; padding: 0 14px; transition: scale var(--duration-spring) var(--ease-spring); }
9
+ .body { position: relative; isolation: isolate; display: inline-flex; min-width: 0; height: var(--control-height-sm); align-items: center; padding: 0 var(--space-inset); transition: scale var(--duration-spring) var(--ease-spring); }
10
10
  .chip:active .body { scale: .97; transition-duration: var(--duration-instant); transition-timing-function: var(--ease-standard); }
11
11
  .surface { position: absolute; z-index: -1; top: 0; bottom: 0; left: 0; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); }
12
12
  /* The check grows from its left edge where the label started; the slot hands its width to the label in one layout step, and the label's offset springs it over. */
13
- .check { position: absolute; top: calc(50% - 7px); left: 12px; display: grid; width: 14px; height: 14px; place-items: center; color: var(--accent-strong); transform-origin: 0 50%; }
13
+ .check { position: absolute; top: calc(50% - var(--space-related)); left: var(--space-group); display: grid; width: 14px; height: 14px; place-items: center; color: var(--accent-strong); transform-origin: 0 50%; }
14
14
  .check svg { display: block; overflow: visible; }
15
15
  .slot { width: 0; flex: none; }
16
16
  .body[data-selected="true"] .slot { width: 18px; }
@@ -23,21 +23,14 @@
23
23
  .chevron { display: grid; flex: none; place-items: center; color: var(--text-muted); }
24
24
  /* Each role has contrast in both themes; color supports, but never replaces, source text. */
25
25
  .comment { color: var(--text-muted); font-style: italic; }
26
- .string { color: oklch(46% .13 150); }
27
- .number { color: oklch(50% .14 55); }
28
- .keyword { color: oklch(48% .15 285); }
29
- .type { color: oklch(46% .13 230); }
30
- .function { color: oklch(43% .12 25); }
31
- .property { color: oklch(44% .12 85); }
32
- .tag { color: oklch(46% .13 230); }
26
+ .string { color: var(--syntax-string); }
27
+ .number { color: var(--syntax-number); }
28
+ .keyword { color: var(--syntax-keyword); }
29
+ .type { color: var(--syntax-type); }
30
+ .function { color: var(--syntax-function); }
31
+ .property { color: var(--syntax-property); }
32
+ .tag { color: var(--syntax-type); }
33
33
  .punctuation { color: var(--text-secondary); }
34
- :global(:root[data-theme="dark"]) .string { color: oklch(77% .15 150); }
35
- :global(:root[data-theme="dark"]) .number { color: oklch(80% .14 75); }
36
- :global(:root[data-theme="dark"]) .keyword { color: oklch(78% .16 285); }
37
- :global(:root[data-theme="dark"]) .type { color: oklch(79% .13 230); }
38
- :global(:root[data-theme="dark"]) .function { color: oklch(80% .13 25); }
39
- :global(:root[data-theme="dark"]) .property { color: oklch(79% .12 85); }
40
- :global(:root[data-theme="dark"]) .tag { color: oklch(79% .13 230); }
41
34
  @media (hover: hover) and (pointer: fine) { .expand:hover { background: var(--surface-muted); color: var(--foreground); } .expand:hover .chevron { color: var(--foreground); } }
42
35
  @media (max-width: 420px) { .header { padding-left: var(--space-group); } .header :global(button) { width: var(--control-height-sm); padding-inline: 0; } .header :global(button) > span:last-of-type { display: none; } .viewport { --code-collapsed: calc(var(--code-lines, 0) * var(--text-xs) * 1.7 + var(--space-inset) * 2); } .pre { padding: var(--space-inset); font-size: var(--text-xs); } }
43
36
  @media (prefers-reduced-motion: reduce) { .expand { transition: none; } }
@@ -11,7 +11,7 @@
11
11
  }
12
12
  :global(:root[data-theme="dark"]) .root { --picker-fill: var(--surface-raised); --checker: repeating-conic-gradient(oklch(42% 0 0) 0 25%, oklch(30% 0 0) 0 50%) 0 0 / 10px 10px; }
13
13
 
14
- .trigger, .head { display: flex; height: 44px; align-items: center; gap: 10px; padding: 6px 16px 6px 6px; }
14
+ .trigger, .head { display: flex; height: 44px; align-items: center; gap: var(--space-group); padding: var(--space-related) var(--space-inset) var(--space-related) var(--space-related); }
15
15
  .trigger {
16
16
  border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--picker-fill); box-shadow: var(--shadow-resting); color: inherit; font: inherit; cursor: pointer;
17
17
  -webkit-tap-highlight-color: transparent; transition: border-color var(--duration-fast) var(--ease-standard);
@@ -22,76 +22,75 @@
22
22
 
23
23
  /* A chip over a checkerboard, so opacity reads as opacity. */
24
24
  .chip { position: relative; display: block; width: 32px; height: 32px; flex: none; overflow: hidden; border-radius: 50%; corner-shape: round; background: var(--checker); }
25
- .chip > span { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; box-shadow: inset 0 0 0 1px oklch(0% 0 0 / .1); }
25
+ .chip > span { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; box-shadow: var(--edge-inset); }
26
26
 
27
27
  /* The floating layer. Its drop shadow follows the clipped shape, so the shadow grows with the panel. */
28
- .float { position: absolute; z-index: 20; top: -1px; left: -1px; filter: drop-shadow(0 18px 36px oklch(0% 0 0 / .12)) drop-shadow(0 2px 6px oklch(0% 0 0 / .06)); pointer-events: none; }
29
- :global(:root[data-theme="dark"]) .float { filter: drop-shadow(0 20px 44px oklch(0% 0 0 / .5)); }
28
+ .float { position: absolute; z-index: var(--layer-popover); top: -1px; left: -1px; filter: var(--shadow-drop); pointer-events: none; }
30
29
  .float[data-open] { pointer-events: auto; }
31
- .panel { width: var(--picker-width); padding-bottom: 14px; border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--picker-fill); will-change: clip-path; }
32
- .head { gap: 10px; padding-right: 6px; }
30
+ .panel { width: var(--picker-width); padding-bottom: var(--space-inset); border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--picker-fill); will-change: clip-path; }
31
+ .head { gap: var(--space-group); padding-right: var(--space-related); }
33
32
  .head .triggerText { flex: 1; }
34
- .headActions { display: flex; gap: 2px; }
33
+ .headActions { display: flex; gap: var(--space-hair); }
35
34
  .iconButton {
36
35
  display: grid; width: 32px; height: 32px; place-items: center; padding: 0; border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--text-secondary); cursor: pointer;
37
36
  transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard);
38
37
  }
39
38
  .iconButton:active { transform: scale(.94); }
40
39
  /* One column locked to the panel: a wider grade label ("AA large") must never widen the area and sliders mid drag. */
41
- .body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; padding: 8px 14px 0; }
40
+ .body { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-inset); padding: var(--space-related) var(--space-inset) 0; }
42
41
 
43
42
  /* Saturation runs left to right, brightness bottom to top, over the pure hue. */
44
43
  .area {
45
44
  position: relative; height: 164px; border-radius: var(--radius-control); corner-shape: var(--corner-shape); cursor: crosshair; touch-action: none;
46
- background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent), var(--picker-hue);
47
- box-shadow: inset 0 0 0 1px oklch(0% 0 0 / .08);
45
+ background: linear-gradient(to top, var(--color-black), transparent), linear-gradient(to right, var(--color-white), transparent), var(--picker-hue);
46
+ box-shadow: var(--edge-inset);
48
47
  }
49
48
  .areaThumb, .sliderThumb {
50
49
  position: absolute; width: 22px; height: 22px; border-radius: 50%; corner-shape: round; translate: -50% -50%; cursor: grab;
51
- box-shadow: 0 0 0 3px #fff, 0 2px 8px oklch(0% 0 0 / .3), inset 0 0 0 1px oklch(0% 0 0 / .12);
50
+ box-shadow: 0 0 0 3px var(--color-white), var(--shadow-knob), var(--edge-inset);
52
51
  transition: scale var(--duration-spring) var(--ease-spring);
53
52
  }
54
53
  .areaThumb { background: var(--picker-opaque); }
55
54
  .area[data-active] .areaThumb, .sliderThumb[data-active] { scale: 1.2; cursor: grabbing; }
56
55
 
57
- .slider { position: relative; height: 14px; margin: 0 11px; border-radius: var(--radius-full); corner-shape: round; cursor: pointer; touch-action: none; }
58
- .slider::before { content: ""; position: absolute; inset: 0 -11px; border-radius: inherit; corner-shape: inherit; background: var(--track); box-shadow: inset 0 0 0 1px oklch(0% 0 0 / .08); }
56
+ .slider { position: relative; height: 14px; margin: 0 var(--space-group); border-radius: var(--radius-full); corner-shape: round; cursor: pointer; touch-action: none; }
57
+ .slider::before { content: ""; position: absolute; inset: 0 calc(var(--space-group) * -1); border-radius: inherit; corner-shape: inherit; background: var(--track); box-shadow: var(--edge-inset); }
59
58
  .sliderThumb { top: 50%; background: var(--checker); }
60
59
  .sliderThumb::after { content: ""; position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; background: var(--thumb-fill); }
61
60
  /* The gradient stops sit at the thumb's travel ends, so the color under the thumb is the value it reports. */
62
61
  .hue { --track: linear-gradient(to right, #f00 11px, #ff0 calc(11px + (100% - 22px) * .1666), #0f0 calc(11px + (100% - 22px) * .3333), #0ff 50%, #00f calc(11px + (100% - 22px) * .6666), #f0f calc(11px + (100% - 22px) * .8333), #f00 calc(100% - 11px)); }
63
62
  .alpha { --track: linear-gradient(to right, transparent 11px, var(--picker-opaque) calc(100% - 11px)), var(--checker); }
64
63
 
65
- .field { display: flex; height: 40px; align-items: stretch; overflow: hidden; margin-top: 2px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-muted); transition: border-color var(--duration-fast) var(--ease-standard); }
64
+ .field { display: flex; height: 40px; align-items: stretch; overflow: hidden; margin-top: var(--space-hair); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-muted); transition: border-color var(--duration-fast) var(--ease-standard); }
66
65
  .field[data-invalid] { border-color: var(--danger); }
67
66
  .format {
68
- display: flex; min-width: 56px; align-items: center; justify-content: center; padding: 0 10px; border: 0; border-right: 1px solid var(--border); background: transparent;
67
+ display: flex; min-width: 56px; align-items: center; justify-content: center; padding: 0 var(--space-group); border: 0; border-right: 1px solid var(--border); background: transparent;
69
68
  color: var(--foreground); font: inherit; font-size: var(--text-xs); font-weight: 500; cursor: pointer; transition: background-color var(--duration-fast) var(--ease-standard);
70
69
  }
71
70
  .inputWrap { position: relative; display: flex; min-width: 0; flex: 1; }
72
- .input, .morph { padding: 0 10px; font: inherit; font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
71
+ .input, .morph { padding: 0 var(--space-group); font: inherit; font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
73
72
  .input { width: 100%; min-width: 0; border: 0; background: transparent; color: var(--foreground); caret-color: var(--foreground); }
74
73
  .input[data-morphing] { color: transparent; }
75
74
  .morph { position: absolute; inset: 0; display: flex; align-items: center; overflow: hidden; color: var(--foreground); pointer-events: none; white-space: nowrap; }
76
- .error { min-height: 0; margin: -10px 2px -4px; color: var(--danger); font-size: var(--text-xs); }
75
+ .error { min-height: 0; margin: calc(var(--space-group) * -1) var(--space-hair) calc(var(--space-tight) * -1); color: var(--danger); font-size: var(--text-xs); }
77
76
  .error:empty { display: none; }
78
77
 
79
- .contrast { display: flex; align-items: center; gap: 10px; font-size: var(--text-sm); }
78
+ .contrast { display: flex; align-items: center; gap: var(--space-group); font-size: var(--text-sm); }
80
79
  .sample { display: grid; width: 36px; height: 28px; flex: none; place-items: center; border: 1px solid var(--border); border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--picker-bg); color: var(--picker-color); font-weight: 500; }
81
80
  .ratio { font-variant-numeric: tabular-nums; font-weight: 500; min-width: 4.4ch; }
82
81
  .against { flex: 1; min-width: 0; overflow: hidden; color: var(--text-secondary); font-size: var(--text-xs); text-overflow: ellipsis; white-space: nowrap; }
83
- .grade { position: relative; display: inline-grid; height: 22px; align-items: center; padding: 0 8px; border-radius: var(--radius-full); corner-shape: round; font-size: var(--text-xs); font-weight: 500; white-space: nowrap; transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard); }
82
+ .grade { position: relative; display: inline-grid; height: 22px; align-items: center; padding: 0 var(--space-related); border-radius: var(--radius-full); corner-shape: round; font-size: var(--text-xs); font-weight: 500; white-space: nowrap; transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard); }
84
83
  .grade[data-grade="pass"] { background: color-mix(in oklab, var(--success) 14%, transparent); color: var(--success); }
85
84
  .grade[data-grade="large"] { background: color-mix(in oklab, var(--warning) 16%, transparent); color: var(--warning); }
86
85
  .grade[data-grade="fail"] { background: color-mix(in oklab, var(--danger) 12%, transparent); color: var(--danger); }
87
86
 
88
- .swatches { display: flex; align-items: center; gap: 6px; min-height: 32px; }
87
+ .swatches { display: flex; align-items: center; gap: var(--space-related); min-height: 32px; }
89
88
  .add {
90
89
  display: grid; width: 30px; height: 30px; flex: none; place-items: center; padding: 0; border: 1px dashed var(--border-strong); border-radius: 50%; corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer;
91
90
  transition: color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard);
92
91
  }
93
92
  .add:active { transform: scale(.92); }
94
- .swatchList { display: flex; min-width: 0; flex: 1; gap: 6px; }
93
+ .swatchList { display: flex; min-width: 0; flex: 1; gap: var(--space-related); }
95
94
  .swatchSlot { position: relative; flex: none; touch-action: pan-y; }
96
95
  .swatch { display: grid; width: 30px; height: 30px; place-items: center; padding: 0; border: 0; border-radius: 50%; corner-shape: round; background: transparent; cursor: pointer; }
97
96
  .swatchChip { width: 26px; height: 26px; transition: scale var(--duration-spring) var(--ease-spring); }
@@ -15,11 +15,11 @@
15
15
  @media (hover: hover) and (pointer: fine) { .clear:hover { background: var(--surface-muted); color: var(--foreground); } }
16
16
  .clear:focus-visible { --focus-outline-offset: 1px; }
17
17
  .hint { color: var(--text-muted); font-size: var(--text-xs); }
18
- .popover { position: absolute; z-index: 80; top: calc(100% + 8px); right: 0; left: 0; overflow: hidden; transform-origin: top center; border: 1px solid var(--border); border-radius: var(--radius-container); padding: 5px; background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); will-change: transform, opacity; }
18
+ .popover { position: absolute; z-index: var(--layer-dropdown); top: calc(100% + var(--space-related)); right: 0; left: 0; overflow: hidden; transform-origin: top center; border: 1px solid var(--border); border-radius: var(--radius-container); padding: var(--space-tight); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); will-change: transform, opacity; }
19
19
  .autoHeight { overflow: hidden; }
20
20
  .listbox { max-height: min(300px, 40vh); overflow-y: auto; overscroll-behavior: contain; }
21
21
  /* Arrow keys move the highlight often, so it changes almost instantly. */
22
- .option { display: flex; min-height: 36px; align-items: center; justify-content: space-between; gap: var(--space-group); border-radius: calc(var(--radius-container) - 5px); padding: 0 12px; color: var(--foreground); font-size: var(--text-sm); cursor: pointer; outline: none; user-select: none; transition: background-color 80ms var(--ease-standard), color 80ms var(--ease-standard); }
22
+ .option { display: flex; min-height: 36px; align-items: center; justify-content: space-between; gap: var(--space-group); border-radius: calc(var(--radius-container) - var(--space-tight)); padding: 0 var(--space-group); color: var(--foreground); font-size: var(--text-sm); cursor: pointer; outline: none; user-select: none; transition: background-color 80ms var(--ease-standard), color 80ms var(--ease-standard); }
23
23
  .option > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
24
24
  .option[data-active="true"] { background: var(--surface-muted); }
25
25
  .option[data-disabled="true"] { opacity: .42; cursor: not-allowed; }