neba 1.18.1 → 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 (185) 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/index.d.ts +1 -1
  30. package/dist/components/chip/Chip.d.ts +32 -3
  31. package/dist/components/chip/Chip.js +1 -1
  32. package/dist/components/code-block/CodeBlock.js +1 -1
  33. package/dist/components/collapsible/Collapsible.js +1 -1
  34. package/dist/components/color-picker/ColorPicker.js +1 -1
  35. package/dist/components/combobox/Combobox.d.ts +11 -0
  36. package/dist/components/combobox/Combobox.js +1 -1
  37. package/dist/components/combobox/index.d.ts +1 -1
  38. package/dist/components/command-palette/CommandPalette.d.ts +21 -5
  39. package/dist/components/command-palette/CommandPalette.js +1 -1
  40. package/dist/components/command-palette/CommandPaletteSheet.d.ts +27 -0
  41. package/dist/components/command-palette/CommandPaletteSheet.js +1 -0
  42. package/dist/components/command-palette/index.d.ts +1 -1
  43. package/dist/components/confirm/Confirm.d.ts +7 -5
  44. package/dist/components/confirm/Confirm.js +1 -1
  45. package/dist/components/data-list/DataList.js +1 -1
  46. package/dist/components/data-table/DataTable.js +1 -1
  47. package/dist/components/data-table/RowsPerPage.d.ts +43 -0
  48. package/dist/components/data-table/RowsPerPage.js +1 -0
  49. package/dist/components/date-picker/DatePicker.js +1 -1
  50. package/dist/components/date-range-picker/DateRangePicker.js +1 -1
  51. package/dist/components/date-time-picker/DateTimePicker.js +1 -1
  52. package/dist/components/dialog/Dialog.js +1 -1
  53. package/dist/components/dialog/index.d.ts +1 -1
  54. package/dist/components/drawer/Drawer.d.ts +1 -1
  55. package/dist/components/drawer/Drawer.js +1 -1
  56. package/dist/components/empty/Empty.js +1 -1
  57. package/dist/components/file-picker/FilePicker.js +1 -1
  58. package/dist/components/floating-action-button/FloatingActionButton.js +1 -1
  59. package/dist/components/floating-action-button/index.d.ts +1 -0
  60. package/dist/components/floating-bottom-navigation/FloatingBottomNavigation.js +1 -1
  61. package/dist/components/footer/Footer.js +1 -1
  62. package/dist/components/gallery/Gallery.js +1 -1
  63. package/dist/components/gauge-chart/GaugeChart.js +1 -1
  64. package/dist/components/header/Header.js +1 -1
  65. package/dist/components/heatmap-chart/HeatmapChart.js +1 -1
  66. package/dist/components/highlight/Highlight.js +1 -1
  67. package/dist/components/hover-card/HoverCard.js +1 -1
  68. package/dist/components/how-to-steps/HowToSteps.d.ts +3 -0
  69. package/dist/components/how-to-steps/HowToSteps.js +1 -1
  70. package/dist/components/image/Image.js +1 -1
  71. package/dist/components/inline-citation/InlineCitation.d.ts +24 -0
  72. package/dist/components/inline-citation/InlineCitation.js +1 -1
  73. package/dist/components/list/List.js +1 -1
  74. package/dist/components/menu/Menu.js +1 -1
  75. package/dist/components/menubar/Menubar.js +1 -1
  76. package/dist/components/meter/Meter.js +1 -1
  77. package/dist/components/mockup/Mockup.js +1 -1
  78. package/dist/components/navigation-menu/NavigationMenu.d.ts +2 -1
  79. package/dist/components/navigation-menu/NavigationMenu.js +1 -1
  80. package/dist/components/number-field/NumberField.js +1 -1
  81. package/dist/components/number-field/index.d.ts +1 -1
  82. package/dist/components/overlay/Overlay.js +1 -1
  83. package/dist/components/page-layout/PageLayout.js +1 -1
  84. package/dist/components/pagination/Pagination.js +1 -1
  85. package/dist/components/panes/Panes.js +1 -1
  86. package/dist/components/pie-chart/PieChart.js +1 -1
  87. package/dist/components/pill/Pill.js +1 -1
  88. package/dist/components/popover/Popover.js +1 -1
  89. package/dist/components/progress-box/ProgressBox.js +1 -1
  90. package/dist/components/progress-circular/ProgressCircular.js +1 -1
  91. package/dist/components/progress-linear/ProgressLinear.js +1 -1
  92. package/dist/components/prompt-input/PromptInput.js +1 -1
  93. package/dist/components/provider/NebaProvider.js +1 -1
  94. package/dist/components/radio-group/RadioGroup.js +1 -1
  95. package/dist/components/radio-group/index.d.ts +1 -1
  96. package/dist/components/rating/Rating.js +1 -1
  97. package/dist/components/reasoning/Reasoning.js +1 -1
  98. package/dist/components/scatter-chart/ScatterChart.js +1 -1
  99. package/dist/components/scroll-area/ScrollArea.js +1 -1
  100. package/dist/components/scroll-zone/ScrollZone.js +1 -1
  101. package/dist/components/segmented-button/SegmentedButton.js +1 -1
  102. package/dist/components/select/Select.d.ts +12 -0
  103. package/dist/components/select/Select.js +1 -1
  104. package/dist/components/select/index.d.ts +1 -1
  105. package/dist/components/shortcut/Shortcut.js +1 -1
  106. package/dist/components/sidebar/Sidebar.js +1 -1
  107. package/dist/components/sidebar/SidebarTrigger.js +1 -1
  108. package/dist/components/skeleton/Skeleton.js +1 -1
  109. package/dist/components/slider/Slider.js +1 -1
  110. package/dist/components/slider/index.d.ts +1 -1
  111. package/dist/components/sources/Sources.js +1 -1
  112. package/dist/components/sparkline/Sparkline.d.ts +7 -0
  113. package/dist/components/sparkline/Sparkline.js +1 -1
  114. package/dist/components/spoiler/Spoiler.js +1 -1
  115. package/dist/components/statistic/Statistic.js +1 -1
  116. package/dist/components/streaming-text/StreamingText.d.ts +10 -6
  117. package/dist/components/streaming-text/StreamingText.js +1 -1
  118. package/dist/components/switch/Switch.js +1 -1
  119. package/dist/components/switch/index.d.ts +1 -1
  120. package/dist/components/table/Table.js +1 -1
  121. package/dist/components/table/index.d.ts +1 -1
  122. package/dist/components/tabs/Tabs.js +1 -1
  123. package/dist/components/text-field/TextField.js +1 -1
  124. package/dist/components/text-field/index.d.ts +1 -1
  125. package/dist/components/text-link/TextLink.js +1 -1
  126. package/dist/components/time-picker/TimePicker.js +1 -1
  127. package/dist/components/timeline-chart/TimelineChart.js +1 -1
  128. package/dist/components/toast/Toast.d.ts +38 -5
  129. package/dist/components/toast/Toast.js +1 -1
  130. package/dist/components/toast/index.d.ts +2 -2
  131. package/dist/components/toast/index.js +1 -1
  132. package/dist/components/toggle/Toggle.js +1 -1
  133. package/dist/components/toggle-group/ToggleGroup.js +1 -1
  134. package/dist/components/tool-call/ToolCall.js +1 -1
  135. package/dist/components/toolbar/Toolbar.js +1 -1
  136. package/dist/components/tooltip/Tooltip.js +1 -1
  137. package/dist/components/tour/Tour.js +1 -1
  138. package/dist/components/tour/index.d.ts +1 -1
  139. package/dist/components/transfer/Transfer.d.ts +7 -0
  140. package/dist/components/transfer/Transfer.js +1 -1
  141. package/dist/components/tree-select/TreeSelect.js +1 -1
  142. package/dist/components/tree-view/TreeView.js +1 -1
  143. package/dist/components/window-pane/WindowPane.js +1 -1
  144. package/dist/internal/animate.d.ts +34 -2
  145. package/dist/internal/animate.js +1 -1
  146. package/dist/internal/cache.d.ts +7 -1
  147. package/dist/internal/cache.js +1 -1
  148. package/dist/internal/calendar.d.ts +40 -1
  149. package/dist/internal/calendar.js +1 -1
  150. package/dist/internal/chart-frame.d.ts +86 -8
  151. package/dist/internal/chart-frame.js +1 -1
  152. package/dist/internal/chart-line.js +1 -1
  153. package/dist/internal/chart.d.ts +13 -0
  154. package/dist/internal/chart.js +1 -1
  155. package/dist/internal/data-table.d.ts +7 -1
  156. package/dist/internal/data-table.js +1 -1
  157. package/dist/internal/date.d.ts +10 -0
  158. package/dist/internal/date.js +1 -1
  159. package/dist/internal/idle.d.ts +10 -0
  160. package/dist/internal/idle.js +1 -0
  161. package/dist/internal/link.d.ts +4 -3
  162. package/dist/internal/media.js +1 -1
  163. package/dist/internal/observe.d.ts +4 -0
  164. package/dist/internal/observe.js +1 -1
  165. package/dist/internal/page-layout.d.ts +30 -2
  166. package/dist/internal/page-layout.js +1 -1
  167. package/dist/internal/picker.js +1 -1
  168. package/dist/internal/progress.d.ts +17 -0
  169. package/dist/internal/progress.js +1 -1
  170. package/dist/internal/refs.d.ts +18 -0
  171. package/dist/internal/refs.js +1 -0
  172. package/dist/internal/run.d.ts +34 -19
  173. package/dist/internal/run.js +1 -1
  174. package/dist/internal/sizer.d.ts +9 -4
  175. package/dist/internal/sizer.js +1 -1
  176. package/dist/internal/styles.d.ts +14 -0
  177. package/dist/internal/styles.js +1 -1
  178. package/dist/internal/tile.d.ts +34 -0
  179. package/dist/internal/tile.js +1 -0
  180. package/dist/internal/wheel.d.ts +24 -1
  181. package/dist/internal/wheel.js +1 -1
  182. package/dist/styles.css +1 -1
  183. package/dist/tailwind.css +366 -61
  184. package/dist/types.d.ts +4 -2
  185. package/package.json +1 -1
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
 
