@eifi1/ui-kit 0.32.0 → 0.32.1

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 (206) hide show
  1. package/dist/account/delete-account-setting.d.ts +1 -1
  2. package/dist/admin/admin-action-confirm.d.ts +1 -1
  3. package/dist/admin/admin-action-log.d.ts +1 -1
  4. package/dist/admin/admin-action-log.js +4 -2
  5. package/dist/admin/admin-action-log.js.map +1 -1
  6. package/dist/admin/invitations-panel.d.ts +1 -1
  7. package/dist/admin/reviewer-scope-editor.d.ts +1 -1
  8. package/dist/admin/role-select.d.ts +1 -1
  9. package/dist/admin/transfer-ownership-dialog.d.ts +1 -1
  10. package/dist/admin/user-roster.d.ts +1 -1
  11. package/dist/auth/register-form.d.ts +1 -1
  12. package/dist/billing/billing-banner.d.ts +1 -1
  13. package/dist/billing/billing-labels.d.ts +1 -1
  14. package/dist/billing/billing-labels.js +3 -1
  15. package/dist/billing/billing-labels.js.map +1 -1
  16. package/dist/billing/billing-lock.d.ts +1 -1
  17. package/dist/billing/billing-standing.d.ts +1 -1
  18. package/dist/billing/plan-card.d.ts +1 -1
  19. package/dist/billing/plan-card.js +1 -1
  20. package/dist/billing/plan-card.js.map +1 -1
  21. package/dist/billing/plan-limit-notice.d.ts +1 -1
  22. package/dist/billing/subscription-actions.d.ts +1 -1
  23. package/dist/billing/subscription-status-chip.d.ts +1 -1
  24. package/dist/chart.d.ts +2 -2
  25. package/dist/chart.js.map +1 -1
  26. package/dist/components/alert-banner.js +1 -1
  27. package/dist/components/alert-banner.js.map +1 -1
  28. package/dist/components/amount-input.d.ts +1 -1
  29. package/dist/components/appearance-labels.d.ts +6 -0
  30. package/dist/components/appearance-labels.js +2 -1
  31. package/dist/components/appearance-labels.js.map +1 -1
  32. package/dist/components/button-group.d.ts +1 -1
  33. package/dist/components/button-group.js +7 -1
  34. package/dist/components/button-group.js.map +1 -1
  35. package/dist/components/calculator.d.ts +1 -1
  36. package/dist/components/chip.js +7 -2
  37. package/dist/components/chip.js.map +1 -1
  38. package/dist/components/choice-card.js +1 -1
  39. package/dist/components/choice-card.js.map +1 -1
  40. package/dist/components/confirm-dialog.d.ts +1 -1
  41. package/dist/components/copy-button.d.ts +1 -1
  42. package/dist/components/danger-confirm.d.ts +1 -1
  43. package/dist/components/data-table-pagination.d.ts +2 -1
  44. package/dist/components/data-table-pagination.js +15 -6
  45. package/dist/components/data-table-pagination.js.map +1 -1
  46. package/dist/components/data-table.js +2 -2
  47. package/dist/components/data-table.js.map +1 -1
  48. package/dist/components/date-picker.js +11 -1
  49. package/dist/components/date-picker.js.map +1 -1
  50. package/dist/components/facing-pair.d.ts +1 -1
  51. package/dist/components/file-button.d.ts +1 -1
  52. package/dist/components/file-dropzone.d.ts +1 -1
  53. package/dist/components/form-actions.d.ts +1 -1
  54. package/dist/components/iban-input.d.ts +1 -1
  55. package/dist/components/inline-edit-field.js +4 -1
  56. package/dist/components/inline-edit-field.js.map +1 -1
  57. package/dist/components/language-select.d.ts +1 -1
  58. package/dist/components/legal-page.d.ts +1 -1
  59. package/dist/components/legal.d.ts +1 -1
  60. package/dist/components/money-field.d.ts +1 -1
  61. package/dist/components/month-picker.js +41 -34
  62. package/dist/components/month-picker.js.map +1 -1
  63. package/dist/components/number-field.d.ts +1 -1
  64. package/dist/components/number-input.d.ts +1 -1
  65. package/dist/components/numpad-sheet.d.ts +1 -1
  66. package/dist/components/phone-input.d.ts +1 -1
  67. package/dist/components/phone-input.js +2 -2
  68. package/dist/components/phone-input.js.map +1 -1
  69. package/dist/components/row-actions.d.ts +1 -1
  70. package/dist/components/row-actions.js +103 -37
  71. package/dist/components/row-actions.js.map +1 -1
  72. package/dist/components/series-chart.d.ts +1 -1
  73. package/dist/components/settings-fields.d.ts +7 -3
  74. package/dist/components/settings-fields.js +2 -1
  75. package/dist/components/settings-fields.js.map +1 -1
  76. package/dist/components/share-card.d.ts +1 -1
  77. package/dist/components/share-card.js +6 -2
  78. package/dist/components/share-card.js.map +1 -1
  79. package/dist/components/status-dot.js +9 -2
  80. package/dist/components/status-dot.js.map +1 -1
  81. package/dist/components/swipeable-row.js +8 -1
  82. package/dist/components/swipeable-row.js.map +1 -1
  83. package/dist/components/text-link.d.ts +1 -1
  84. package/dist/components/time-input.d.ts +1 -1
  85. package/dist/components/toggle-group.d.ts +9 -2
  86. package/dist/components/toggle-group.js +3 -3
  87. package/dist/components/toggle-group.js.map +1 -1
  88. package/dist/components/translation-review-editor.js +1 -1
  89. package/dist/components/translation-review-editor.js.map +1 -1
  90. package/dist/components/translation-review.js +2 -2
  91. package/dist/components/translation-review.js.map +1 -1
  92. package/dist/components/ui.d.ts +1 -1
  93. package/dist/components/ui.js +5 -5
  94. package/dist/components/ui.js.map +1 -1
  95. package/dist/components/wizard-stepper.js +20 -14
  96. package/dist/components/wizard-stepper.js.map +1 -1
  97. package/dist/{delete-account-setting-BvdQdbv3.d.ts → delete-account-setting-FLyJf-kZ.d.ts} +43 -8
  98. package/dist/feedback/feedback-attachment.d.ts +1 -1
  99. package/dist/feedback/feedback-context.d.ts +1 -1
  100. package/dist/feedback/feedback-crash.d.ts +1 -1
  101. package/dist/feedback/feedback-dialog.d.ts +1 -1
  102. package/dist/feedback/feedback-inbox.d.ts +1 -1
  103. package/dist/feedback/feedback-labels.d.ts +1 -1
  104. package/dist/feedback/feedback-menu.d.ts +1 -1
  105. package/dist/feedback/feedback-record.d.ts +1 -1
  106. package/dist/feedback/feedback-row-detail.d.ts +1 -1
  107. package/dist/feedback/feedback-status-undo.d.ts +1 -1
  108. package/dist/feedback/feedback-submit.d.ts +1 -1
  109. package/dist/feedback/feedback-swipe.d.ts +1 -1
  110. package/dist/feedback/feedback-table.d.ts +1 -1
  111. package/dist/feedback/feedback-thread.d.ts +1 -1
  112. package/dist/feedback.d.ts +1 -1
  113. package/dist/hooks/use-breakpoint.d.ts +19 -8
  114. package/dist/hooks/use-breakpoint.js +1 -1
  115. package/dist/hooks/use-breakpoint.js.map +1 -1
  116. package/dist/hooks/use-file-drop.d.ts +1 -1
  117. package/dist/i18n/defaults.d.ts +1 -1
  118. package/dist/i18n/german.d.ts +1 -1
  119. package/dist/i18n/german.js +7 -3
  120. package/dist/i18n/german.js.map +1 -1
  121. package/dist/i18n/kit-labels.d.ts +1 -1
  122. package/dist/i18n/languages.d.ts +1 -1
  123. package/dist/i18n/locales/de-CH.d.ts +1 -1
  124. package/dist/i18n/locales/en.d.ts +1 -1
  125. package/dist/i18n/locales/es.d.ts +1 -1
  126. package/dist/i18n/locales/es.js +7 -3
  127. package/dist/i18n/locales/es.js.map +1 -1
  128. package/dist/i18n/locales/fr.d.ts +1 -1
  129. package/dist/i18n/locales/fr.js +7 -3
  130. package/dist/i18n/locales/fr.js.map +1 -1
  131. package/dist/i18n/locales/hu.d.ts +1 -1
  132. package/dist/i18n/locales/hu.js +7 -3
  133. package/dist/i18n/locales/hu.js.map +1 -1
  134. package/dist/i18n/locales/it.d.ts +1 -1
  135. package/dist/i18n/locales/it.js +7 -3
  136. package/dist/i18n/locales/it.js.map +1 -1
  137. package/dist/i18n/locales/zh.d.ts +1 -1
  138. package/dist/i18n/locales/zh.js +7 -3
  139. package/dist/i18n/locales/zh.js.map +1 -1
  140. package/dist/i18n/review.d.ts +1 -1
  141. package/dist/index.d.ts +3 -3
  142. package/dist/index.js +3 -1
  143. package/dist/index.js.map +1 -1
  144. package/dist/landing/landing-actions.d.ts +1 -1
  145. package/dist/landing/landing-sections.d.ts +1 -1
  146. package/dist/landing/landing-sections.js +7 -3
  147. package/dist/landing/landing-sections.js.map +1 -1
  148. package/dist/landing/public-header.d.ts +1 -1
  149. package/dist/landing/routing.d.ts +1 -1
  150. package/dist/rhf/fields.d.ts +1 -1
  151. package/dist/rhf/form.d.ts +1 -1
  152. package/dist/rhf.d.ts +1 -1
  153. package/dist/settings/settings-section.d.ts +1 -1
  154. package/dist/settings/use-account-appearance.d.ts +1 -1
  155. package/dist/settings/use-account-appearance.js.map +1 -1
  156. package/dist/shell/app-shell.d.ts +1 -1
  157. package/dist/shell/top-bar-brand.d.ts +1 -1
  158. package/dist/shell.d.ts +1 -1
  159. package/dist/theme/contrast.d.ts +3 -2
  160. package/dist/theme/contrast.js.map +1 -1
  161. package/dist/theme/text-size.d.ts +19 -3
  162. package/dist/theme/text-size.js.map +1 -1
  163. package/dist/wizard/stepper-nav.d.ts +1 -1
  164. package/dist/wizard/wizard-summary.js +1 -1
  165. package/dist/wizard/wizard-summary.js.map +1 -1
  166. package/dist/wizard.d.ts +1 -1
  167. package/package.json +1 -1
  168. package/src/admin/admin-action-log.tsx +6 -1
  169. package/src/billing/billing-labels.ts +16 -0
  170. package/src/billing/plan-card.tsx +1 -1
  171. package/src/chart.ts +2 -0
  172. package/src/components/alert-banner.tsx +5 -1
  173. package/src/components/appearance-labels.ts +7 -0
  174. package/src/components/button-group.tsx +21 -0
  175. package/src/components/chip.tsx +34 -6
  176. package/src/components/choice-card.tsx +1 -1
  177. package/src/components/data-table-pagination.tsx +60 -31
  178. package/src/components/data-table.tsx +8 -4
  179. package/src/components/date-picker.tsx +32 -1
  180. package/src/components/inline-edit-field.tsx +20 -1
  181. package/src/components/month-picker.tsx +6 -1
  182. package/src/components/phone-input.tsx +7 -2
  183. package/src/components/row-actions.tsx +155 -49
  184. package/src/components/settings-fields.tsx +8 -3
  185. package/src/components/share-card.tsx +21 -2
  186. package/src/components/status-dot.tsx +25 -2
  187. package/src/components/swipeable-row.tsx +8 -1
  188. package/src/components/toggle-group.tsx +26 -12
  189. package/src/components/translation-review-editor.tsx +1 -1
  190. package/src/components/translation-review.tsx +5 -4
  191. package/src/components/ui.tsx +22 -8
  192. package/src/components/wizard-stepper.tsx +5 -1
  193. package/src/hooks/use-breakpoint.ts +20 -9
  194. package/src/i18n/german.ts +4 -0
  195. package/src/i18n/locales/es.ts +4 -0
  196. package/src/i18n/locales/fr.ts +4 -0
  197. package/src/i18n/locales/hu.ts +4 -0
  198. package/src/i18n/locales/it.ts +4 -0
  199. package/src/i18n/locales/zh.ts +4 -0
  200. package/src/index.ts +4 -2
  201. package/src/landing/landing-sections.tsx +7 -3
  202. package/src/settings/use-account-appearance.ts +1 -1
  203. package/src/theme/contrast.ts +3 -2
  204. package/src/theme/text-size.ts +19 -3
  205. package/src/wizard/wizard-summary.tsx +4 -2
  206. package/tokens.css +15 -7
