neba 1.18.0 → 1.19.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 (200) hide show
  1. package/dist/a2ui/adapter-catalog.json +1 -1
  2. package/dist/a2ui/catalog.json +2 -2
  3. package/dist/components/accordion/Accordion.js +1 -1
  4. package/dist/components/agent-steps/AgentSteps.js +1 -1
  5. package/dist/components/alert/Alert.js +1 -1
  6. package/dist/components/anchor/Anchor.js +1 -1
  7. package/dist/components/animate-counter/AnimateCounter.js +1 -1
  8. package/dist/components/animate-headline/AnimateHeadline.js +1 -1
  9. package/dist/components/animate-lighting/AnimateLighting.js +1 -1
  10. package/dist/components/animate-marquee/AnimateMarquee.js +1 -1
  11. package/dist/components/animate-scramble/AnimateScramble.js +1 -1
  12. package/dist/components/animate-typing/AnimateTyping.js +1 -1
  13. package/dist/components/app-logo/AppLogo.js +1 -1
  14. package/dist/components/approval/Approval.js +1 -1
  15. package/dist/components/aspect-ratio/AspectRatio.js +1 -1
  16. package/dist/components/avatar/Avatar.js +1 -1
  17. package/dist/components/badge/Badge.js +1 -1
  18. package/dist/components/bar-chart/BarChart.js +1 -1
  19. package/dist/components/blockquote/Blockquote.js +1 -1
  20. package/dist/components/bottom-navigation/BottomNavigation.js +1 -1
  21. package/dist/components/breadcrumb/Breadcrumb.js +1 -1
  22. package/dist/components/button/Button.js +1 -1
  23. package/dist/components/button-group/ButtonGroup.js +1 -1
  24. package/dist/components/calendar/Calendar.js +1 -1
  25. package/dist/components/carousel/Carousel.js +1 -1
  26. package/dist/components/chat-bubble/ChatBubble.js +1 -1
  27. package/dist/components/checkbox/Checkbox.js +1 -1
  28. package/dist/components/checkbox/index.d.ts +1 -1
  29. package/dist/components/checkbox-group/CheckboxGroup.d.ts +84 -0
  30. package/dist/components/checkbox-group/CheckboxGroup.js +1 -0
  31. package/dist/components/checkbox-group/index.d.ts +2 -0
  32. package/dist/components/checkbox-group/index.js +1 -0
  33. package/dist/components/chip/Chip.d.ts +32 -3
  34. package/dist/components/chip/Chip.js +1 -1
  35. package/dist/components/code-block/CodeBlock.js +1 -1
  36. package/dist/components/collapsible/Collapsible.js +1 -1
  37. package/dist/components/color-picker/ColorPicker.js +1 -1
  38. package/dist/components/combobox/Combobox.d.ts +11 -0
  39. package/dist/components/combobox/Combobox.js +1 -1
  40. package/dist/components/combobox/index.d.ts +1 -1
  41. package/dist/components/command-palette/CommandPalette.d.ts +21 -5
  42. package/dist/components/command-palette/CommandPalette.js +1 -1
  43. package/dist/components/command-palette/CommandPaletteSheet.d.ts +27 -0
  44. package/dist/components/command-palette/CommandPaletteSheet.js +1 -0
  45. package/dist/components/command-palette/index.d.ts +1 -1
  46. package/dist/components/confirm/Confirm.d.ts +7 -5
  47. package/dist/components/confirm/Confirm.js +1 -1
  48. package/dist/components/data-list/DataList.js +1 -1
  49. package/dist/components/data-table/DataTable.js +1 -1
  50. package/dist/components/data-table/RowsPerPage.d.ts +43 -0
  51. package/dist/components/data-table/RowsPerPage.js +1 -0
  52. package/dist/components/date-picker/DatePicker.js +1 -1
  53. package/dist/components/date-range-picker/DateRangePicker.js +1 -1
  54. package/dist/components/date-time-picker/DateTimePicker.js +1 -1
  55. package/dist/components/dialog/Dialog.js +1 -1
  56. package/dist/components/dialog/index.d.ts +1 -1
  57. package/dist/components/drawer/Drawer.d.ts +1 -1
  58. package/dist/components/drawer/Drawer.js +1 -1
  59. package/dist/components/empty/Empty.js +1 -1
  60. package/dist/components/fieldset/Fieldset.d.ts +12 -1
  61. package/dist/components/fieldset/Fieldset.js +1 -1
  62. package/dist/components/file-picker/FilePicker.js +1 -1
  63. package/dist/components/floating-action-button/FloatingActionButton.js +1 -1
  64. package/dist/components/floating-action-button/index.d.ts +1 -0
  65. package/dist/components/floating-bottom-navigation/FloatingBottomNavigation.js +1 -1
  66. package/dist/components/footer/Footer.js +1 -1
  67. package/dist/components/form/Form.d.ts +8 -2
  68. package/dist/components/form/Form.js +1 -1
  69. package/dist/components/gallery/Gallery.js +1 -1
  70. package/dist/components/gauge-chart/GaugeChart.js +1 -1
  71. package/dist/components/header/Header.js +1 -1
  72. package/dist/components/heatmap-chart/HeatmapChart.js +1 -1
  73. package/dist/components/highlight/Highlight.js +1 -1
  74. package/dist/components/hover-card/HoverCard.js +1 -1
  75. package/dist/components/how-to-steps/HowToSteps.d.ts +3 -0
  76. package/dist/components/how-to-steps/HowToSteps.js +1 -1
  77. package/dist/components/image/Image.js +1 -1
  78. package/dist/components/inline-citation/InlineCitation.d.ts +24 -0
  79. package/dist/components/inline-citation/InlineCitation.js +1 -1
  80. package/dist/components/list/List.d.ts +9 -0
  81. package/dist/components/list/List.js +1 -1
  82. package/dist/components/menu/Menu.js +1 -1
  83. package/dist/components/menubar/Menubar.js +1 -1
  84. package/dist/components/meter/Meter.js +1 -1
  85. package/dist/components/mockup/Mockup.js +1 -1
  86. package/dist/components/navigation-menu/NavigationMenu.d.ts +2 -1
  87. package/dist/components/navigation-menu/NavigationMenu.js +1 -1
  88. package/dist/components/number-field/NumberField.js +1 -1
  89. package/dist/components/number-field/index.d.ts +1 -1
  90. package/dist/components/overlay/Overlay.js +1 -1
  91. package/dist/components/page-layout/PageLayout.js +1 -1
  92. package/dist/components/pagination/Pagination.js +1 -1
  93. package/dist/components/panes/Panes.js +1 -1
  94. package/dist/components/pie-chart/PieChart.js +1 -1
  95. package/dist/components/pill/Pill.js +1 -1
  96. package/dist/components/popover/Popover.js +1 -1
  97. package/dist/components/progress-box/ProgressBox.js +1 -1
  98. package/dist/components/progress-circular/ProgressCircular.js +1 -1
  99. package/dist/components/progress-linear/ProgressLinear.js +1 -1
  100. package/dist/components/prompt-input/PromptInput.js +1 -1
  101. package/dist/components/provider/NebaProvider.d.ts +3 -2
  102. package/dist/components/provider/NebaProvider.js +1 -1
  103. package/dist/components/radio-group/RadioGroup.d.ts +13 -0
  104. package/dist/components/radio-group/RadioGroup.js +1 -1
  105. package/dist/components/radio-group/index.d.ts +1 -1
  106. package/dist/components/rating/Rating.js +1 -1
  107. package/dist/components/reasoning/Reasoning.js +1 -1
  108. package/dist/components/scatter-chart/ScatterChart.js +1 -1
  109. package/dist/components/scroll-area/ScrollArea.js +1 -1
  110. package/dist/components/scroll-zone/ScrollZone.js +1 -1
  111. package/dist/components/segmented-button/SegmentedButton.js +1 -1
  112. package/dist/components/select/Select.d.ts +12 -0
  113. package/dist/components/select/Select.js +1 -1
  114. package/dist/components/select/index.d.ts +1 -1
  115. package/dist/components/shortcut/Shortcut.js +1 -1
  116. package/dist/components/sidebar/Sidebar.js +1 -1
  117. package/dist/components/sidebar/SidebarTrigger.js +1 -1
  118. package/dist/components/skeleton/Skeleton.js +1 -1
  119. package/dist/components/slider/Slider.js +1 -1
  120. package/dist/components/slider/index.d.ts +1 -1
  121. package/dist/components/sources/Sources.js +1 -1
  122. package/dist/components/sparkline/Sparkline.d.ts +7 -0
  123. package/dist/components/sparkline/Sparkline.js +1 -1
  124. package/dist/components/spoiler/Spoiler.js +1 -1
  125. package/dist/components/statistic/Statistic.js +1 -1
  126. package/dist/components/streaming-text/StreamingText.d.ts +10 -6
  127. package/dist/components/streaming-text/StreamingText.js +1 -1
  128. package/dist/components/switch/Switch.js +1 -1
  129. package/dist/components/switch/index.d.ts +1 -1
  130. package/dist/components/table/Table.js +1 -1
  131. package/dist/components/table/index.d.ts +1 -1
  132. package/dist/components/tabs/Tabs.js +1 -1
  133. package/dist/components/text-field/TextField.js +1 -1
  134. package/dist/components/text-field/index.d.ts +1 -1
  135. package/dist/components/text-link/TextLink.js +1 -1
  136. package/dist/components/time-picker/TimePicker.js +1 -1
  137. package/dist/components/timeline-chart/TimelineChart.js +1 -1
  138. package/dist/components/toast/Toast.d.ts +38 -5
  139. package/dist/components/toast/Toast.js +1 -1
  140. package/dist/components/toast/index.d.ts +2 -2
  141. package/dist/components/toast/index.js +1 -1
  142. package/dist/components/toggle/Toggle.js +1 -1
  143. package/dist/components/toggle-group/ToggleGroup.js +1 -1
  144. package/dist/components/tool-call/ToolCall.js +1 -1
  145. package/dist/components/toolbar/Toolbar.js +1 -1
  146. package/dist/components/tooltip/Tooltip.js +1 -1
  147. package/dist/components/tour/Tour.js +1 -1
  148. package/dist/components/tour/index.d.ts +1 -1
  149. package/dist/components/transfer/Transfer.d.ts +7 -0
  150. package/dist/components/transfer/Transfer.js +1 -1
  151. package/dist/components/tree-select/TreeSelect.js +1 -1
  152. package/dist/components/tree-view/TreeView.js +1 -1
  153. package/dist/components/window-pane/WindowPane.js +1 -1
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +1 -1
  156. package/dist/internal/animate.d.ts +34 -2
  157. package/dist/internal/animate.js +1 -1
  158. package/dist/internal/cache.d.ts +7 -1
  159. package/dist/internal/cache.js +1 -1
  160. package/dist/internal/calendar.d.ts +40 -1
  161. package/dist/internal/calendar.js +1 -1
  162. package/dist/internal/chart-frame.d.ts +86 -8
  163. package/dist/internal/chart-frame.js +1 -1
  164. package/dist/internal/chart-line.js +1 -1
  165. package/dist/internal/chart.d.ts +13 -0
  166. package/dist/internal/chart.js +1 -1
  167. package/dist/internal/checkbox-group.d.ts +29 -0
  168. package/dist/internal/checkbox-group.js +1 -0
  169. package/dist/internal/data-table.d.ts +7 -1
  170. package/dist/internal/data-table.js +1 -1
  171. package/dist/internal/date.d.ts +10 -0
  172. package/dist/internal/date.js +1 -1
  173. package/dist/internal/defaults.d.ts +14 -2
  174. package/dist/internal/idle.d.ts +10 -0
  175. package/dist/internal/idle.js +1 -0
  176. package/dist/internal/link.d.ts +4 -3
  177. package/dist/internal/media.js +1 -1
  178. package/dist/internal/observe.d.ts +4 -0
  179. package/dist/internal/observe.js +1 -1
  180. package/dist/internal/page-layout.d.ts +30 -2
  181. package/dist/internal/page-layout.js +1 -1
  182. package/dist/internal/picker.js +1 -1
  183. package/dist/internal/progress.d.ts +17 -0
  184. package/dist/internal/progress.js +1 -1
  185. package/dist/internal/refs.d.ts +18 -0
  186. package/dist/internal/refs.js +1 -0
  187. package/dist/internal/run.d.ts +34 -19
  188. package/dist/internal/run.js +1 -1
  189. package/dist/internal/sizer.d.ts +9 -4
  190. package/dist/internal/sizer.js +1 -1
  191. package/dist/internal/styles.d.ts +27 -1
  192. package/dist/internal/styles.js +1 -1
  193. package/dist/internal/tile.d.ts +34 -0
  194. package/dist/internal/tile.js +1 -0
  195. package/dist/internal/wheel.d.ts +24 -1
  196. package/dist/internal/wheel.js +1 -1
  197. package/dist/styles.css +1 -1
  198. package/dist/tailwind.css +369 -62
  199. package/dist/types.d.ts +4 -2
  200. package/package.json +2 -2