@@ -235,7 +255,8 @@
235
255
  fill is only 88% opaque, and the pair has to clear 4.5:1 over a white page
236
256
  — which pins every family with white text to the high 40s / low 50s. A
237
257
  brighter *fill* means a dark ink, which is why `warning` is the one family
238
- 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.
239
260
 
240
261
  `success` moved 152 → 148 and `info` 218 → 223. Both are hue regions where
241
262
  sRGB is starved at mid lightness, and a few degrees toward the gamut's wide
@@ -481,11 +502,11 @@
481
502
  --neba-plate-top: rgb(255 255 255 / 0.16);
482
503
  --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
483
504
 
484
- --neba-glass-bg: rgb(255 255 255 / 0.05);
505
+ --neba-glass-bg: color-mix(in srgb, white 5%, var(--neba-backing));
485
506
 
486
- --neba-panel: rgb(255 255 255 / 0.05);
487
- --neba-panel-hover: rgb(255 255 255 / 0.07);
488
- --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));
489
510
 
490
511
  --neba-groove: rgb(255 255 255 / 0.05);
491
512
  --neba-groove-hover: rgb(255 255 255 / 0.07);
@@ -498,7 +519,7 @@
498
519
  --neba-shadow-ambient: rgb(0 0 0 / 0.55);