@@ -9,6 +9,7 @@ import {
9
9
  DISABLED_REASON_LINE_CLASS,
10
10
  FIELD_CAPTION_CLASS,
11
11
  hasContent,
12
+ isTextHint,
12
13
  LabelStrip,
13
14
  useDisabledReasonLine,
14
15
  useInlineHint,
@@ -130,8 +131,15 @@ export interface ToggleGroupBaseProps<T extends string>
130
131
  * two stack and both keep their corners.
131
132
  */
132
133
  chromeClassName?: string;
133
- /** A {@link FieldHint} on the label line, as on a labelled {@link Select}. Only with
134
- * `label`. */
134
+ /**
135
+ * As on a labelled {@link Select}: a {@link FieldHint} "?" on the label line, and plain
136
+ * text (a string) as a caption under the group. Only with `label`.
137
+ *
138
+ * 0.32.1 (keksdose's 0.32 report): text went on the label line in every placement, so
139
+ * `TextSizeSetting`'s hint with `labelPlacement="above"` sat beside the label in body
140
+ * type, and at Extra large became a column one word wide — on the setting a reader who
141
+ * needs large text opens first. The label line holds the "?" only (`isTextHint`).
142
+ */
135
143
  hint?: ReactNode;
136
144
  /** The message under the field when it is wrong: paints the field's border with
137
145
  * `--danger`, marks the group `aria-invalid` and describes it with the message, as
@@ -196,9 +204,9 @@ const CHROME_PAD: Record<"sm" | "md", string> = { md: "pt-5 pb-0.5", sm: "pt-5 p
196
204
  const CHROME_SEGMENT: Record<"sm" | "md", string> = { md: "py-0 leading-[1.125rem]", sm: "py-0 leading-4" };
197
205
 
198
206
  /**
199
- * A field of four or more options at Large and Extra large (0.32,
207
+ * A group at Large and Extra large (0.32,
200
208
  * docs/text-size-harmonization.md §4 "nothing truncates", §3.3): the segments flow onto a
201
- * second row inside the field's frame instead of overflowing it. At 125 % a 390 px phone
209
+ * second row inside the group's frame instead of overflowing it. At 125 % a 390 px phone
202
210
  * is 312 px of Normal type, and "Month / Quarter / Half-year / Year" in one row of the
203
211
  * field ran 7 px past it: a one-row flex group is as wide as all its labels end to end
204
212
  * when a grid or a stretched column asks how small it can get, however its segments
@@ -212,12 +220,16 @@ const CHROME_SEGMENT: Record<"sm" | "md", string> = { md: "py-0 leading-[1.125re
212
220
  * Only where it has to: `flex-wrap` breaks a row only when the segments do not fit, so
213
221
  * a wide field at Large stays one row, and a segment shares out what its row leaves
214
222
  * (`flex-1`); a label wider than the whole row still truncates as the last resort.
215
- * Three or fewer options keep one row: each holds a third of the field. A `large:`
216
- * class, so it costs no render and is right on the first paint. `overflow="wrap"` wraps
217
- * at every size already; outside the field the group keeps its `overflow`.
223
+ * A `large:` class, so it costs no render and is right on the first paint.
224
+ * `overflow="wrap"` wraps at every size already.
225
+ *
226
+ * 0.32.1 (Kurvenschmiede's 0.32 report): every count and every placement but the
227
+ * strip, not only a field of four or more. Since a row breaks only where its labels do
228
+ * not fit, a wrap only ever replaces a truncation — and three options in a card at
229
+ * Extra large on a 360 px phone read "Syst… Li… D…". The strip keeps one row: its
230
+ * 26 px group is what lines it up with the 42 px fields beside it (`STRIP_SEGMENT`).
218
231
  */
219
232
  const WRAP_AT_LARGE = "large:flex-wrap";
220
- const WRAP_AT_LARGE_FROM = 4;
221
233
 
222
234
  /**
223
235
  * The strip placement's segments: a 26px group (1 + 2 + 20 + 2 + 1) under the 16px
@@ -335,9 +347,10 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
335
347
  const locked = hasContent(disabledReason);
336
348
  // §4: at Large and on touch the reason is a line under the group, not a bubble.
337
349
  const reasonLine = useDisabledReasonLine(disabledReasonDisplay) && locked;
338
- // §4: likewise a FieldHint "?" on the label line becomes a caption under the group.
350
+ // §4: likewise a FieldHint "?" on the label line becomes a caption under the group at
351
+ // Large and on touch; text is a caption at every size, as Select's (see `hint`).
339
352
  const inlineHint = useInlineHint(hint);
340
- const hintAsCaption = inlineHint !== hint ? inlineHint : undefined;
353
+ const hintAsCaption = isTextHint(hint) ? hint : inlineHint !== hint ? inlineHint : undefined;
341
354
  const labelHint = hintAsCaption === undefined ? hint : undefined;
342
355
  // Locked looks disabled — the dimmed group, the not-allowed cursor, no hover offer —
343
356
  // but stays focusable; `disabled` alone also takes the segments out of the tab order.
@@ -346,10 +359,11 @@ export function ToggleGroup<T extends string>(props: ToggleGroupProps<T>): React
346
359
  // `field` is the chrome; a label placed above or in a strip keeps the bare group's
347
360
  // own box.
348
361
  const field = labelled && labelPlacement === "field";
349
- // At Large and Extra large a FIELD of four or more options wraps (see WRAP_AT_LARGE).
350
- const wrapAtLarge = field && overflow === "truncate" && options.length >= WRAP_AT_LARGE_FROM;
351
362
  const above = labelled && labelPlacement === "above";
352
363
  const strip = labelled && labelPlacement === "strip";
364
+ // At Large and Extra large a truncating group wraps instead, the strip excepted (see
365
+ // WRAP_AT_LARGE).
366
+ const wrapAtLarge = overflow === "truncate" && !strip;
353
367
  const hasError = labelled && hasContent(error);
354
368
  // Taken off the rest so neither reaches the DOM; `props` keeps them paired, which is
355
369
  // what lets the `onChange` below be called with `null` only in the mode that allows it.
@@ -150,7 +150,7 @@ export function TranslationReviewEditor({
150
150
  <div className={cn("min-w-0 space-y-3 text-sm", className)}>
151
151
  <code className="block break-all text-xs text-[var(--text-muted)]">{row.key}</code>
152
152
 
153
- <div className="grid gap-3 md:grid-cols-2">
153
+ <div className="grid grid-cols-1 gap-3 md:grid-cols-2">
154
154
  <div className="min-w-0">
155
155
  <SectionLabel as="span" size="xs">
156
156
  {referenceLabel}
@@ -2,7 +2,7 @@ import { useEffect, useId, useMemo, useRef, useState } from "react";
2
2
  import type { ReactNode } from "react";
3
3
  import { Check, ListChecks, PencilLine, RotateCcw } from "lucide-react";
4
4
 
5
- import { useBreakpoint } from "../hooks/use-breakpoint";
5
+ import { usePhoneLayout } from "../hooks/use-breakpoint";
6
6
  import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
7
7
  import { cn } from "../lib/cn";
8
8
  import {
@@ -458,9 +458,10 @@ export function TranslationReviewPanel({
458
458
  const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
459
459
  const locale = useKitLocale();
460
460
  const lock = useWriteLock();
461
- // The wide screen's breakpoint — the one DataTable switches its layout on, so "a phone"
462
- // means the same here as in the table, at every text size.
463
- const phone = !useBreakpoint("md", true);
461
+ // The phone layout DataTable switches its layout on, so "a phone" means the same here
462
+ // as in the table, at every text size: `usePhoneLayout()` itself (§10.4; 0.32.1,
463
+ // keksdose's 0.32 report — `!useBreakpoint("md")` could disagree with it at 767.5 px).
464
+ const phone = usePhoneLayout();
464
465
  const baseId = useId();
465
466
  const rootRef = useRef<HTMLDivElement>(null);
466
467
  const editable = !readOnly && onSave !== undefined;
@@ -3218,8 +3218,14 @@ export interface CardHeaderProps extends ComponentPropsWithoutRef<"div"> {
3218
3218
  * description instead of beside them. For an action that is wider than an icon —
3219
3219
  * a "Set up two-factor" button, a checkbox and a filter — which, kept in the
3220
3220
  * top-end column on a 390px phone, squeezed the title into a ribbon two words wide
3221
- * (keksdose's settings cards). Off by default: an icon action (the wizard summary's
3222
- * pencil) is narrow enough to stay beside the title at every width.
3221
+ * (keksdose's settings cards). Off by default at Normal: an icon action (the wizard
3222
+ * summary's pencil) is narrow enough to stay beside the title at every width.
3223
+ *
3224
+ * Unset, it stacks at Large and Extra large only (0.32.1, the 360 px Extra-large
3225
+ * sweep; docs/text-size-harmonization.md §4): there an IconButton shows its label as
3226
+ * text, so the "icon" action is a word-wide pill, and "Refresh sessions" beside
3227
+ * "Sessions" ran 47 px past a 360 px phone. `false` keeps the action beside the title
3228
+ * at every size, for an action the caller knows stays narrow (`labelVisible={false}`).
3223
3229
  */
3224
3230
  stackAction?: boolean;
3225
3231
  }
@@ -3239,7 +3245,18 @@ export type CardActionProps = ComponentPropsWithoutRef<"div">;
3239
3245
  export type CardContentProps = ComponentPropsWithoutRef<"div">;
3240
3246
  export type CardFooterProps = ComponentPropsWithoutRef<"div">;
3241
3247
 
3242
- export function CardHeader({ className, stackAction = false, ...props }: CardHeaderProps) {
3248
+ /** {@link CardHeaderProps.stackAction}: one column under `sm`, and the action back into
3249
+ * the flow (DOM order, so after the description when it is written after it), at the
3250
+ * start edge. The descendant selector outranks CardAction's own placement classes. */
3251
+ const CARD_STACK_ACTION =
3252
+ "max-sm:grid-cols-1 max-sm:[&>[data-slot=card-action]]:col-start-1 max-sm:[&>[data-slot=card-action]]:row-span-1 max-sm:[&>[data-slot=card-action]]:row-start-auto max-sm:[&>[data-slot=card-action]]:justify-self-start";
3253
+ /** The same at Large and Extra large only — `stackAction` unset (see its note). A
3254
+ * `large:` class over the scaled `max-sm`, so it costs no render and is right on the
3255
+ * first paint. */
3256
+ const CARD_STACK_ACTION_AT_LARGE =
3257
+ "large:max-sm:grid-cols-1 large:max-sm:[&>[data-slot=card-action]]:col-start-1 large:max-sm:[&>[data-slot=card-action]]:row-span-1 large:max-sm:[&>[data-slot=card-action]]:row-start-auto large:max-sm:[&>[data-slot=card-action]]:justify-self-start";
3258
+
3259
+ export function CardHeader({ className, stackAction, ...props }: CardHeaderProps) {
3243
3260
  const padded = useContext(CardPaddedContext);
3244
3261
  const compact = useContext(CardDensityContext) === "compact";
3245
3262
  return (
@@ -3261,11 +3278,8 @@ export function CardHeader({ className, stackAction = false, ...props }: CardHea
3261
3278
  "grid auto-rows-min items-start gap-1.5 [--card-header-cols:1fr] has-data-[slot=card-action]:[--card-header-cols:1fr_auto] grid-cols-[var(--card-header-cols,1fr)]",
3262
3279
  padded ? "px-0 pt-0" : "px-6 pt-6",
3263
3280
  compact && "gap-0.5",
3264
- // One column under `sm`, and the action back into the flow (DOM order, so
3265
- // after the description when it is written after it), at the start edge.
3266
- // The descendant selector outranks CardAction's own placement classes.
3267
- stackAction &&
3268
- "max-sm:grid-cols-1 max-sm:[&>[data-slot=card-action]]:col-start-1 max-sm:[&>[data-slot=card-action]]:row-span-1 max-sm:[&>[data-slot=card-action]]:row-start-auto max-sm:[&>[data-slot=card-action]]:justify-self-start",
3281
+ stackAction === true && CARD_STACK_ACTION,
3282
+ stackAction === undefined && CARD_STACK_ACTION_AT_LARGE,
3269
3283
  className,
3270
3284
  )}
3271
3285
  {...props}
@@ -13,7 +13,11 @@ export function WizardStepper({
13
13
  }) {
14
14
  const currentIndex = steps.findIndex((s) => s.key === current);
15
15
  return (
16
- <ol className="flex items-center gap-1 text-xs" aria-label={ariaLabel}>
16
+ // `flex-wrap`: the steps flow onto a second line rather than past the screen
17
+ // (0.32.1, the 360 px Extra-large sweep; docs/text-size-harmonization.md §4).
18
+ // "Upload — Map columns — Confirm" ran 22 px past a 360 px phone at Extra large.
19
+ // Where the steps fit, they stay one row.
20
+ <ol className="flex flex-wrap items-center gap-1 text-xs" aria-label={ariaLabel}>
17
21
  {steps.map((step, i) => (
18
22
  <li key={step.key} className="flex items-center gap-1">
19
23
  {i > 0 && <span className="w-4 border-t border-[var(--border)]" />}
@@ -82,21 +82,32 @@ export function lengthPx(length: number | RemLength | undefined, rootRemPx: numb
82
82
  /**
83
83
  * The media query a breakpoint variant means at `scale` (`TEXT_SCALE` of the size in
84
84
  * force). `"md"` → `(min-width: 768px)` at Normal, `(min-width: 960px)` at Large;
85
- * `"max-md"` → `(max-width: 767px)` at Normal. In px, scaled by the browser's own
86
- * default font size as well, so a person who set 20 px in the browser gets 960 px for
87
- * `md` at Normal — as the CSS does.
85
+ * `"max-md"` → `(width < 768px)` at Normal. In px, scaled by the browser's own default
86
+ * font size as well, so a person who set 20 px in the browser gets 960 px for `md` at
87
+ * Normal — as the CSS does.
88
88
  *
89
- * At Normal these are, character for character, the strings the kit queried before
90
- * (`PHONE_QUERY`, DataTable's `isMdUp`), so a test's `matchMedia` stub keeps working.
91
- * Like those, the pair is exact at whole-pixel widths; on a fractional viewport width
92
- * between the two (767.2 px at a 1.25 device-pixel ratio) neither matches, where the
93
- * CSS's `width < 48rem` says phone. Verified in Chromium at 700–1300 px × three sizes.
89
+ * `max-*` is written in range syntax, `(width < N)`, against the same `N` its `min-*`
90
+ * twin starts at, so the two are exact complements at EVERY width: exactly one of
91
+ * `md` and `max-md` matches, as with tokens.css's `width >= 48rem` / `width < 48rem`
92
+ * (and Tailwind's own `max-*`). Until 0.32.1 it was `(max-width: 767px)`, which left a
93
+ * gap: on a fractional viewport width (767.5 px under zoom or at some Android
94
+ * device-pixel ratios) neither query matched, so DataTable, asking `!md`, said phone
95
+ * while `usePhoneLayout()`, asking `max-md`, said not (0.32.1, keksdose's 0.32 report;
96
+ * docs/text-size-harmonization.md §3.3: "the CSS and JS answers always agree").
97
+ *
98
+ * Range syntax in media queries is Chrome / Edge 104, Firefox 63, Safari 16.4. That is
99
+ * inside the floor the kit already sets: tokens.css's breakpoint variants are written in
100
+ * it, and Tailwind v4 itself needs Safari 16.4, Chrome 111 and Firefox 128.
101
+ *
102
+ * A test's `matchMedia` stub that keyed on the old string (`"max-width: 767px"`, the
103
+ * deprecated `PHONE_QUERY`) has to answer `(width < 768px)` now. `min-*` is unchanged,
104
+ * character for character.
94
105
  */
95
106
  export function breakpointQuery(variant: BreakpointVariant, scale = 1, remPx: number = readBrowserRemPx()): string {
96
107
  const max = variant.startsWith("max-");
97
108
  const name = (max ? variant.slice(4) : variant) as Breakpoint;
98
109
  const px = Math.round(BREAKPOINT_REM[name] * remPx * scale);
99
- return max ? `(max-width: ${px - 1}px)` : `(min-width: ${px}px)`;
110
+ return max ? `(width < ${px}px)` : `(min-width: ${px}px)`;
100
111
  }
101
112
 
102
113
  /**
@@ -1203,6 +1203,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1203
1203
  deletion_request: "Löschung beantragt",
1204
1204
  deletion_cancel: "Löschung aufgehoben",
1205
1205
  erase: "Konto endgültig gelöscht",
1206
+ plan: "Tarif geändert",
1206
1207
  },
1207
1208
  },
1208
1209
  transferOwnership: {
@@ -1533,6 +1534,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1533
1534
  : `${n(count)} Änderungen warten auf einen Tarif`,
1534
1535
  // "Verwerfen", as `wizard.cancelConfirmLabel`.
1535
1536
  discardWaiting: "Wartende Änderungen verwerfen",
1537
+ notConfigured: "Zahlungen sind noch nicht eingerichtet. Bitte versuchen Sie es später noch einmal.",
1538
+ disabled: "Abonnements sind hier nicht verfügbar.",
1536
1539
  },
1537
1540
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1538
1541
  // the "Darstellung" group.
@@ -1544,6 +1547,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1544
1547
  contrastHelp:
1545
1548
  "Mehr Kontrast macht dezente Texte und Linien dunkler und Fokusrahmen dicker. „System“ richtet sich nach diesem Gerät.",
1546
1549
  contrastModes: { system: "System", standard: "Standard", more: "Mehr" },
1550
+ saveFailed: "Konnte nicht in Ihrem Konto gespeichert werden. Auf diesem Gerät gilt es trotzdem.",
1547
1551
  },
1548
1552
  rowActions: {
1549
1553
  actions: "Aktionen",
@@ -1175,6 +1175,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1175
1175
  deletion_request: "Eliminación solicitada",
1176
1176
  deletion_cancel: "Eliminación cancelada",
1177
1177
  erase: "Cuenta borrada",
1178
+ plan: "Plan cambiado",
1178
1179
  },
1179
1180
  },
1180
1181
  transferOwnership: {
@@ -1489,6 +1490,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1489
1490
  waitingChanges: (count) => `${n(count)} ${plural(count, "cambio", "cambios")} a la espera de un plan`,
1490
1491
  // "Descartar", as `wizard.cancelConfirmLabel`.
1491
1492
  discardWaiting: "Descartar los cambios en espera",
1493
+ notConfigured: "Los pagos aún no están configurados. Inténtelo de nuevo más tarde.",
1494
+ disabled: "Las suscripciones no están disponibles aquí.",
1492
1495
  },
1493
1496
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1494
1497
  // the "Apariencia" group.
@@ -1500,6 +1503,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1500
1503
  contrastHelp:
1501
1504
  "El contraste aumentado oscurece los textos tenues y las líneas, y engrosa los marcos de foco. «Sistema» sigue el ajuste de este dispositivo.",
1502
1505
  contrastModes: { system: "Sistema", standard: "Estándar", more: "Aumentado" },
1506
+ saveFailed: "No se ha podido guardar en su cuenta. En este dispositivo se aplica igualmente.",
1503
1507
  },
1504
1508
  rowActions: {
1505
1509
  actions: "Acciones",
@@ -1201,6 +1201,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1201
1201
  deletion_request: "Suppression demandée",
1202
1202
  deletion_cancel: "Suppression annulée",
1203
1203
  erase: "Compte effacé",
1204
+ plan: "Formule modifiée",
1204
1205
  },
1205
1206
  },
1206
1207
  transferOwnership: {
@@ -1521,6 +1522,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1521
1522
  `${n(count)} ${plural(count, "modification en attente", "modifications en attente")} d’une formule`,
1522
1523
  // "Abandonner", as `wizard.cancelConfirmLabel`.
1523
1524
  discardWaiting: "Abandonner les modifications en attente",
1525
+ notConfigured: "Les paiements ne sont pas encore configurés. Veuillez réessayer plus tard.",
1526
+ disabled: "Les abonnements ne sont pas disponibles ici.",
1524
1527
  },
1525
1528
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1526
1529
  // the "Apparence" group. The steps agree with "taille", the modes with "contraste".
@@ -1532,6 +1535,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1532
1535
  contrastHelp:
1533
1536
  "Le contraste renforcé assombrit les textes discrets et les lignes, et épaissit les contours de focus. «\u202fSystème\u202f» suit le réglage de cet appareil.",
1534
1537
  contrastModes: { system: "Système", standard: "Standard", more: "Renforcé" },
1538
+ saveFailed: "Impossible de l’enregistrer dans votre compte. Le réglage s’applique tout de même sur cet appareil.",
1535
1539
  },
1536
1540
  rowActions: {
1537
1541
  actions: "Actions",
@@ -1178,6 +1178,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1178
1178
  deletion_request: "Törlés kérelmezve",
1179
1179
  deletion_cancel: "Törlés visszavonva",
1180
1180
  erase: "Fiók véglegesen törölve",
1181
+ plan: "Csomag módosítva",
1181
1182
  },
1182
1183
  },
1183
1184
  transferOwnership: {
@@ -1493,6 +1494,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1493
1494
  waitingChanges: (count) => `${n(count)} módosítás csomagra vár`,
1494
1495
  // "Elvetés", as `wizard.cancelConfirmLabel`.
1495
1496
  discardWaiting: "Várakozó módosítások elvetése",
1497
+ notConfigured: "A fizetés még nincs beállítva. Kérjük, próbálja újra később.",
1498
+ disabled: "Az előfizetés itt nem érhető el.",
1496
1499
  },
1497
1500
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1498
1501
  // the "Megjelenés" group.
@@ -1504,6 +1507,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1504
1507
  contrastHelp:
1505
1508
  "A fokozott kontraszt sötétebbé teszi a halvány szövegeket és vonalakat, a fókuszkereteket pedig vastagabbá. A „Rendszer” ennek az eszköznek a beállítását követi.",
1506
1509
  contrastModes: { system: "Rendszer", standard: "Normál", more: "Fokozott" },
1510
+ saveFailed: "Nem sikerült menteni a fiókjába. Ezen az eszközön ettől még érvényes.",
1507
1511
  },
1508
1512
  rowActions: {
1509
1513
  actions: "Műveletek",
@@ -1184,6 +1184,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1184
1184
  deletion_request: "Eliminazione richiesta",
1185
1185
  deletion_cancel: "Eliminazione annullata",
1186
1186
  erase: "Account cancellato",
1187
+ plan: "Piano modificato",
1187
1188
  },
1188
1189
  },
1189
1190
  transferOwnership: {
@@ -1505,6 +1506,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1505
1506
  `${n(count)} ${plural(count, "modifica in attesa", "modifiche in attesa")} di un piano`,
1506
1507
  // "Scarta", as `wizard.cancelConfirmLabel`.
1507
1508
  discardWaiting: "Scarta le modifiche in attesa",
1509
+ notConfigured: "I pagamenti non sono ancora configurati. Riprovi più tardi.",
1510
+ disabled: "Gli abbonamenti non sono disponibili qui.",
1508
1511
  },
1509
1512
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1510
1513
  // the "Aspetto" group.
@@ -1516,6 +1519,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1516
1519
  contrastHelp:
1517
1520
  "Il contrasto aumentato scurisce i testi tenui e le linee e ispessisce i riquadri di focus. «Sistema» segue l’impostazione di questo dispositivo.",
1518
1521
  contrastModes: { system: "Sistema", standard: "Standard", more: "Aumentato" },
1522
+ saveFailed: "Non è stato possibile salvarlo nel Suo account. Su questo dispositivo vale comunque.",
1519
1523
  },
1520
1524
  rowActions: {
1521
1525
  actions: "Azioni",
@@ -1131,6 +1131,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1131
1131
  deletion_request: "已申请删除",
1132
1132
  deletion_cancel: "已取消删除",
1133
1133
  erase: "账户已永久删除",
1134
+ plan: "套餐已更改",
1134
1135
  },
1135
1136
  },
1136
1137
  transferOwnership: {
@@ -1429,6 +1430,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1429
1430
  waitingChanges: (count) => `${n(count)} 项修改正在等待套餐`,
1430
1431
  // "放弃", as `wizard.cancelConfirmLabel`.
1431
1432
  discardWaiting: "放弃等待中的修改",
1433
+ notConfigured: "付款尚未设置,请稍后再试。",
1434
+ disabled: "此处不提供订阅。",
1432
1435
  },
1433
1436
  // 0.32.0 — docs/text-size-harmonization.md §6: the text-size and contrast settings in
1434
1437
  // the "外观" group.
@@ -1439,6 +1442,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1439
1442
  contrast: "对比度",
1440
1443
  contrastHelp: "增强对比度会加深浅色文字和线条,并加粗焦点框。“跟随系统”沿用此设备的设置。",
1441
1444
  contrastModes: { system: "跟随系统", standard: "标准", more: "增强" },
1445
+ saveFailed: "无法保存到您的账户。此设备上仍然生效。",
1442
1446
  },
1443
1447
  rowActions: {
1444
1448
  actions: "操作",
package/src/index.ts CHANGED
@@ -173,6 +173,8 @@ export type {
173
173
  } from "./components/file-button";
174
174
  export * from "./components/treemap";
175
175
  export * from "./components/series-chart";
176
+ // 0.32.1 (keksdose's 0.32 report): the height every chart takes, for an app's wrappers.
177
+ export type { ChartHeight } from "./components/chart-height";
176
178
  export * from "./components/chart-zoom";
177
179
  export * from "./components/toggle-legend";
178
180
  export * from "./components/facing-pair";
@@ -759,8 +761,8 @@ export type {
759
761
  // ── 0.32.0: text size and contrast (docs/text-size-harmonization.md) ──
760
762
  export * from "./theme/text-size";
761
763
  export * from "./theme/contrast";
762
- export { useBreakpoint, usePhoneLayout, breakpointQuery, BREAKPOINT_REM } from "./hooks/use-breakpoint";
763
- export type { Breakpoint, BreakpointVariant } from "./hooks/use-breakpoint";
764
+ export { useBreakpoint, usePhoneLayout, breakpointQuery, BREAKPOINT_REM, remPx, useRemPx } from "./hooks/use-breakpoint";
765
+ export type { Breakpoint, BreakpointVariant, RemLength } from "./hooks/use-breakpoint";
764
766
  export { useAccountAppearance, resolveAccountAppearance } from "./settings/use-account-appearance";
765
767
  export type {
766
768
  AppearanceSource,
@@ -97,7 +97,11 @@ export function Hero({
97
97
  {...rest}
98
98
  data-slot="landing-hero"
99
99
  className={cn(
100
- "mx-auto grid max-w-6xl items-center gap-12 px-4 py-16 sm:py-24",
100
+ // `grid-cols-1` below `lg` (0.32.1, Kurvenschmiede's 0.32 report): without a
101
+ // template the one column is `auto` and grows to the visual's longest unwrapped
102
+ // line — 80 px past a 360 px phone at Extra large. `minmax(0, 1fr)` holds it to
103
+ // the page. The same in FeatureRow and TrustStrip.
104
+ "mx-auto grid max-w-6xl grid-cols-1 items-center gap-12 px-4 py-16 sm:py-24",
101
105
  visual != null && "lg:grid-cols-2",
102
106
  className,
103
107
  )}
@@ -186,7 +190,7 @@ export function FeatureRow({
186
190
  <div
187
191
  {...rest}
188
192
  data-slot="landing-feature-row"
189
- className={cn("group/feature grid items-center gap-8 py-10", visual != null && "lg:grid-cols-2 lg:gap-16", className)}
193
+ className={cn("group/feature grid grid-cols-1 items-center gap-8 py-10", visual != null && "lg:grid-cols-2 lg:gap-16", className)}
190
194
  >
191
195
  <div className="lg:group-even/feature:order-last">
192
196
  {(icon != null || eyebrow != null) && (
@@ -245,7 +249,7 @@ export interface TrustStripProps extends Omit<ComponentPropsWithoutRef<"section"
245
249
  export function TrustStrip({ items, headingAs: Heading = "h3", className, ...rest }: TrustStripProps) {
246
250
  return (
247
251
  <section {...rest} data-slot="landing-trust" className={cn("mx-auto max-w-6xl px-4 py-12 sm:py-16", className)}>
248
- <ul className={cn("grid gap-8 sm:grid-cols-2", items.length === 3 ? "lg:grid-cols-3" : "lg:grid-cols-4")}>
252
+ <ul className={cn("grid grid-cols-1 gap-8 sm:grid-cols-2", items.length === 3 ? "lg:grid-cols-3" : "lg:grid-cols-4")}>
249
253
  {items.map((item, i) => (
250
254
  <li key={i}>
251
255
  {item.icon != null && (
@@ -79,7 +79,7 @@ export interface UseAccountAppearanceOptions {
79
79
  export interface AccountAppearance extends ResolvedAccountAppearance {
80
80
  /** The user picked a size: it becomes the device's choice and — signed in, and not a
81
81
  * demo — the account's. The promise is `save`'s; the device keeps the pick if it
82
- * fails, and the app says so. */
82
+ * fails, and the app says so (`appearance.saveFailed`, 0.32.1). */
83
83
  pickTextSize: (size: TextSize) => Promise<unknown>;
84
84
  /** As {@link pickTextSize}, for the contrast mode. */
85
85
  pickContrast: (mode: ContrastMode) => Promise<unknown>;
@@ -130,9 +130,10 @@ export function readPersistedContrast(storageKey: string): ContrastMode | null {
130
130
  /**
131
131
  * Pre-paint (§10.3, §10.5): this device's choice under `storageKey` — else the account's
132
132
  * last known value, else "system" — resolved against `prefers-contrast` and applied
133
- * BEFORE `createRoot`. Call it next to `applyPersistedPalette`, in either order: the
133
+ * BEFORE `createRoot`. Call it next to `applyPersistedPalette`, in EITHER order — the
134
134
  * palette steps itself when `data-contrast` is already set, and this re-applies the
135
- * palette's set when it is not. Returns the level applied.
135
+ * palette's set when it is not; no app has to put one first (0.32.1, keksdose's 0.32
136
+ * report asked which). Returns the level applied.
136
137
  */
137
138
  export function applyPersistedContrast(storageKey: string, options: PersistedAppearanceOptions = {}): ContrastLevel {
138
139
  let account: unknown;
@@ -119,6 +119,11 @@ export function useTextSize(): TextSizeInfo {
119
119
  *
120
120
  * `size` is one of {@link TEXT_SIZES}, or `null` / absent when this device never chose.
121
121
  * A future change adds a key; it never renames `state.size` or changes its values.
122
+ *
123
+ * A hand-written seed (a test, a screenshot script) must carry `"version":1`: the store
124
+ * drops a stored state of another version as a migration it doesn't know, silently
125
+ * (keksdose's 0.32 report). The inline snippet reads `state.size` whatever the version,
126
+ * so the two would disagree. In the kit's showcase, `?text-size=` needs no seed.
122
127
  */
123
128
  export interface PersistedTextSize {
124
129
  state?: { size?: TextSize | null };
@@ -189,9 +194,20 @@ export function applyPersistedTextSize(storageKey: string, options: PersistedApp
189
194
  * ```
190
195
  *
191
196
  * Paste the constant's value in place of `(function(k,a){…})`. It reads the frozen
192
- * {@link PersistedTextSize} format, never throws, and sets only `data-text-size`; the
193
- * splash itself sizes in rem so the attribute reaches it (tokens.css, or the splash's own
194
- * `html[data-text-size=large]{font-size:125%}` if it paints before the stylesheet).
197
+ * {@link PersistedTextSize} format, never throws, and sets only `data-text-size`.
198
+ *
199
+ * The splash sizes in rem, and needs the root rules inline beside it, since the app's
200
+ * stylesheet is a separate request and arrives after the splash paints (0.32.1,
201
+ * keksdose's 0.32 report: that is every app, not an edge case):
202
+ *
203
+ * ```html
204
+ * <style>html[data-text-size=large]{font-size:125%}html[data-text-size=xlarge]{font-size:150%}</style>
205
+ * ```
206
+ *
207
+ * Under a hash-based Content Security Policy (`script-src 'sha256-…'`) this is a second
208
+ * inline script with its own hash: add it to the policy, and recompute it whenever the
209
+ * storage key or the account function in the call changes (keksdose added it to both
210
+ * its Caddyfiles, and its guard test hashes every inline script).
195
211
  */
196
212
  export const TEXT_SIZE_INLINE_SCRIPT =
197
213
  "(function(k,a){function ok(v){return v==='normal'||v==='large'||v==='xlarge'}" +
@@ -40,8 +40,10 @@ export function WizardSummary({
40
40
  <Card key={sIdx}>
41
41
  {/* CardHeader is a grid, and CardAction is the top-right column it
42
42
  reserves — the edit button has to go through it or it stacks under
43
- the title instead of sitting beside it. */}
44
- <CardHeader className="pb-2">
43
+ the title instead of sitting beside it. `stackAction={false}`: the pencil
44
+ is named by `aria-label` and stays an icon at Large, so it keeps its
45
+ place beside the title there too (0.32.1). */}
46
+ <CardHeader className="pb-2" stackAction={false}>
45
47
  <CardTitle className="text-base">{section.label}</CardTitle>
46
48
  {section.stepIndex !== undefined && onEditStep && (
47
49
  <CardAction>
package/tokens.css CHANGED
@@ -693,10 +693,18 @@ body {
693
693
  .animate-drill { animation-name: overlay-fade-in; }
694
694
  }
695
695
 
696
- /* Scrollbars: keep light defaults; theme to slate when dark mode is active. */
696
+ /* Scrollbars: keep light defaults; theme to the palette when dark mode is active.
697
+ *
698
+ * From the tokens, not literals (0.32.1, keksdose's 0.32 report): the thumb is a border
699
+ * (`--border`) on the page (`--bg-page`), so a palette preset carries through, and so
700
+ * does More contrast (§5), which steps `--border` until it clears 3:1 against every
701
+ * surface, the page among them: WCAG 1.4.11's floor for a control's visible part. At
702
+ * standard contrast it is as quiet as the slate-700-on-slate-900 it replaces (1.5:1
703
+ * against 1.7:1 in the default dark preset). Hover takes `--border-strong`, the border
704
+ * pulled toward the text, which grows with it. */
697
705
  .dark {
698
706
  color-scheme: dark;
699
- scrollbar-color: rgb(51 65 85) rgb(15 23 42); /* slate-700 thumb on slate-900 track */
707
+ scrollbar-color: var(--border) var(--bg-page);
700
708
  }
701
709
 
702
710
  .dark ::-webkit-scrollbar {
@@ -705,19 +713,19 @@ body {
705
713
  }
706
714
 
707
715
  .dark ::-webkit-scrollbar-track {
708
- background-color: rgb(15 23 42); /* slate-900 */
716
+ background-color: var(--bg-page);
709
717
  }
710
718
 
711
719
  .dark ::-webkit-scrollbar-thumb {
712
- background-color: rgb(51 65 85); /* slate-700 */
720
+ background-color: var(--border);
713
721
  border-radius: 6px;
714
- border: 2px solid rgb(15 23 42); /* slate-900 — creates inset padding */
722
+ border: 2px solid var(--bg-page); /* the track's colour: inset padding */
715
723
  }
716
724
 
717
725
  .dark ::-webkit-scrollbar-thumb:hover {
718
- background-color: rgb(71 85 105); /* slate-600 */
726
+ background-color: var(--border-strong);
719
727
  }
720
728
 
721
729
  .dark ::-webkit-scrollbar-corner {
722
- background-color: rgb(15 23 42); /* slate-900 */
730
+ background-color: var(--bg-page);
723
731
  }