barua-ui 0.12.1 → 0.13.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.
package/README.md CHANGED
@@ -77,7 +77,8 @@ before first paint.
77
77
  - **Provider** — `BaruaProvider` (theme / `accent` re-tint / Liquid Glass
78
78
  `glass` mode / Tier-2 `refraction`), `ThemeScript`, `useToast`
79
79
  - **Actions** — `Button`, `ButtonLink`, `Fab`, `Toolbar`
80
- - **Forms** — `Field`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`,
80
+ - **Forms** — `Field`, `Input`, `Textarea`, `NativeSelect` (the platform
81
+ control; product surfaces use `Dropdown variant="select"`), `Checkbox`, `Radio`,
81
82
  `Switch`, `Slider`, `OtpInput`
82
83
  - **Content** — `Card` family (incl. `StoryCard`, flush iOS-Settings groups),
83
84
  `List`/`ListItem`, `Badge`, `Tag`, `Chip`, `Avatar`(+`AvatarGroup`),
package/css/barua.css CHANGED
@@ -26,3 +26,4 @@
26
26
  @import url("components/control-center.css");
27
27
  @import url("components/marketing.css");
28
28
  @import url("liquid.css");
29
+ @import url("components/forced-colors.css");
package/css/base.css CHANGED
@@ -116,17 +116,23 @@
116
116
  }
117
117
 
118
118
  /* ---- Focus ring (Focus Ring component) -------------------------------- */
119
- :focus-visible {
120
- outline: none;
121
- box-shadow: var(--b-focus-ring);
122
- border-radius: var(--b-radius-xs);
123
- }
124
- .b-focus-ring:focus-visible,
125
- button:focus-visible, [href]:focus-visible,
126
- input:focus-visible, select:focus-visible, textarea:focus-visible,
127
- [tabindex]:focus-visible {
128
- outline: none;
129
- box-shadow: var(--b-focus-ring);
119
+ /* One ring for everything that can take focus, on the outline channel, at
120
+ zero specificity so a component can restyle it and nothing can cancel it
121
+ by accident: the old box-shadow ring was silently defeated by any
122
+ component that drew its own shadow. */
123
+ :where(:focus-visible, .b-focus-ring:focus-visible) {
124
+ outline: var(--b-focus-ring);
125
+ outline-offset: var(--b-focus-ring-offset);
126
+ }
127
+ /* Inputs whose host draws the ring for them. */
128
+ :where(.b-cmdk__input, .b-multiselect input):focus-visible { outline: none; }
129
+ /* Rows flush inside a clipping container: an outline outside the box is
130
+ cut off by the container's overflow, so these pull it inward. */
131
+ :where(.b-list-item, .b-disclosure > summary, .b-tab, .b-segmented__item,
132
+ .b-btn-group > .b-btn, .b-menu__item, .b-sidebar__item, .b-rail__item,
133
+ .b-bottomnav__item, .b-cmdk__item, .b-stepper-input :is(button, input),
134
+ .b-action-sheet__group button, .b-swipe__actions button) {
135
+ --b-focus-ring-offset: calc(-1 * var(--b-focus-ring-width));
130
136
  }
131
137
 
132
138
  /* ---- Interaction-state conventions ------------------------------------
@@ -138,11 +144,15 @@
138
144
  .is-dragging — mid-drag
139
145
  :disabled / .is-disabled — non-interactive
140
146
  ------------------------------------------------------------------------- */
141
- :disabled, .is-disabled {
147
+ /* Dim the control, never the group: a disabled <fieldset> already makes
148
+ every control inside it match :disabled, so dimming the group too stacked
149
+ 0.4 x 0.4 = 0.16 and greyed the <legend>, which HTML keeps enabled. */
150
+ :disabled:not(fieldset), .is-disabled {
142
151
  opacity: var(--b-opacity-disabled);
143
152
  cursor: not-allowed !important;
144
153
  pointer-events: var(--b-disabled-pointer, auto);
145
154
  }
155
+ fieldset:disabled { cursor: not-allowed; }
146
156
  .is-dragging { opacity: 0.55; cursor: grabbing !important; }
147
157
 
148
158
  [draggable="true"] { cursor: grab; }
@@ -13,14 +13,18 @@
13
13
  justify-content: center;
14
14
  gap: var(--b-space-2);
15
15
  height: var(--b-control-h-md);
16
- padding-inline: var(--b-space-4);
16
+ /* The border is real and transparent, the padding pays for it, and the
17
+ rendered box does not move by a pixel. Windows High Contrast strips
18
+ backgrounds and shadows; a button that was only a fill became bare
19
+ words on the page. */
20
+ padding-inline: calc(var(--b-space-4) - 1px);
17
21
  font-size: var(--b-text-subheadline);
18
22
  font-weight: var(--b-weight-semibold);
19
23
  line-height: 1;
20
24
  white-space: nowrap;
21
25
  color: var(--b-text);
22
26
  background: var(--b-fill-secondary);
23
- border: none;
27
+ border: 1px solid transparent;
24
28
  border-radius: var(--b-radius-md);
25
29
  cursor: pointer;
26
30
  user-select: none;
@@ -37,11 +41,11 @@
37
41
  /* Variants */
38
42
  .b-btn--primary {
39
43
  color: var(--b-color-on-accent);
40
- background: var(--b-color-accent);
44
+ background: var(--b-color-accent-fill);
41
45
  box-shadow: var(--b-shadow-xs);
42
46
  }
43
- .b-btn--primary:hover { background: var(--b-color-accent-hover); }
44
- .b-btn--primary.is-active { background: var(--b-color-accent-active); }
47
+ .b-btn--primary:hover { background: var(--b-color-accent-fill-hover); }
48
+ .b-btn--primary.is-active { background: var(--b-color-accent-fill-active); }
45
49
 
46
50
  .b-btn--tinted {
47
51
  color: var(--b-color-accent-text);
@@ -56,7 +60,7 @@
56
60
  color: var(--b-color-on-accent);
57
61
  background:
58
62
  radial-gradient(120% 140% at 30% 12%, rgba(255, 255, 255, 0.38), transparent 42%),
59
- var(--b-color-accent);
63
+ var(--b-color-accent-fill);
60
64
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4),
61
65
  inset 0 0 0 1px color-mix(in srgb, var(--b-color-accent) 55%, transparent),
62
66
  var(--b-shadow-accent);
@@ -73,7 +77,7 @@
73
77
 
74
78
  .b-btn--outline {
75
79
  background: transparent;
76
- box-shadow: inset 0 0 0 1px var(--b-border-strong);
80
+ border-color: var(--b-border-strong);
77
81
  }
78
82
  .b-btn--outline:hover { background: var(--b-fill-quaternary); }
79
83
 
@@ -204,18 +208,18 @@
204
208
  animation: b-glass-shimmer var(--b-glass-shimmer-duration) var(--b-ease-out) forwards;
205
209
  }
206
210
 
207
- .b-btn--danger { color: var(--b-color-on-accent); background: var(--b-color-danger); }
208
- .b-btn--danger:hover { background: color-mix(in srgb, var(--b-color-danger) 85%, black); }
209
- .b-btn--danger-tinted { color: var(--b-color-danger); background: var(--b-color-danger-soft); }
211
+ .b-btn--danger { color: var(--b-color-on-accent); background: var(--b-color-danger-fill); }
212
+ .b-btn--danger:hover { background: color-mix(in srgb, var(--b-color-danger-fill) 85%, black); }
213
+ .b-btn--danger-tinted { color: var(--b-color-danger-text); background: var(--b-color-danger-soft); }
210
214
  .b-btn--danger-tinted:hover { background: color-mix(in srgb, var(--b-color-red) 22%, transparent); }
211
- .b-btn--success { color: var(--b-color-on-accent); background: var(--b-color-success); }
215
+ .b-btn--success { color: var(--b-color-on-accent); background: var(--b-color-success-fill); }
212
216
  .b-btn--success:hover { background: color-mix(in srgb, var(--b-color-success) 85%, black); }
213
217
 
214
218
  /* Sizes */
215
- .b-btn--xs { height: var(--b-control-h-xs); padding-inline: var(--b-space-2); font-size: var(--b-text-caption); border-radius: var(--b-radius-sm); }
216
- .b-btn--sm { height: var(--b-control-h-sm); padding-inline: var(--b-space-3); font-size: var(--b-text-footnote); border-radius: var(--b-radius-sm); }
217
- .b-btn--lg { height: var(--b-control-h-lg); padding-inline: var(--b-space-5); font-size: var(--b-text-callout); border-radius: var(--b-radius-lg); }
218
- .b-btn--xl { height: var(--b-control-h-xl); padding-inline: var(--b-space-6); font-size: var(--b-text-body); border-radius: var(--b-radius-lg); }
219
+ .b-btn--xs { height: var(--b-control-h-xs); padding-inline: calc(var(--b-space-2) - 1px); font-size: var(--b-text-caption); border-radius: var(--b-radius-sm); }
220
+ .b-btn--sm { height: var(--b-control-h-sm); padding-inline: calc(var(--b-space-3) - 1px); font-size: var(--b-text-footnote); border-radius: var(--b-radius-sm); }
221
+ .b-btn--lg { height: var(--b-control-h-lg); padding-inline: calc(var(--b-space-5) - 1px); font-size: var(--b-text-callout); border-radius: var(--b-radius-lg); }
222
+ .b-btn--xl { height: var(--b-control-h-xl); padding-inline: calc(var(--b-space-6) - 1px); font-size: var(--b-text-body); border-radius: var(--b-radius-lg); }
219
223
  .b-btn--pill { border-radius: var(--b-radius-full); }
220
224
  .b-btn--block { display: flex; width: 100%; }
221
225
 
@@ -255,9 +259,10 @@
255
259
  inset-inline-end: var(--b-space-6);
256
260
  z-index: var(--b-z-sticky);
257
261
  width: 3.5rem; height: 3.5rem;
262
+ border: 1px solid transparent;
258
263
  border-radius: var(--b-radius-full);
259
264
  color: var(--b-color-on-accent);
260
- background: var(--b-color-accent);
265
+ background: var(--b-color-accent-fill);
261
266
  box-shadow: var(--b-shadow-lg), var(--b-shadow-accent);
262
267
  }
263
268
  .b-fab:hover { background: var(--b-color-accent-hover); translate: 0 -2px; }
@@ -320,7 +325,7 @@
320
325
  .b-link:hover { text-decoration: underline; text-underline-offset: 0.2em; }
321
326
  .b-link--muted { color: var(--b-text-secondary); }
322
327
  .b-link--muted:hover { color: var(--b-text); }
323
- .b-link--danger { color: var(--b-color-danger); }
328
+ .b-link--danger { color: var(--b-color-danger-text); }
324
329
  .b-link svg { width: 1em; height: 1em; }
325
330
  .b-link-group {
326
331
  display: flex;
@@ -42,7 +42,7 @@
42
42
  margin-inline: auto;
43
43
  font-size: 1.4rem;
44
44
  color: var(--b-color-on-accent);
45
- background: linear-gradient(135deg, var(--b-color-accent), var(--b-color-indigo));
45
+ background: linear-gradient(135deg, var(--b-color-accent-fill), var(--b-color-indigo));
46
46
  border-radius: var(--b-radius-lg);
47
47
  box-shadow: var(--b-shadow-accent);
48
48
  }
@@ -96,8 +96,6 @@
96
96
  }
97
97
  .b-otp input:focus-visible {
98
98
  border-color: var(--b-color-accent);
99
- box-shadow: var(--b-focus-ring);
100
- outline: none;
101
99
  }
102
100
  .b-otp input.is-filled { border-color: var(--b-color-accent); }
103
101
  .b-otp.is-invalid input { border-color: var(--b-color-danger); animation: b-shake 0.35s var(--b-ease-standard); }
@@ -381,6 +381,39 @@
381
381
  border-collapse: collapse;
382
382
  font-size: var(--b-text-footnote);
383
383
  }
384
+ /* A table with a prose column has no good phone shape: it lays out at
385
+ min-content and scrolls, so a Notes column becomes twelve lines of three
386
+ words each. --stack turns each row into a block under 40rem, with the
387
+ column heading written before every cell from data-label. The cells stay
388
+ table cells for assistive tech; only their display changes. Data grids
389
+ never take this: numbers want columns. */
390
+ /* .b-table.b-table--stack, not .b-table--stack: a docs table is usually
391
+ --compact as well, and --compact's cell padding sits at equal specificity
392
+ later in this file. The phone shape has to win over density by
393
+ specificity, not by where it happens to sit. */
394
+ @media (max-width: 40rem) {
395
+ .b-table.b-table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
396
+ .b-table.b-table--stack, .b-table.b-table--stack tbody, .b-table.b-table--stack tr, .b-table.b-table--stack td { display: block; }
397
+ .b-table.b-table--stack tr { padding-block: var(--b-space-3); border-block-end: 1px solid var(--b-separator); }
398
+ .b-table.b-table--stack td {
399
+ position: relative;
400
+ padding: var(--b-space-1) 0 var(--b-space-1) var(--b-table-label-w, 34%);
401
+ border: 0;
402
+ min-height: 1.5em;
403
+ }
404
+ .b-table.b-table--stack td::before {
405
+ content: attr(data-label);
406
+ position: absolute;
407
+ inset-inline-start: 0;
408
+ width: calc(var(--b-table-label-w, 34%) - var(--b-space-3));
409
+ font-size: var(--b-text-caption);
410
+ font-weight: var(--b-weight-semibold);
411
+ color: var(--b-text-secondary);
412
+ overflow: hidden;
413
+ text-overflow: ellipsis;
414
+ white-space: nowrap;
415
+ }
416
+ }
384
417
  .b-table th {
385
418
  padding: var(--b-space-3) var(--b-space-4);
386
419
  text-align: start;
@@ -421,8 +454,19 @@
421
454
  /* Data grid extras — sticky header + row actions */
422
455
  .b-datagrid { max-height: var(--b-datagrid-h, 26rem); overflow: auto; }
423
456
  .b-datagrid .b-table th { position: sticky; top: 0; z-index: 1; background: var(--b-surface-2); }
424
- .b-datagrid .b-table td .b-row-actions { visibility: hidden; }
425
- .b-datagrid .b-table tr:hover .b-row-actions { visibility: visible; }
457
+ /* Hidden by opacity, not visibility: visibility:hidden drops the buttons
458
+ from the tab order, so a keyboard user could never reach Edit or Delete.
459
+ Revealed by hover or by focus landing anywhere in the row. */
460
+ .b-datagrid .b-table td .b-row-actions {
461
+ opacity: 0;
462
+ transition: opacity var(--b-duration-fast) var(--b-ease-out);
463
+ }
464
+ .b-datagrid .b-table tr:is(:hover, :focus-within) .b-row-actions { opacity: 1; }
465
+ /* No hover on touch, and an opacity-0 button is still tappable, so an
466
+ invisible Delete would be a trap. */
467
+ @media (hover: none) {
468
+ .b-datagrid .b-table td .b-row-actions { opacity: 1; }
469
+ }
426
470
 
427
471
  /* ---- Tree ---------------------------------------------------------------------- */
428
472
  .b-tree { list-style: none; margin: 0; padding: 0; font-size: var(--b-text-subheadline); }
@@ -567,10 +611,10 @@
567
611
  white-space: nowrap;
568
612
  }
569
613
  .b-badge--accent { color: var(--b-color-accent-text); background: var(--b-color-accent-soft); }
570
- .b-badge--success { color: var(--b-color-success); background: var(--b-color-success-soft); }
571
- .b-badge--warning { color: light-dark(#b46200, var(--b-color-warning)); background: var(--b-color-warning-soft); }
572
- .b-badge--danger { color: var(--b-color-danger); background: var(--b-color-danger-soft); }
573
- .b-badge--solid { color: var(--b-color-on-accent); background: var(--b-color-accent); }
614
+ .b-badge--success { color: var(--b-color-success-text); background: var(--b-color-success-soft); }
615
+ .b-badge--warning { color: var(--b-color-warning-text); background: var(--b-color-warning-soft); }
616
+ .b-badge--danger { color: var(--b-color-danger-text); background: var(--b-color-danger-soft); }
617
+ .b-badge--solid { color: var(--b-color-on-accent); background: var(--b-color-accent-fill); }
574
618
  .b-badge--dot::before {
575
619
  content: "";
576
620
  width: 0.4rem; height: 0.4rem;
@@ -621,12 +665,12 @@
621
665
  align-items: center;
622
666
  gap: var(--b-space-1);
623
667
  height: var(--b-control-h-sm);
624
- padding-inline: var(--b-space-3);
668
+ padding-inline: calc(var(--b-space-3) - 1px);
625
669
  font-size: var(--b-text-footnote);
626
670
  font-weight: var(--b-weight-medium);
627
671
  color: var(--b-text);
628
672
  background: var(--b-fill-tertiary);
629
- border: none;
673
+ border: 1px solid transparent;
630
674
  border-radius: var(--b-radius-full);
631
675
  cursor: pointer;
632
676
  transition: background var(--b-duration-fast), color var(--b-duration-fast);
@@ -634,7 +678,7 @@
634
678
  .b-chip:hover { background: var(--b-fill-secondary); }
635
679
  .b-chip.is-selected, .b-chip[aria-pressed="true"] {
636
680
  color: var(--b-color-on-accent);
637
- background: var(--b-color-accent);
681
+ background: var(--b-color-accent-fill);
638
682
  }
639
683
  .b-chip__remove {
640
684
  display: inline-grid;
@@ -772,8 +816,12 @@
772
816
  .b-code__copy {
773
817
  padding: 0.2rem var(--b-space-2);
774
818
  font-size: var(--b-text-caption);
775
- color: rgba(235, 235, 245, 0.7);
776
- background: rgba(255, 255, 255, 0.08);
819
+ /* Ink and veil come from the host. A light literal here was right inside
820
+ the always-dark code block and invisible when the docs put the same
821
+ button on a light footer: the copy button was there and nobody could
822
+ see it. */
823
+ color: color-mix(in srgb, currentColor 78%, transparent);
824
+ background: color-mix(in srgb, currentColor 10%, transparent);
777
825
  border: none;
778
826
  border-radius: var(--b-radius-sm);
779
827
  cursor: pointer;
@@ -818,8 +866,8 @@
818
866
  font-size: var(--b-text-footnote);
819
867
  font-weight: var(--b-weight-semibold);
820
868
  }
821
- .b-stat__delta.is-up { color: var(--b-color-success); }
822
- .b-stat__delta.is-down { color: var(--b-color-danger); }
869
+ .b-stat__delta.is-up { color: var(--b-color-success-text); }
870
+ .b-stat__delta.is-down { color: var(--b-color-danger-text); }
823
871
 
824
872
  /* ---- Empty State ---------------------------------------------------------------------------------- */
825
873
  .b-empty {
@@ -848,16 +896,24 @@
848
896
  color: var(--b-text-secondary);
849
897
  }
850
898
  .b-empty .b-btn { margin-block-start: var(--b-space-3); }
851
- }
899
+
852
900
  /* A picture behind flat cards is a picture nobody sees. When a wallpaper is
853
901
  hanging (Barua.wallpaper adds .b-has-wallpaper), surfaces on the wall
854
902
  become the neutral material — derived from whatever is behind them, so it
855
903
  holds over any picture. A product that wants opaque surfaces simply never
856
- hangs one. */
857
- .b-has-wallpaper .b-workspace,
858
- .b-has-wallpaper .b-workspace__main,
859
- .b-has-wallpaper .b-inspector { background: transparent; }
860
- .b-has-wallpaper .b-card {
904
+ hangs one.
905
+
906
+ This block lives INSIDE @layer components, so a product's unlayered
907
+ override still wins, which is the contract barua.css states. A closing
908
+ brace had landed above it and the whole block was unlayered: it beat
909
+ every card variant and every product override the moment a wallpaper was
910
+ hung. The class only ever lives on <html>, so :root lifts these to (0,3,0)
911
+ and they beat Liquid Glass's (0,2,0) panes by specificity, not by escaping
912
+ the layer. */
913
+ :root.b-has-wallpaper .b-workspace,
914
+ :root.b-has-wallpaper .b-workspace__main,
915
+ :root.b-has-wallpaper .b-inspector { background: transparent; }
916
+ :root.b-has-wallpaper .b-card {
861
917
  background: var(--b-material-neutral-bg);
862
918
  -webkit-backdrop-filter: blur(var(--b-blur-lg)) saturate(1.1);
863
919
  backdrop-filter: blur(var(--b-blur-lg)) saturate(1.1);
@@ -867,14 +923,15 @@
867
923
  material keeps its blur and doubles the veil, so the picture still shows at
868
924
  the edges without competing with the ink. The veil follows the wallpaper's
869
925
  own scheme, so it lightens on a bright picture and darkens on a dark one. */
870
- .b-has-wallpaper .b-card--thick {
926
+ :root.b-has-wallpaper .b-card--thick {
871
927
  background: var(--b-material-thick-bg);
872
928
  }
873
929
 
874
930
  /* A panel inside glass is a fill, never a second material. */
875
- .b-has-wallpaper .b-card .b-card {
931
+ :root.b-has-wallpaper .b-card .b-card {
876
932
  background: var(--b-fill-quaternary);
877
933
  -webkit-backdrop-filter: none;
878
934
  backdrop-filter: none;
879
935
  }
936
+ }
880
937
 
@@ -71,9 +71,9 @@
71
71
  font-weight: var(--b-weight-medium);
72
72
  text-align: center;
73
73
  color: var(--b-color-on-accent);
74
- background: linear-gradient(90deg, var(--b-color-accent), color-mix(in srgb, var(--b-color-indigo) 80%, var(--b-color-accent)));
74
+ background: linear-gradient(90deg, var(--b-color-accent-fill), color-mix(in srgb, var(--b-color-indigo) 80%, var(--b-color-accent-fill)));
75
75
  }
76
- .b-banner a { color: inherit; text-decoration: underline; text-underline-offset: 0.2em; }
76
+ .b-banner a:not(.b-btn) { color: inherit; text-decoration: underline; text-underline-offset: 0.2em; }
77
77
  .b-banner__close {
78
78
  background: none; border: none;
79
79
  color: inherit; opacity: 0.8; cursor: pointer;
@@ -0,0 +1,60 @@
1
+ @layer components {
2
+ /* ==========================================================================
3
+ Forced colours (Windows High Contrast, and any browser with
4
+ forced-colors: active).
5
+
6
+ The platform strips every background and shadow and repaints the page in
7
+ a handful of system colours. A control whose shape lived only in its fill
8
+ became bare words; a switch became nothing at all; a selected segment and
9
+ an unselected one looked the same. Two rules make a component survive:
10
+ its edge is a real border (transparent in normal mode, painted here) and
11
+ any state that was a fill opts out of the repaint to keep a system fill.
12
+ The system colours are the only literals allowed in a component file.
13
+ ========================================================================== */
14
+ @media (forced-colors: active) {
15
+ /* Edges for everything that was shape-by-fill. */
16
+ .b-btn, .b-fab, .b-chip, .b-segmented, .b-segmented__item,
17
+ .b-switch input, .b-switch input::after,
18
+ .b-badge, .b-tag, .b-progress, .b-progress__bar, .b-tab { border-color: ButtonText; }
19
+ .b-switch input { background: Canvas; }
20
+ .b-switch input::after { background: ButtonText; }
21
+
22
+ /* A state that was a fill stays a fill, in the system's own colours. */
23
+ .b-btn--primary, .b-btn--liquid, .b-btn--danger, .b-btn--success,
24
+ .b-segmented__item.is-active, .b-segmented__item[aria-selected="true"],
25
+ .b-tab.is-active, .b-tab[aria-selected="true"],
26
+ .b-chip.is-selected, .b-chip[aria-pressed="true"],
27
+ .b-menu__item.is-active, .b-pagination__item.is-active,
28
+ .b-sidebar__item.is-active, .b-rail__item.is-active,
29
+ .b-switch input:checked,
30
+ .b-checkbox input:checked, .b-checkbox input:indeterminate,
31
+ .b-calendar__day.is-selected, .b-progress__bar, .b-range__fill {
32
+ forced-color-adjust: none;
33
+ background: Highlight;
34
+ color: HighlightText;
35
+ border-color: Highlight;
36
+ }
37
+ .b-switch input:checked::after { forced-color-adjust: none; background: HighlightText; }
38
+
39
+ /* Range inputs: the platform already draws these in system colours. */
40
+ .b-slider, .b-slider::-webkit-slider-thumb,
41
+ .b-range > input[type="range"],
42
+ .b-range > input[type="range"]::-webkit-slider-thumb { appearance: auto; }
43
+ .b-range::before { border: 1px solid ButtonText; }
44
+
45
+ /* Focus and disabled must read as such. */
46
+ :focus-visible { outline-color: Highlight; }
47
+ :disabled, .is-disabled, .b-btn[aria-disabled="true"] { color: GrayText; border-color: GrayText; }
48
+ .b-switch input:disabled { border-color: GrayText; }
49
+ .b-switch input:disabled::after { background: GrayText; }
50
+
51
+ /* Glass has nothing to blur; a translucent veil over Canvas reads as a
52
+ box, and the platform wants Canvas. */
53
+ .b-card, .b-topnav, .b-menu, .b-modal, .b-drawer, .b-sheet, .b-bottom-sheet {
54
+ -webkit-backdrop-filter: none;
55
+ backdrop-filter: none;
56
+ background: Canvas;
57
+ border-color: CanvasText;
58
+ }
59
+ }
60
+ }
@@ -23,7 +23,7 @@
23
23
  }
24
24
  .b-label--required::after {
25
25
  content: " *";
26
- color: var(--b-color-danger);
26
+ color: var(--b-color-danger-text);
27
27
  }
28
28
 
29
29
  .b-help {
@@ -35,7 +35,7 @@
35
35
  align-items: center;
36
36
  gap: var(--b-space-1);
37
37
  font-size: var(--b-text-footnote);
38
- color: var(--b-color-danger);
38
+ color: var(--b-color-danger-text);
39
39
  }
40
40
 
41
41
  /* ---- Inputs ------------------------------------------------------------ */
@@ -56,8 +56,6 @@
56
56
  .b-input:hover, .b-textarea:hover, .b-select:hover { border-color: var(--b-border-strong); }
57
57
  .b-input:focus-visible, .b-textarea:focus-visible, .b-select:focus-visible {
58
58
  border-color: var(--b-color-accent);
59
- box-shadow: var(--b-focus-ring);
60
- outline: none;
61
59
  }
62
60
  .b-input--filled { background: var(--b-fill-tertiary); border-color: transparent; }
63
61
  .b-input--filled:hover { background: var(--b-fill-secondary); }
@@ -81,7 +79,7 @@
81
79
  }
82
80
  .b-field.is-invalid .b-input:focus-visible,
83
81
  .b-input.is-invalid:focus-visible, .b-input:user-invalid:focus-visible {
84
- box-shadow: 0 0 0 3.5px color-mix(in srgb, var(--b-color-danger) 25%, transparent);
82
+ --b-focus-ring-color: var(--b-color-danger);
85
83
  }
86
84
  .b-field.is-valid .b-input, .b-input.is-valid { border-color: var(--b-color-success); }
87
85
 
@@ -237,6 +235,9 @@
237
235
  margin: 0;
238
236
  flex: none;
239
237
  background: var(--b-fill);
238
+ /* Painted only by forced colours: the track was a fill and the thumb a
239
+ shadow, and both vanish in Windows High Contrast. */
240
+ border: 1.5px solid transparent;
240
241
  border-radius: var(--b-radius-full);
241
242
  position: relative;
242
243
  cursor: pointer;
@@ -250,6 +251,7 @@
250
251
  translate: 0 -50%;
251
252
  width: 1.3rem; height: 1.3rem;
252
253
  background: #fff;
254
+ border: 1px solid transparent;
253
255
  border-radius: 50%;
254
256
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 0 0 0.5px rgba(0, 0, 0, 0.06);
255
257
  transition: inset-inline-start var(--b-duration-normal) var(--b-ease-spring);
@@ -387,6 +389,7 @@
387
389
 
388
390
  /* ---- File Upload ------------------------------------------------------- */
389
391
  .b-upload {
392
+ position: relative;
390
393
  display: flex;
391
394
  flex-direction: column;
392
395
  align-items: center;
@@ -407,7 +410,27 @@
407
410
  background: var(--b-color-accent-soft);
408
411
  color: var(--b-color-accent-text);
409
412
  }
410
- .b-upload input[type="file"] { display: none; }
413
+ /* Keyboard focus on the wrapped input lights the zone, like a multiselect. */
414
+ .b-upload:focus-within {
415
+ border-color: var(--b-color-accent);
416
+ outline: var(--b-focus-ring);
417
+ outline-offset: var(--b-focus-ring-offset);
418
+ }
419
+ /* Hidden visually but still focusable and in the accessibility tree, the
420
+ .b-sr-only recipe. display:none took the only focusable thing in the
421
+ label out of the tab order, so the picker could not be opened without a
422
+ mouse. */
423
+ .b-upload input[type="file"] {
424
+ position: absolute;
425
+ width: 1px;
426
+ height: 1px;
427
+ padding: 0;
428
+ margin: -1px;
429
+ overflow: hidden;
430
+ clip-path: inset(50%);
431
+ white-space: nowrap;
432
+ border: 0;
433
+ }
411
434
  .b-upload svg { width: 2rem; height: 2rem; opacity: 0.7; }
412
435
  .b-upload__hint { font-size: var(--b-text-footnote); color: var(--b-text-tertiary); }
413
436
 
@@ -52,10 +52,13 @@
52
52
  }
53
53
  .b-marketing-nav--lg .b-marketing-nav__brand { font-size: var(--b-text-title3); }
54
54
  .b-marketing-nav--lg .b-marketing-nav__links { gap: clamp(var(--b-space-4), 2.4vw, var(--b-space-8)); }
55
- .b-marketing-nav--lg a { font-weight: var(--b-weight-medium); }
55
+ .b-marketing-nav--lg a:not(.b-btn) { font-weight: var(--b-weight-medium); }
56
56
  .b-marketing-nav__links { display: flex; gap: var(--b-space-5); margin-inline: auto; }
57
- .b-marketing-nav a { color: var(--b-text-secondary); text-decoration: none; }
58
- .b-marketing-nav a:hover { color: var(--b-text); }
57
+ /* Bare links only. A .b-btn dropped in the bar keeps its own ink: an element
58
+ selector inside a container is (0,1,1) and quietly beat .b-btn--primary's
59
+ (0,1,0), which left the landing page's call to action grey on blue. */
60
+ .b-marketing-nav a:not(.b-btn) { color: var(--b-text-secondary); text-decoration: none; }
61
+ .b-marketing-nav a:not(.b-btn):hover { color: var(--b-text); }
59
62
 
60
63
  /* ---- Chapter: one idea, centred, with room around it --------------------- */
61
64
  .b-chapter {
@@ -278,7 +281,7 @@
278
281
  gap: var(--b-space-6);
279
282
  padding: clamp(var(--b-space-6), 4vw, var(--b-space-10));
280
283
  color: var(--b-color-on-accent);
281
- background: var(--b-color-accent);
284
+ background: var(--b-color-accent-fill);
282
285
  border-radius: var(--b-radius-2xl);
283
286
  color-scheme: dark;
284
287
  }
@@ -779,7 +782,7 @@
779
782
  font-weight: var(--b-weight-semibold);
780
783
  color: var(--b-text);
781
784
  }
782
- .b-site-footer__group a { display: block; padding-block: 0.2rem; color: var(--b-text-secondary); text-decoration: none; }
785
+ .b-site-footer__group a:not(.b-btn) { display: block; padding-block: 0.2rem; color: var(--b-text-secondary); text-decoration: none; }
783
786
  .b-site-footer__group a:hover { color: var(--b-text); text-decoration: underline; }
784
787
  .b-site-footer__legal {
785
788
  max-width: var(--b-container-xl);
@@ -235,7 +235,7 @@
235
235
  .b-poster:hover .b-poster__frame { translate: 0 -2px; box-shadow: var(--b-elevation-3); }
236
236
  }
237
237
  .b-poster:focus-visible { outline: none; }
238
- .b-poster:focus-visible .b-poster__frame,
238
+ .b-poster:focus-visible .b-poster__frame { outline: var(--b-focus-ring); outline-offset: 2px; }
239
239
  .b-poster.is-selected .b-poster__frame { outline: 2px solid var(--b-color-accent); outline-offset: 2px; }
240
240
 
241
241
  /* A wall of posters: as many across as fit — three on a phone, eight on a
@@ -264,7 +264,9 @@
264
264
  opacity: 0;
265
265
  transition: opacity var(--b-duration-normal);
266
266
  }
267
- .b-video:hover .b-video__overlay, .b-video.is-paused .b-video__overlay { opacity: 1; }
267
+ .b-video:hover .b-video__overlay,
268
+ .b-video:focus-within .b-video__overlay,
269
+ .b-video.is-paused .b-video__overlay { opacity: 1; }
268
270
  .b-video__play {
269
271
  display: grid;
270
272
  place-items: center;
@@ -281,6 +283,68 @@
281
283
  }
282
284
  .b-video__play:hover { scale: 1.08; }
283
285
 
286
+ /* ---- Film player controls -------------------------------------------------------
287
+ `.b-media-controls` is the generic bar. A film needs its controls over the
288
+ picture rather than beside it, and needs them gone while someone watches.
289
+ These four existed only in the player's markup — the classes were used and
290
+ never defined, so the bar sat unstyled below the video with browser-default
291
+ buttons and full-size glyphs. */
292
+ .b-video__bar {
293
+ position: absolute;
294
+ inset-inline: 0;
295
+ inset-block-end: 0;
296
+ display: flex;
297
+ align-items: center;
298
+ gap: var(--b-space-2);
299
+ padding: var(--b-space-3);
300
+ color: #fff;
301
+ color-scheme: dark;
302
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.72));
303
+ opacity: 0;
304
+ transition: opacity var(--b-duration-normal) var(--b-ease-out);
305
+ }
306
+ .b-video:hover .b-video__bar,
307
+ .b-video:focus-within .b-video__bar,
308
+ .b-video.is-paused .b-video__bar { opacity: 1; }
309
+ /* A touch screen has no hover, so the bar would otherwise appear only by
310
+ pausing — which is exactly when someone full screen cannot reach it. */
311
+ @media (hover: none) {
312
+ .b-video__bar { opacity: 1; }
313
+ }
314
+
315
+ .b-video__btn {
316
+ flex: none;
317
+ display: inline-flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ width: 2.25rem;
321
+ height: 2.25rem;
322
+ padding: 0;
323
+ border: 0;
324
+ border-radius: var(--b-radius-full);
325
+ background: transparent;
326
+ color: inherit;
327
+ cursor: pointer;
328
+ transition: background var(--b-duration-fast) var(--b-ease-out);
329
+ }
330
+ .b-video__btn:hover { background: rgb(255 255 255 / 0.18); }
331
+ .b-video__btn svg { width: 1.5rem; height: 1.5rem; fill: currentColor; }
332
+
333
+ .b-video__time {
334
+ flex: none;
335
+ font-size: var(--b-text-caption);
336
+ font-variant-numeric: tabular-nums;
337
+ opacity: 0.9;
338
+ }
339
+
340
+ .b-video__seek {
341
+ flex: 1;
342
+ min-width: 0;
343
+ height: 1.5rem;
344
+ accent-color: var(--b-color-accent);
345
+ cursor: pointer;
346
+ }
347
+
284
348
  /* ---- Media Controls (shared control bar) --------------------------------------- */
285
349
  .b-media-controls {
286
350
  position: absolute;