499
520
 
500
521
  --neba-primary-solid: oklch(57% 0.208 262);
501
- --neba-primary-solid-hover: oklch(62% 0.181 262);
522
+ --neba-primary-solid-hover: oklch(61% 0.181 262);
502
523
  --neba-primary-solid-active: oklch(48% 0.196 262);
503
524
  --neba-primary-on-solid: oklch(99% 0.004 262);
504
525
  --neba-primary-accent: oklch(78% 0.103 262);
@@ -517,7 +538,7 @@
517
538
 
518
539
  --neba-warning-solid: oklch(76% 0.145 80);
519
540
  --neba-warning-solid-hover: oklch(81% 0.154 80);
520
- --neba-warning-solid-active: oklch(66% 0.126 80);
541
+ --neba-warning-solid-active: oklch(69.5% 0.126 80);
521
542
  --neba-warning-on-solid: oklch(25% 0.06 70);
522
543
  --neba-warning-accent: oklch(82% 0.14 80);
523
544
 
@@ -622,11 +643,11 @@
622
643
  --neba-plate-top: rgb(255 255 255 / 0.16);
623
644
  --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
624
645
 
625
- --neba-glass-bg: rgb(255 255 255 / 0.05);
646
+ --neba-glass-bg: color-mix(in srgb, white 5%, var(--neba-backing));
626
647
 
627
- --neba-panel: rgb(255 255 255 / 0.05);
628
- --neba-panel-hover: rgb(255 255 255 / 0.07);
629
- --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));
630
651
 
631
652
  --neba-groove: rgb(255 255 255 / 0.05);
632
653
  --neba-groove-hover: rgb(255 255 255 / 0.07);
@@ -639,7 +660,7 @@
639
660
  --neba-shadow-ambient: rgb(0 0 0 / 0.55);
640
661
 
641
662
  --neba-primary-solid: oklch(57% 0.208 262);
642
- --neba-primary-solid-hover: oklch(62% 0.181 262);
663
+ --neba-primary-solid-hover: oklch(61% 0.181 262);
643
664
  --neba-primary-solid-active: oklch(48% 0.196 262);
644
665
  --neba-primary-on-solid: oklch(99% 0.004 262);
645
666
  --neba-primary-accent: oklch(78% 0.103 262);
@@ -658,7 +679,7 @@
658
679
 
659
680
  --neba-warning-solid: oklch(76% 0.145 80);
660
681
  --neba-warning-solid-hover: oklch(81% 0.154 80);
661
- --neba-warning-solid-active: oklch(66% 0.126 80);
682
+ --neba-warning-solid-active: oklch(69.5% 0.126 80);
662
683
  --neba-warning-on-solid: oklch(25% 0.06 70);
663
684
  --neba-warning-accent: oklch(82% 0.14 80);
664
685
 
@@ -719,6 +740,53 @@
719
740
  --neba-chart-on-8: var(--neba-surface);
720
741
  }
721
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
+
722
790
  /* ---------------------------------------------------------------------------
723
791
  * Derived tokens
724
792
  *
@@ -743,6 +811,12 @@
743
811
  * blue — and the worst family on the strongest wash clears 4.5:1 in both.
744
812
  * `accent` itself is untouched, so what a TextLink or a Statistic's delta is
745
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.
746
820
  * ------------------------------------------------------------------------- */
747
821
 
748
822
  :root,
@@ -769,17 +843,17 @@
769
843
  --neba-primary-fill: color-mix(
770
844
  in srgb,
771
845
  var(--neba-primary-solid) var(--neba-fill-alpha),
772
- transparent
846
+ var(--neba-backing)
773
847
  );
774
848
  --neba-primary-fill-hover: color-mix(
775
849
  in srgb,
776
850
  var(--neba-primary-solid-hover) var(--neba-fill-alpha),
777
- transparent
851
+ var(--neba-backing)
778
852
  );
779
853
  --neba-primary-fill-active: color-mix(
780
854
  in srgb,
781
855
  var(--neba-primary-solid-active) var(--neba-fill-alpha),
782
- transparent
856
+ var(--neba-backing)
783
857
  );
784
858
  --neba-primary-soft: color-mix(in srgb, var(--neba-primary-accent) 10%, transparent);
785
859
  --neba-primary-soft-hover: color-mix(in srgb, var(--neba-primary-accent) 17%, transparent);
@@ -807,17 +881,17 @@
807
881
  --neba-secondary-fill: color-mix(
808
882
  in srgb,
809
883
  var(--neba-secondary-solid) var(--neba-fill-alpha),
810
- transparent
884
+ var(--neba-backing)
811
885
  );
812
886
  --neba-secondary-fill-hover: color-mix(
813
887
  in srgb,
814
888
  var(--neba-secondary-solid-hover) var(--neba-fill-alpha),
815
- transparent
889
+ var(--neba-backing)
816
890
  );
817
891
  --neba-secondary-fill-active: color-mix(
818
892
  in srgb,
819
893
  var(--neba-secondary-solid-active) var(--neba-fill-alpha),
820
- transparent
894
+ var(--neba-backing)
821
895
  );
822
896
  --neba-secondary-soft: color-mix(in srgb, var(--neba-secondary-accent) 10%, transparent);
823
897
  --neba-secondary-soft-hover: color-mix(in srgb, var(--neba-secondary-accent) 17%, transparent);
@@ -845,17 +919,17 @@
845
919
  --neba-success-fill: color-mix(
846
920
  in srgb,
847
921
  var(--neba-success-solid) var(--neba-fill-alpha),
848
- transparent
922
+ var(--neba-backing)
849
923
  );
850
924
  --neba-success-fill-hover: color-mix(
851
925
  in srgb,
852
926
  var(--neba-success-solid-hover) var(--neba-fill-alpha),
853
- transparent
927
+ var(--neba-backing)
854
928
  );
855
929
  --neba-success-fill-active: color-mix(
856
930
  in srgb,
857
931
  var(--neba-success-solid-active) var(--neba-fill-alpha),
858
- transparent
932
+ var(--neba-backing)
859
933
  );
860
934
  --neba-success-soft: color-mix(in srgb, var(--neba-success-accent) 10%, transparent);
861
935
  --neba-success-soft-hover: color-mix(in srgb, var(--neba-success-accent) 17%, transparent);
@@ -883,17 +957,17 @@
883
957
  --neba-warning-fill: color-mix(
884
958
  in srgb,
885
959
  var(--neba-warning-solid) var(--neba-fill-alpha),
886
- transparent
960
+ var(--neba-backing)
887
961
  );
888
962
  --neba-warning-fill-hover: color-mix(
889
963
  in srgb,
890
964
  var(--neba-warning-solid-hover) var(--neba-fill-alpha),
891
- transparent
965
+ var(--neba-backing)
892
966
  );
893
967
  --neba-warning-fill-active: color-mix(
894
968
  in srgb,
895
969
  var(--neba-warning-solid-active) var(--neba-fill-alpha),
896
- transparent
970
+ var(--neba-backing)
897
971
  );
898
972
  --neba-warning-soft: color-mix(in srgb, var(--neba-warning-accent) 12%, transparent);
899
973
  --neba-warning-soft-hover: color-mix(in srgb, var(--neba-warning-accent) 20%, transparent);
@@ -921,17 +995,17 @@
921
995
  --neba-danger-fill: color-mix(
922
996
  in srgb,
923
997
  var(--neba-danger-solid) var(--neba-fill-alpha),
924
- transparent
998
+ var(--neba-backing)
925
999
  );
926
1000
  --neba-danger-fill-hover: color-mix(
927
1001
  in srgb,
928
1002
  var(--neba-danger-solid-hover) var(--neba-fill-alpha),
929
- transparent
1003
+ var(--neba-backing)
930
1004
  );