package/dist/tailwind.css CHANGED
@@ -100,6 +100,16 @@
100
100
  --neba-fill-alpha: 88%;
101
101
  --neba-blur: blur(9px) saturate(1.5);
102
102
 
103
+ /* What every translucent sheet and fill is laid over before it reaches the
104
+ page. `transparent` is the acrylic: the page shows through, and the blur
105
+ above is what keeps it from being read. `var(--neba-surface)` stops it,
106
+ and each sheet comes out the colour the acrylic has over a plain page —
107
+ which is what the reduced-transparency block below the dark theme sets,
108
+ together with `--neba-blur: none`. One value for both themes, so it is declared here
109
+ only; the rule that changes it has to name every theme root, because the
110
+ surface it resolves against is that root's. */
111
+ --neba-backing: transparent;
112
+
103
113
  /* How much of the family's accent a focus ring is. The ring sits 2px off the
104
114
  control, so what it is read against is the page, and on a white sheet 55%
105
115
  of any accent is 2.2 to 2.6:1 — under the 3:1 a focus indicator needs, on
@@ -129,8 +139,12 @@
129
139
  any backdrop that is not pure white it settled into grey. Raising it to 66%
130
140
  is the one place alpha is the right lever rather than the blur: the blur
131
141
  decides whether the backdrop is *legible*, and 9px still hides it. This
132
- decides whether the sheet reads as white, and it has to. */
133
- --neba-glass-bg: rgb(255 255 255 / 0.66);
142
+ decides whether the sheet reads as white, and it has to.
143
+
144
+ The white is mixed into `--neba-backing` rather than written with an
145
+ alpha, and so are the panel steps below. Into `transparent` the two are
146
+ the same colour. */
147
+ --neba-glass-bg: color-mix(in srgb, white 66%, var(--neba-backing));
134
148
 
