actual-css 0.4.2 → 0.6.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 (117) hide show
  1. package/CHANGELOG.md +66 -3
  2. package/README.md +2 -2
  3. package/dist/actual.css +54 -14
  4. package/dist/actual.full.css +744 -185
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/dist/actual.min.css +1 -1
  8. package/docs/design-notes/browser-support.md +14 -14
  9. package/docs/design-notes/enhancement-contract.md +30 -13
  10. package/docs/design-notes/forced-colors.md +11 -7
  11. package/docs/design-notes/grid.md +4 -4
  12. package/docs/design-notes/naming.md +15 -9
  13. package/docs/design-notes/platform-alignment.md +9 -0
  14. package/docs/design-notes/references.md +4 -0
  15. package/docs/design-notes/spacing-units.md +286 -0
  16. package/docs/design-notes/steps.md +748 -0
  17. package/docs/design-notes/widget-primitives.md +32 -15
  18. package/docs/navigation.json +2 -0
  19. package/docs/pages/components/alert.md +14 -9
  20. package/docs/pages/components/app-navigation.md +2 -0
  21. package/docs/pages/components/aura.md +102 -6
  22. package/docs/pages/components/avatar.md +9 -6
  23. package/docs/pages/components/badge.md +27 -9
  24. package/docs/pages/components/button.md +25 -18
  25. package/docs/pages/components/card.md +27 -7
  26. package/docs/pages/components/chat.md +1 -1
  27. package/docs/pages/components/data-list.md +72 -0
  28. package/docs/pages/components/dialog.md +12 -7
  29. package/docs/pages/components/drawer.md +3 -1
  30. package/docs/pages/components/fab.md +14 -10
  31. package/docs/pages/components/indicator.md +6 -6
  32. package/docs/pages/components/list.md +11 -2
  33. package/docs/pages/components/meter.md +56 -1
  34. package/docs/pages/components/navbar.md +1 -1
  35. package/docs/pages/components/pagination.md +1 -1
  36. package/docs/pages/components/progress.md +10 -0
  37. package/docs/pages/components/rating.md +4 -4
  38. package/docs/pages/components/skeleton.md +3 -1
  39. package/docs/pages/components/spinner.md +1 -1
  40. package/docs/pages/components/steps.md +379 -47
  41. package/docs/pages/components/table.md +6 -5
  42. package/docs/pages/enhancements/flyout.md +14 -10
  43. package/docs/pages/enhancements/overview.md +2 -2
  44. package/docs/pages/enhancements/scrollspy.md +1 -1
  45. package/docs/pages/enhancements/status-bar.md +10 -1
  46. package/docs/pages/enhancements/tabs.md +1 -1
  47. package/docs/pages/examples/overview.md +4 -1
  48. package/docs/pages/forms/choice-cards.md +4 -4
  49. package/docs/pages/forms/combobox.md +87 -0
  50. package/docs/pages/forms/floating-field.md +5 -3
  51. package/docs/pages/forms/joined-controls.md +6 -4
  52. package/docs/pages/forms/labels.md +8 -5
  53. package/docs/pages/forms/overview.md +26 -19
  54. package/docs/pages/forms/password.md +3 -2
  55. package/docs/pages/forms/range.md +15 -2
  56. package/docs/pages/forms/states-and-sizes.md +7 -5
  57. package/docs/pages/forms/validation.md +15 -9
  58. package/docs/pages/foundations/philosophy.md +11 -1
  59. package/docs/pages/foundations/tokens.md +16 -9
  60. package/docs/pages/foundations/typography.md +11 -3
  61. package/docs/pages/guides/bootstrap.md +3 -3
  62. package/docs/pages/guides/introduction.md +2 -2
  63. package/docs/pages/guides/modular-import.md +21 -10
  64. package/docs/pages/guides/progressive-enhancement.md +75 -54
  65. package/docs/pages/guides/tailwind.md +2 -2
  66. package/docs/pages/layout/app-shell.md +4 -4
  67. package/docs/pages/layout/choosing.md +10 -6
  68. package/docs/pages/layout/grid.md +11 -10
  69. package/docs/pages/layout/scroll-snap.md +2 -0
  70. package/docs/pages/layout/sidebar-layout.md +2 -0
  71. package/docs/pages/layout/stack.md +10 -6
  72. package/docs/pages/layout/topbar.md +2 -0
  73. package/docs/pages/patterns/structured-lists.md +7 -7
  74. package/docs/pages/utilities/accessibility.md +19 -1
  75. package/docs/pages/utilities/flex.md +20 -13
  76. package/docs/pages/utilities/links.md +1 -1
  77. package/docs/pages/utilities/list.md +1 -1
  78. package/docs/pages/utilities/overflow.md +14 -7
  79. package/docs/pages/utilities/overview.md +22 -0
  80. package/docs/pages/utilities/shape.md +1 -1
  81. package/docs/pages/utilities/sizing-wrapping.md +10 -8
  82. package/docs/pages/utilities/spacing.md +26 -19
  83. package/docs/pages/utilities/text.md +1 -1
  84. package/llms.txt +27 -4
  85. package/package.json +3 -2
  86. package/scripts/reserved-classes.json +8 -1
  87. package/src/css/components/data-list.css +27 -0
  88. package/src/css/components/drawer.css +30 -11
  89. package/src/css/components/fab.css +44 -8
  90. package/src/css/components/flyout.css +17 -12
  91. package/src/css/components/index.css +1 -0
  92. package/src/css/components/list.css +3 -1
  93. package/src/css/components/meter.css +28 -1
  94. package/src/css/components/modal.css +28 -11
  95. package/src/css/components/progress.css +5 -0
  96. package/src/css/components/status-bar.css +31 -15
  97. package/src/css/components/steps.css +289 -55
  98. package/src/css/components/surface.css +5 -3
  99. package/src/css/components/tooltip.css +5 -8
  100. package/src/css/core/base.css +7 -1
  101. package/src/css/core/intents.css +6 -0
  102. package/src/css/core/tokens.css +24 -9
  103. package/src/css/core/variants.css +17 -4
  104. package/src/css/effects/aura.css +101 -11
  105. package/src/css/forms/custom-select.css +1 -1
  106. package/src/css/forms/floating-field.css +8 -6
  107. package/src/css/forms/form.css +4 -4
  108. package/src/css/layout/container-query.css +37 -13
  109. package/src/css/layout/grid.css +7 -7
  110. package/src/css/layout/index.css +1 -1
  111. package/src/css/typography/prose.css +2 -3
  112. package/src/css/utilities/base.css +16 -0
  113. package/src/js/password.js +1 -1
  114. package/src/js/selectors.js +1 -0
  115. package/src/js/status.js +88 -7
  116. package/src/js/surface.js +2 -11
  117. package/src/js/transition.js +20 -0