931
1005
  --neba-danger-fill-active: color-mix(
932
1006
  in srgb,
933
1007
  var(--neba-danger-solid-active) var(--neba-fill-alpha),
934
- transparent
1008
+ var(--neba-backing)
935
1009
  );
936
1010
  --neba-danger-soft: color-mix(in srgb, var(--neba-danger-accent) 10%, transparent);
937
1011
  --neba-danger-soft-hover: color-mix(in srgb, var(--neba-danger-accent) 17%, transparent);
@@ -956,16 +1030,20 @@
956
1030
  transparent
957
1031
  );
958
1032
 
959
- --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
+ );
960
1038
  --neba-info-fill-hover: color-mix(
961
1039
  in srgb,
962
1040
  var(--neba-info-solid-hover) var(--neba-fill-alpha),
963
- transparent
1041
+ var(--neba-backing)
964
1042
  );
965
1043
  --neba-info-fill-active: color-mix(
966
1044
  in srgb,
967
1045
  var(--neba-info-solid-active) var(--neba-fill-alpha),
968
- transparent
1046
+ var(--neba-backing)
969
1047
  );
970
1048
  --neba-info-soft: color-mix(in srgb, var(--neba-info-accent) 10%, transparent);
971
1049
  --neba-info-soft-hover: color-mix(in srgb, var(--neba-info-accent) 17%, transparent);
@@ -2375,6 +2453,86 @@
2375
2453
  box-shadow: none;
2376
2454
  }
2377
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
+
2378
2536
  /* ---------------------------------------------------------------------------
2379
2537
  * Tree guides
2380
2538
  *
@@ -2650,20 +2808,28 @@
2650
2808
  *
2651
2809
  * `currentColor` rather than a token, because a bubble's ink is white on
2652
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.
2653
2818
  */
2654
2819
  @keyframes neba-typing {
2655
2820
  0%,
2656
2821
  60%,
2657
2822
  100% {
2658
- background-color: color-mix(in oklab, currentColor 28%, transparent);
2823
+ opacity: 0.28;
2659
2824
  }
2660
2825
  30% {
2661
- background-color: currentColor;
2826
+ opacity: 1;
2662
2827
  }
2663
2828
  }
2664
2829
 
2665
2830
  .neba-typing-dot {
2666
- background-color: color-mix(in oklab, currentColor 28%, transparent);
2831
+ background-color: currentColor;
2832
+ opacity: 0.28;
2667
2833
  animation: neba-typing 1.25s var(--neba-ease) infinite;
2668
2834
  animation-delay: calc(var(--n-i, 0) * 160ms);
2669
2835
  }
@@ -2700,6 +2866,26 @@
2700
2866
  animation: neba-skeleton-sweep 1.5s var(--neba-ease) infinite;
2701
2867
  }
2702
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
+
2703
2889
  /*
2704
2890
  * Under RTL the start edge is on the right, so both sweeps travel leftwards.
2705
2891
  *
@@ -2728,14 +2914,16 @@
2728
2914
 
2729
2915
  /* The reduced-motion stand-in: the highlight stops travelling and the whole
2730
2916
  placeholder breathes instead. Colour, which is the axis every other state in
2731
- 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. */
2732
2920
  @keyframes neba-skeleton-pulse {
2733
2921
  0%,
2734
2922
  100% {
2735
- background-color: transparent;
2923
+ opacity: 0;
2736
2924
  }
2737
2925
  50% {
2738
- background-color: var(--n-panel-press);
2926
+ opacity: 1;
2739
2927
  }
2740
2928
  }
2741
2929
 
@@ -2891,6 +3079,69 @@
2891
3079
  animation-name: neba-anim-rotate;
2892
3080
  }
2893
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
+
2894
3145
  /* Symmetric on purpose: a blink that ended anywhere but where it started would
2895
3146
  leave the element half drawn whenever the count ran out. */
2896
3147
  @keyframes neba-anim-blink {
@@ -2994,24 +3245,28 @@
2994
3245
  /*
2995
3246
  * Lighting: a light travelling around the outside of something.
2996
3247
  *
2997
- * The arc is a conic gradient and what moves is its `from` angle, which is only
2998
- * animatable because the angle is a registered custom property with a type. The
2999
- * alternative — rotating the pseudo-element itself — swings its corners out
3000
- * 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.
3001
3259
  *
3002
3260
  * `z-index: -1` inside an isolated stacking context puts the glow behind the
3003
3261
  * content but still inside the component, so it reads as light escaping from
3004
3262
  * under a sheet rather than as a border drawn around one.
3005
3263
  */
3006
- @property --neba-glow-angle {
3007
- syntax: '<angle>';
3008
- initial-value: 0deg;
3009
- inherits: false;
3010
- }
3011
-
3012
3264
  @keyframes neba-anim-lighting {
3265
+ from {
3266
+ rotate: 0deg;
3267
+ }
3013
3268
  to {
3014
- --neba-glow-angle: 360deg;
3269
+ rotate: 360deg;
3015
3270
  }
3016
3271
  }
3017
3272
 
@@ -3020,20 +3275,30 @@
3020
3275
  isolation: isolate;
3021
3276
  }
3022
3277
 
3023
- .neba-anim-lighting::before {
3024
- content: '';
3278
+ .neba-lighting-ring {
3025
3279
  position: absolute;
3026
3280
  z-index: -1;
3027
3281
  inset: calc(-1 * var(--n-anim-glow-width, 3px));
3282
+ overflow: hidden;
3028
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%;
3029
3296
  background: conic-gradient(
3030
- from var(--neba-glow-angle),
3031
3297
  transparent 0deg,
3032
3298
  var(--n-anim-glow, var(--n-accent)) var(--n-anim-glow-arc, 50deg),
3033
3299
  transparent calc(var(--n-anim-glow-arc, 50deg) * 2),
3034
3300
  transparent 360deg
3035
3301
  );
3036
- filter: blur(var(--n-anim-glow-blur, 4px));
3037
3302
  animation-name: neba-anim-lighting;
3038
3303
  animation-duration: var(--n-anim-duration, 3s);
3039
3304
  animation-delay: var(--n-anim-delay, 0ms);
@@ -3182,6 +3447,38 @@
3182
3447
  animation: neba-anim-caret 1s step-end infinite;
3183
3448
  }
3184
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
+
3185
3482
  /* ---------------------------------------------------------------------------
3186
3483
  * Text arriving from somewhere else
3187
3484
  *
@@ -3203,8 +3500,12 @@
3203
3500
  }
3204
3501
  }
3205
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. */
3206
3507
  .neba-stream-word {
3207
- 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;
3208
3509
  }
3209
3510
 
3210
3511
  /* The block at the end of a stream. The same caret an `AnimateTyping` draws,
@@ -3271,6 +3572,7 @@
3271
3572
  inset-inline-start: 0;
3272
3573
  inline-size: 100%;
3273
3574
  background-image: none;
3575
+ background-color: var(--n-panel-press);
3274
3576
  animation: neba-skeleton-pulse 2.4s var(--neba-ease) infinite;
3275
3577
  }
3276
3578
 
@@ -3321,9 +3623,12 @@
3321
3623
 
3322
3624
  /* The arc stops travelling and becomes an even glow. The same trade the
3323
3625
  skeleton makes: the decoration survives, the motion does not. */
3324
- .neba-anim-lighting::before {
3626
+ .neba-lighting-ring {
3325
3627
  background: var(--n-anim-glow, var(--n-accent));
3326
- animation: none;
3628
+ }
3629
+
3630
+ .neba-lighting-beam {
3631
+ display: none;
3327
3632
  }
3328
3633
 
3329
3634
  /* A marquee that cannot move is a row of content, so it is drawn as one: