actual-css 0.5.0 → 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 +17 -1
- package/dist/actual.css +1 -0
- package/dist/actual.full.css +480 -117
- 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/steps.md +748 -0
- package/docs/design-notes/widget-primitives.md +32 -15
- package/docs/navigation.json +1 -0
- package/docs/pages/components/alert.md +13 -8
- package/docs/pages/components/aura.md +102 -6
- package/docs/pages/components/avatar.md +9 -6
- package/docs/pages/components/badge.md +11 -7
- package/docs/pages/components/button.md +17 -17
- package/docs/pages/components/card.md +15 -8
- package/docs/pages/components/chat.md +1 -1
- package/docs/pages/components/dialog.md +10 -7
- package/docs/pages/components/drawer.md +1 -1
- package/docs/pages/components/fab.md +12 -10
- package/docs/pages/components/indicator.md +6 -6
- package/docs/pages/components/list.md +11 -2
- package/docs/pages/components/navbar.md +1 -1
- package/docs/pages/components/pagination.md +1 -1
- package/docs/pages/components/rating.md +4 -4
- package/docs/pages/components/skeleton.md +1 -1
- package/docs/pages/components/spinner.md +1 -1
- package/docs/pages/components/steps.md +376 -52
- package/docs/pages/components/table.md +5 -5
- package/docs/pages/enhancements/flyout.md +12 -10
- package/docs/pages/enhancements/overview.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/tabs.md +1 -1
- package/docs/pages/examples/overview.md +2 -0
- 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 +4 -4
- package/docs/pages/forms/labels.md +8 -5
- package/docs/pages/forms/overview.md +25 -18
- package/docs/pages/forms/password.md +3 -2
- package/docs/pages/forms/range.md +13 -2
- package/docs/pages/forms/states-and-sizes.md +6 -4
- package/docs/pages/forms/validation.md +13 -9
- package/docs/pages/foundations/tokens.md +9 -6
- package/docs/pages/foundations/typography.md +1 -1
- package/docs/pages/guides/bootstrap.md +3 -3
- package/docs/pages/guides/introduction.md +1 -1
- 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/stack.md +10 -6
- package/docs/pages/patterns/structured-lists.md +7 -7
- package/docs/pages/utilities/accessibility.md +1 -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 +4 -1
- package/src/css/components/fab.css +44 -8
- package/src/css/components/list.css +3 -1
- package/src/css/components/steps.css +277 -69
- package/src/css/core/tokens.css +1 -0
- 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/layout/container-query.css +37 -13
- package/src/css/layout/grid.css +7 -7
- package/src/css/layout/index.css +1 -1
- package/src/js/password.js +1 -1
package/dist/actual.full.css
CHANGED
|
@@ -201,6 +201,7 @@ select {
|
|
|
201
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");
|
|
202
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");
|
|
203
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");
|
|
204
205
|
|
|
205
206
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
206
207
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -1073,7 +1074,7 @@ small {
|
|
|
1073
1074
|
* .sidebar-layout main content + secondary aside
|
|
1074
1075
|
* .grid responsive collection; item width is the contract
|
|
1075
1076
|
* .grid-N structural density of N; balanced inside a container named
|
|
1076
|
-
* actual-
|
|
1077
|
+
* actual-container (in grid.css)
|
|
1077
1078
|
* .column-layout explicit 12-column region composition; the author owns
|
|
1078
1079
|
* narrow-container recomposition
|
|
1079
1080
|
* .scroller theme-aware scrollbar density and colour
|
|
@@ -1084,20 +1085,44 @@ small {
|
|
|
1084
1085
|
*
|
|
1085
1086
|
* Prefer these compositions over viewport breakpoint utilities.
|
|
1086
1087
|
*/
|
|
1087
|
-
/* Convenience helper
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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. */
|
|
1099
1124
|
.container-query {
|
|
1100
|
-
container: actual-
|
|
1125
|
+
container: actual-container / inline-size;
|
|
1101
1126
|
}
|
|
1102
1127
|
|
|
1103
1128
|
/* Vertical flow. .stack owns the block axis only: block margins reset, inline
|
|
@@ -1468,13 +1493,13 @@ small {
|
|
|
1468
1493
|
min-inline-size: 0;
|
|
1469
1494
|
}
|
|
1470
1495
|
|
|
1471
|
-
/* Balanced subdivision, activated by an ancestor container named actual-
|
|
1496
|
+
/* Balanced subdivision, activated by an ancestor container named actual-container.
|
|
1472
1497
|
* A .grid-N inside such a container enters only divisor states of N. The name
|
|
1473
1498
|
* is deliberate: a grid must not key off the nearest anonymous query container,
|
|
1474
1499
|
* or a region that sets container-type: inline-size for an unrelated reason
|
|
1475
1500
|
* would silently change the grid. The author opts the grid in by naming the
|
|
1476
1501
|
* context — .container-query is the official helper, or declare
|
|
1477
|
-
* container: actual-
|
|
1502
|
+
* container: actual-container / inline-size on your own region.
|
|
1478
1503
|
*
|
|
1479
1504
|
* .grid-2 2 -> 1
|
|
1480
1505
|
* .grid-3 3 -> 1
|
|
@@ -1491,7 +1516,7 @@ small {
|
|
|
1491
1516
|
* inline contribution and collapses it inside .cluster, .topbar, floats, and
|
|
1492
1517
|
* any shrink-to-fit box. See docs/design-notes/grid.md.
|
|
1493
1518
|
*
|
|
1494
|
-
* Without an actual-
|
|
1519
|
+
* Without an actual-container ancestor none of these @container rules resolve, so
|
|
1495
1520
|
* the auto-fill baseline above stays. The min-width: 0 rule is the lowest
|
|
1496
1521
|
* step: it pins the grid to one column inside a container before any larger
|
|
1497
1522
|
* threshold fires (a bare grid would otherwise show a 2 + 1 orphan at 28-48rem).
|
|
@@ -1507,25 +1532,25 @@ small {
|
|
|
1507
1532
|
* Ascending min-width only: the last matching step wins by cascade order.
|
|
1508
1533
|
*/
|
|
1509
1534
|
@supports (container-type: inline-size) {
|
|
1510
|
-
@container actual-
|
|
1535
|
+
@container actual-container (min-width: 0px) {
|
|
1511
1536
|
:where(.grid-2, .grid-3, .grid-4, .grid-6) {
|
|
1512
1537
|
grid-template-columns: 1fr;
|
|
1513
1538
|
}
|
|
1514
1539
|
}
|
|
1515
1540
|
|
|
1516
|
-
@container actual-
|
|
1541
|
+
@container actual-container (min-width: 28rem) {
|
|
1517
1542
|
:where(.grid-2, .grid-4, .grid-6) {
|
|
1518
1543
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1519
1544
|
}
|
|
1520
1545
|
}
|
|
1521
1546
|
|
|
1522
|
-
@container actual-
|
|
1547
|
+
@container actual-container (min-width: 48rem) {
|
|
1523
1548
|
:where(.grid-3, .grid-6) {
|
|
1524
1549
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1525
1550
|
}
|
|
1526
1551
|
}
|
|
1527
1552
|
|
|
1528
|
-
@container actual-
|
|
1553
|
+
@container actual-container (min-width: 64rem) {
|
|
1529
1554
|
:where(.grid-4) {
|
|
1530
1555
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1531
1556
|
}
|
|
@@ -2975,7 +3000,7 @@ small {
|
|
|
2975
3000
|
|
|
2976
3001
|
&::picker(select) {
|
|
2977
3002
|
appearance: base-select;
|
|
2978
|
-
max-
|
|
3003
|
+
max-block-size: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
2979
3004
|
margin-block: var(--space-20);
|
|
2980
3005
|
padding: 0;
|
|
2981
3006
|
border: var(--border-width) solid var(--border);
|
|
@@ -3052,8 +3077,10 @@ small {
|
|
|
3052
3077
|
* messages stay in the surrounding .field, not inside the cell, so the
|
|
3053
3078
|
* resting label can center on the control alone.
|
|
3054
3079
|
*
|
|
3055
|
-
* The control must precede the label: the float is driven by the
|
|
3056
|
-
* 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
|
|
3057
3084
|
* placeholder=" " to expose a reliable empty state — without it the label
|
|
3058
3085
|
* only reacts to focus.
|
|
3059
3086
|
*
|
|
@@ -3101,14 +3128,14 @@ small {
|
|
|
3101
3128
|
}
|
|
3102
3129
|
|
|
3103
3130
|
/* Single-line controls rest in the middle until they receive a value. */
|
|
3104
|
-
.floating-field > :is(.input, .select)
|
|
3131
|
+
.floating-field > :is(.input, .select) ~ .field-label {
|
|
3105
3132
|
inset-block-start: 50%;
|
|
3106
3133
|
transform: translateY(-50%);
|
|
3107
3134
|
}
|
|
3108
3135
|
|
|
3109
3136
|
/* An empty textarea label follows the control's normal first-line position;
|
|
3110
3137
|
the control itself keeps the extra headroom reserved for its floated state. */
|
|
3111
|
-
.floating-field > .textarea
|
|
3138
|
+
.floating-field > .textarea ~ .field-label {
|
|
3112
3139
|
inset-block-start: calc(1em + var(--border-width));
|
|
3113
3140
|
transform: none;
|
|
3114
3141
|
}
|
|
@@ -3117,7 +3144,7 @@ small {
|
|
|
3117
3144
|
not have propagated the value to the placeholder state yet. */
|
|
3118
3145
|
.floating-field
|
|
3119
3146
|
> :is(.input, .textarea):is(:focus, :autofill, :not(:placeholder-shown))
|
|
3120
|
-
|
|
3147
|
+
~ .field-label {
|
|
3121
3148
|
inset-block-start: var(--floating-label-top);
|
|
3122
3149
|
transform: none;
|
|
3123
3150
|
font-size: var(--font-size-xs);
|
|
@@ -3136,7 +3163,7 @@ small {
|
|
|
3136
3163
|
.input[type="month"],
|
|
3137
3164
|
.input[type="week"]
|
|
3138
3165
|
)
|
|
3139
|
-
|
|
3166
|
+
~ .field-label {
|
|
3140
3167
|
inset-block-start: var(--floating-label-top);
|
|
3141
3168
|
transform: none;
|
|
3142
3169
|
font-size: var(--font-size-xs);
|
|
@@ -6033,15 +6060,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
6033
6060
|
|
|
6034
6061
|
/*
|
|
6035
6062
|
* Fixed floating action placement and a native-details speed dial.
|
|
6036
|
-
* 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).
|
|
6037
6065
|
*
|
|
6038
6066
|
* Public hooks:
|
|
6039
6067
|
* --fab-offset
|
|
6040
6068
|
* --fab-gap
|
|
6069
|
+
* --fab-icon-size
|
|
6041
6070
|
*/
|
|
6042
6071
|
.fab {
|
|
6043
6072
|
--fab-offset: var(--space-40);
|
|
6044
6073
|
--fab-gap: var(--space-20);
|
|
6074
|
+
--fab-icon-size: 1.5rem;
|
|
6045
6075
|
|
|
6046
6076
|
position: fixed;
|
|
6047
6077
|
z-index: var(--z-menu);
|
|
@@ -6050,6 +6080,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6050
6080
|
}
|
|
6051
6081
|
|
|
6052
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;
|
|
6053
6086
|
list-style: none;
|
|
6054
6087
|
cursor: pointer;
|
|
6055
6088
|
}
|
|
@@ -6058,11 +6091,35 @@ a.avatar[aria-disabled="true"] {
|
|
|
6058
6091
|
display: none;
|
|
6059
6092
|
}
|
|
6060
6093
|
|
|
6061
|
-
/*
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
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;
|
|
6066
6123
|
}
|
|
6067
6124
|
|
|
6068
6125
|
.fab-actions {
|
|
@@ -6120,8 +6177,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6120
6177
|
transition-delay: 0s;
|
|
6121
6178
|
}
|
|
6122
6179
|
|
|
6123
|
-
.fab[open] > summary
|
|
6124
|
-
|
|
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;
|
|
6125
6188
|
}
|
|
6126
6189
|
|
|
6127
6190
|
@media print {
|
|
@@ -6233,22 +6296,56 @@ a.avatar[aria-disabled="true"] {
|
|
|
6233
6296
|
}
|
|
6234
6297
|
|
|
6235
6298
|
/*
|
|
6236
|
-
* Steps — semantic ordered-list progression
|
|
6237
|
-
*
|
|
6238
|
-
*
|
|
6239
|
-
*
|
|
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:
|
|
6240
6316
|
*
|
|
6241
|
-
*
|
|
6242
|
-
*
|
|
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.
|
|
6324
|
+
*
|
|
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.
|
|
6243
6337
|
*
|
|
6244
6338
|
* Public hooks:
|
|
6245
|
-
* --step-size
|
|
6246
|
-
* --step-
|
|
6247
|
-
* --step-
|
|
6248
|
-
* --step-
|
|
6249
|
-
* --step-
|
|
6250
|
-
* --step-
|
|
6251
|
-
* --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
|
|
6252
6349
|
*
|
|
6253
6350
|
* Internal:
|
|
6254
6351
|
* --step-marker-bg
|
|
@@ -6256,10 +6353,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
6256
6353
|
* --step-marker-fg
|
|
6257
6354
|
* --step-line
|
|
6258
6355
|
*/
|
|
6356
|
+
|
|
6357
|
+
/* Step primitive --------------------------------------------------------- */
|
|
6358
|
+
|
|
6259
6359
|
.steps {
|
|
6260
6360
|
--step-size: 1.75rem;
|
|
6261
|
-
--step-
|
|
6262
|
-
--step-gap: var(--space-20);
|
|
6361
|
+
--step-inline-gap: var(--space-20);
|
|
6263
6362
|
--step-line-size: var(--border-width);
|
|
6264
6363
|
--step-marker-bg: var(--surface);
|
|
6265
6364
|
--step-marker-border: var(--border);
|
|
@@ -6269,29 +6368,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6269
6368
|
|
|
6270
6369
|
counter-reset: actual-step;
|
|
6271
6370
|
display: flex;
|
|
6272
|
-
flex-direction: row;
|
|
6273
|
-
inline-size: 100%;
|
|
6274
6371
|
margin: 0;
|
|
6275
6372
|
padding: 0;
|
|
6276
6373
|
list-style: none;
|
|
6277
|
-
/* Fill when it fits, scroll when it cannot: items never shrink below
|
|
6278
|
-
--step-min, so labels keep a readable width and the sequence scrolls
|
|
6279
|
-
horizontally instead of squishing. overscroll-behavior keeps the scroll
|
|
6280
|
-
from chaining to the page. */
|
|
6281
|
-
overflow-x: auto;
|
|
6282
|
-
overscroll-behavior-inline: contain;
|
|
6283
6374
|
}
|
|
6284
6375
|
|
|
6285
6376
|
.steps > li {
|
|
6286
6377
|
position: relative;
|
|
6287
|
-
display: grid;
|
|
6288
|
-
flex: 1 0 var(--step-min);
|
|
6289
|
-
grid-template-rows: var(--step-size) auto;
|
|
6290
|
-
gap: var(--step-gap);
|
|
6291
|
-
justify-items: center;
|
|
6292
|
-
min-inline-size: max(var(--step-size), var(--step-min));
|
|
6293
6378
|
color: var(--text-muted);
|
|
6294
|
-
text-align: center;
|
|
6295
6379
|
counter-increment: actual-step;
|
|
6296
6380
|
}
|
|
6297
6381
|
|
|
@@ -6312,30 +6396,38 @@ a.avatar[aria-disabled="true"] {
|
|
|
6312
6396
|
line-height: 1;
|
|
6313
6397
|
}
|
|
6314
6398
|
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
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;
|
|
6328
6415
|
}
|
|
6329
6416
|
|
|
6330
|
-
|
|
6331
|
-
|
|
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: "";
|
|
6332
6424
|
}
|
|
6333
6425
|
|
|
6334
|
-
/*
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
.steps > .complete {
|
|
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 {
|
|
6339
6431
|
--step-marker-bg: var(--state-selected);
|
|
6340
6432
|
--step-marker-border: var(--state-selected);
|
|
6341
6433
|
--step-marker-fg: var(--state-selected-fg);
|
|
@@ -6344,10 +6436,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6344
6436
|
color: var(--text);
|
|
6345
6437
|
}
|
|
6346
6438
|
|
|
6347
|
-
/*
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
current step is owned by the previous step, which is complete. */
|
|
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. */
|
|
6351
6442
|
.steps > [aria-current="step"] {
|
|
6352
6443
|
--step-marker-bg: var(--surface);
|
|
6353
6444
|
--step-marker-border: var(--state-selected);
|
|
@@ -6357,31 +6448,211 @@ a.avatar[aria-disabled="true"] {
|
|
|
6357
6448
|
color: var(--text);
|
|
6358
6449
|
}
|
|
6359
6450
|
|
|
6360
|
-
/* Thicker border is the current affordance in every mode — structural, so it
|
|
6361
|
-
survives forced colors, and it never competes with the outline channel owned
|
|
6362
|
-
by focus. */
|
|
6363
6451
|
.steps > [aria-current="step"]::before {
|
|
6364
6452
|
border-width: calc(var(--border-width) * 2);
|
|
6365
6453
|
}
|
|
6366
6454
|
|
|
6367
|
-
/*
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
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);
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6461
|
+
.steps > .step-complete:not([aria-current="step"])::before {
|
|
6372
6462
|
content: var(--step-complete-mark, counter(actual-step));
|
|
6373
6463
|
}
|
|
6374
6464
|
|
|
6375
|
-
/*
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
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
|
+
}
|
|
6382
6591
|
}
|
|
6383
6592
|
}
|
|
6384
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
|
+
|
|
6385
6656
|
|
|
6386
6657
|
/* Navigation */
|
|
6387
6658
|
/*
|
|
@@ -6846,7 +7117,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6846
7117
|
.list > li > .list-item {
|
|
6847
7118
|
display: grid;
|
|
6848
7119
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
6849
|
-
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;
|
|
6850
7123
|
gap: var(--list-item-gap);
|
|
6851
7124
|
min-block-size: var(--list-item-min-size);
|
|
6852
7125
|
padding-block: var(--list-item-pad-block);
|
|
@@ -7189,12 +7462,23 @@ a.list-item:hover {
|
|
|
7189
7462
|
/*
|
|
7190
7463
|
* Decorative intent-colored frame for one direct child.
|
|
7191
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
|
+
*
|
|
7192
7472
|
* Public hooks:
|
|
7193
7473
|
* --aura-color
|
|
7194
7474
|
* --aura-width
|
|
7195
7475
|
* --aura-blur
|
|
7476
|
+
* --aura-intensity
|
|
7196
7477
|
* --aura-radius
|
|
7197
7478
|
* --aura-duration
|
|
7479
|
+
*
|
|
7480
|
+
* Internal:
|
|
7481
|
+
* --aura-angle
|
|
7198
7482
|
*/
|
|
7199
7483
|
@property --aura-angle {
|
|
7200
7484
|
syntax: "<angle>";
|
|
@@ -7211,7 +7495,8 @@ a.list-item:hover {
|
|
|
7211
7495
|
.aura {
|
|
7212
7496
|
--aura-color: var(--intent, var(--primary));
|
|
7213
7497
|
--aura-width: 2px;
|
|
7214
|
-
--aura-blur: 0.
|
|
7498
|
+
--aura-blur: 0.25rem;
|
|
7499
|
+
--aura-intensity: 1;
|
|
7215
7500
|
--aura-radius: var(--radius-lg);
|
|
7216
7501
|
--aura-duration: 6s;
|
|
7217
7502
|
|
|
@@ -7222,9 +7507,19 @@ a.list-item:hover {
|
|
|
7222
7507
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
7223
7508
|
border: var(--border-width) solid transparent;
|
|
7224
7509
|
background: var(--aura-color);
|
|
7225
|
-
|
|
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
|
+
);
|
|
7226
7520
|
}
|
|
7227
7521
|
|
|
7522
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. */
|
|
7228
7523
|
.aura::before {
|
|
7229
7524
|
position: absolute;
|
|
7230
7525
|
z-index: -1;
|
|
@@ -7232,15 +7527,40 @@ a.list-item:hover {
|
|
|
7232
7527
|
border-radius: inherit;
|
|
7233
7528
|
background: inherit;
|
|
7234
7529
|
filter: blur(var(--aura-blur));
|
|
7235
|
-
opacity: 0.
|
|
7530
|
+
opacity: calc(0.55 * var(--aura-intensity));
|
|
7531
|
+
pointer-events: none;
|
|
7532
|
+
content: "";
|
|
7533
|
+
}
|
|
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));
|
|
7236
7549
|
pointer-events: none;
|
|
7237
7550
|
content: "";
|
|
7238
7551
|
}
|
|
7239
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. */
|
|
7240
7558
|
.aura > * {
|
|
7241
7559
|
position: relative;
|
|
7242
7560
|
}
|
|
7243
7561
|
|
|
7562
|
+
/* Glow lights the whole perimeter instead of a short accent, so both halos
|
|
7563
|
+
become a single soft field around the child. */
|
|
7244
7564
|
.aura-glow {
|
|
7245
7565
|
background: var(--aura-color);
|
|
7246
7566
|
}
|
|
@@ -7249,6 +7569,29 @@ a.list-item:hover {
|
|
|
7249
7569
|
.aura:not(.aura-glow) {
|
|
7250
7570
|
animation: aura-rotate var(--aura-duration) linear infinite;
|
|
7251
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
|
+
}
|
|
7252
7595
|
}
|
|
7253
7596
|
|
|
7254
7597
|
@keyframes aura-rotate {
|
|
@@ -7257,18 +7600,38 @@ a.list-item:hover {
|
|
|
7257
7600
|
}
|
|
7258
7601
|
}
|
|
7259
7602
|
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
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);
|
|
7263
7611
|
}
|
|
7612
|
+
}
|
|
7264
7613
|
|
|
7265
|
-
|
|
7266
|
-
|
|
7614
|
+
@keyframes aura-breathe-far {
|
|
7615
|
+
50% {
|
|
7616
|
+
opacity: calc(0.28 * var(--aura-intensity));
|
|
7617
|
+
transform: scale(1.07);
|
|
7267
7618
|
}
|
|
7619
|
+
}
|
|
7268
7620
|
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
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);
|
|
7272
7635
|
}
|
|
7273
7636
|
|
|
7274
7637
|
/* Utilities — small, single-purpose escape hatches. */
|