@@ -1,9 +1,12 @@
1
1
  /*
2
2
  * Status bar — singleton floating status area.
3
3
  *
4
- * One element in the HTML, empty by default, hidden when :empty but kept in the
5
- * accessibility tree so it stays a reliable live region. JavaScript only sets
6
- * text content; the visual shows and hides with the content.
4
+ * One element in the HTML, empty by default. .is-open is the visual state, set
5
+ * by status.js like any other surface: it lands after the message is written
6
+ * and lifts before the bar is cleared, so the bar leaves in the exact state it
7
+ * was shown in — same text, same width, same intent color — instead of snapping
8
+ * to a neutral empty pill mid-exit. Emptying the text is the *end* of the exit,
9
+ * not its trigger.
7
10
  *
8
11
  * Consumes intent like badge or alert. No need for status-bar-specific modifier
9
12
  * classes — .danger / .success / .warning / .neutral from intents.css drive
@@ -17,6 +20,7 @@
17
20
  * Internal:
18
21
  * --status-bg
19
22
  * --status-fg
23
+ * --status-viewport-offset (written by status.js while the bar is open)
20
24
  */
21
25
 
22
26
  /* Intent boundary — inherited intents do not tint this component.
@@ -31,7 +35,9 @@
31
35
  --status-fg: var(--intent-fg, var(--surface));
32
36
 
33
37
  position: fixed;
34
- inset-block-end: var(--space-40);
38
+ /* Fixed positioning tracks the layout viewport, which the mobile software
39
+ keyboard covers. status.js writes the occluded band into the offset. */
40
+ inset-block-end: calc(var(--space-40) + var(--status-viewport-offset, 0px));
35
41
  inset-inline: var(--space-40);
36
42
  z-index: var(--z-status);
37
43
 
@@ -53,22 +59,32 @@
53
59
  color: var(--status-fg);
54
60
  box-shadow: var(--shadow-popout);
55
61
 