135
149
  /* The container surface, in the three strengths a panel-shaped component
136
150
  needs — Box, Card and TextField read these directly.
@@ -146,9 +160,9 @@
146
160
 
147
161
  The ladder is opacity, not lightness — the sheet catches more light as it
148
162
  is engaged, rather than turning grey. */
149
- --neba-panel: rgb(255 255 255 / 0.66);
150
- --neba-panel-hover: rgb(255 255 255 / 0.82);
151
- --neba-panel-press: rgb(255 255 255 / 0.92);
163
+ --neba-panel: color-mix(in srgb, white 66%, var(--neba-backing));
164
+ --neba-panel-hover: color-mix(in srgb, white 82%, var(--neba-backing));
165
+ --neba-panel-press: color-mix(in srgb, white 92%, var(--neba-backing));
152
166
 
153
167
  /* The one surface in the library that is a groove rather than a sheet: the
154
168
  track a Switch's thumb runs along.
@@ -165,7 +179,13 @@
165
179
  surface here does — the blur behind it is what makes it acrylic rather than
166
180
  paint — and it stays neutral because the family is what "on" says. On the
167
181
  dark theme the panel was already darker than the thumb, so these are the
168
- panel's own two steps and nothing moves. */
182
+ panel's own two steps and nothing moves.
183
+
184
+ It is not mixed into `--neba-backing`, and neither is any wash: a groove,
185
+ a `soft` tint or a hairline is cut into a sheet or laid on one, and once
186
+ the sheet is opaque nothing behind it shows through them either. Mixed
187
+ into the page's own colour, a track inside a dark Card would be the
188
+ Card's colour and the groove would be gone. */
169
189
  --neba-groove: rgb(16 24 40 / 0.14);
170
190
  --neba-groove-hover: rgb(16 24 40 / 0.19);
171
191
 
@@ -219,7 +239,9 @@
219
239
  tooltip, a toast. One number, because they are all the same kind of thing
220
240
  — something drawn over the page the reader was looking at — and because
221
241
  the value a host needs is its own. A site whose fixed header is at 1200
222
- sets this once rather than beating fourteen `z-50`s with a selector. */
242
+ sets this once rather than beating fourteen `z-50`s with a selector.
243
+ The toast stack reads `--neba-z-toast`, which is not declared here: the
244
+ viewport falls back to one above this, so it follows a host's value. */
223
245
  --neba-z-portal: 50;
224
246
 
225
247
  /* Color families -------------------------------------------------------
@@ -233,7 +255,8 @@
233
255
  fill is only 88% opaque, and the pair has to clear 4.5:1 over a white page
234
256
  — which pins every family with white text to the high 40s / low 50s. A
235
257
  brighter *fill* means a dark ink, which is why `warning` is the one family
236
- that already goes there. Verified per step, including hover and active.
258
+ that already goes there. Verified per step, including hover and active, in
259
+ every theme: `test/styles/fill-contrast.test.ts` holds it.
237
260
 
238
261
  `success` moved 152 → 148 and `info` 218 → 223. Both are hue regions where
239
262
  sRGB is starved at mid lightness, and a few degrees toward the gamut's wide
@@ -479,11 +502,11 @@
479
502
  --neba-plate-top: rgb(255 255 255 / 0.16);
480
503
  --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
481
504
 
482
- --neba-glass-bg: rgb(255 255 255 / 0.05);
505
+ --neba-glass-bg: color-mix(in srgb, white 5%, var(--neba-backing));
483
506
 
484
- --neba-panel: rgb(255 255 255 / 0.05);
485
- --neba-panel-hover: rgb(255 255 255 / 0.07);
486
- --neba-panel-press: rgb(255 255 255 / 0.09);
507
+ --neba-panel: color-mix(in srgb, white 5%, var(--neba-backing));
508
+ --neba-panel-hover: color-mix(in srgb, white 7%, var(--neba-backing));
509
+ --neba-panel-press: color-mix(in srgb, white 9%, var(--neba-backing));
487
510
 
488
511
  --neba-groove: rgb(255 255 255 / 0.05);
489
512
  --neba-groove-hover: rgb(255 255 255 / 0.07);
@@ -496,7 +519,7 @@
496
519
  --neba-shadow-ambient: rgb(0 0 0 / 0.55);
497
520
 
498
521
  --neba-primary-solid: oklch(57% 0.208 262);
499
- --neba-primary-solid-hover: oklch(62% 0.181 262);
522
+ --neba-primary-solid-hover: oklch(61% 0.181 262);
500
523
  --neba-primary-solid-active: oklch(48% 0.196 262);
501
524
  --neba-primary-on-solid: oklch(99% 0.004 262);
502
525
  --neba-primary-accent: oklch(78% 0.103 262);
@@ -515,7 +538,7 @@
515
538
 
516
539
  --neba-warning-solid: oklch(76% 0.145 80);
517
540
  --neba-warning-solid-hover: oklch(81% 0.154 80);
518
- --neba-warning-solid-active: oklch(66% 0.126 80);
541
+ --neba-warning-solid-active: oklch(69.5% 0.126 80);
519
542
  --neba-warning-on-solid: oklch(25% 0.06 70);
520
543
  --neba-warning-accent: oklch(82% 0.14 80);
521
544
 
@@ -620,11 +643,11 @@
620
643
  --neba-plate-top: rgb(255 255 255 / 0.16);
621
644
  --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
622
645
 
623
- --neba-glass-bg: rgb(255 255 255 / 0.05);
646
+ --neba-glass-bg: color-mix(in srgb, white 5%, var(--neba-backing));
624
647
 
625
- --neba-panel: rgb(255 255 255 / 0.05);
626
- --neba-panel-hover: rgb(255 255 255 / 0.07);
627
- --neba-panel-press: rgb(255 255 255 / 0.09);
648
+ --neba-panel: color-mix(in srgb, white 5%, var(--neba-backing));
649
+ --neba-panel-hover: color-mix(in srgb, white 7%, var(--neba-backing));
650
+ --neba-panel-press: color-mix(in srgb, white 9%, var(--neba-backing));
628
651
 
629
652
  --neba-groove: rgb(255 255 255 / 0.05);
630
653
  --neba-groove-hover: rgb(255 255 255 / 0.07);
@@ -637,7 +660,7 @@
637
660
  --neba-shadow-ambient: rgb(0 0 0 / 0.55);
638
661
 
639
662
  --neba-primary-solid: oklch(57% 0.208 262);
640
- --neba-primary-solid-hover: oklch(62% 0.181 262);
663
+ --neba-primary-solid-hover: oklch(61% 0.181 262);
641
664
  --neba-primary-solid-active: oklch(48% 0.196 262);
642
665
  --neba-primary-on-solid: oklch(99% 0.004 262);
643
666
  --neba-primary-accent: oklch(78% 0.103 262);
@@ -656,7 +679,7 @@
656
679
 
657
680
  --neba-warning-solid: oklch(76% 0.145 80);
658
681
  --neba-warning-solid-hover: oklch(81% 0.154 80);
659
- --neba-warning-solid-active: oklch(66% 0.126 80);
682
+ --neba-warning-solid-active: oklch(69.5% 0.126 80);
660
683
  --neba-warning-on-solid: oklch(25% 0.06 70);
661
684
  --neba-warning-accent: oklch(82% 0.14 80);
662
685
 
@@ -717,6 +740,53 @@
717
740
  --neba-chart-on-8: var(--neba-surface);
718
741
  }
719
742
 
743
+ /* ---------------------------------------------------------------------------
744
+ * Reduced transparency
745
+ *
746
+ * A reader whose system is set to reduce transparency gets opaque sheets and no
747
+ * backdrop blur. Taking the blur away alone is not enough, and is worse than
748
+ * leaving it: the blur is what keeps the page behind a sheet from being read,
749
+ * so with nothing else changed a menu over a paragraph in the dark theme is
750
+ * nine per cent of white over sharp text, and a light Card shows a ghost of the
751
+ * words under it.
752
+ *
753
+ * So the sheets and the fills are laid on the page's own surface instead of on
754
+ * whatever is behind them. Each one comes out the colour the acrylic has over a
755
+ * plain page — plain white for every light sheet, white over white being
756
+ * white — and its ink was measured on exactly that colour. What is lost
757
+ * is the compounding on the dark side: a sheet inside a sheet is no longer
758
+ * lighter than the one around it, because both are laid on the page. The
759
+ * hairline and the plate edge still say where the inner one begins, and the
760
+ * muted ink reads better on it than it did. Washes, grooves and hairlines stay
761
+ * as they are; they sit on a sheet, and the sheet is opaque now. The scrim
762
+ * behind a modal stays as well, since showing the page dimmed is its job.
763
+ *
764
+ * It also takes the most expensive thing off the page: a backdrop filter is
765
+ * applied again on every frame the page moves, so a scroll pays for the blur
766
+ * under every surface on the screen.
767
+ *
768
+ * Every theme root is named, because `var(--neba-surface)` resolves on the
769
+ * element that declares it, and on `:root` alone a `.dark` box inside a light
770
+ * page would lay its sheets on the light page's white.
771
+ * ------------------------------------------------------------------------- */
772
+
773
+ @media (prefers-reduced-transparency: reduce) {
774
+ :root,
775
+ .dark,
776
+ .light,
777
+ [data-theme='dark'],
778
+ [data-theme='light'] {
779
+ --neba-backing: var(--neba-surface);
780
+ --neba-blur: none;
781
+ }
782
+
783
+ /* The system-dark block declares the blur on `:root` at three classes'
784
+ weight, which the one class above does not beat. */
785
+ :root:not(.light):not([data-theme='light']) {
786
+ --neba-blur: none;
787
+ }
788
+ }
789
+
720
790
  /* ---------------------------------------------------------------------------
721
791
  * Derived tokens
722
792
  *
@@ -741,6 +811,12 @@
741
811
  * blue — and the worst family on the strongest wash clears 4.5:1 in both.
742
812
  * `accent` itself is untouched, so what a TextLink or a Statistic's delta is
743
813
  * written in on the bare sheet does not change.
814
+ *
815
+ * A fill is the family's solid mixed into `--neba-backing` rather than into
816
+ * `transparent`, so that it turns opaque with the sheets when the reader asks
817
+ * for less transparency. It keeps `--neba-fill-alpha` when it does: the ink on
818
+ * a fill was solved against that much of it over the page, and the whole solid
819
+ * instead would take the dark theme's primary hover from about 4.5:1 to 3.6:1.
744
820
  * ------------------------------------------------------------------------- */
745
821
 
746
822
  :root,
@@ -767,17 +843,17 @@
767
843
  --neba-primary-fill: color-mix(
768
844
  in srgb,
769
845
  var(--neba-primary-solid) var(--neba-fill-alpha),
770
- transparent
846
+ var(--neba-backing)
771
847
  );
772
848
  --neba-primary-fill-hover: color-mix(
773
849
  in srgb,
774
850
  var(--neba-primary-solid-hover) var(--neba-fill-alpha),
775
- transparent
851
+ var(--neba-backing)
776
852
  );
777
853
  --neba-primary-fill-active: color-mix(
778
854
  in srgb,
779
855
  var(--neba-primary-solid-active) var(--neba-fill-alpha),
780
- transparent
856
+ var(--neba-backing)
781
857
  );
782
858
  --neba-primary-soft: color-mix(in srgb, var(--neba-primary-accent) 10%, transparent);
783
859
  --neba-primary-soft-hover: color-mix(in srgb, var(--neba-primary-accent) 17%, transparent);
@@ -805,17 +881,17 @@
805
881
  --neba-secondary-fill: color-mix(
806
882
  in srgb,
807
883
  var(--neba-secondary-solid) var(--neba-fill-alpha),
808
- transparent
884
+ var(--neba-backing)
809
885
  );
810
886
  --neba-secondary-fill-hover: color-mix(
811
887
  in srgb,
812
888
  var(--neba-secondary-solid-hover) var(--neba-fill-alpha),
813
- transparent
889
+ var(--neba-backing)
814
890
  );
815
891
  --neba-secondary-fill-active: color-mix(
816
892
  in srgb,
817
893
  var(--neba-secondary-solid-active) var(--neba-fill-alpha),
818
- transparent
894
+ var(--neba-backing)
819
895
  );
820
896
  --neba-secondary-soft: color-mix(in srgb, var(--neba-secondary-accent) 10%, transparent);
821
897
  --neba-secondary-soft-hover: color-mix(in srgb, var(--neba-secondary-accent) 17%, transparent);
@@ -843,17 +919,17 @@
843
919
  --neba-success-fill: color-mix(
844
920
  in srgb,
845
921
  var(--neba-success-solid) var(--neba-fill-alpha),
846
- transparent
922
+ var(--neba-backing)
847
923
  );
848
924
  --neba-success-fill-hover: color-mix(
849
925
  in srgb,
850
926
  var(--neba-success-solid-hover) var(--neba-fill-alpha),
851
- transparent
927
+ var(--neba-backing)
852
928
  );
853
929
  --neba-success-fill-active: color-mix(
854
930
  in srgb,
855
931
  var(--neba-success-solid-active) var(--neba-fill-alpha),
856
- transparent
932
+ var(--neba-backing)
857
933
  );
858
934
  --neba-success-soft: color-mix(in srgb, var(--neba-success-accent) 10%, transparent);
859
935
  --neba-success-soft-hover: color-mix(in srgb, var(--neba-success-accent) 17%, transparent);
@@ -881,17 +957,17 @@
881
957
  --neba-warning-fill: color-mix(
882
958
  in srgb,
883
959
  var(--neba-warning-solid) var(--neba-fill-alpha),
884
- transparent
960
+ var(--neba-backing)
885
961
  );
886
962
  --neba-warning-fill-hover: color-mix(
887
963
  in srgb,
888
964
  var(--neba-warning-solid-hover) var(--neba-fill-alpha),
889
- transparent
965
+ var(--neba-backing)
890
966
  );
891
967
  --neba-warning-fill-active: color-mix(
892
968
  in srgb,
893
969
  var(--neba-warning-solid-active) var(--neba-fill-alpha),
894
- transparent
970
+ var(--neba-backing)
895
971
  );
896
972
  --neba-warning-soft: color-mix(in srgb, var(--neba-warning-accent) 12%, transparent);
897
973
  --neba-warning-soft-hover: color-mix(in srgb, var(--neba-warning-accent) 20%, transparent);
@@ -919,17 +995,17 @@
919
995
  --neba-danger-fill: color-mix(
920
996
  in srgb,
921
997
  var(--neba-danger-solid) var(--neba-fill-alpha),
922
- transparent
998
+ var(--neba-backing)
923
999
  );
924
1000
  --neba-danger-fill-hover: color-mix(
925
1001
  in srgb,
926
1002
  var(--neba-danger-solid-hover) var(--neba-fill-alpha),
927
- transparent
1003
+ var(--neba-backing)
928
1004
  );
929
1005
  --neba-danger-fill-active: color-mix(
930
1006
  in srgb,
931
1007
  var(--neba-danger-solid-active) var(--neba-fill-alpha),
932
- transparent
1008
+ var(--neba-backing)
933
1009
  );
934
1010
  --neba-danger-soft: color-mix(in srgb, var(--neba-danger-accent) 10%, transparent);
935
1011
  --neba-danger-soft-hover: color-mix(in srgb, var(--neba-danger-accent) 17%, transparent);
@@ -954,16 +1030,20 @@
954
1030
  transparent
955
1031
  );
956
1032
 
957
- --neba-info-fill: color-mix(in srgb, var(--neba-info-solid) var(--neba-fill-alpha), transparent);
1033
+ --neba-info-fill: color-mix(
1034
+ in srgb,
1035
+ var(--neba-info-solid) var(--neba-fill-alpha),
1036
+ var(--neba-backing)
1037
+ );
958
1038
  --neba-info-fill-hover: color-mix(
959
1039
  in srgb,
960
1040
  var(--neba-info-solid-hover) var(--neba-fill-alpha),
961
- transparent
1041
+ var(--neba-backing)
962
1042
  );
963
1043
  --neba-info-fill-active: color-mix(
964
1044
  in srgb,
965
1045
  var(--neba-info-solid-active) var(--neba-fill-alpha),
966
- transparent
1046
+ var(--neba-backing)
967
1047
  );
968
1048
  --neba-info-soft: color-mix(in srgb, var(--neba-info-accent) 10%, transparent);
969
1049
  --neba-info-soft-hover: color-mix(in srgb, var(--neba-info-accent) 17%, transparent);
@@ -2373,6 +2453,86 @@
2373
2453
  box-shadow: none;
2374
2454
  }
2375
2455
 
2456
+ /* The frame the tile arrives in. The light's own opacity transition would
2457
+ otherwise fade the stand-in out under the tile as a bloom. */
2458
+ .neba-segmented[data-settling] .neba-segment::before {
2459
+ transition: none;
2460
+ }
2461
+
2462
+ /* ---------------------------------------------------------------------------
2463
+ * A tab bar before its indicator is placed
2464
+ *
2465
+ * The pill or bar under the chosen tab is Base UI's indicator, and Base UI
2466
+ * keeps it `hidden` until it has measured the chosen tab, which only happens
2467
+ * once the page is running. A server-rendered `solid` bar therefore had no
2468
+ * pill under the chosen tab until hydration, and the other two had no line.
2469
+ * Until the indicator is shown, the chosen tab draws the same thing itself,
2470
+ * and hands it over in the frame the indicator appears: both follow the one
2471
+ * `hidden` attribute, so there is no frame with two and no frame with none.
2472
+ *
2473
+ * On `::after` rather than the tab's own background, which has a transition
2474
+ * and would fade out over the indicator. `::after` is the tab's afterglow,
2475
+ * which a tab leaves transparent, so the fade it goes back to shows nothing.
2476
+ * `:has()` is past the browser floor in Firefox 113 to 120, where the whole
2477
+ * rule is dropped and the tab is told apart by its colour until hydration, as
2478
+ * it was before.
2479
+ * ------------------------------------------------------------------------- */
2480
+
2481
+ .neba-tabs-list:has(> .neba-tabs-indicator[hidden]) > [data-active]::after {
2482
+ content: '';
2483
+ position: absolute;
2484
+ z-index: -1;
2485
+ pointer-events: none;
2486
+ opacity: 1;
2487
+ transition: none;
2488
+ }
2489
+
2490
+ /* `solid`: the tile, the same sheet as the indicator's. */
2491
+ .neba-tabs-list[data-indicator='tile']:has(> .neba-tabs-indicator[hidden]) > [data-active]::after {
2492
+ inset: 0;
2493
+ border-radius: inherit;
2494
+ background: var(--n-panel-press);
2495
+ background-image: var(--neba-grain), var(--neba-sheen);
2496
+ background-blend-mode: overlay, normal;
2497
+ box-shadow: var(--neba-shadow-1), var(--neba-plate-glass);
2498
+ backdrop-filter: var(--neba-blur);
2499
+ }
2500
+
2501
+ /* `outline` and `text`: a 2px line on the edge the bar runs along. */
2502
+ .neba-tabs-list[data-indicator='bar']:has(> .neba-tabs-indicator[hidden]) > [data-active]::after {
2503
+ inset: auto 0 0;
2504
+ height: 2px;
2505
+ border-radius: 9999px;
2506
+ background: var(--n-accent);
2507
+ }
2508
+
2509
+ .neba-tabs-list[data-indicator='bar'][data-orientation='vertical']:has(
2510
+ > .neba-tabs-indicator[hidden]
2511
+ )
2512
+ > [data-active]::after {
2513
+ inset-block: 0;
2514
+ inset-inline: auto 0;
2515
+ width: 2px;
2516
+ height: auto;
2517
+ }
2518
+
2519
+ /* A bar that wraps: the line is the tab's own bottom edge, or its end edge. */
2520
+ .neba-tabs-list[data-indicator='underline']:has(> .neba-tabs-indicator[hidden])
2521
+ > [data-active]::after {
2522
+ inset: 0;
2523
+ border-radius: 9999px;
2524
+ border-bottom: 2px solid var(--n-accent);
2525
+ background: transparent;
2526
+ }
2527
+
2528
+ .neba-tabs-list[data-indicator='underline'][data-orientation='vertical']:has(
2529
+ > .neba-tabs-indicator[hidden]
2530
+ )
2531
+ > [data-active]::after {
2532
+ border-bottom: 0;
2533
+ border-inline-end: 2px solid var(--n-accent);
2534
+ }
2535
+
2376
2536
  /* ---------------------------------------------------------------------------
2377
2537
  * Tree guides
2378
2538
  *
@@ -2648,20 +2808,28 @@
2648
2808
  *
2649
2809
  * `currentColor` rather than a token, because a bubble's ink is white on
2650
2810
  * `solid` and the page's own on the other two.
2811
+ *
2812
+ * The colour changes through `opacity` on a solid dot rather than through the
2813
+ * dot's `background-color`. A dot has nothing inside it, so the two draw the
2814
+ * same pixels at every frame — `currentColor` at 28% is `currentColor` at an
2815
+ * alpha of 0.28 — but a background is repainted on the main thread every frame
2816
+ * for as long as somebody is typing, and an opacity is handed to the
2817
+ * compositor.
2651
2818
  */
2652
2819
  @keyframes neba-typing {
2653
2820
  0%,
2654
2821
  60%,
2655
2822
  100% {
2656
- background-color: color-mix(in oklab, currentColor 28%, transparent);
2823
+ opacity: 0.28;
2657
2824
  }
2658
2825
  30% {
2659
- background-color: currentColor;
2826
+ opacity: 1;
2660
2827
  }
2661
2828
  }
2662
2829
 
2663
2830
  .neba-typing-dot {
2664
- background-color: color-mix(in oklab, currentColor 28%, transparent);
2831
+ background-color: currentColor;
2832
+ opacity: 0.28;
2665
2833
  animation: neba-typing 1.25s var(--neba-ease) infinite;
2666
2834
  animation-delay: calc(var(--n-i, 0) * 160ms);
2667
2835
  }
@@ -2698,6 +2866,26 @@
2698
2866
  animation: neba-skeleton-sweep 1.5s var(--neba-ease) infinite;
2699
2867
  }
2700
2868
 
2869
+ /* Off screen, a sweep holds still. `Skeleton` marks its root once it has
2870
+ scrolled out of view, which is also the root of a stack of lines. */
2871
+ [data-offscreen].neba-skeleton::after,
2872
+ [data-offscreen] .neba-skeleton::after {
2873
+ animation-play-state: paused;
2874
+ }
2875
+
2876
+ /* Where a reader asked for reduced transparency the panel colours are laid on
2877
+ the page's surface, which in the dark theme is darker than the wash a
2878
+ placeholder is drawn in, so the highlight crossed it as a shadow. Screened,
2879
+ it can only ever lighten what it passes over: white over a light placeholder
2880
+ is white as before, and the dark theme's lifts it as its nine per cent of
2881
+ white used to. The pulse that stands in for the sweep under reduced motion
2882
+ is the same layer and is screened with it. */
2883
+ @media (prefers-reduced-transparency: reduce) {
2884
+ .neba-skeleton::after {
2885
+ mix-blend-mode: screen;
2886
+ }
2887
+ }
2888
+
2701
2889
  /*
2702
2890
  * Under RTL the start edge is on the right, so both sweeps travel leftwards.
2703
2891
  *
@@ -2726,14 +2914,16 @@
2726
2914
 
2727
2915
  /* The reduced-motion stand-in: the highlight stops travelling and the whole
2728
2916
  placeholder breathes instead. Colour, which is the axis every other state in
2729
- the library already uses. */
2917
+ the library already uses — drawn as the opacity of a layer that is already
2918
+ that colour, which is the same pixels as a background fading in from
2919
+ transparent, without a repaint on every frame of a loading page. */
2730
2920
  @keyframes neba-skeleton-pulse {
2731
2921
  0%,
2732
2922
  100% {
2733
- background-color: transparent;
2923
+ opacity: 0;
2734
2924
  }
2735
2925
  50% {
2736
- background-color: var(--n-panel-press);
2926
+ opacity: 1;
2737
2927
  }
2738
2928
  }
2739
2929
 
@@ -2889,6 +3079,69 @@
2889
3079
  animation-name: neba-anim-rotate;
2890
3080
  }
2891
3081
 
3082
+ /*
3083
+ * An entrance waiting to be scrolled into view, drawn where the layout put it.
3084
+ *
3085
+ * Its first frame is somewhere else — a slide a full width to one side, a zoom
3086
+ * shrunk to the middle of a box taller than the screen — and the observer that
3087
+ * decides when it starts measures the box as drawn. A full-width slide moved a
3088
+ * full width sideways, or one moved out of a box with `overflow: hidden`, was
3089
+ * never on the screen at all and never started. So the move is taken away until
3090
+ * it starts, and the observer measures the box the reader is scrolling
3091
+ * towards. A first frame that fades is unseen there anyway. One that does not
3092
+ * fade, marked `shown`, is hidden in place as well, or it would jump to its
3093
+ * first frame as it started — but only where the page is going to wait for a
3094
+ * script, so with less motion asked for, with scripting off, and in a browser
3095
+ * that cannot say, it is drawn as it was. An opacity rather than `visibility`,
3096
+ * so what is inside can still take the focus, and focusing it scrolls it into
3097
+ * view. `!important` because an animation's held frame outranks every ordinary
3098
+ * declaration.
3099
+ */
3100
+ .neba-anim-slide[data-waiting=''] {
3101
+ translate: none !important;
3102
+ }
3103
+
3104
+ .neba-anim-scale[data-waiting=''] {
3105
+ scale: none !important;
3106
+ }
3107
+
3108
+ .neba-anim-rotate[data-waiting=''] {
3109
+ rotate: none !important;
3110
+ }
3111
+
3112
+ @media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
3113
+ .neba-anim[data-waiting='shown'] {
3114
+ opacity: 0 !important;
3115
+ }
3116
+
3117
+ .neba-anim-slide[data-waiting='shown'] {
3118
+ translate: none !important;
3119
+ }
3120
+
3121
+ .neba-anim-scale[data-waiting='shown'] {
3122
+ scale: none !important;
3123
+ }
3124
+
3125
+ .neba-anim-rotate[data-waiting='shown'] {
3126
+ rotate: none !important;
3127
+ }
3128
+ }
3129
+
3130
+ /*
3131
+ * With scripting off, nothing will ever let a waiting effect go, so it is not
3132
+ * held at all. An entrance waiting for `visible`, `hover` or `manual` sat on an
3133
+ * invisible first frame for good; it is drawn as it ends instead, which is what
3134
+ * the reduced-motion block below does for the same element. A browser that
3135
+ * does not know `scripting` skips the block, and only a reader with scripting
3136
+ * off could tell.
3137
+ */
3138
+ @media (scripting: none) {
3139
+ .neba-anim[data-state='paused'],
3140
+ [data-state='paused'] .neba-anim {
3141
+ animation: none;
3142
+ }
3143
+ }
3144
+
2892
3145
  /* Symmetric on purpose: a blink that ended anywhere but where it started would
2893
3146
  leave the element half drawn whenever the count ran out. */
2894
3147
  @keyframes neba-anim-blink {
@@ -2992,24 +3245,28 @@
2992
3245
  /*
2993
3246
  * Lighting: a light travelling around the outside of something.
2994
3247
  *
2995
- * The arc is a conic gradient and what moves is its `from` angle, which is only
2996
- * animatable because the angle is a registered custom property with a type. The
2997
- * alternative — rotating the pseudo-element itself — swings its corners out
2998
- * past the element on every quarter turn.
3248
+ * The arc is a conic gradient on a square, and what moves is the square, on
3249
+ * `rotate`. The ring around it holds it: a box the size of the element plus the
3250
+ * spread, with the element's corners, clipping the square to itself and
3251
+ * blurring what is left. A rotation is the one way to move the light that the
3252
+ * compositor runs without the page — the arc used to travel on the gradient's
3253
+ * `from` angle, a registered custom property, which restyled and repainted a
3254
+ * blurred gradient on the main thread on every frame for as long as the light
3255
+ * was on. Rotating a box the element's own shape instead swings its corners out
3256
+ * past the element on every quarter turn, which is what the ring is for. The
3257
+ * square is as wide as the ring is wide and tall together, which covers the
3258
+ * ring at every angle, and is measured in the ring's own container units.
2999
3259
  *
3000
3260
  * `z-index: -1` inside an isolated stacking context puts the glow behind the
3001
3261
  * content but still inside the component, so it reads as light escaping from
3002
3262
  * under a sheet rather than as a border drawn around one.
3003
3263
  */
3004
- @property --neba-glow-angle {
3005
- syntax: '<angle>';
3006
- initial-value: 0deg;
3007
- inherits: false;
3008
- }
3009
-
3010
3264
  @keyframes neba-anim-lighting {
3265
+ from {
3266
+ rotate: 0deg;
3267
+ }
3011
3268
  to {
3012
- --neba-glow-angle: 360deg;
3269
+ rotate: 360deg;
3013
3270
  }
3014
3271
  }
3015
3272
 
@@ -3018,20 +3275,30 @@
3018
3275
  isolation: isolate;
3019
3276
  }
3020
3277
 
3021
- .neba-anim-lighting::before {
3022
- content: '';
3278
+ .neba-lighting-ring {
3023
3279
  position: absolute;
3024
3280
  z-index: -1;
3025
3281
  inset: calc(-1 * var(--n-anim-glow-width, 3px));
3282
+ overflow: hidden;
3026
3283
  border-radius: inherit;
3284
+ filter: blur(var(--n-anim-glow-blur, 4px));
3285
+ container-type: size;
3286
+ pointer-events: none;
3287
+ }
3288
+
3289
+ .neba-lighting-beam {
3290
+ position: absolute;
3291
+ top: 50%;
3292
+ left: 50%;
3293
+ width: calc(100cqw + 100cqh);
3294
+ height: calc(100cqw + 100cqh);
3295
+ translate: -50% -50%;
3027
3296
  background: conic-gradient(
3028
- from var(--neba-glow-angle),
3029
3297
  transparent 0deg,
3030
3298
  var(--n-anim-glow, var(--n-accent)) var(--n-anim-glow-arc, 50deg),
3031
3299
  transparent calc(var(--n-anim-glow-arc, 50deg) * 2),
3032
3300
  transparent 360deg
3033
3301
  );
3034
- filter: blur(var(--n-anim-glow-blur, 4px));
3035
3302
  animation-name: neba-anim-lighting;
3036
3303
  animation-duration: var(--n-anim-duration, 3s);
3037
3304
  animation-delay: var(--n-anim-delay, 0ms);
@@ -3180,6 +3447,38 @@
3180
3447
  animation: neba-anim-caret 1s step-end infinite;
3181
3448
  }
3182
3449
 
3450
+ /*
3451
+ * The text effects without their motion: AnimateTyping, AnimateScramble and
3452
+ * AnimateCounter.
3453
+ *
3454
+ * Each lays out its final text from the first frame, unseen, under the copy
3455
+ * that moves. A reader who has asked for less motion is shown the final text,
3456
+ * but by the script, which is not there in the server's HTML or until the page
3457
+ * hydrates: until then the line was empty, noise, or the `from` number. With
3458
+ * scripting off it stayed that way. So the stylesheet answers both cases, by
3459
+ * drawing the final layer and not the moving one — which is also what the
3460
+ * script draws once it runs, so nothing moves when it does. The moving layer
3461
+ * and a counter's `from` are taken out of the layout as well as out of sight:
3462
+ * the script lets `from` go once the answer is drawn, and a box still as wide
3463
+ * as `1,000` around a `5` closed up the moment it did. A browser that does not
3464
+ * know `scripting` keeps the reduced-motion half.
3465
+ */
3466
+ @media (prefers-reduced-motion: reduce), (scripting: none) {
3467
+ [data-neba-animation] .neba-text-final,
3468
+ [data-neba-animation] .neba-text-noise {
3469
+ visibility: visible;
3470
+ }
3471
+
3472
+ [data-neba-animation] .neba-text-moving,
3473
+ [data-neba-animation] .neba-text-final ~ [data-sample] {
3474
+ display: none;
3475
+ }
3476
+
3477
+ [data-neba-animation] .neba-text-noise::after {
3478
+ content: none;
3479
+ }
3480
+ }
3481
+
3183
3482
  /* ---------------------------------------------------------------------------
3184
3483
  * Text arriving from somewhere else
3185
3484
  *
@@ -3201,8 +3500,12 @@
3201
3500
  }
3202
3501
  }
3203
3502
 
3503
+ /* `backwards` rather than `both`: the keyframe ends on the word's own opacity,
3504
+ so holding its last frame draws nothing, and a word whose fade is over is
3505
+ then an element with no animation on it rather than one still holding a
3506
+ finished one, which the browser keeps in its list of effects to restyle. */
3204
3507
  .neba-stream-word {
3205
- animation: neba-stream-word-in var(--neba-duration) var(--neba-ease) both;
3508
+ animation: neba-stream-word-in var(--neba-duration) var(--neba-ease) backwards;
3206
3509
  }
3207
3510
 
3208
3511
  /* The block at the end of a stream. The same caret an `AnimateTyping` draws,
@@ -3269,6 +3572,7 @@
3269
3572
  inset-inline-start: 0;
3270
3573
  inline-size: 100%;
3271
3574
  background-image: none;
3575
+ background-color: var(--n-panel-press);
3272
3576
  animation: neba-skeleton-pulse 2.4s var(--neba-ease) infinite;
3273
3577
  }
3274
3578
 
@@ -3319,9 +3623,12 @@
3319
3623
 
3320
3624
  /* The arc stops travelling and becomes an even glow. The same trade the
3321
3625
  skeleton makes: the decoration survives, the motion does not. */
3322
- .neba-anim-lighting::before {
3626
+ .neba-lighting-ring {
3323
3627
  background: var(--n-anim-glow, var(--n-accent));
3324
- animation: none;
3628
+ }
3629
+
3630
+ .neba-lighting-beam {
3631
+ display: none;
3325
3632
  }
3326
3633
 
3327
3634
  /* A marquee that cannot move is a row of content, so it is drawn as one: