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.
- package/CHANGELOG.md +66 -3
- package/README.md +2 -2
- package/dist/actual.css +54 -14
- package/dist/actual.full.css +744 -185
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/browser-support.md +14 -14
- package/docs/design-notes/enhancement-contract.md +30 -13
- package/docs/design-notes/forced-colors.md +11 -7
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +15 -9
- package/docs/design-notes/platform-alignment.md +9 -0
- package/docs/design-notes/references.md +4 -0
- package/docs/design-notes/spacing-units.md +286 -0
- package/docs/design-notes/steps.md +748 -0
- package/docs/design-notes/widget-primitives.md +32 -15
- package/docs/navigation.json +2 -0
- package/docs/pages/components/alert.md +14 -9
- package/docs/pages/components/app-navigation.md +2 -0
- package/docs/pages/components/aura.md +102 -6
- package/docs/pages/components/avatar.md +9 -6
- package/docs/pages/components/badge.md +27 -9
- package/docs/pages/components/button.md +25 -18
- package/docs/pages/components/card.md +27 -7
- package/docs/pages/components/chat.md +1 -1
- package/docs/pages/components/data-list.md +72 -0
- package/docs/pages/components/dialog.md +12 -7
- package/docs/pages/components/drawer.md +3 -1
- package/docs/pages/components/fab.md +14 -10
- package/docs/pages/components/indicator.md +6 -6
- package/docs/pages/components/list.md +11 -2
- package/docs/pages/components/meter.md +56 -1
- package/docs/pages/components/navbar.md +1 -1
- package/docs/pages/components/pagination.md +1 -1
- package/docs/pages/components/progress.md +10 -0
- package/docs/pages/components/rating.md +4 -4
- package/docs/pages/components/skeleton.md +3 -1
- package/docs/pages/components/spinner.md +1 -1
- package/docs/pages/components/steps.md +379 -47
- package/docs/pages/components/table.md +6 -5
- package/docs/pages/enhancements/flyout.md +14 -10
- package/docs/pages/enhancements/overview.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +10 -1
- package/docs/pages/enhancements/tabs.md +1 -1
- package/docs/pages/examples/overview.md +4 -1
- package/docs/pages/forms/choice-cards.md +4 -4
- package/docs/pages/forms/combobox.md +87 -0
- package/docs/pages/forms/floating-field.md +5 -3
- package/docs/pages/forms/joined-controls.md +6 -4
- package/docs/pages/forms/labels.md +8 -5
- package/docs/pages/forms/overview.md +26 -19
- package/docs/pages/forms/password.md +3 -2
- package/docs/pages/forms/range.md +15 -2
- package/docs/pages/forms/states-and-sizes.md +7 -5
- package/docs/pages/forms/validation.md +15 -9
- package/docs/pages/foundations/philosophy.md +11 -1
- package/docs/pages/foundations/tokens.md +16 -9
- package/docs/pages/foundations/typography.md +11 -3
- package/docs/pages/guides/bootstrap.md +3 -3
- package/docs/pages/guides/introduction.md +2 -2
- package/docs/pages/guides/modular-import.md +21 -10
- package/docs/pages/guides/progressive-enhancement.md +75 -54
- package/docs/pages/guides/tailwind.md +2 -2
- package/docs/pages/layout/app-shell.md +4 -4
- package/docs/pages/layout/choosing.md +10 -6
- package/docs/pages/layout/grid.md +11 -10
- package/docs/pages/layout/scroll-snap.md +2 -0
- package/docs/pages/layout/sidebar-layout.md +2 -0
- package/docs/pages/layout/stack.md +10 -6
- package/docs/pages/layout/topbar.md +2 -0
- package/docs/pages/patterns/structured-lists.md +7 -7
- package/docs/pages/utilities/accessibility.md +19 -1
- package/docs/pages/utilities/flex.md +20 -13
- package/docs/pages/utilities/links.md +1 -1
- package/docs/pages/utilities/list.md +1 -1
- package/docs/pages/utilities/overflow.md +14 -7
- package/docs/pages/utilities/overview.md +22 -0
- package/docs/pages/utilities/shape.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +10 -8
- package/docs/pages/utilities/spacing.md +26 -19
- package/docs/pages/utilities/text.md +1 -1
- package/llms.txt +27 -4
- package/package.json +3 -2
- package/scripts/reserved-classes.json +8 -1
- package/src/css/components/data-list.css +27 -0
- package/src/css/components/drawer.css +30 -11
- package/src/css/components/fab.css +44 -8
- package/src/css/components/flyout.css +17 -12
- package/src/css/components/index.css +1 -0
- package/src/css/components/list.css +3 -1
- package/src/css/components/meter.css +28 -1
- package/src/css/components/modal.css +28 -11
- package/src/css/components/progress.css +5 -0
- package/src/css/components/status-bar.css +31 -15
- package/src/css/components/steps.css +289 -55
- package/src/css/components/surface.css +5 -3
- package/src/css/components/tooltip.css +5 -8
- package/src/css/core/base.css +7 -1
- package/src/css/core/intents.css +6 -0
- package/src/css/core/tokens.css +24 -9
- package/src/css/core/variants.css +17 -4
- package/src/css/effects/aura.css +101 -11
- package/src/css/forms/custom-select.css +1 -1
- package/src/css/forms/floating-field.css +8 -6
- package/src/css/forms/form.css +4 -4
- package/src/css/layout/container-query.css +37 -13
- package/src/css/layout/grid.css +7 -7
- package/src/css/layout/index.css +1 -1
- package/src/css/typography/prose.css +2 -3
- package/src/css/utilities/base.css +16 -0
- package/src/js/password.js +1 -1
- package/src/js/selectors.js +1 -0
- package/src/js/status.js +88 -7
- package/src/js/surface.js +2 -11
- package/src/js/transition.js +20 -0
package/dist/actual.full.css
CHANGED
|
@@ -60,8 +60,8 @@ select {
|
|
|
60
60
|
*
|
|
61
61
|
* Derived: "Theme-derived aliases" and "State tokens" reference intent and
|
|
62
62
|
* surface tokens and follow the theme; do not hard-code values into them.
|
|
63
|
-
* Internal: indicator offsets, --
|
|
64
|
-
*
|
|
63
|
+
* Internal: indicator offsets, --disabled-opacity and the like are
|
|
64
|
+
* consumed by components, not authored.
|
|
65
65
|
*
|
|
66
66
|
* Component-local hooks and internal state relays live with their
|
|
67
67
|
* components. See docs/pages/foundations/tokens.md for the public/internal
|
|
@@ -85,9 +85,10 @@ select {
|
|
|
85
85
|
--font-size-md: 1rem;
|
|
86
86
|
--font-size-lg: 1.125rem;
|
|
87
87
|
|
|
88
|
-
/* Spacing — official steps are named in tens so the scale stays open
|
|
89
|
-
|
|
90
|
-
--space-45…)
|
|
88
|
+
/* Spacing — official steps are named in tens so the scale stays open.
|
|
89
|
+
Actual CSS will never define a --space-* that is not a multiple of 10:
|
|
90
|
+
the intermediate names (--space-15, --space-45…) are reserved for
|
|
91
|
+
applications, and no future framework step can collide with them. */
|
|
91
92
|
--gap: 0.75rem;
|
|
92
93
|
--density-space: var(--space-40);
|
|
93
94
|
--density-compact-size: 1.5rem;
|
|
@@ -105,14 +106,25 @@ select {
|
|
|
105
106
|
--shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
|
|
106
107
|
|
|
107
108
|
/* Motion — see reset.css for the prefers-reduced-motion hard reset.
|
|
108
|
-
--duration
|
|
109
|
-
animation rates below tune one-shot open/close and decorative loops.
|
|
109
|
+
--duration scales component transitions (hover/focus); the dedicated
|
|
110
|
+
animation rates below tune one-shot open/close and decorative loops.
|
|
111
|
+
Generic state/interaction motion uses the CSS default `ease` directly
|
|
112
|
+
(registered nowhere). --ease-enter/--ease-exit express the character of
|
|
113
|
+
one-shot presence changes, owned by the open/closed state rules that
|
|
114
|
+
separate them (status-bar, drawer, modal). The pair follows the standard
|
|
115
|
+
M3 rhythm; the more aggressive emphasized-decelerate entrance is
|
|
116
|
+
deliberately not offered because it only pays off on 250–400ms motions,
|
|
117
|
+
beyond this vocabulary. Toggles that share one transition list at
|
|
118
|
+
--duration-fast (flyout, tooltip) and pure-opacity backdrops stay on
|
|
119
|
+
`ease`: at 100ms the curve has almost no resolving time, and a scrim is
|
|
120
|
+
not traveling anywhere. */
|
|
110
121
|
--duration: 150ms;
|
|
111
122
|
--duration-fast: 100ms;
|
|
112
123
|
--duration-slow: 200ms;
|
|
113
124
|
--duration-spin: 0.75s;
|
|
114
125
|
--duration-shimmer: 1.2s;
|
|
115
|
-
--ease:
|
|
126
|
+
--ease-enter: cubic-bezier(0.2, 0, 0, 1);
|
|
127
|
+
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
|
116
128
|
|
|
117
129
|
/* Focus ring — shared by every interactive component's :focus-visible */
|
|
118
130
|
--focus-outline-color: currentColor;
|
|
@@ -130,7 +142,9 @@ select {
|
|
|
130
142
|
--backdrop-opacity: 0.45;
|
|
131
143
|
--backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity));
|
|
132
144
|
|
|
133
|
-
/*
|
|
145
|
+
/* Bar thickness shared by .progress and .meter — two conceptual variants of
|
|
146
|
+
the same geometry. Public: set it on the element or on an application
|
|
147
|
+
class rather than reaching for a size variant. */
|
|
134
148
|
--bar-height: 0.65rem;
|
|
135
149
|
|
|
136
150
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
@@ -187,6 +201,7 @@ select {
|
|
|
187
201
|
--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");
|
|
188
202
|
--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");
|
|
189
203
|
--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");
|
|
204
|
+
--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");
|
|
190
205
|
|
|
191
206
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
192
207
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -453,6 +468,9 @@ select {
|
|
|
453
468
|
*
|
|
454
469
|
* Sits between reset (browser defaults) and prose (rich content scope).
|
|
455
470
|
* These styles apply everywhere — cards, dialogs, heroes — not only in prose.
|
|
471
|
+
*
|
|
472
|
+
* Public hooks:
|
|
473
|
+
* --link
|
|
456
474
|
*/
|
|
457
475
|
|
|
458
476
|
body {
|
|
@@ -462,8 +480,11 @@ body {
|
|
|
462
480
|
line-height: var(--line-height);
|
|
463
481
|
}
|
|
464
482
|
|
|
483
|
+
/* Bare links inherit their ink by default, so nothing changes unless a theme
|
|
484
|
+
opts in by setting --link. Because this is an element selector, any component
|
|
485
|
+
rule that colors its own anchors still wins. */
|
|
465
486
|
a {
|
|
466
|
-
color: inherit;
|
|
487
|
+
color: var(--link, inherit);
|
|
467
488
|
text-underline-offset: 0.18em;
|
|
468
489
|
}
|
|
469
490
|
|
|
@@ -547,11 +568,17 @@ small {
|
|
|
547
568
|
--intent-fg: var(--neutral-fg);
|
|
548
569
|
}
|
|
549
570
|
|
|
571
|
+
/* Paint an inherited or local intent on a simple foreground element without
|
|
572
|
+
turning the intent classes into general-purpose color utilities. */
|
|
573
|
+
.intent-color {
|
|
574
|
+
color: var(--intent);
|
|
575
|
+
}
|
|
576
|
+
|
|
550
577
|
/*
|
|
551
578
|
* Cross-cutting variant token mapping.
|
|
552
579
|
*
|
|
553
|
-
* Variant scope: .solid/.soft/.outline set --ui-* for components that
|
|
554
|
-
* shared variants. .inverted is a surface modifier, not a variant: it paints
|
|
580
|
+
* Variant scope: .solid/.soft/.outline/.surface set --ui-* for components that
|
|
581
|
+
* opt into shared variants. .inverted is a surface modifier, not a variant: it paints
|
|
555
582
|
* the element directly (background/color/border-color) so it works on any bare
|
|
556
583
|
* block, and remaps the shared --ui-* tokens so participating components
|
|
557
584
|
* (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
|
|
@@ -570,8 +597,10 @@ small {
|
|
|
570
597
|
* components may consume --density-* when the density context should affect
|
|
571
598
|
* their local rhythm. Compact components may consume --density-compact-size.
|
|
572
599
|
* Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
|
|
573
|
-
* applying .sm/.lg to card, dialog, accordion, navbar, table
|
|
574
|
-
* a no-op.
|
|
600
|
+
* applying .sm/.lg to card, dialog, accordion, navbar, table, progress, meter
|
|
601
|
+
* is a no-op. Progress and meter are deliberate: a bar keeps its inline size
|
|
602
|
+
* and only changes thickness, which --bar-height expresses better than a
|
|
603
|
+
* three-step scale.
|
|
575
604
|
*
|
|
576
605
|
* Intent list source of truth: intents.css. Components consume --intent* tokens
|
|
577
606
|
* instead of re-listing intent classes.
|
|
@@ -594,6 +623,17 @@ small {
|
|
|
594
623
|
--ui-border: var(--intent, currentColor);
|
|
595
624
|
}
|
|
596
625
|
|
|
626
|
+
/* Neutral chrome, intent only in the ink: the page surface with the theme
|
|
627
|
+
border. .soft is the intent-tinted treatment (its baseline fallback uses
|
|
628
|
+
--surface-subtle) and .outline has no fill at all, so neither can express
|
|
629
|
+
it. Deliberately literal — no color-mix enhancement — so this stays the one
|
|
630
|
+
variant that reads exactly as the theme's own surface. */
|
|
631
|
+
.surface {
|
|
632
|
+
--ui-bg: var(--surface);
|
|
633
|
+
--ui-fg: var(--intent, var(--text));
|
|
634
|
+
--ui-border: var(--border);
|
|
635
|
+
}
|
|
636
|
+
|
|
597
637
|
/* Inverse surface treatment. Paints the element directly (so it works on any
|
|
598
638
|
bare block) and remaps the shared --ui-* tokens so opt-in components
|
|
599
639
|
(.card, .navbar) invert through their own rules. Because this loads before
|
|
@@ -776,9 +816,8 @@ small {
|
|
|
776
816
|
font-size: var(--prose-h4);
|
|
777
817
|
}
|
|
778
818
|
|
|
779
|
-
/*
|
|
780
|
-
|
|
781
|
-
it without having to restate the rule. */
|
|
819
|
+
/* --link is a theme-level token (core/base.css also reads it for bare links);
|
|
820
|
+
prose falls back to --primary so a theme that never sets it is unaffected. */
|
|
782
821
|
.prose :where(a) {
|
|
783
822
|
color: var(--link, var(--primary));
|
|
784
823
|
text-decoration-line: underline;
|
|
@@ -1035,7 +1074,7 @@ small {
|
|
|
1035
1074
|
* .sidebar-layout main content + secondary aside
|
|
1036
1075
|
* .grid responsive collection; item width is the contract
|
|
1037
1076
|
* .grid-N structural density of N; balanced inside a container named
|
|
1038
|
-
* actual-
|
|
1077
|
+
* actual-container (in grid.css)
|
|
1039
1078
|
* .column-layout explicit 12-column region composition; the author owns
|
|
1040
1079
|
* narrow-container recomposition
|
|
1041
1080
|
* .scroller theme-aware scrollbar density and colour
|
|
@@ -1046,20 +1085,44 @@ small {
|
|
|
1046
1085
|
*
|
|
1047
1086
|
* Prefer these compositions over viewport breakpoint utilities.
|
|
1048
1087
|
*/
|
|
1049
|
-
/* Convenience helper
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1088
|
+
/* Convenience helper for size-aware components: establishes an inline-size
|
|
1089
|
+
query container named actual-container. The name is the contract, not this
|
|
1090
|
+
class — an author can declare `container: actual-container / inline-size` on
|
|
1091
|
+
their own region and get the same result.
|
|
1092
|
+
|
|
1093
|
+
actual-container is shared. Granting it to a region may enable size-aware
|
|
1094
|
+
enhancements for any Actual component inside that region, not only the one
|
|
1095
|
+
that motivated the container: .grid-N reads it for balanced subdivision,
|
|
1096
|
+
.steps for its label collapse, and future components will read the same
|
|
1097
|
+
context. That is the scope of the API, not a caveat.
|
|
1098
|
+
|
|
1099
|
+
An anonymous query container is not part of it. A region that declares
|
|
1100
|
+
`container-type: inline-size` for the application's own purposes carries no
|
|
1101
|
+
actual-container name, so no framework component changes behaviour there.
|
|
1102
|
+
Opting into the protocol is always explicit. Nothing here is a prerequisite
|
|
1103
|
+
either — every primitive is responsive without it; this adds precision.
|
|
1104
|
+
|
|
1105
|
+
Place it where the width you want measured lives: usually a wrapper for a
|
|
1106
|
+
grid, whose queried children sit inside it, and the component itself for
|
|
1107
|
+
.steps, whose own row is the width in question.
|
|
1108
|
+
|
|
1109
|
+
The rule for where it is safe is the containment rule, not a list of
|
|
1110
|
+
primitives. Query containment removes the element's intrinsic inline-size
|
|
1111
|
+
contribution, so:
|
|
1112
|
+
|
|
1113
|
+
safe the element's width is imposed by its context
|
|
1114
|
+
(.steps sets inline-size: 100%, so it survives even as a
|
|
1115
|
+
.cluster child — it wraps and fills)
|
|
1116
|
+
|
|
1117
|
+
unsafe the element's width depends on its contents' intrinsic
|
|
1118
|
+
contribution (a bare .grid child of .cluster measures 0 once
|
|
1119
|
+
contained, as does a float, inline-block, table, grid auto track,
|
|
1120
|
+
max-content parent, or an abspos box sized by its contents)
|
|
1121
|
+
|
|
1122
|
+
So .cluster is neither safe nor unsafe; what matters is where the child's
|
|
1123
|
+
width comes from. */
|
|
1061
1124
|
.container-query {
|
|
1062
|
-
container: actual-
|
|
1125
|
+
container: actual-container / inline-size;
|
|
1063
1126
|
}
|
|
1064
1127
|
|
|
1065
1128
|
/* Vertical flow. .stack owns the block axis only: block margins reset, inline
|
|
@@ -1430,13 +1493,13 @@ small {
|
|
|
1430
1493
|
min-inline-size: 0;
|
|
1431
1494
|
}
|
|
1432
1495
|
|
|
1433
|
-
/* Balanced subdivision, activated by an ancestor container named actual-
|
|
1496
|
+
/* Balanced subdivision, activated by an ancestor container named actual-container.
|
|
1434
1497
|
* A .grid-N inside such a container enters only divisor states of N. The name
|
|
1435
1498
|
* is deliberate: a grid must not key off the nearest anonymous query container,
|
|
1436
1499
|
* or a region that sets container-type: inline-size for an unrelated reason
|
|
1437
1500
|
* would silently change the grid. The author opts the grid in by naming the
|
|
1438
1501
|
* context — .container-query is the official helper, or declare
|
|
1439
|
-
* container: actual-
|
|
1502
|
+
* container: actual-container / inline-size on your own region.
|
|
1440
1503
|
*
|
|
1441
1504
|
* .grid-2 2 -> 1
|
|
1442
1505
|
* .grid-3 3 -> 1
|
|
@@ -1453,7 +1516,7 @@ small {
|
|
|
1453
1516
|
* inline contribution and collapses it inside .cluster, .topbar, floats, and
|
|
1454
1517
|
* any shrink-to-fit box. See docs/design-notes/grid.md.
|
|
1455
1518
|
*
|
|
1456
|
-
* Without an actual-
|
|
1519
|
+
* Without an actual-container ancestor none of these @container rules resolve, so
|
|
1457
1520
|
* the auto-fill baseline above stays. The min-width: 0 rule is the lowest
|
|
1458
1521
|
* step: it pins the grid to one column inside a container before any larger
|
|
1459
1522
|
* threshold fires (a bare grid would otherwise show a 2 + 1 orphan at 28-48rem).
|
|
@@ -1469,25 +1532,25 @@ small {
|
|
|
1469
1532
|
* Ascending min-width only: the last matching step wins by cascade order.
|
|
1470
1533
|
*/
|
|
1471
1534
|
@supports (container-type: inline-size) {
|
|
1472
|
-
@container actual-
|
|
1535
|
+
@container actual-container (min-width: 0px) {
|
|
1473
1536
|
:where(.grid-2, .grid-3, .grid-4, .grid-6) {
|
|
1474
1537
|
grid-template-columns: 1fr;
|
|
1475
1538
|
}
|
|
1476
1539
|
}
|
|
1477
1540
|
|
|
1478
|
-
@container actual-
|
|
1541
|
+
@container actual-container (min-width: 28rem) {
|
|
1479
1542
|
:where(.grid-2, .grid-4, .grid-6) {
|
|
1480
1543
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1481
1544
|
}
|
|
1482
1545
|
}
|
|
1483
1546
|
|
|
1484
|
-
@container actual-
|
|
1547
|
+
@container actual-container (min-width: 48rem) {
|
|
1485
1548
|
:where(.grid-3, .grid-6) {
|
|
1486
1549
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1487
1550
|
}
|
|
1488
1551
|
}
|
|
1489
1552
|
|
|
1490
|
-
@container actual-
|
|
1553
|
+
@container actual-container (min-width: 64rem) {
|
|
1491
1554
|
:where(.grid-4) {
|
|
1492
1555
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1493
1556
|
}
|
|
@@ -1822,6 +1885,10 @@ small {
|
|
|
1822
1885
|
* Individual controls keep their own files; .choice label layout lives in
|
|
1823
1886
|
* choice.css; .form-actions lives in form-actions.css.
|
|
1824
1887
|
*
|
|
1888
|
+
* .field-group owns its own box only. Distance between sibling groups is a
|
|
1889
|
+
* composition concern (.stack on the form), never a sibling margin here: the
|
|
1890
|
+
* component cannot know whether its siblings are stacked or gridded.
|
|
1891
|
+
*
|
|
1825
1892
|
* Public API: .field, .field-label, .field-help, .field-error, .field-group,
|
|
1826
1893
|
* .required-mark.
|
|
1827
1894
|
*
|
|
@@ -1842,10 +1909,6 @@ small {
|
|
|
1842
1909
|
border-radius: var(--radius);
|
|
1843
1910
|
}
|
|
1844
1911
|
|
|
1845
|
-
.field-group + .field-group {
|
|
1846
|
-
margin-block-start: var(--space-40);
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
1912
|
.field-label,
|
|
1850
1913
|
.field-group > legend {
|
|
1851
1914
|
color: var(--text);
|
|
@@ -2937,7 +3000,7 @@ small {
|
|
|
2937
3000
|
|
|
2938
3001
|
&::picker(select) {
|
|
2939
3002
|
appearance: base-select;
|
|
2940
|
-
max-
|
|
3003
|
+
max-block-size: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
2941
3004
|
margin-block: var(--space-20);
|
|
2942
3005
|
padding: 0;
|
|
2943
3006
|
border: var(--border-width) solid var(--border);
|
|
@@ -3014,8 +3077,10 @@ small {
|
|
|
3014
3077
|
* messages stay in the surrounding .field, not inside the cell, so the
|
|
3015
3078
|
* resting label can center on the control alone.
|
|
3016
3079
|
*
|
|
3017
|
-
* The control must precede the label: the float is driven by the
|
|
3018
|
-
* combinator and :placeholder-shown.
|
|
3080
|
+
* The control must precede the label: the float is driven by the general
|
|
3081
|
+
* sibling combinator and :placeholder-shown. Injected nodes may appear between
|
|
3082
|
+
* the control and the label (password managers, extension wrappers); the cell
|
|
3083
|
+
* contract stays a single control plus one label. Every .input/.textarea needs
|
|
3019
3084
|
* placeholder=" " to expose a reliable empty state — without it the label
|
|
3020
3085
|
* only reacts to focus.
|
|
3021
3086
|
*
|
|
@@ -3063,14 +3128,14 @@ small {
|
|
|
3063
3128
|
}
|
|
3064
3129
|
|
|
3065
3130
|
/* Single-line controls rest in the middle until they receive a value. */
|
|
3066
|
-
.floating-field > :is(.input, .select)
|
|
3131
|
+
.floating-field > :is(.input, .select) ~ .field-label {
|
|
3067
3132
|
inset-block-start: 50%;
|
|
3068
3133
|
transform: translateY(-50%);
|
|
3069
3134
|
}
|
|
3070
3135
|
|
|
3071
3136
|
/* An empty textarea label follows the control's normal first-line position;
|
|
3072
3137
|
the control itself keeps the extra headroom reserved for its floated state. */
|
|
3073
|
-
.floating-field > .textarea
|
|
3138
|
+
.floating-field > .textarea ~ .field-label {
|
|
3074
3139
|
inset-block-start: calc(1em + var(--border-width));
|
|
3075
3140
|
transform: none;
|
|
3076
3141
|
}
|
|
@@ -3079,7 +3144,7 @@ small {
|
|
|
3079
3144
|
not have propagated the value to the placeholder state yet. */
|
|
3080
3145
|
.floating-field
|
|
3081
3146
|
> :is(.input, .textarea):is(:focus, :autofill, :not(:placeholder-shown))
|
|
3082
|
-
|
|
3147
|
+
~ .field-label {
|
|
3083
3148
|
inset-block-start: var(--floating-label-top);
|
|
3084
3149
|
transform: none;
|
|
3085
3150
|
font-size: var(--font-size-xs);
|
|
@@ -3098,7 +3163,7 @@ small {
|
|
|
3098
3163
|
.input[type="month"],
|
|
3099
3164
|
.input[type="week"]
|
|
3100
3165
|
)
|
|
3101
|
-
|
|
3166
|
+
~ .field-label {
|
|
3102
3167
|
inset-block-start: var(--floating-label-top);
|
|
3103
3168
|
transform: none;
|
|
3104
3169
|
font-size: var(--font-size-xs);
|
|
@@ -4161,7 +4226,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4161
4226
|
z-index: calc(var(--z-menu) - 1);
|
|
4162
4227
|
background: var(--backdrop-fill);
|
|
4163
4228
|
opacity: 1;
|
|
4164
|
-
|
|
4229
|
+
/* A scrim never travels; it fades on the neutral `ease`. Duration matches
|
|
4230
|
+
the flyout sheet it accompanies so it never lags the panel. */
|
|
4231
|
+
transition: opacity var(--duration-fast) ease;
|
|
4165
4232
|
}
|
|
4166
4233
|
|
|
4167
4234
|
.surface-backdrop[hidden] {
|
|
@@ -4178,8 +4245,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4178
4245
|
@supports (transition-behavior: allow-discrete) {
|
|
4179
4246
|
.surface-backdrop {
|
|
4180
4247
|
transition:
|
|
4181
|
-
opacity var(--duration)
|
|
4182
|
-
display var(--duration) allow-discrete;
|
|
4248
|
+
opacity var(--duration-fast) ease,
|
|
4249
|
+
display var(--duration-fast) allow-discrete;
|
|
4183
4250
|
}
|
|
4184
4251
|
}
|
|
4185
4252
|
@media print {
|
|
@@ -4229,12 +4296,14 @@ dialog.modal {
|
|
|
4229
4296
|
* Keep the open dialog root untransformed. Fixed overlays mounted inside a
|
|
4230
4297
|
* modal dialog use viewport coordinates; transform/translate/scale on this
|
|
4231
4298
|
* element would make them position against the dialog instead. The closed
|
|
4232
|
-
* state can still carry a small transform for enter/exit motion.
|
|
4299
|
+
* state can still carry a small transform for enter/exit motion. The lift
|
|
4300
|
+
* is a pure vertical offset: scaling a wide panel around its center drags
|
|
4301
|
+
* both edges, which reads as horizontal movement.
|
|
4233
4302
|
*/
|
|
4234
|
-
transform: translateY(0.35rem)
|
|
4303
|
+
transform: translateY(0.35rem);
|
|
4235
4304
|
transition:
|
|
4236
|
-
opacity var(--duration),
|
|
4237
|
-
transform var(--duration);
|
|
4305
|
+
opacity var(--duration) var(--ease-exit),
|
|
4306
|
+
transform var(--duration) var(--ease-exit);
|
|
4238
4307
|
}
|
|
4239
4308
|
|
|
4240
4309
|
dialog.modal > form,
|
|
@@ -4353,33 +4422,46 @@ dialog.modal.dialog-confirmation > form > footer {
|
|
|
4353
4422
|
dialog.modal[open] {
|
|
4354
4423
|
opacity: 1;
|
|
4355
4424
|
transform: none;
|
|
4425
|
+
transition:
|
|
4426
|
+
opacity var(--duration) var(--ease-enter),
|
|
4427
|
+
transform var(--duration) var(--ease-enter);
|
|
4356
4428
|
}
|
|
4357
4429
|
|
|
4358
4430
|
@starting-style {
|
|
4359
4431
|
dialog.modal[open] {
|
|
4360
4432
|
opacity: 0;
|
|
4361
|
-
transform: translateY(0.35rem)
|
|
4433
|
+
transform: translateY(0.35rem);
|
|
4362
4434
|
}
|
|
4363
4435
|
}
|
|
4364
4436
|
|
|
4365
4437
|
dialog.modal::backdrop {
|
|
4366
4438
|
background: var(--backdrop-fill);
|
|
4367
4439
|
opacity: 0;
|
|
4368
|
-
|
|
4440
|
+
/* Scrim curve stays neutral `ease`; only the panel carries the character
|
|
4441
|
+
curves. */
|
|
4442
|
+
transition: opacity var(--duration) ease;
|
|
4369
4443
|
}
|
|
4370
4444
|
|
|
4371
4445
|
@supports (transition-behavior: allow-discrete) {
|
|
4372
4446
|
dialog.modal {
|
|
4373
4447
|
transition:
|
|
4374
|
-
opacity var(--duration),
|
|
4375
|
-
transform var(--duration),
|
|
4448
|
+
opacity var(--duration) var(--ease-exit),
|
|
4449
|
+
transform var(--duration) var(--ease-exit),
|
|
4450
|
+
overlay var(--duration) allow-discrete,
|
|
4451
|
+
display var(--duration) allow-discrete;
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
dialog.modal[open] {
|
|
4455
|
+
transition:
|
|
4456
|
+
opacity var(--duration) var(--ease-enter),
|
|
4457
|
+
transform var(--duration) var(--ease-enter),
|
|
4376
4458
|
overlay var(--duration) allow-discrete,
|
|
4377
4459
|
display var(--duration) allow-discrete;
|
|
4378
4460
|
}
|
|
4379
4461
|
|
|
4380
4462
|
dialog.modal::backdrop {
|
|
4381
4463
|
transition:
|
|
4382
|
-
opacity var(--duration),
|
|
4464
|
+
opacity var(--duration) ease,
|
|
4383
4465
|
overlay var(--duration) allow-discrete,
|
|
4384
4466
|
display var(--duration) allow-discrete;
|
|
4385
4467
|
}
|
|
@@ -4404,13 +4486,15 @@ html.has-modal-open.had-scrollbar {
|
|
|
4404
4486
|
}
|
|
4405
4487
|
|
|
4406
4488
|
@media (prefers-reduced-motion: no-preference) {
|
|
4489
|
+
/* Wrong-close feedback and view-transition transport are motion quirks, not
|
|
4490
|
+
presence enter/exit, so they stay on the plain CSS default easing. */
|
|
4407
4491
|
dialog.modal.is-static {
|
|
4408
|
-
animation: actual-dialog-static var(--duration-slow)
|
|
4492
|
+
animation: actual-dialog-static var(--duration-slow) ease;
|
|
4409
4493
|
}
|
|
4410
4494
|
|
|
4411
4495
|
::view-transition-group(actual-dialog) {
|
|
4412
4496
|
animation-duration: var(--duration);
|
|
4413
|
-
animation-timing-function:
|
|
4497
|
+
animation-timing-function: ease;
|
|
4414
4498
|
}
|
|
4415
4499
|
}
|
|
4416
4500
|
|
|
@@ -4509,11 +4593,16 @@ html.has-modal-open.had-scrollbar {
|
|
|
4509
4593
|
*
|
|
4510
4594
|
* Internal:
|
|
4511
4595
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
4596
|
+
* --drawer-motion-duration (shared by panel and ::backdrop so exit duration
|
|
4597
|
+
* changes in one place; entry state redefines it longer)
|
|
4512
4598
|
*/
|
|
4513
4599
|
dialog.drawer {
|
|
4514
4600
|
--drawer-size: 20rem;
|
|
4515
4601
|
--drawer-pad: var(--space-40);
|
|
4516
4602
|
--drawer-closed-translate: -100%;
|
|
4603
|
+
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
4604
|
+
the same variable through inheritance and never lags the panel. */
|
|
4605
|
+
--drawer-motion-duration: var(--duration);
|
|
4517
4606
|
|
|
4518
4607
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
4519
4608
|
provides the containing block for the absolutely positioned close button.
|
|
@@ -4537,16 +4626,20 @@ dialog.drawer {
|
|
|
4537
4626
|
opacity: 0;
|
|
4538
4627
|
transform: translateX(var(--drawer-closed-translate));
|
|
4539
4628
|
transition:
|
|
4540
|
-
opacity var(--duration-
|
|
4541
|
-
transform var(--duration-
|
|
4629
|
+
opacity var(--drawer-motion-duration) var(--ease-exit),
|
|
4630
|
+
transform var(--drawer-motion-duration) var(--ease-exit);
|
|
4542
4631
|
}
|
|
4543
4632
|
|
|
4544
4633
|
dialog.drawer[open] {
|
|
4634
|
+
--drawer-motion-duration: var(--duration-slow);
|
|
4545
4635
|
display: grid;
|
|
4546
4636
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
4547
4637
|
gap: var(--space-40);
|
|
4548
4638
|
opacity: 1;
|
|
4549
4639
|
transform: translateX(0);
|
|
4640
|
+
transition:
|
|
4641
|
+
opacity var(--drawer-motion-duration) var(--ease-enter),
|
|
4642
|
+
transform var(--drawer-motion-duration) var(--ease-enter);
|
|
4550
4643
|
}
|
|
4551
4644
|
|
|
4552
4645
|
/* Backdrop color sourced from components/surface.css so it matches .modal
|
|
@@ -4554,7 +4647,9 @@ dialog.drawer[open] {
|
|
|
4554
4647
|
dialog.drawer::backdrop {
|
|
4555
4648
|
background: var(--backdrop-fill);
|
|
4556
4649
|
opacity: 0;
|
|
4557
|
-
|
|
4650
|
+
/* Scrim curve stays neutral `ease`; only the panel's character curves
|
|
4651
|
+
apply. Duration follows --drawer-motion-duration through inheritance. */
|
|
4652
|
+
transition: opacity var(--drawer-motion-duration) ease;
|
|
4558
4653
|
}
|
|
4559
4654
|
|
|
4560
4655
|
dialog.drawer[data-side="end"] {
|
|
@@ -4590,17 +4685,25 @@ dialog.drawer nav {
|
|
|
4590
4685
|
@supports (transition-behavior: allow-discrete) {
|
|
4591
4686
|
dialog.drawer {
|
|
4592
4687
|
transition:
|
|
4593
|
-
opacity var(--duration-
|
|
4594
|
-
transform var(--duration-
|
|
4595
|
-
overlay var(--duration
|
|
4596
|
-
display var(--duration
|
|
4688
|
+
opacity var(--drawer-motion-duration) var(--ease-exit),
|
|
4689
|
+
transform var(--drawer-motion-duration) var(--ease-exit),
|
|
4690
|
+
overlay var(--drawer-motion-duration) allow-discrete,
|
|
4691
|
+
display var(--drawer-motion-duration) allow-discrete;
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4694
|
+
dialog.drawer[open] {
|
|
4695
|
+
transition:
|
|
4696
|
+
opacity var(--drawer-motion-duration) var(--ease-enter),
|
|
4697
|
+
transform var(--drawer-motion-duration) var(--ease-enter),
|
|
4698
|
+
overlay var(--drawer-motion-duration) allow-discrete,
|
|
4699
|
+
display var(--drawer-motion-duration) allow-discrete;
|
|
4597
4700
|
}
|
|
4598
4701
|
|
|
4599
4702
|
dialog.drawer::backdrop {
|
|
4600
4703
|
transition:
|
|
4601
|
-
opacity var(--duration
|
|
4602
|
-
overlay var(--duration
|
|
4603
|
-
display var(--duration
|
|
4704
|
+
opacity var(--drawer-motion-duration) ease,
|
|
4705
|
+
overlay var(--drawer-motion-duration) allow-discrete,
|
|
4706
|
+
display var(--drawer-motion-duration) allow-discrete;
|
|
4604
4707
|
}
|
|
4605
4708
|
}
|
|
4606
4709
|
|
|
@@ -4636,7 +4739,7 @@ dialog.drawer nav {
|
|
|
4636
4739
|
|
|
4637
4740
|
@media (prefers-reduced-motion: no-preference) {
|
|
4638
4741
|
dialog.drawer.is-static {
|
|
4639
|
-
animation: actual-dialog-static var(--duration-slow)
|
|
4742
|
+
animation: actual-dialog-static var(--duration-slow) ease;
|
|
4640
4743
|
}
|
|
4641
4744
|
}
|
|
4642
4745
|
|
|
@@ -4730,11 +4833,10 @@ dialog.drawer > form > header {
|
|
|
4730
4833
|
box-shadow: var(--shadow-popout);
|
|
4731
4834
|
list-style: none;
|
|
4732
4835
|
opacity: 1;
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
translate var(--duration-fast) var(--ease);
|
|
4836
|
+
/* Anchored flyouts fade in and out; scaling around the center would detach
|
|
4837
|
+
the anchored edge from its trigger on wide panels. The sheet presentation
|
|
4838
|
+
below adds its own translate motion. */
|
|
4839
|
+
transition: opacity var(--duration-fast) ease;
|
|
4738
4840
|
}
|
|
4739
4841
|
|
|
4740
4842
|
.flyout.is-open:not(.is-sheet) {
|
|
@@ -4744,7 +4846,6 @@ dialog.drawer > form > header {
|
|
|
4744
4846
|
@starting-style {
|
|
4745
4847
|
.flyout.is-open {
|
|
4746
4848
|
opacity: 0;
|
|
4747
|
-
scale: 0.98;
|
|
4748
4849
|
}
|
|
4749
4850
|
}
|
|
4750
4851
|
|
|
@@ -4766,22 +4867,24 @@ dialog.drawer > form > header {
|
|
|
4766
4867
|
max(var(--space-30), env(safe-area-inset-bottom));
|
|
4767
4868
|
border-end-start-radius: 0;
|
|
4768
4869
|
border-end-end-radius: 0;
|
|
4870
|
+
/* Sheet motion is a directional slide from the viewport edge; it replaces
|
|
4871
|
+
the base fade-only recipe. */
|
|
4872
|
+
transition:
|
|
4873
|
+
opacity var(--duration-fast) ease,
|
|
4874
|
+
translate var(--duration-fast) ease;
|
|
4769
4875
|
}
|
|
4770
4876
|
|
|
4771
4877
|
.flyout[hidden] {
|
|
4772
4878
|
display: none;
|
|
4773
4879
|
opacity: 0;
|
|
4774
|
-
scale: 0.98;
|
|
4775
4880
|
}
|
|
4776
4881
|
|
|
4777
4882
|
.flyout.is-sheet[hidden] {
|
|
4778
|
-
scale: 1;
|
|
4779
4883
|
translate: 0 var(--space-30);
|
|
4780
4884
|
}
|
|
4781
4885
|
|
|
4782
4886
|
@starting-style {
|
|
4783
4887
|
.flyout.is-sheet.is-open {
|
|
4784
|
-
scale: 1;
|
|
4785
4888
|
translate: 0 var(--space-30);
|
|
4786
4889
|
}
|
|
4787
4890
|
}
|
|
@@ -4789,9 +4892,14 @@ dialog.drawer > form > header {
|
|
|
4789
4892
|
@supports (transition-behavior: allow-discrete) {
|
|
4790
4893
|
.flyout {
|
|
4791
4894
|
transition:
|
|
4792
|
-
opacity var(--duration-fast)
|
|
4793
|
-
|
|
4794
|
-
|
|
4895
|
+
opacity var(--duration-fast) ease,
|
|
4896
|
+
display var(--duration-fast) allow-discrete;
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
.flyout.is-sheet {
|
|
4900
|
+
transition:
|
|
4901
|
+
opacity var(--duration-fast) ease,
|
|
4902
|
+
translate var(--duration-fast) ease,
|
|
4795
4903
|
display var(--duration-fast) allow-discrete;
|
|
4796
4904
|
}
|
|
4797
4905
|
}
|
|
@@ -4996,31 +5104,28 @@ dialog.drawer > form > header {
|
|
|
4996
5104
|
overflow-wrap: anywhere;
|
|
4997
5105
|
pointer-events: auto;
|
|
4998
5106
|
user-select: none;
|
|
5107
|
+
/* Tooltips fade only: the anchor edge, marked by the arrow, must stay put
|
|
5108
|
+
during motion, and a 4% scale was perceptible on small text. Scaling moves
|
|
5109
|
+
the arrow tip away from its trigger on every placement. */
|
|
4999
5110
|
opacity: 1;
|
|
5000
|
-
|
|
5001
|
-
transition:
|
|
5002
|
-
opacity var(--duration-fast) var(--ease),
|
|
5003
|
-
scale var(--duration-fast) var(--ease);
|
|
5111
|
+
transition: opacity var(--duration-fast) ease;
|
|
5004
5112
|
}
|
|
5005
5113
|
|
|
5006
5114
|
.tooltip[hidden] {
|
|
5007
5115
|
display: none;
|
|
5008
5116
|
opacity: 0;
|
|
5009
|
-
scale: 0.96;
|
|
5010
5117
|
}
|
|
5011
5118
|
|
|
5012
5119
|
@starting-style {
|
|
5013
5120
|
.tooltip:not([hidden]) {
|
|
5014
5121
|
opacity: 0;
|
|
5015
|
-
scale: 0.96;
|
|
5016
5122
|
}
|
|
5017
5123
|
}
|
|
5018
5124
|
|
|
5019
5125
|
@supports (transition-behavior: allow-discrete) {
|
|
5020
5126
|
.tooltip {
|
|
5021
5127
|
transition:
|
|
5022
|
-
opacity var(--duration-fast)
|
|
5023
|
-
scale var(--duration-fast) var(--ease),
|
|
5128
|
+
opacity var(--duration-fast) ease,
|
|
5024
5129
|
display var(--duration-fast) allow-discrete;
|
|
5025
5130
|
}
|
|
5026
5131
|
}
|
|
@@ -5361,9 +5466,14 @@ dialog.drawer > form > header {
|
|
|
5361
5466
|
* Consumes the shared --intent/--primary for the value bar and --bar-height
|
|
5362
5467
|
* for thickness. Indeterminate state animates a shimmer using --duration-shimmer.
|
|
5363
5468
|
*
|
|
5469
|
+
* Thickness is a dimension, not a density step: the bar always spans its
|
|
5470
|
+
* container and only gets thinner or thicker, so --bar-height is the API and
|
|
5471
|
+
* .sm/.lg are deliberately not supported.
|
|
5472
|
+
*
|
|
5364
5473
|
* Public hooks:
|
|
5365
5474
|
* --progress-track
|
|
5366
5475
|
* --progress-value
|
|
5476
|
+
* --bar-height
|
|
5367
5477
|
*/
|
|
5368
5478
|
/* Intent boundary — inherited intents do not tint this component.
|
|
5369
5479
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -5467,9 +5577,12 @@ dialog.drawer > form > header {
|
|
|
5467
5577
|
/*
|
|
5468
5578
|
* Status bar — singleton floating status area.
|
|
5469
5579
|
*
|
|
5470
|
-
* One element in the HTML, empty by default
|
|
5471
|
-
*
|
|
5472
|
-
*
|
|
5580
|
+
* One element in the HTML, empty by default. .is-open is the visual state, set
|
|
5581
|
+
* by status.js like any other surface: it lands after the message is written
|
|
5582
|
+
* and lifts before the bar is cleared, so the bar leaves in the exact state it
|
|
5583
|
+
* was shown in — same text, same width, same intent color — instead of snapping
|
|
5584
|
+
* to a neutral empty pill mid-exit. Emptying the text is the *end* of the exit,
|
|
5585
|
+
* not its trigger.
|
|
5473
5586
|
*
|
|
5474
5587
|
* Consumes intent like badge or alert. No need for status-bar-specific modifier
|
|
5475
5588
|
* classes — .danger / .success / .warning / .neutral from intents.css drive
|
|
@@ -5483,6 +5596,7 @@ dialog.drawer > form > header {
|
|
|
5483
5596
|
* Internal:
|
|
5484
5597
|
* --status-bg
|
|
5485
5598
|
* --status-fg
|
|
5599
|
+
* --status-viewport-offset (written by status.js while the bar is open)
|
|
5486
5600
|
*/
|
|
5487
5601
|
|
|
5488
5602
|
/* Intent boundary — inherited intents do not tint this component.
|
|
@@ -5497,7 +5611,9 @@ dialog.drawer > form > header {
|
|
|
5497
5611
|
--status-fg: var(--intent-fg, var(--surface));
|
|
5498
5612
|
|
|
5499
5613
|
position: fixed;
|
|
5500
|
-
|
|
5614
|
+
/* Fixed positioning tracks the layout viewport, which the mobile software
|
|
5615
|
+
keyboard covers. status.js writes the occluded band into the offset. */
|
|
5616
|
+
inset-block-end: calc(var(--space-40) + var(--status-viewport-offset, 0px));
|
|
5501
5617
|
inset-inline: var(--space-40);
|
|
5502
5618
|
z-index: var(--z-status);
|
|
5503
5619
|
|
|
@@ -5519,22 +5635,32 @@ dialog.drawer > form > header {
|
|
|
5519
5635
|
color: var(--status-fg);
|
|
5520
5636
|
box-shadow: var(--shadow-popout);
|
|
5521
5637
|
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5638
|
+
/* Closed by default. Opacity, not visibility or display: the bar has to stay
|
|
5639
|
+
in the accessibility tree to work as a live region. */
|
|
5640
|
+
opacity: 0;
|
|
5641
|
+
translate: 0 var(--space-30);
|
|
5525
5642
|
pointer-events: none;
|
|
5526
5643
|
|
|
5644
|
+
/* Exit is shorter than entry so dismissed feedback gets out of the way.
|
|
5645
|
+
background stays a shorthand so --status-bg still accepts gradients;
|
|
5646
|
+
solid colors expose an interpolable computed background-color. The
|
|
5647
|
+
intent color follows the state/interaction `ease`, not the presence
|
|
5648
|
+
curves. */
|
|
5527
5649
|
transition:
|
|
5528
|
-
opacity var(--duration-
|
|
5529
|
-
|
|
5530
|
-
|
|
5650
|
+
opacity var(--duration-fast) var(--ease-exit),
|
|
5651
|
+
translate var(--duration) var(--ease-exit),
|
|
5652
|
+
background-color var(--duration) ease;
|
|
5531
5653
|
}
|
|
5532
5654
|
|
|
5533
|
-
.status-bar
|
|
5534
|
-
opacity:
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5655
|
+
.status-bar.is-open {
|
|
5656
|
+
opacity: 1;
|
|
5657
|
+
translate: 0 0;
|
|
5658
|
+
/* Entry takes a little longer and decelerates, making the message legible
|
|
5659
|
+
before its motion settles. */
|
|
5660
|
+
transition:
|
|
5661
|
+
opacity var(--duration) var(--ease-enter),
|
|
5662
|
+
translate var(--duration-slow) var(--ease-enter),
|
|
5663
|
+
background-color var(--duration) ease;
|
|
5538
5664
|
}
|
|
5539
5665
|
@media print {
|
|
5540
5666
|
.status-bar {
|
|
@@ -5934,15 +6060,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
5934
6060
|
|
|
5935
6061
|
/*
|
|
5936
6062
|
* Fixed floating action placement and a native-details speed dial.
|
|
5937
|
-
* Button owns the
|
|
6063
|
+
* Button owns the trigger's size, shape, and intent; FAB owns placement and
|
|
6064
|
+
* the speed dial's structural open/close markers (--icon-plus / --icon-close).
|
|
5938
6065
|
*
|
|
5939
6066
|
* Public hooks:
|
|
5940
6067
|
* --fab-offset
|
|
5941
6068
|
* --fab-gap
|
|
6069
|
+
* --fab-icon-size
|
|
5942
6070
|
*/
|
|
5943
6071
|
.fab {
|
|
5944
6072
|
--fab-offset: var(--space-40);
|
|
5945
6073
|
--fab-gap: var(--space-20);
|
|
6074
|
+
--fab-icon-size: 1.5rem;
|
|
5946
6075
|
|
|
5947
6076
|
position: fixed;
|
|
5948
6077
|
z-index: var(--z-menu);
|
|
@@ -5951,6 +6080,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
5951
6080
|
}
|
|
5952
6081
|
|
|
5953
6082
|
.fab > summary {
|
|
6083
|
+
/* Centers the toggle markers: the summary is the markers' positioned
|
|
6084
|
+
context, not just the app-button class. */
|
|
6085
|
+
position: relative;
|
|
5954
6086
|
list-style: none;
|
|
5955
6087
|
cursor: pointer;
|
|
5956
6088
|
}
|
|
@@ -5959,11 +6091,35 @@ a.avatar[aria-disabled="true"] {
|
|
|
5959
6091
|
display: none;
|
|
5960
6092
|
}
|
|
5961
6093
|
|
|
5962
|
-
/*
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
6094
|
+
/* The speed dial trigger is an icon-only toggle. FAB owns both structural
|
|
6095
|
+
markers: masked pseudo-elements swap the shared plus (closed) and close
|
|
6096
|
+
(open) glyphs at the same size, so the two states never drift apart. */
|
|
6097
|
+
.fab > summary::before,
|
|
6098
|
+
.fab > summary::after {
|
|
6099
|
+
content: "";
|
|
6100
|
+
position: absolute;
|
|
6101
|
+
inset: 0;
|
|
6102
|
+
margin: auto;
|
|
6103
|
+
inline-size: var(--fab-icon-size);
|
|
6104
|
+
block-size: var(--fab-icon-size);
|
|
6105
|
+
background: currentColor;
|
|
6106
|
+
opacity: 0;
|
|
6107
|
+
transform: scale(0.8);
|
|
6108
|
+
transition:
|
|
6109
|
+
opacity var(--duration),
|
|
6110
|
+
transform var(--duration);
|
|
6111
|
+
}
|
|
6112
|
+
|
|
6113
|
+
.fab > summary::before {
|
|
6114
|
+
-webkit-mask: var(--icon-plus) center / contain no-repeat;
|
|
6115
|
+
mask: var(--icon-plus) center / contain no-repeat;
|
|
6116
|
+
opacity: 1;
|
|
6117
|
+
transform: none;
|
|
6118
|
+
}
|
|
6119
|
+
|
|
6120
|
+
.fab > summary::after {
|
|
6121
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
6122
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
5967
6123
|
}
|
|
5968
6124
|
|
|
5969
6125
|
.fab-actions {
|
|
@@ -6021,8 +6177,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6021
6177
|
transition-delay: 0s;
|
|
6022
6178
|
}
|
|
6023
6179
|
|
|
6024
|
-
.fab[open] > summary
|
|
6025
|
-
|
|
6180
|
+
.fab[open] > summary::before {
|
|
6181
|
+
opacity: 0;
|
|
6182
|
+
transform: scale(0.8);
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
.fab[open] > summary::after {
|
|
6186
|
+
opacity: 1;
|
|
6187
|
+
transform: none;
|
|
6026
6188
|
}
|
|
6027
6189
|
|
|
6028
6190
|
@media print {
|
|
@@ -6134,22 +6296,56 @@ a.avatar[aria-disabled="true"] {
|
|
|
6134
6296
|
}
|
|
6135
6297
|
|
|
6136
6298
|
/*
|
|
6137
|
-
* Steps — semantic ordered-list progression
|
|
6138
|
-
*
|
|
6139
|
-
*
|
|
6140
|
-
*
|
|
6299
|
+
* Steps — semantic ordered-list progression for a multi-step process.
|
|
6300
|
+
*
|
|
6301
|
+
* One primitive, two explicit compositions. Both classes are required:
|
|
6302
|
+
*
|
|
6303
|
+
* <ol class="steps steps-horizontal"> horizontal, 2 to 5 stages
|
|
6304
|
+
* <ol class="steps steps-vertical"> vertical, any number of stages
|
|
6305
|
+
*
|
|
6306
|
+
* `.steps` is what a step *is* — the counter, the marker, `.step-label`,
|
|
6307
|
+
* `.step-complete`, `aria-current="step"`. It carries no orientation, so it is
|
|
6308
|
+
* never written alone. `.steps-horizontal` and `.steps-vertical` are what a
|
|
6309
|
+
* sequence *looks like*, and they are peers: neither is the default the other
|
|
6310
|
+
* has to exclude, so no rule says `:not(...)` and no rule undoes another's
|
|
6311
|
+
* layout.
|
|
6312
|
+
*
|
|
6313
|
+
* Orientation and labels are independent dimensions. `.step-label` is optional
|
|
6314
|
+
* in either orientation; when a sequence carries names, every step carries
|
|
6315
|
+
* one:
|
|
6316
|
+
*
|
|
6317
|
+
* empty steps .step-label on every step
|
|
6318
|
+
* horizontal compact progression named sequence, stacked or inline
|
|
6319
|
+
* vertical compact rail named sequence, labels beside markers
|
|
6320
|
+
*
|
|
6321
|
+
* An empty step is a marker and nothing else, so it is compact by structure —
|
|
6322
|
+
* `> li:empty` in both orientations, no variant class, and nothing authored
|
|
6323
|
+
* only to be hidden again. The page around it owns the step name.
|
|
6141
6324
|
*
|
|
6142
|
-
*
|
|
6143
|
-
*
|
|
6325
|
+
* A label may be an `a[href]`, which is the whole interactive surface: the
|
|
6326
|
+
* marker is never navigable, so navigating a flow means naming its
|
|
6327
|
+
* destinations. Anything else focusable in a step is out of contract.
|
|
6328
|
+
*
|
|
6329
|
+
* The framework never hides an authored label to solve a layout problem. A
|
|
6330
|
+
* labelled row that outgrows its space scrolls. An `actual-container` size
|
|
6331
|
+
* context buys it one thing: the wide representation, marker beside label.
|
|
6332
|
+
*
|
|
6333
|
+
* See docs/design-notes/steps.md for the threshold, the selector shapes, the
|
|
6334
|
+
* rejected alternatives, and why there is no forced-colors branch.
|
|
6335
|
+
*
|
|
6336
|
+
* Each public hook is followed by the class that owns it.
|
|
6144
6337
|
*
|
|
6145
6338
|
* Public hooks:
|
|
6146
|
-
* --step-size
|
|
6147
|
-
* --step-
|
|
6148
|
-
* --step-
|
|
6149
|
-
* --step-
|
|
6150
|
-
* --step-
|
|
6151
|
-
* --step-
|
|
6152
|
-
* --step-
|
|
6339
|
+
* --step-size .steps
|
|
6340
|
+
* --step-inline-gap .steps
|
|
6341
|
+
* --step-line-size .steps
|
|
6342
|
+
* --step-marker-radius .steps
|
|
6343
|
+
* --step-complete-mark .steps
|
|
6344
|
+
* --step-min .steps-horizontal
|
|
6345
|
+
* --step-gap .steps-horizontal
|
|
6346
|
+
* --step-connector .steps-horizontal
|
|
6347
|
+
* --step-inline-connector .steps-horizontal
|
|
6348
|
+
* --step-vertical-gap .steps-vertical
|
|
6153
6349
|
*
|
|
6154
6350
|
* Internal:
|
|
6155
6351
|
* --step-marker-bg
|
|
@@ -6157,10 +6353,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
6157
6353
|
* --step-marker-fg
|
|
6158
6354
|
* --step-line
|
|
6159
6355
|
*/
|
|
6356
|
+
|
|
6357
|
+
/* Step primitive --------------------------------------------------------- */
|
|
6358
|
+
|
|
6160
6359
|
.steps {
|
|
6161
6360
|
--step-size: 1.75rem;
|
|
6162
|
-
--step-
|
|
6163
|
-
--step-gap: var(--space-20);
|
|
6361
|
+
--step-inline-gap: var(--space-20);
|
|
6164
6362
|
--step-line-size: var(--border-width);
|
|
6165
6363
|
--step-marker-bg: var(--surface);
|
|
6166
6364
|
--step-marker-border: var(--border);
|
|
@@ -6170,29 +6368,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6170
6368
|
|
|
6171
6369
|
counter-reset: actual-step;
|
|
6172
6370
|
display: flex;
|
|
6173
|
-
flex-direction: row;
|
|
6174
|
-
inline-size: 100%;
|
|
6175
6371
|
margin: 0;
|
|
6176
6372
|
padding: 0;
|
|
6177
6373
|
list-style: none;
|
|
6178
|
-
/* Fill when it fits, scroll when it cannot: items never shrink below
|
|
6179
|
-
--step-min, so labels keep a readable width and the sequence scrolls
|
|
6180
|
-
horizontally instead of squishing. overscroll-behavior keeps the scroll
|
|
6181
|
-
from chaining to the page. */
|
|
6182
|
-
overflow-x: auto;
|
|
6183
|
-
overscroll-behavior-inline: contain;
|
|
6184
6374
|
}
|
|
6185
6375
|
|
|
6186
6376
|
.steps > li {
|
|
6187
6377
|
position: relative;
|
|
6188
|
-
display: grid;
|
|
6189
|
-
flex: 1 0 var(--step-min);
|
|
6190
|
-
grid-template-rows: var(--step-size) auto;
|
|
6191
|
-
gap: var(--step-gap);
|
|
6192
|
-
justify-items: center;
|
|
6193
|
-
min-inline-size: max(var(--step-size), var(--step-min));
|
|
6194
6378
|
color: var(--text-muted);
|
|
6195
|
-
text-align: center;
|
|
6196
6379
|
counter-increment: actual-step;
|
|
6197
6380
|
}
|
|
6198
6381
|
|
|
@@ -6213,50 +6396,263 @@ a.avatar[aria-disabled="true"] {
|
|
|
6213
6396
|
line-height: 1;
|
|
6214
6397
|
}
|
|
6215
6398
|
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6399
|
+
/* The marker carries the state, so the label reads a notch below it — set on
|
|
6400
|
+
the component, never per representation: a size that changed with the layout
|
|
6401
|
+
would resize the text under a reader dragging a window. `color: inherit` is
|
|
6402
|
+
here rather than in a rule of its own for `a.step-label`, because a span
|
|
6403
|
+
inherits its ink anyway; only a link has anything to take back, and taking it
|
|
6404
|
+
back for every label costs nothing and one rule less. A navigable label keeps
|
|
6405
|
+
its underline, hover and focus ring — just not a link color, which would read
|
|
6406
|
+
ahead of the marker states.
|
|
6407
|
+
|
|
6408
|
+
Scoped to `.steps`, unlike `.menu-item-text` and the framework's other
|
|
6409
|
+
sub-part classes: a bare `.step-label` weighs (0,1,0), exactly tying
|
|
6410
|
+
`.prose :where(a)`, so a stepper in prose would be decided by bundle order. */
|
|
6411
|
+
.steps .step-label {
|
|
6412
|
+
font-size: var(--font-size-sm);
|
|
6413
|
+
line-height: var(--line-height-tight);
|
|
6414
|
+
color: inherit;
|
|
6229
6415
|
}
|
|
6230
6416
|
|
|
6231
|
-
|
|
6232
|
-
|
|
6417
|
+
/* Each composition gives the connector its own geometry and background, so the
|
|
6418
|
+
only shared fact is which steps get one — every step but the last. Stated as
|
|
6419
|
+
the condition rather than as a `content: none` rule undoing a `content: ""`
|
|
6420
|
+
rule: a pseudo-element with no content generates no box, so a composition can
|
|
6421
|
+
address `> li::after` freely and still not trail a line past the last step. */
|
|
6422
|
+
.steps > li:not(:last-child)::after {
|
|
6423
|
+
content: "";
|
|
6233
6424
|
}
|
|
6234
6425
|
|
|
6235
|
-
|
|
6426
|
+
/* Prefixed like `.step-label`, unlike the framework's bare `.primary` /
|
|
6427
|
+
`.outline` modifiers: those qualify a component on its own element, this
|
|
6428
|
+
marks one item inside one. A bare `.complete` would also claim a very
|
|
6429
|
+
ordinary English word globally, for a single component's single state. */
|
|
6430
|
+
.steps > .step-complete {
|
|
6236
6431
|
--step-marker-bg: var(--state-selected);
|
|
6237
6432
|
--step-marker-border: var(--state-selected);
|
|
6238
6433
|
--step-marker-fg: var(--state-selected-fg);
|
|
6434
|
+
--step-line: var(--state-selected);
|
|
6239
6435
|
|
|
6240
6436
|
color: var(--text);
|
|
6241
6437
|
}
|
|
6242
6438
|
|
|
6243
|
-
/*
|
|
6244
|
-
|
|
6245
|
-
.
|
|
6439
|
+
/* Complete owns the accented connector that reaches the current step, so the
|
|
6440
|
+
connector leaving current goes back to neutral. Declared after
|
|
6441
|
+
.step-complete at equal specificity: current wins on every channel. */
|
|
6442
|
+
.steps > [aria-current="step"] {
|
|
6443
|
+
--step-marker-bg: var(--surface);
|
|
6444
|
+
--step-marker-border: var(--state-selected);
|
|
6445
|
+
--step-marker-fg: var(--state-selected);
|
|
6446
|
+
--step-line: var(--border);
|
|
6447
|
+
|
|
6448
|
+
color: var(--text);
|
|
6449
|
+
}
|
|
6450
|
+
|
|
6451
|
+
.steps > [aria-current="step"]::before {
|
|
6246
6452
|
border-width: calc(var(--border-width) * 2);
|
|
6247
6453
|
}
|
|
6248
6454
|
|
|
6249
|
-
|
|
6250
|
-
|
|
6455
|
+
/* Weight marks "where I am now". Complete needs none: its filled disc already
|
|
6456
|
+
reads as strongly. */
|
|
6457
|
+
.steps > [aria-current="step"] .step-label {
|
|
6458
|
+
font-weight: var(--font-weight-strong);
|
|
6251
6459
|
}
|
|
6252
6460
|
|
|
6253
|
-
|
|
6254
|
-
--step-complete-mark accepts any content value and falls back to the
|
|
6255
|
-
counter, so existing numbered steppers are unchanged. */
|
|
6256
|
-
.steps > .complete::before {
|
|
6461
|
+
.steps > .step-complete:not([aria-current="step"])::before {
|
|
6257
6462
|
content: var(--step-complete-mark, counter(actual-step));
|
|
6258
6463
|
}
|
|
6259
6464
|
|
|
6465
|
+
/* Horizontal ------------------------------------------------------------- */
|
|
6466
|
+
|
|
6467
|
+
.steps-horizontal {
|
|
6468
|
+
--step-min: 7rem;
|
|
6469
|
+
--step-gap: var(--space-20);
|
|
6470
|
+
|
|
6471
|
+
inline-size: 100%;
|
|
6472
|
+
overflow-x: auto;
|
|
6473
|
+
/* Stated, never left to `overflow-x` alone: one scrolling axis computes the
|
|
6474
|
+
other to `auto`, and a row hugs fractional content closely enough that a
|
|
6475
|
+
rounding difference then raises a phantom vertical scrollbar. */
|
|
6476
|
+
overflow-y: hidden;
|
|
6477
|
+
overscroll-behavior-inline: contain;
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6480
|
+
/* A scroll container clips at the padding edge, so a navigable row reserves the
|
|
6481
|
+
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
6482
|
+
link has a ring to protect. */
|
|
6483
|
+
.steps-horizontal:has(a[href]) {
|
|
6484
|
+
padding-block: calc(var(--focus-outline-offset) + var(--border-width) * 2);
|
|
6485
|
+
}
|
|
6486
|
+
|
|
6487
|
+
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
6488
|
+
implicit second row, and two tracks are also the only thing `gap` has to sit
|
|
6489
|
+
between. So an empty step measures exactly `--step-size`, and the rule below
|
|
6490
|
+
takes back nothing. `var(--step-size) auto` would build that second row
|
|
6491
|
+
whether or not a label ever arrives, and the compact form would then have to
|
|
6492
|
+
remove both the track and the gap. */
|
|
6493
|
+
.steps-horizontal > li {
|
|
6494
|
+
display: grid;
|
|
6495
|
+
flex: 1 0 var(--step-min);
|
|
6496
|
+
grid-template-rows: var(--step-size);
|
|
6497
|
+
gap: var(--step-gap);
|
|
6498
|
+
justify-items: center;
|
|
6499
|
+
min-inline-size: max(var(--step-size), var(--step-min));
|
|
6500
|
+
text-align: center;
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6503
|
+
/* An empty step is a marker and nothing else, so it needs a marker's width
|
|
6504
|
+
rather than a reading width. That is what lets five of them fit a phone with
|
|
6505
|
+
no size context and no variant class.
|
|
6506
|
+
|
|
6507
|
+
`justify-items` is not an override of the row's centring: the marker is
|
|
6508
|
+
centred because a label sits under it and the two share a centre, and with no
|
|
6509
|
+
label there is nothing to centre against. The marker takes the start of its
|
|
6510
|
+
own share instead, the connector below fills the rest of it, and the last
|
|
6511
|
+
step has no share to fill — so the track runs edge to edge and lines up with
|
|
6512
|
+
the text around it, the way the inline representation does.
|
|
6513
|
+
|
|
6514
|
+
`:empty` says exactly what the markup says, the way `.badge:empty` does, and
|
|
6515
|
+
it reads the item's own content: bare text in an `<li>` keeps a reading width
|
|
6516
|
+
like its wrapped peers, where `> li:has(.step-label)` would give it a marker
|
|
6517
|
+
floor and skew the row. It also predates `:has()` by a decade, so the compact
|
|
6518
|
+
form reaches the Degraded tier and only the inline bonus needs `:has()`. */
|
|
6519
|
+
.steps-horizontal > li:empty {
|
|
6520
|
+
flex: 1 1 0;
|
|
6521
|
+
justify-items: start;
|
|
6522
|
+
min-inline-size: var(--step-size);
|
|
6523
|
+
}
|
|
6524
|
+
|
|
6525
|
+
.steps-horizontal > li:empty::after {
|
|
6526
|
+
inset-inline-start: var(--step-size);
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
.steps-horizontal > li:empty:last-child {
|
|
6530
|
+
flex: 0 0 auto;
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
/* The pseudo-element is marker-height so --step-connector may be any complete
|
|
6534
|
+
background value. */
|
|
6535
|
+
.steps-horizontal > li::after {
|
|
6536
|
+
position: absolute;
|
|
6537
|
+
inset-block-start: 0;
|
|
6538
|
+
inset-inline-start: calc(50% + var(--step-size) / 2);
|
|
6539
|
+
inline-size: calc(100% - var(--step-size));
|
|
6540
|
+
block-size: var(--step-size);
|
|
6541
|
+
background: var(
|
|
6542
|
+
--step-connector,
|
|
6543
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
6544
|
+
100% var(--step-line-size) no-repeat
|
|
6545
|
+
);
|
|
6546
|
+
}
|
|
6547
|
+
|
|
6548
|
+
/*
|
|
6549
|
+
* The one horizontal container query. Past 60rem a labelled row has the room
|
|
6550
|
+
* to put marker and label inline and still leave the connector a real region
|
|
6551
|
+
* between groups, rather than a stub. Calibrated on five steps — the widest
|
|
6552
|
+
* supported row, and therefore the binding case — and it counts nothing, so it
|
|
6553
|
+
* serves every count. Below it, stacked; a marker row is already the shape
|
|
6554
|
+
* this query produces and stays out of it.
|
|
6555
|
+
*
|
|
6556
|
+
* Horizontal is a 2-5 step component. Outside that domain the rule simply
|
|
6557
|
+
* applies as written, because a row that is out of contract is not owed a
|
|
6558
|
+
* curated fallback.
|
|
6559
|
+
*/
|
|
6560
|
+
@supports (container-type: inline-size) {
|
|
6561
|
+
@container actual-container (inline-size >= 60rem) {
|
|
6562
|
+
.steps-horizontal:has(.step-label) > li {
|
|
6563
|
+
display: flex;
|
|
6564
|
+
flex: 1 1 auto;
|
|
6565
|
+
align-items: center;
|
|
6566
|
+
gap: var(--step-inline-gap);
|
|
6567
|
+
min-inline-size: 0;
|
|
6568
|
+
text-align: start;
|
|
6569
|
+
white-space: nowrap;
|
|
6570
|
+
}
|
|
6571
|
+
|
|
6572
|
+
.steps-horizontal:has(.step-label) > li::after {
|
|
6573
|
+
position: static;
|
|
6574
|
+
flex: 1 1 2rem;
|
|
6575
|
+
inline-size: auto;
|
|
6576
|
+
block-size: var(--step-size);
|
|
6577
|
+
margin-inline-end: var(--step-inline-gap);
|
|
6578
|
+
background: var(
|
|
6579
|
+
--step-inline-connector,
|
|
6580
|
+
var(
|
|
6581
|
+
--step-connector,
|
|
6582
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
6583
|
+
100% var(--step-line-size) no-repeat
|
|
6584
|
+
)
|
|
6585
|
+
);
|
|
6586
|
+
}
|
|
6587
|
+
|
|
6588
|
+
.steps-horizontal:has(.step-label) > li:last-child {
|
|
6589
|
+
flex: 0 0 auto;
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
/* Vertical --------------------------------------------------------------- */
|
|
6595
|
+
|
|
6596
|
+
.steps-vertical {
|
|
6597
|
+
--step-vertical-gap: var(--space-40);
|
|
6598
|
+
|
|
6599
|
+
flex-direction: column;
|
|
6600
|
+
gap: var(--step-vertical-gap);
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
/* `justify-items` looks like a reset and is not one — nothing here resets
|
|
6604
|
+
anything, since the horizontal rules never applied. Grid items blockify, so
|
|
6605
|
+
without it a label stretches the whole 1fr track: 264px against 75px for an
|
|
6606
|
+
`a.step-label`, a full-width focus ring the row does not give the same link. */
|
|
6607
|
+
.steps-vertical > li {
|
|
6608
|
+
display: grid;
|
|
6609
|
+
grid-template-columns: var(--step-size) minmax(0, 1fr);
|
|
6610
|
+
align-items: start;
|
|
6611
|
+
justify-items: start;
|
|
6612
|
+
gap: var(--step-inline-gap);
|
|
6613
|
+
min-block-size: var(--step-size);
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6616
|
+
/* An empty step is a marker rail. The column cannot use the row's implicit
|
|
6617
|
+
second track, because `grid-auto-flow` is `row`: an auto-placed label would
|
|
6618
|
+
land *under* its marker. Its second track is therefore explicit, and this
|
|
6619
|
+
takes it back.
|
|
6620
|
+
|
|
6621
|
+
Silent in a stretched column, where the item fills its line whatever its
|
|
6622
|
+
tracks say. It bites in a shrink-to-fit box — which is what a sticky
|
|
6623
|
+
progress rail usually is — where the label track and the gap to it make the
|
|
6624
|
+
rail `--step-inline-gap` wider than the markers it draws: measured 36px
|
|
6625
|
+
against 28px. No `gap: 0` needed; one track has nothing to sit beside. */
|
|
6626
|
+
.steps-vertical > li:empty {
|
|
6627
|
+
grid-template-columns: var(--step-size);
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6630
|
+
/* The row starts at the top so a wrapped label begins level with its marker.
|
|
6631
|
+
align-self only bites on a single line, which would otherwise float above
|
|
6632
|
+
the marker's centre.
|
|
6633
|
+
|
|
6634
|
+
The one `.step-label` rule that keeps a child combinator: `align-self` is a
|
|
6635
|
+
grid-item property, so this declaration is meaningless on anything but a
|
|
6636
|
+
direct child of the row. Elsewhere the label is addressed as a descendant —
|
|
6637
|
+
the framework owns the structure, and repeating it buys nothing. */
|
|
6638
|
+
.steps-vertical > li > .step-label {
|
|
6639
|
+
align-self: center;
|
|
6640
|
+
}
|
|
6641
|
+
|
|
6642
|
+
/* The vertical track publishes no hook of its own: a horizontal connector value
|
|
6643
|
+
cannot transpose onto this geometry, and this selector is already a reachable
|
|
6644
|
+
override for the design that finally asks. */
|
|
6645
|
+
.steps-vertical > li::after {
|
|
6646
|
+
position: absolute;
|
|
6647
|
+
inset-block-start: var(--step-size);
|
|
6648
|
+
inset-inline-start: calc((var(--step-size) - var(--step-line-size)) / 2);
|
|
6649
|
+
inline-size: var(--step-line-size);
|
|
6650
|
+
block-size: calc(100% - var(--step-size) + var(--step-vertical-gap));
|
|
6651
|
+
background:
|
|
6652
|
+
linear-gradient(var(--step-line), var(--step-line)) center / var(--step-line-size) 100%
|
|
6653
|
+
no-repeat;
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6260
6656
|
|
|
6261
6657
|
/* Navigation */
|
|
6262
6658
|
/*
|
|
@@ -6658,6 +7054,34 @@ a.avatar[aria-disabled="true"] {
|
|
|
6658
7054
|
|
|
6659
7055
|
|
|
6660
7056
|
/* Data */
|
|
7057
|
+
/*
|
|
7058
|
+
* Data list — term/value pairs for the properties of a single object.
|
|
7059
|
+
*
|
|
7060
|
+
* Expects alternating <dt>/<dd> children: one term, one value per property.
|
|
7061
|
+
* A plain <dl> handles richer relationships (several terms or descriptions)
|
|
7062
|
+
* and stays untouched by this component.
|
|
7063
|
+
*
|
|
7064
|
+
* Why margin resets: neutralizes .prose list margins if .data-list appears
|
|
7065
|
+
* inside an authored rich-text scope; the grid owns row/column rhythm.
|
|
7066
|
+
*/
|
|
7067
|
+
.data-list {
|
|
7068
|
+
display: grid;
|
|
7069
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
7070
|
+
align-items: baseline;
|
|
7071
|
+
gap: var(--space-20) var(--space-40);
|
|
7072
|
+
}
|
|
7073
|
+
|
|
7074
|
+
.data-list > dt {
|
|
7075
|
+
min-inline-size: 0;
|
|
7076
|
+
margin-block-start: 0;
|
|
7077
|
+
font-weight: var(--font-weight-strong);
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
.data-list > dd {
|
|
7081
|
+
min-inline-size: 0;
|
|
7082
|
+
margin: 0;
|
|
7083
|
+
}
|
|
7084
|
+
|
|
6661
7085
|
/*
|
|
6662
7086
|
* Application list — repeated rows with optional leading and trailing regions.
|
|
6663
7087
|
* Content determines the number of lines; the component only owns the row
|
|
@@ -6693,7 +7117,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6693
7117
|
.list > li > .list-item {
|
|
6694
7118
|
display: grid;
|
|
6695
7119
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
6696
|
-
align
|
|
7120
|
+
/* Regions align to the first line, so leading, title, and trailing controls
|
|
7121
|
+
share one horizontal line while supporting text wraps beneath. */
|
|
7122
|
+
align-items: start;
|
|
6697
7123
|
gap: var(--list-item-gap);
|
|
6698
7124
|
min-block-size: var(--list-item-min-size);
|
|
6699
7125
|
padding-block: var(--list-item-pad-block);
|
|
@@ -6816,10 +7242,18 @@ a.list-item:hover {
|
|
|
6816
7242
|
}
|
|
6817
7243
|
|
|
6818
7244
|
/*
|
|
6819
|
-
* Meter — native
|
|
7245
|
+
* Meter — native and segmented gauges for known-range values.
|
|
6820
7246
|
*
|
|
6821
7247
|
* Maps the three standard meter zones (optimum / suboptimum / even-less-good)
|
|
6822
7248
|
* to the shared success / warning / danger intent colors.
|
|
7249
|
+
*
|
|
7250
|
+
* Thickness is a dimension, not a density step: the bar always spans its
|
|
7251
|
+
* container and only gets thinner or thicker, so --bar-height is the API and
|
|
7252
|
+
* .sm/.lg are deliberately not supported.
|
|
7253
|
+
*
|
|
7254
|
+
* Public hooks:
|
|
7255
|
+
* --meter-value
|
|
7256
|
+
* --bar-height
|
|
6823
7257
|
*/
|
|
6824
7258
|
|
|
6825
7259
|
.meter {
|
|
@@ -6834,6 +7268,25 @@ a.list-item:hover {
|
|
|
6834
7268
|
background: var(--surface-subtle);
|
|
6835
7269
|
}
|
|
6836
7270
|
|
|
7271
|
+
/* A non-native meter is a weighted flex container. The element type keeps the
|
|
7272
|
+
native <meter> API and the composed segmented API distinct without another
|
|
7273
|
+
public variant class. */
|
|
7274
|
+
.meter:not(meter) {
|
|
7275
|
+
--intent: initial;
|
|
7276
|
+
|
|
7277
|
+
display: flex;
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
.meter:not(meter) > * {
|
|
7281
|
+
flex: var(--meter-value, 1) 1 0;
|
|
7282
|
+
min-inline-size: 0;
|
|
7283
|
+
background: var(--intent);
|
|
7284
|
+
}
|
|
7285
|
+
|
|
7286
|
+
.meter:not(meter) > * + * {
|
|
7287
|
+
border-inline-start: var(--border-width) solid var(--surface);
|
|
7288
|
+
}
|
|
7289
|
+
|
|
6837
7290
|
.meter::-webkit-meter-bar {
|
|
6838
7291
|
-webkit-appearance: none;
|
|
6839
7292
|
block-size: var(--bar-height);
|
|
@@ -7009,12 +7462,23 @@ a.list-item:hover {
|
|
|
7009
7462
|
/*
|
|
7010
7463
|
* Decorative intent-colored frame for one direct child.
|
|
7011
7464
|
*
|
|
7465
|
+
* The effect is built from three layers with distinct roles: a frame carrying
|
|
7466
|
+
* a short accent of light, a small halo that belongs to the frame and keeps
|
|
7467
|
+
* that light attached to the edge, and a wide static halo that belongs to the
|
|
7468
|
+
* object and gives it a permanent, diffuse presence. Only the small halo
|
|
7469
|
+
* follows the animated gradient, so a rotating aura repaints one blurred layer
|
|
7470
|
+
* per frame, not two.
|
|
7471
|
+
*
|
|
7012
7472
|
* Public hooks:
|
|
7013
7473
|
* --aura-color
|
|
7014
7474
|
* --aura-width
|
|
7015
7475
|
* --aura-blur
|
|
7476
|
+
* --aura-intensity
|
|
7016
7477
|
* --aura-radius
|
|
7017
7478
|
* --aura-duration
|
|
7479
|
+
*
|
|
7480
|
+
* Internal:
|
|
7481
|
+
* --aura-angle
|
|
7018
7482
|
*/
|
|
7019
7483
|
@property --aura-angle {
|
|
7020
7484
|
syntax: "<angle>";
|
|
@@ -7031,7 +7495,8 @@ a.list-item:hover {
|
|
|
7031
7495
|
.aura {
|
|
7032
7496
|
--aura-color: var(--intent, var(--primary));
|
|
7033
7497
|
--aura-width: 2px;
|
|
7034
|
-
--aura-blur: 0.
|
|
7498
|
+
--aura-blur: 0.25rem;
|
|
7499
|
+
--aura-intensity: 1;
|
|
7035
7500
|
--aura-radius: var(--radius-lg);
|
|
7036
7501
|
--aura-duration: 6s;
|
|
7037
7502
|
|
|
@@ -7042,9 +7507,19 @@ a.list-item:hover {
|
|
|
7042
7507
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
7043
7508
|
border: var(--border-width) solid transparent;
|
|
7044
7509
|
background: var(--aura-color);
|
|
7045
|
-
|
|
7510
|
+
|
|
7511
|
+
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
7512
|
+
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
7513
|
+
across 2px draws an outline, the opposite of the effect. */
|
|
7514
|
+
background: conic-gradient(
|
|
7515
|
+
from var(--aura-angle),
|
|
7516
|
+
transparent 0 78%,
|
|
7517
|
+
var(--aura-color) 93%,
|
|
7518
|
+
transparent
|
|
7519
|
+
);
|
|
7046
7520
|
}
|
|
7047
7521
|
|
|
7522
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. */
|
|
7048
7523
|
.aura::before {
|
|
7049
7524
|
position: absolute;
|
|
7050
7525
|
z-index: -1;
|
|
@@ -7052,15 +7527,40 @@ a.list-item:hover {
|
|
|
7052
7527
|
border-radius: inherit;
|
|
7053
7528
|
background: inherit;
|
|
7054
7529
|
filter: blur(var(--aura-blur));
|
|
7055
|
-
opacity: 0.
|
|
7530
|
+
opacity: calc(0.55 * var(--aura-intensity));
|
|
7056
7531
|
pointer-events: none;
|
|
7057
7532
|
content: "";
|
|
7058
7533
|
}
|
|
7059
7534
|
|
|
7535
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
7536
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
7537
|
+
layer. It outgrows the frame, otherwise the child covers it and the whole
|
|
7538
|
+
effect reads as a drop shadow. This is the layer that stays lit at all
|
|
7539
|
+
times, so it — not the frame — is what keeps the object present between two
|
|
7540
|
+
passes of the accent. */
|
|
7541
|
+
.aura::after {
|
|
7542
|
+
position: absolute;
|
|
7543
|
+
z-index: -1;
|
|
7544
|
+
inset: calc(-1 * var(--aura-blur));
|
|
7545
|
+
border-radius: calc(var(--aura-radius) + var(--aura-width) + var(--aura-blur));
|
|
7546
|
+
background: var(--aura-color);
|
|
7547
|
+
filter: blur(calc(var(--aura-blur) * 4));
|
|
7548
|
+
opacity: calc(0.15 * var(--aura-intensity));
|
|
7549
|
+
pointer-events: none;
|
|
7550
|
+
content: "";
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
/* Lifts the child above both halos. It also has to be opaque: the frame paints
|
|
7554
|
+
across the whole box and only its edge is meant to show, so a transparent
|
|
7555
|
+
child (an .outline alert, a .ghost button) is filled with the frame color
|
|
7556
|
+
rather than framed by it. Documented as a constraint, not worked around —
|
|
7557
|
+
masking a ring would cost more than the case is worth. */
|
|
7060
7558
|
.aura > * {
|
|
7061
7559
|
position: relative;
|
|
7062
7560
|
}
|
|
7063
7561
|
|
|
7562
|
+
/* Glow lights the whole perimeter instead of a short accent, so both halos
|
|
7563
|
+
become a single soft field around the child. */
|
|
7064
7564
|
.aura-glow {
|
|
7065
7565
|
background: var(--aura-color);
|
|
7066
7566
|
}
|
|
@@ -7069,6 +7569,29 @@ a.list-item:hover {
|
|
|
7069
7569
|
.aura:not(.aura-glow) {
|
|
7070
7570
|
animation: aura-rotate var(--aura-duration) linear infinite;
|
|
7071
7571
|
}
|
|
7572
|
+
|
|
7573
|
+
/* Both halos breathe: moving the near one alone changes a line's brightness,
|
|
7574
|
+
which barely registers. Opening the wide field with it is what reads as a
|
|
7575
|
+
breath. */
|
|
7576
|
+
.aura-glow::before {
|
|
7577
|
+
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
.aura-glow::after {
|
|
7581
|
+
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
7582
|
+
}
|
|
7583
|
+
}
|
|
7584
|
+
|
|
7585
|
+
/* Without motion the accent would freeze at an arbitrary angle and read as a
|
|
7586
|
+
broken animation, so it becomes a deliberate composition: two opposite
|
|
7587
|
+
corners. Fixed radii keep those corners stable across aspect ratios;
|
|
7588
|
+
proportional radii turn wide or tall elements into lit edges instead. */
|
|
7589
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7590
|
+
.aura:not(.aura-glow) {
|
|
7591
|
+
background:
|
|
7592
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%),
|
|
7593
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%);
|
|
7594
|
+
}
|
|
7072
7595
|
}
|
|
7073
7596
|
|
|
7074
7597
|
@keyframes aura-rotate {
|
|
@@ -7077,18 +7600,38 @@ a.list-item:hover {
|
|
|
7077
7600
|
}
|
|
7078
7601
|
}
|
|
7079
7602
|
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7603
|
+
/* Only the peak is declared, so each halo breathes up from its own resting
|
|
7604
|
+
opacity and never dips below the static glow. The peaks scale with the same
|
|
7605
|
+
intensity as the resting values, which keeps the breath proportional at
|
|
7606
|
+
every setting. */
|
|
7607
|
+
@keyframes aura-breathe-near {
|
|
7608
|
+
50% {
|
|
7609
|
+
opacity: calc(0.95 * var(--aura-intensity));
|
|
7610
|
+
transform: scale(1.03);
|
|
7083
7611
|
}
|
|
7612
|
+
}
|
|
7084
7613
|
|
|
7085
|
-
|
|
7086
|
-
|
|
7614
|
+
@keyframes aura-breathe-far {
|
|
7615
|
+
50% {
|
|
7616
|
+
opacity: calc(0.28 * var(--aura-intensity));
|
|
7617
|
+
transform: scale(1.07);
|
|
7087
7618
|
}
|
|
7619
|
+
}
|
|
7088
7620
|
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7621
|
+
/* Each rule is a lone :has() setting a decorative radius, so a browser without
|
|
7622
|
+
:has() simply drops it and keeps the default. No @supports needed — that
|
|
7623
|
+
guard only earns its place around a selector list that mixes :has() with
|
|
7624
|
+
selectors worth keeping. */
|
|
7625
|
+
.aura:has(> .btn) {
|
|
7626
|
+
--aura-radius: var(--radius);
|
|
7627
|
+
}
|
|
7628
|
+
|
|
7629
|
+
.aura:has(> :is(.card, .alert)) {
|
|
7630
|
+
--aura-radius: var(--radius-lg);
|
|
7631
|
+
}
|
|
7632
|
+
|
|
7633
|
+
.aura:has(> .badge) {
|
|
7634
|
+
--aura-radius: var(--radius-full);
|
|
7092
7635
|
}
|
|
7093
7636
|
|
|
7094
7637
|
/* Utilities — small, single-purpose escape hatches. */
|
|
@@ -7112,6 +7655,22 @@ a.list-item:hover {
|
|
|
7112
7655
|
border: 0;
|
|
7113
7656
|
}
|
|
7114
7657
|
|
|
7658
|
+
/* Enlarge a small control's touch target on touch-primary devices without
|
|
7659
|
+
growing the visual. Opt-in — apply to genuinely small targets only. The
|
|
7660
|
+
::after exists only under (pointer: coarse), so on fine pointers the class
|
|
7661
|
+
is fully neutral. */
|
|
7662
|
+
@media (pointer: coarse) {
|
|
7663
|
+
.touch-target {
|
|
7664
|
+
position: relative;
|
|
7665
|
+
}
|
|
7666
|
+
|
|
7667
|
+
.touch-target::after {
|
|
7668
|
+
content: "";
|
|
7669
|
+
position: absolute;
|
|
7670
|
+
inset: calc(var(--space-20) * -1);
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
|
|
7115
7674
|
.overflow-auto {
|
|
7116
7675
|
overflow: auto;
|
|
7117
7676
|
}
|