56
- opacity: 1;
57
- scale: 1;
58
- translate: 0 0;
62
+ /* Closed by default. Opacity, not visibility or display: the bar has to stay
63
+ in the accessibility tree to work as a live region. */
64
+ opacity: 0;
65
+ translate: 0 var(--space-30);
59
66
  pointer-events: none;
60
67
 
68
+ /* Exit is shorter than entry so dismissed feedback gets out of the way.
69
+ background stays a shorthand so --status-bg still accepts gradients;
70
+ solid colors expose an interpolable computed background-color. The
71
+ intent color follows the state/interaction `ease`, not the presence
72
+ curves. */
61
73
  transition:
62
- opacity var(--duration-slow) var(--ease),
63
- scale var(--duration-slow) var(--ease),
64
- translate var(--duration-slow) var(--ease);
74
+ opacity var(--duration-fast) var(--ease-exit),
75
+ translate var(--duration) var(--ease-exit),
76
+ background-color var(--duration) ease;
65
77
  }
66
78
 
67
- .status-bar:empty {
68
- opacity: 0;
69
- scale: 0.96;
70
- translate: 0 var(--space-20);
71
- pointer-events: none;
79
+ .status-bar.is-open {
80
+ opacity: 1;
81
+ translate: 0 0;
82
+ /* Entry takes a little longer and decelerates, making the message legible
83
+ before its motion settles. */
84
+ transition:
85
+ opacity var(--duration) var(--ease-enter),
86
+ translate var(--duration-slow) var(--ease-enter),
87
+ background-color var(--duration) ease;
72
88
  }
73
89
  @media print {
74
90
  .status-bar {
@@ -1,20 +1,54 @@
1
1
  /*
2
- * Steps — semantic ordered-list progression. One intrinsic layout: items fill
3
- * the row when they fit and hold a minimum width otherwise, so the sequence
4
- * scrolls horizontally only when it genuinely cannot fit. No breakpoint decides
5
- * this — the item minimum tracks the real content, not an arbitrary width.
2
+ * Steps — semantic ordered-list progression for a multi-step process.
6
3
  *
7
- * Markers come from the list counter; state stays in .complete and
8
- * aria-current="step", so the component needs no runtime.
4
+ * One primitive, two explicit compositions. Both classes are required:
5
+ *
6
+ * <ol class="steps steps-horizontal"> horizontal, 2 to 5 stages
7
+ * <ol class="steps steps-vertical"> vertical, any number of stages
8
+ *
9
+ * `.steps` is what a step *is* — the counter, the marker, `.step-label`,
10
+ * `.step-complete`, `aria-current="step"`. It carries no orientation, so it is
11
+ * never written alone. `.steps-horizontal` and `.steps-vertical` are what a
12
+ * sequence *looks like*, and they are peers: neither is the default the other
13
+ * has to exclude, so no rule says `:not(...)` and no rule undoes another's
14
+ * layout.
15
+ *
16
+ * Orientation and labels are independent dimensions. `.step-label` is optional
17
+ * in either orientation; when a sequence carries names, every step carries
18
+ * one:
19
+ *
20
+ * empty steps .step-label on every step
21
+ * horizontal compact progression named sequence, stacked or inline
22
+ * vertical compact rail named sequence, labels beside markers
23
+ *
24
+ * An empty step is a marker and nothing else, so it is compact by structure —
25
+ * `> li:empty` in both orientations, no variant class, and nothing authored
26
+ * only to be hidden again. The page around it owns the step name.
27
+ *
28
+ * A label may be an `a[href]`, which is the whole interactive surface: the
29
+ * marker is never navigable, so navigating a flow means naming its
30
+ * destinations. Anything else focusable in a step is out of contract.
31
+ *
32
+ * The framework never hides an authored label to solve a layout problem. A
33
+ * labelled row that outgrows its space scrolls. An `actual-container` size
34
+ * context buys it one thing: the wide representation, marker beside label.
35
+ *
36
+ * See docs/design-notes/steps.md for the threshold, the selector shapes, the
37
+ * rejected alternatives, and why there is no forced-colors branch.
38
+ *
39
+ * Each public hook is followed by the class that owns it.
9
40
  *
10
41
  * Public hooks:
11
- * --step-size
12
- * --step-min
13
- * --step-gap
14
- * --step-line-size
15
- * --step-marker-radius
16
- * --step-complete-mark
17
- * --step-connector
42
+ * --step-size .steps
43
+ * --step-inline-gap .steps
44
+ * --step-line-size .steps
45
+ * --step-marker-radius .steps
46
+ * --step-complete-mark .steps
47
+ * --step-min .steps-horizontal
48
+ * --step-gap .steps-horizontal
49
+ * --step-connector .steps-horizontal
50
+ * --step-inline-connector .steps-horizontal
51
+ * --step-vertical-gap .steps-vertical
18
52
  *
19
53
  * Internal:
20
54
  * --step-marker-bg
@@ -22,10 +56,12 @@
22
56
  * --step-marker-fg
23
57
  * --step-line
24
58
  */
59
+
60
+ /* Step primitive --------------------------------------------------------- */
61
+
25
62
  .steps {
26
63
  --step-size: 1.75rem;
27
- --step-min: 7rem;
28
- --step-gap: var(--space-20);
64
+ --step-inline-gap: var(--space-20);
29
65
  --step-line-size: var(--border-width);
30
66
  --step-marker-bg: var(--surface);
31
67
  --step-marker-border: var(--border);
@@ -35,29 +71,14 @@
35
71
 
36
72
  counter-reset: actual-step;
37
73
  display: flex;
38
- flex-direction: row;
39
- inline-size: 100%;
40
74
  margin: 0;
41
75
  padding: 0;
42
76
  list-style: none;
43
- /* Fill when it fits, scroll when it cannot: items never shrink below
44
- --step-min, so labels keep a readable width and the sequence scrolls
45
- horizontally instead of squishing. overscroll-behavior keeps the scroll
46
- from chaining to the page. */
47
- overflow-x: auto;
48
- overscroll-behavior-inline: contain;
49
77
  }
50
78
 
51
79
  .steps > li {
52
80
  position: relative;
53
- display: grid;
54
- flex: 1 0 var(--step-min);
55
- grid-template-rows: var(--step-size) auto;
56
- gap: var(--step-gap);
57
- justify-items: center;
58
- min-inline-size: max(var(--step-size), var(--step-min));
59
81
  color: var(--text-muted);
60
- text-align: center;
61
82
  counter-increment: actual-step;
62
83
  }
63
84
 
@@ -78,46 +99,259 @@
78
99
  line-height: 1;
79
100
  }
80
101
 
81
- .steps > li::after {
82
- content: "";
83
- position: absolute;
84
- inset-block-start: calc((var(--step-size) - var(--step-line-size)) / 2);
85
- inset-inline-start: calc(50% + var(--step-size) / 2);
86
- inline-size: calc(100% - var(--step-size));
87
- block-size: var(--step-line-size);
88
- /* A custom property resolves its var() before inheritance, so a default
89
- --step-connector: var(--step-line) on .steps would freeze the parent line
90
- color and a .complete child's connector would never follow its own line.
91
- Falling back to the local --step-line here keeps each connector in sync;
92
- an explicit --step-connector still wins when the author sets one. */
93
- background: var(--step-connector, var(--step-line));
102
+ /* The marker carries the state, so the label reads a notch below it — set on
103
+ the component, never per representation: a size that changed with the layout
104
+ would resize the text under a reader dragging a window. `color: inherit` is
105
+ here rather than in a rule of its own for `a.step-label`, because a span
106
+ inherits its ink anyway; only a link has anything to take back, and taking it
107
+ back for every label costs nothing and one rule less. A navigable label keeps
108
+ its underline, hover and focus ring — just not a link color, which would read
109
+ ahead of the marker states.
110
+
111
+ Scoped to `.steps`, unlike `.menu-item-text` and the framework's other
112
+ sub-part classes: a bare `.step-label` weighs (0,1,0), exactly tying
113
+ `.prose :where(a)`, so a stepper in prose would be decided by bundle order. */
114
+ .steps .step-label {
115
+ font-size: var(--font-size-sm);
116
+ line-height: var(--line-height-tight);
117
+ color: inherit;
94
118
  }
95
119
 
96
- .steps > li:last-child::after {
97
- content: none;
120
+ /* Each composition gives the connector its own geometry and background, so the
121
+ only shared fact is which steps get one — every step but the last. Stated as
122
+ the condition rather than as a `content: none` rule undoing a `content: ""`
123
+ rule: a pseudo-element with no content generates no box, so a composition can
124
+ address `> li::after` freely and still not trail a line past the last step. */
125
+ .steps > li:not(:last-child)::after {
126
+ content: "";
98
127
  }
99
128
 
100
- .steps > :is(.complete, [aria-current="step"]) {
129
+ /* Prefixed like `.step-label`, unlike the framework's bare `.primary` /
130
+ `.outline` modifiers: those qualify a component on its own element, this
131
+ marks one item inside one. A bare `.complete` would also claim a very
132
+ ordinary English word globally, for a single component's single state. */
133
+ .steps > .step-complete {
101
134
  --step-marker-bg: var(--state-selected);
102
135
  --step-marker-border: var(--state-selected);
103
136
  --step-marker-fg: var(--state-selected-fg);
137
+ --step-line: var(--state-selected);
138
+
139
+ color: var(--text);
140
+ }
141
+
142
+ /* Complete owns the accented connector that reaches the current step, so the
143
+ connector leaving current goes back to neutral. Declared after
144
+ .step-complete at equal specificity: current wins on every channel. */
145
+ .steps > [aria-current="step"] {
146
+ --step-marker-bg: var(--surface);
147
+ --step-marker-border: var(--state-selected);
148
+ --step-marker-fg: var(--state-selected);
149
+ --step-line: var(--border);
104
150
 
105
151
  color: var(--text);
106
152
  }
107
153
 
108
- /* Thicker border is the selected affordance in every mode — structural, and it
109
- never competes with the outline channel owned by focus. */
110
- .steps > :is(.complete, [aria-current="step"])::before {
154
+ .steps > [aria-current="step"]::before {
111
155
  border-width: calc(var(--border-width) * 2);
112
156
  }
113
157
 
114
- .steps > .complete {
115
- --step-line: var(--state-selected);
158
+ /* Weight marks "where I am now". Complete needs none: its filled disc already
159
+ reads as strongly. */
160
+ .steps > [aria-current="step"] .step-label {
161
+ font-weight: var(--font-weight-strong);
116
162
  }
117
163
 
118
- /* A finished step may replace its number with a mark (e.g. a check glyph):
119
- --step-complete-mark accepts any content value and falls back to the
120
- counter, so existing numbered steppers are unchanged. */
121
- .steps > .complete::before {
164
+ .steps > .step-complete:not([aria-current="step"])::before {
122
165
  content: var(--step-complete-mark, counter(actual-step));
123
166
  }
167
+
168
+ /* Horizontal ------------------------------------------------------------- */
169
+
170
+ .steps-horizontal {
171
+ --step-min: 7rem;
172
+ --step-gap: var(--space-20);
173
+
174
+ inline-size: 100%;
175
+ overflow-x: auto;
176
+ /* Stated, never left to `overflow-x` alone: one scrolling axis computes the
177
+ other to `auto`, and a row hugs fractional content closely enough that a
178
+ rounding difference then raises a phantom vertical scrollbar. */
179
+ overflow-y: hidden;
180
+ overscroll-behavior-inline: contain;
181
+ }
182
+
183
+ /* A scroll container clips at the padding edge, so a navigable row reserves the
184
+ focus ring's own width and offset. Only this composition scrolls, and only a
185
+ link has a ring to protect. */
186
+ .steps-horizontal:has(a[href]) {
187
+ padding-block: calc(var(--focus-outline-offset) + var(--border-width) * 2);
188
+ }
189
+
190
+ /* One explicit row, the marker's: a `.step-label` is auto-placed into an
191
+ implicit second row, and two tracks are also the only thing `gap` has to sit
192
+ between. So an empty step measures exactly `--step-size`, and the rule below
193
+ takes back nothing. `var(--step-size) auto` would build that second row
194
+ whether or not a label ever arrives, and the compact form would then have to
195
+ remove both the track and the gap. */
196
+ .steps-horizontal > li {
197
+ display: grid;
198
+ flex: 1 0 var(--step-min);
199
+ grid-template-rows: var(--step-size);
200
+ gap: var(--step-gap);
201
+ justify-items: center;
202
+ min-inline-size: max(var(--step-size), var(--step-min));
203
+ text-align: center;
204
+ }
205
+
206
+ /* An empty step is a marker and nothing else, so it needs a marker's width
207
+ rather than a reading width. That is what lets five of them fit a phone with
208
+ no size context and no variant class.
209
+
210
+ `justify-items` is not an override of the row's centring: the marker is
211
+ centred because a label sits under it and the two share a centre, and with no
212
+ label there is nothing to centre against. The marker takes the start of its
213
+ own share instead, the connector below fills the rest of it, and the last
214
+ step has no share to fill — so the track runs edge to edge and lines up with
215
+ the text around it, the way the inline representation does.
216
+
217
+ `:empty` says exactly what the markup says, the way `.badge:empty` does, and
218
+ it reads the item's own content: bare text in an `<li>` keeps a reading width
219
+ like its wrapped peers, where `> li:has(.step-label)` would give it a marker
220
+ floor and skew the row. It also predates `:has()` by a decade, so the compact
221
+ form reaches the Degraded tier and only the inline bonus needs `:has()`. */
222
+ .steps-horizontal > li:empty {
223
+ flex: 1 1 0;
224
+ justify-items: start;
225
+ min-inline-size: var(--step-size);
226
+ }
227
+
228
+ .steps-horizontal > li:empty::after {
229
+ inset-inline-start: var(--step-size);
230
+ }
231
+
232
+ .steps-horizontal > li:empty:last-child {
233
+ flex: 0 0 auto;
234
+ }
235
+
236
+ /* The pseudo-element is marker-height so --step-connector may be any complete
237
+ background value. */
238
+ .steps-horizontal > li::after {
239
+ position: absolute;
240
+ inset-block-start: 0;
241
+ inset-inline-start: calc(50% + var(--step-size) / 2);
242
+ inline-size: calc(100% - var(--step-size));
243
+ block-size: var(--step-size);
244
+ background: var(
245
+ --step-connector,
246
+ linear-gradient(var(--step-line), var(--step-line)) center /
247
+ 100% var(--step-line-size) no-repeat
248
+ );
249
+ }
250
+
251
+ /*
252
+ * The one horizontal container query. Past 60rem a labelled row has the room
253
+ * to put marker and label inline and still leave the connector a real region
254
+ * between groups, rather than a stub. Calibrated on five steps — the widest
255
+ * supported row, and therefore the binding case — and it counts nothing, so it
256
+ * serves every count. Below it, stacked; a marker row is already the shape
257
+ * this query produces and stays out of it.
258
+ *
259
+ * Horizontal is a 2-5 step component. Outside that domain the rule simply
260
+ * applies as written, because a row that is out of contract is not owed a
261
+ * curated fallback.
262
+ */
263
+ @supports (container-type: inline-size) {
264
+ @container actual-container (inline-size >= 60rem) {
265
+ .steps-horizontal:has(.step-label) > li {
266
+ display: flex;
267
+ flex: 1 1 auto;
268
+ align-items: center;
269
+ gap: var(--step-inline-gap);
270
+ min-inline-size: 0;
271
+ text-align: start;
272
+ white-space: nowrap;
273
+ }
274
+
275
+ .steps-horizontal:has(.step-label) > li::after {
276
+ position: static;
277
+ flex: 1 1 2rem;
278
+ inline-size: auto;
279
+ block-size: var(--step-size);
280
+ margin-inline-end: var(--step-inline-gap);
281
+ background: var(
282
+ --step-inline-connector,
283
+ var(
284
+ --step-connector,
285
+ linear-gradient(var(--step-line), var(--step-line)) center /
286
+ 100% var(--step-line-size) no-repeat
287
+ )
288
+ );
289
+ }
290
+
291
+ .steps-horizontal:has(.step-label) > li:last-child {
292
+ flex: 0 0 auto;
293
+ }
294
+ }
295
+ }
296
+
297
+ /* Vertical --------------------------------------------------------------- */
298
+
299
+ .steps-vertical {
300
+ --step-vertical-gap: var(--space-40);
301
+
302
+ flex-direction: column;
303
+ gap: var(--step-vertical-gap);
304
+ }
305
+
306
+ /* `justify-items` looks like a reset and is not one — nothing here resets
307
+ anything, since the horizontal rules never applied. Grid items blockify, so
308
+ without it a label stretches the whole 1fr track: 264px against 75px for an
309
+ `a.step-label`, a full-width focus ring the row does not give the same link. */
310
+ .steps-vertical > li {
311
+ display: grid;
312
+ grid-template-columns: var(--step-size) minmax(0, 1fr);
313
+ align-items: start;
314
+ justify-items: start;
315
+ gap: var(--step-inline-gap);
316
+ min-block-size: var(--step-size);
317
+ }
318
+
319
+ /* An empty step is a marker rail. The column cannot use the row's implicit
320
+ second track, because `grid-auto-flow` is `row`: an auto-placed label would
321
+ land *under* its marker. Its second track is therefore explicit, and this
322
+ takes it back.
323
+
324
+ Silent in a stretched column, where the item fills its line whatever its
325
+ tracks say. It bites in a shrink-to-fit box — which is what a sticky
326
+ progress rail usually is — where the label track and the gap to it make the
327
+ rail `--step-inline-gap` wider than the markers it draws: measured 36px
328
+ against 28px. No `gap: 0` needed; one track has nothing to sit beside. */
329
+ .steps-vertical > li:empty {
330
+ grid-template-columns: var(--step-size);
331
+ }
332
+
333
+ /* The row starts at the top so a wrapped label begins level with its marker.
334
+ align-self only bites on a single line, which would otherwise float above
335
+ the marker's centre.
336
+
337
+ The one `.step-label` rule that keeps a child combinator: `align-self` is a
338
+ grid-item property, so this declaration is meaningless on anything but a
339
+ direct child of the row. Elsewhere the label is addressed as a descendant —
340
+ the framework owns the structure, and repeating it buys nothing. */
341
+ .steps-vertical > li > .step-label {
342
+ align-self: center;
343
+ }
344
+
345
+ /* The vertical track publishes no hook of its own: a horizontal connector value
346
+ cannot transpose onto this geometry, and this selector is already a reachable
347
+ override for the design that finally asks. */
348
+ .steps-vertical > li::after {
349
+ position: absolute;
350
+ inset-block-start: var(--step-size);
351
+ inset-inline-start: calc((var(--step-size) - var(--step-line-size)) / 2);
352
+ inline-size: var(--step-line-size);
353
+ block-size: calc(100% - var(--step-size) + var(--step-vertical-gap));
354
+ background:
355
+ linear-gradient(var(--step-line), var(--step-line)) center / var(--step-line-size) 100%
356
+ no-repeat;
357
+ }
@@ -19,7 +19,9 @@
19
19
  z-index: calc(var(--z-menu) - 1);
20
20
  background: var(--backdrop-fill);
21
21
  opacity: 1;
22
- transition: opacity var(--duration) var(--ease);
22
+ /* A scrim never travels; it fades on the neutral `ease`. Duration matches
23
+ the flyout sheet it accompanies so it never lags the panel. */
24
+ transition: opacity var(--duration-fast) ease;
23
25
  }
24
26
 
25
27
  .surface-backdrop[hidden] {
@@ -36,8 +38,8 @@
36
38
  @supports (transition-behavior: allow-discrete) {
37
39
  .surface-backdrop {
38
40
  transition:
39
- opacity var(--duration) var(--ease),
40
- display var(--duration) allow-discrete;
41
+ opacity var(--duration-fast) ease,
42
+ display var(--duration-fast) allow-discrete;
41
43
  }
42
44
  }
43
45
  @media print {
@@ -32,31 +32,28 @@
32
32
  overflow-wrap: anywhere;
33
33
  pointer-events: auto;
34
34
  user-select: none;
35
+ /* Tooltips fade only: the anchor edge, marked by the arrow, must stay put
36
+ during motion, and a 4% scale was perceptible on small text. Scaling moves
37
+ the arrow tip away from its trigger on every placement. */
35
38
  opacity: 1;
36
- scale: 1;
37
- transition:
38
- opacity var(--duration-fast) var(--ease),
39
- scale var(--duration-fast) var(--ease);
39
+ transition: opacity var(--duration-fast) ease;
40
40
  }
41
41
 
42
42
  .tooltip[hidden] {
43
43
  display: none;
44
44
  opacity: 0;
45
- scale: 0.96;
46
45
  }
47
46
 
48
47
  @starting-style {
49
48
  .tooltip:not([hidden]) {
50
49
  opacity: 0;
51
- scale: 0.96;
52
50
  }
53
51
  }
54
52
 
55
53
  @supports (transition-behavior: allow-discrete) {
56
54
  .tooltip {
57
55
  transition:
58
- opacity var(--duration-fast) var(--ease),
59
- scale var(--duration-fast) var(--ease),
56
+ opacity var(--duration-fast) ease,
60
57
  display var(--duration-fast) allow-discrete;
61
58
  }
62
59
  }
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * Sits between reset (browser defaults) and prose (rich content scope).
5
5
  * These styles apply everywhere — cards, dialogs, heroes — not only in prose.
6
+ *
7
+ * Public hooks:
8
+ * --link
6
9
  */
7
10
 
8
11
  body {
@@ -12,8 +15,11 @@ body {
12
15
  line-height: var(--line-height);
13
16
  }
14
17
 
18
+ /* Bare links inherit their ink by default, so nothing changes unless a theme
19
+ opts in by setting --link. Because this is an element selector, any component
20
+ rule that colors its own anchors still wins. */
15
21
  a {
16
- color: inherit;
22
+ color: var(--link, inherit);
17
23
  text-underline-offset: 0.18em;
18
24
  }
19
25
 
@@ -31,3 +31,9 @@
31
31
  --intent: var(--neutral);
32
32
  --intent-fg: var(--neutral-fg);
33
33
  }
34
+
35
+ /* Paint an inherited or local intent on a simple foreground element without
36
+ turning the intent classes into general-purpose color utilities. */
37
+ .intent-color {
38
+ color: var(--intent);
39
+ }
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * Derived: "Theme-derived aliases" and "State tokens" reference intent and
10
10
  * surface tokens and follow the theme; do not hard-code values into them.
11
- * Internal: indicator offsets, --bar-height, --disabled-opacity and the
12
- * like are consumed by components, not authored.
11
+ * Internal: indicator offsets, --disabled-opacity and the like are
12
+ * consumed by components, not authored.
13
13
  *
14
14
  * Component-local hooks and internal state relays live with their
15
15
  * components. See docs/pages/foundations/tokens.md for the public/internal
@@ -33,9 +33,10 @@
33
33
  --font-size-md: 1rem;
34
34
  --font-size-lg: 1.125rem;
35
35
 
36
- /* Spacing — official steps are named in tens so the scale stays open for
37
- application themes to intercalate intermediate values (--space-15,
38
- --space-45…) without colliding with future framework steps. */
36
+ /* Spacing — official steps are named in tens so the scale stays open.
37
+ Actual CSS will never define a --space-* that is not a multiple of 10:
38
+ the intermediate names (--space-15, --space-45…) are reserved for
39
+ applications, and no future framework step can collide with them. */
39
40
  --gap: 0.75rem;
40
41
  --density-space: var(--space-40);
41
42
  --density-compact-size: 1.5rem;
@@ -53,14 +54,25 @@
53
54
  --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
54
55
 
55
56
  /* Motion — see reset.css for the prefers-reduced-motion hard reset.
56
- --duration drives component transitions (hover/focus); the dedicated
57
- animation rates below tune one-shot open/close and decorative loops. */
57
+ --duration scales component transitions (hover/focus); the dedicated
58
+ animation rates below tune one-shot open/close and decorative loops.
59
+ Generic state/interaction motion uses the CSS default `ease` directly
60
+ (registered nowhere). --ease-enter/--ease-exit express the character of
61
+ one-shot presence changes, owned by the open/closed state rules that
62
+ separate them (status-bar, drawer, modal). The pair follows the standard
63
+ M3 rhythm; the more aggressive emphasized-decelerate entrance is
64
+ deliberately not offered because it only pays off on 250–400ms motions,
65
+ beyond this vocabulary. Toggles that share one transition list at
66
+ --duration-fast (flyout, tooltip) and pure-opacity backdrops stay on
67
+ `ease`: at 100ms the curve has almost no resolving time, and a scrim is
68
+ not traveling anywhere. */
58
69
  --duration: 150ms;
59
70
  --duration-fast: 100ms;
60
71
  --duration-slow: 200ms;
61
72
  --duration-spin: 0.75s;
62
73
  --duration-shimmer: 1.2s;
63
- --ease: ease;
74
+ --ease-enter: cubic-bezier(0.2, 0, 0, 1);
75
+ --ease-exit: cubic-bezier(0.4, 0, 1, 1);
64
76
 
65
77
  /* Focus ring — shared by every interactive component's :focus-visible */
66
78
  --focus-outline-color: currentColor;
@@ -78,7 +90,9 @@
78
90
  --backdrop-opacity: 0.45;
79
91
  --backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity));
80
92
 
81
- /* Progress / meter track height */
93
+ /* Bar thickness shared by .progress and .meter — two conceptual variants of
94
+ the same geometry. Public: set it on the element or on an application
95
+ class rather than reaching for a size variant. */
82
96
  --bar-height: 0.65rem;
83
97
 
84
98
  /* Controls — inputs and buttons share --control-size so heights always match.
@@ -135,6 +149,7 @@
135
149
  --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
136
150
  --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
137
151
  --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E");
152
+ --icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
138
153
 
139
154
  /* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
140
155
  Consumed by full-viewport components (app-shell, drawer, modal, flyout).