@echodial/deck 0.1.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/LICENSE +21 -0
- package/README.md +1327 -0
- package/bin/deck.mjs +219 -0
- package/build.mjs +338 -0
- package/dist/api-buckets.json +10486 -0
- package/dist/api.json +57896 -0
- package/dist/brand/deck-apple-touch-icon.png +0 -0
- package/dist/brand/deck-logo-dark.svg +1 -0
- package/dist/brand/deck-logo-light.svg +1 -0
- package/dist/brand/deck-logo-stacked.svg +1 -0
- package/dist/brand/deck-logo.svg +1 -0
- package/dist/brand/deck-mark.svg +7 -0
- package/dist/brand/deck-og.png +0 -0
- package/dist/collisions.json +223 -0
- package/dist/deck-adapters.js +482 -0
- package/dist/deck-adapters.min.js +23 -0
- package/dist/deck-extras.js +823 -0
- package/dist/deck-extras.min.js +5 -0
- package/dist/deck-icons.svg +184 -0
- package/dist/deck.bundle.js +2638 -0
- package/dist/deck.bundle.min.js +86 -0
- package/dist/deck.css +7904 -0
- package/dist/deck.esm.js +2644 -0
- package/dist/deck.js +1331 -0
- package/dist/deck.min.css +32 -0
- package/dist/deck.min.js +60 -0
- package/dist/layers/anchor.css +192 -0
- package/dist/layers/buttons.css +210 -0
- package/dist/layers/charts.css +337 -0
- package/dist/layers/combobox.css +248 -0
- package/dist/layers/components.css +868 -0
- package/dist/layers/container.css +199 -0
- package/dist/layers/datagrid.css +335 -0
- package/dist/layers/datepicker.css +305 -0
- package/dist/layers/forms.css +401 -0
- package/dist/layers/gradients.css +302 -0
- package/dist/layers/inputs.css +522 -0
- package/dist/layers/layers.css +87 -0
- package/dist/layers/layout.css +235 -0
- package/dist/layers/logical.css +226 -0
- package/dist/layers/media.css +400 -0
- package/dist/layers/mobile.css +245 -0
- package/dist/layers/motion.css +379 -0
- package/dist/layers/nav.css +771 -0
- package/dist/layers/perf.css +234 -0
- package/dist/layers/print.css +198 -0
- package/dist/layers/reset.css +103 -0
- package/dist/layers/space3d.css +280 -0
- package/dist/layers/toasts.css +201 -0
- package/dist/layers/tokens.css +229 -0
- package/dist/layers/type.css +114 -0
- package/dist/layers/utilities.css +241 -0
- package/dist/sizes.json +52 -0
- package/dist/usage.json +6340 -0
- package/package.json +94 -0
- package/src/00-layers.css +87 -0
- package/src/01-tokens.css +229 -0
- package/src/02-reset.css +103 -0
- package/src/03-type.css +114 -0
- package/src/04-layout.css +235 -0
- package/src/05-buttons.css +210 -0
- package/src/06-forms.css +401 -0
- package/src/07-components.css +868 -0
- package/src/08-mobile.css +245 -0
- package/src/09-utilities.css +241 -0
- package/src/10-datepicker.css +305 -0
- package/src/11-combobox.css +248 -0
- package/src/12-datagrid.css +335 -0
- package/src/13-toasts.css +201 -0
- package/src/14-charts.css +337 -0
- package/src/16-motion.css +379 -0
- package/src/18-container.css +199 -0
- package/src/19-logical.css +226 -0
- package/src/20-gradients.css +302 -0
- package/src/21-space3d.css +280 -0
- package/src/22-nav.css +771 -0
- package/src/23-inputs.css +522 -0
- package/src/24-media.css +400 -0
- package/src/25-anchor.css +192 -0
- package/src/26-perf.css +234 -0
- package/src/99-print.css +198 -0
- package/src/brand/deck-apple-touch-icon.png +0 -0
- package/src/brand/deck-logo-dark.svg +1 -0
- package/src/brand/deck-logo-light.svg +1 -0
- package/src/brand/deck-logo-stacked.svg +1 -0
- package/src/brand/deck-logo.svg +1 -0
- package/src/brand/deck-mark.svg +7 -0
- package/src/brand/deck-og.png +0 -0
- package/src/brand/sources.json +7 -0
- package/src/deck-icons.svg +184 -0
- package/src/js/deck-adapters.js +482 -0
- package/src/js/deck-extras.js +823 -0
- package/src/js/deck.js +1331 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 22 Navigation and structure
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
@layer deck.components {
|
|
6
|
+
|
|
7
|
+
/* --- Carousel ------------------------------------------------------------
|
|
8
|
+
Scroll snap does the work. Arrows and dots are progressive enhancement,
|
|
9
|
+
so it still swipes correctly with JavaScript switched off.
|
|
10
|
+
---------------------------------------------------------------------- */
|
|
11
|
+
.carousel { position: relative; }
|
|
12
|
+
|
|
13
|
+
/* The scroll-snap container holding the slides. */
|
|
14
|
+
.carousel-track {
|
|
15
|
+
display: flex;
|
|
16
|
+
gap: var(--gap, var(--space-4));
|
|
17
|
+
overflow-x: auto;
|
|
18
|
+
scroll-snap-type: x mandatory;
|
|
19
|
+
overscroll-behavior-inline: contain;
|
|
20
|
+
scroll-behavior: smooth;
|
|
21
|
+
scrollbar-width: none;
|
|
22
|
+
border-radius: var(--r-md);
|
|
23
|
+
}
|
|
24
|
+
.carousel-track::-webkit-scrollbar { display: none; }
|
|
25
|
+
/* One slide. flex-basis: 100% by default; .carousel-peek makes it 86% so
|
|
26
|
+
the next one is partly visible, which is the only persistent cue that the
|
|
27
|
+
track scrolls once the scrollbar is hidden. */
|
|
28
|
+
.carousel-slide {
|
|
29
|
+
flex: 0 0 100%;
|
|
30
|
+
scroll-snap-align: center;
|
|
31
|
+
scroll-snap-stop: always;
|
|
32
|
+
min-inline-size: 0;
|
|
33
|
+
/* The slide is the containing block for its own .carousel-caption. Without
|
|
34
|
+
this the nearest positioned ancestor is .carousel, so every caption in
|
|
35
|
+
the track lands in the same place and they stack on top of each other. */
|
|
36
|
+
position: relative;
|
|
37
|
+
}
|
|
38
|
+
.carousel-peek .carousel-slide { flex-basis: 86%; }
|
|
39
|
+
.carousel-multi .carousel-slide { flex-basis: calc((100% - var(--space-4) * 2) / 3); }
|
|
40
|
+
@media (max-width: 48rem) {
|
|
41
|
+
.carousel-multi .carousel-slide { flex-basis: 78%; }
|
|
42
|
+
}
|
|
43
|
+
.carousel-slide img { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
44
|
+
|
|
45
|
+
/* Previous and next. Hidden on a coarse pointer, where the reader swipes
|
|
46
|
+
instead — so do not also hide the dots. */
|
|
47
|
+
.carousel-arrow {
|
|
48
|
+
position: absolute;
|
|
49
|
+
inset-block-start: 50%;
|
|
50
|
+
translate: 0 -50%;
|
|
51
|
+
z-index: 2;
|
|
52
|
+
inline-size: 40px;
|
|
53
|
+
block-size: 40px;
|
|
54
|
+
display: grid;
|
|
55
|
+
place-items: center;
|
|
56
|
+
border-radius: var(--r-full);
|
|
57
|
+
background: color-mix(in oklab, var(--surface) 88%, transparent);
|
|
58
|
+
backdrop-filter: blur(8px);
|
|
59
|
+
-webkit-backdrop-filter: blur(8px);
|
|
60
|
+
border: 1px solid var(--line);
|
|
61
|
+
box-shadow: var(--shadow-2);
|
|
62
|
+
color: var(--text);
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
transition: opacity var(--dur-2) var(--ease-out), scale var(--dur-1) var(--ease-out);
|
|
65
|
+
}
|
|
66
|
+
.carousel-arrow:hover { scale: 1.06; }
|
|
67
|
+
.carousel-arrow[disabled] { opacity: 0; pointer-events: none; }
|
|
68
|
+
.carousel-prev { inset-inline-start: var(--space-3); }
|
|
69
|
+
.carousel-next { inset-inline-end: var(--space-3); }
|
|
70
|
+
@media (pointer: coarse) { .carousel-arrow { display: none; } }
|
|
71
|
+
|
|
72
|
+
.carousel-dots {
|
|
73
|
+
display: flex;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
gap: var(--space-2);
|
|
76
|
+
padding-block-start: var(--space-3);
|
|
77
|
+
}
|
|
78
|
+
.carousel-dot {
|
|
79
|
+
inline-size: 7px;
|
|
80
|
+
block-size: 7px;
|
|
81
|
+
border-radius: var(--r-full);
|
|
82
|
+
background: var(--ink-300);
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
padding: 0;
|
|
85
|
+
transition: inline-size var(--dur-2) var(--ease-out), background-color var(--dur-2) var(--ease-out);
|
|
86
|
+
}
|
|
87
|
+
.carousel-dot[aria-current="true"] { inline-size: 22px; background: var(--brand-600); }
|
|
88
|
+
/* the tap target is bigger than the dot */
|
|
89
|
+
.carousel-dot::before { content: ""; position: absolute; inline-size: 32px; block-size: 32px; translate: -50% -50%; inset: 50% auto auto 50%; }
|
|
90
|
+
.carousel-dots > * { position: relative; }
|
|
91
|
+
|
|
92
|
+
/* --- Native carousel -----------------------------------------------------
|
|
93
|
+
::scroll-marker gives every slide its own dot and ::scroll-button gives the
|
|
94
|
+
track its arrows, with the active state, the disabled state, the click
|
|
95
|
+
handling and the arrow-key focus order all done by the browser.
|
|
96
|
+
Where these exist, deck-extras.js skips building and wiring the DIY ones
|
|
97
|
+
and .carousel-dots / .carousel-arrow simply step aside -- the markup stays
|
|
98
|
+
exactly as it was. The buttons take the logical keywords rather than left
|
|
99
|
+
and right, so an RTL carousel flips them without a rule in deck.rtl.
|
|
100
|
+
---------------------------------------------------------------------- */
|
|
101
|
+
@supports selector(::scroll-marker) {
|
|
102
|
+
.carousel-dots, .carousel-arrow { display: none; }
|
|
103
|
+
|
|
104
|
+
.carousel-track { scroll-marker-group: after; }
|
|
105
|
+
.carousel-track::scroll-marker-group {
|
|
106
|
+
display: flex;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
gap: var(--space-2);
|
|
109
|
+
padding-block-start: var(--space-3);
|
|
110
|
+
}
|
|
111
|
+
.carousel-slide::scroll-marker {
|
|
112
|
+
content: "";
|
|
113
|
+
inline-size: 7px;
|
|
114
|
+
block-size: 7px;
|
|
115
|
+
border-radius: var(--r-full);
|
|
116
|
+
background: var(--ink-300);
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: inline-size var(--dur-2) var(--ease-out),
|
|
119
|
+
background-color var(--dur-2) var(--ease-out);
|
|
120
|
+
}
|
|
121
|
+
.carousel-slide::scroll-marker:target-current {
|
|
122
|
+
inline-size: 22px;
|
|
123
|
+
background: var(--brand-600);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.carousel-track::scroll-button(*) {
|
|
127
|
+
position: absolute;
|
|
128
|
+
inset-block-start: 50%;
|
|
129
|
+
translate: 0 -50%;
|
|
130
|
+
z-index: 2;
|
|
131
|
+
inline-size: 40px;
|
|
132
|
+
block-size: 40px;
|
|
133
|
+
display: grid;
|
|
134
|
+
place-items: center;
|
|
135
|
+
border-radius: var(--r-full);
|
|
136
|
+
background: color-mix(in oklab, var(--surface) 88%, transparent);
|
|
137
|
+
backdrop-filter: blur(8px);
|
|
138
|
+
-webkit-backdrop-filter: blur(8px);
|
|
139
|
+
border: 1px solid var(--line);
|
|
140
|
+
box-shadow: var(--shadow-2);
|
|
141
|
+
color: var(--text);
|
|
142
|
+
font-size: var(--text-xl);
|
|
143
|
+
line-height: 1;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition: opacity var(--dur-2) var(--ease-out), scale var(--dur-1) var(--ease-out);
|
|
146
|
+
}
|
|
147
|
+
.carousel-track::scroll-button(*):hover { scale: 1.06; }
|
|
148
|
+
.carousel-track::scroll-button(*):disabled { opacity: 0; pointer-events: none; }
|
|
149
|
+
.carousel-track::scroll-button(inline-start) {
|
|
150
|
+
content: "\2039" / "Previous slide";
|
|
151
|
+
inset-inline-start: var(--space-3);
|
|
152
|
+
}
|
|
153
|
+
.carousel-track::scroll-button(inline-end) {
|
|
154
|
+
content: "\203A" / "Next slide";
|
|
155
|
+
inset-inline-end: var(--space-3);
|
|
156
|
+
}
|
|
157
|
+
@media (pointer: coarse) {
|
|
158
|
+
.carousel-track::scroll-button(*) { display: none; }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Text over a slide. The generous block-start padding is there to give the
|
|
163
|
+
scrim room to fade — without a background the near-white text sits
|
|
164
|
+
directly on the image and disappears over anything light. */
|
|
165
|
+
.carousel-caption {
|
|
166
|
+
position: absolute;
|
|
167
|
+
inset-inline: 0;
|
|
168
|
+
inset-block-end: 0;
|
|
169
|
+
padding: var(--space-8) var(--space-5) var(--space-5);
|
|
170
|
+
color: oklch(98% 0 0);
|
|
171
|
+
background: var(--scrim);
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}
|
|
174
|
+
.carousel-caption :is(a, button) { pointer-events: auto; }
|
|
175
|
+
|
|
176
|
+
/* --- Drawer --------------------------------------------------------------
|
|
177
|
+
A side panel. The bottom sheet is in 08-mobile; this one comes in from an
|
|
178
|
+
edge and is the right shape for filters, settings, and a mobile nav.
|
|
179
|
+
---------------------------------------------------------------------- */
|
|
180
|
+
.drawer {
|
|
181
|
+
--drawer-size: min(22rem, 88vw);
|
|
182
|
+
position: fixed;
|
|
183
|
+
inset-block: 0;
|
|
184
|
+
inset-inline-start: 0;
|
|
185
|
+
inset-inline-end: auto;
|
|
186
|
+
margin: 0;
|
|
187
|
+
max-block-size: none;
|
|
188
|
+
block-size: 100dvh;
|
|
189
|
+
inline-size: var(--drawer-size);
|
|
190
|
+
max-inline-size: var(--drawer-size);
|
|
191
|
+
padding: 0;
|
|
192
|
+
border: 0;
|
|
193
|
+
border-inline-end: 1px solid var(--line);
|
|
194
|
+
background: var(--surface);
|
|
195
|
+
color: var(--text);
|
|
196
|
+
box-shadow: var(--shadow-4);
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
translate: -100% 0;
|
|
200
|
+
transition: translate var(--dur-3) var(--ease-emphasized),
|
|
201
|
+
display var(--dur-3) allow-discrete,
|
|
202
|
+
overlay var(--dur-3) allow-discrete;
|
|
203
|
+
}
|
|
204
|
+
.drawer[open] { translate: 0; }
|
|
205
|
+
@starting-style { .drawer[open] { translate: -100% 0; } }
|
|
206
|
+
|
|
207
|
+
/* Pins the drawer to the ending edge instead of the starting one. The inset
|
|
208
|
+
is logical and mirrors for free; the translate is not, which is why this
|
|
209
|
+
rule declares its own. */
|
|
210
|
+
.drawer-end {
|
|
211
|
+
inset-inline: auto 0;
|
|
212
|
+
border-inline: 1px solid var(--line) 0;
|
|
213
|
+
border-inline-start: 1px solid var(--line);
|
|
214
|
+
border-inline-end: 0;
|
|
215
|
+
translate: 100% 0;
|
|
216
|
+
}
|
|
217
|
+
@starting-style { .drawer-end[open] { translate: 100% 0; } }
|
|
218
|
+
|
|
219
|
+
.drawer::backdrop {
|
|
220
|
+
background: oklch(18% .02 var(--hue-neutral) / .5);
|
|
221
|
+
opacity: 0;
|
|
222
|
+
transition: opacity var(--dur-3) var(--ease-out),
|
|
223
|
+
display var(--dur-3) allow-discrete,
|
|
224
|
+
overlay var(--dur-3) allow-discrete;
|
|
225
|
+
}
|
|
226
|
+
.drawer[open]::backdrop { opacity: 1; }
|
|
227
|
+
@starting-style { .drawer[open]::backdrop { opacity: 0; } }
|
|
228
|
+
|
|
229
|
+
/* The title row. Pads for a notch with max(--space-4, safe-area-inset-top). */
|
|
230
|
+
.drawer-header {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: var(--space-3);
|
|
234
|
+
padding: var(--space-4) var(--space-5);
|
|
235
|
+
padding-block-start: max(var(--space-4), env(safe-area-inset-top));
|
|
236
|
+
border-block-end: 1px solid var(--line);
|
|
237
|
+
flex: 0 0 auto;
|
|
238
|
+
}
|
|
239
|
+
/* The drawer's heading. Point aria-labelledby at it. */
|
|
240
|
+
.drawer-title { font-size: var(--text-md); font-weight: 640; }
|
|
241
|
+
/* The scrolling region. overscroll-behavior: contain stops a scroll that
|
|
242
|
+
reaches the end from chaining to the page behind. */
|
|
243
|
+
.drawer-body {
|
|
244
|
+
flex: 1 1 auto;
|
|
245
|
+
overflow-y: auto;
|
|
246
|
+
overscroll-behavior: contain;
|
|
247
|
+
padding: var(--space-4) var(--space-5);
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
gap: var(--space-4);
|
|
251
|
+
}
|
|
252
|
+
/* The action row, pinned to the bottom. Pads for the home indicator with
|
|
253
|
+
max(--space-4, safe-area-inset-bottom). */
|
|
254
|
+
.drawer-footer {
|
|
255
|
+
flex: 0 0 auto;
|
|
256
|
+
display: flex;
|
|
257
|
+
gap: var(--space-3);
|
|
258
|
+
padding: var(--space-4) var(--space-5);
|
|
259
|
+
padding-block-end: max(var(--space-4), env(safe-area-inset-bottom));
|
|
260
|
+
border-block-start: 1px solid var(--line);
|
|
261
|
+
background: var(--surface-2);
|
|
262
|
+
}
|
|
263
|
+
.drawer-footer > .btn { flex: 1 1 auto; }
|
|
264
|
+
|
|
265
|
+
/* --- Mega menu -----------------------------------------------------------
|
|
266
|
+
A wide panel under a nav item. Built on the popover attribute, so escape
|
|
267
|
+
and outside-click are the browser's job.
|
|
268
|
+
---------------------------------------------------------------------- */
|
|
269
|
+
.mega {
|
|
270
|
+
position: absolute;
|
|
271
|
+
inset: auto;
|
|
272
|
+
margin: 0;
|
|
273
|
+
inline-size: min(64rem, calc(100vw - var(--space-8)));
|
|
274
|
+
padding: var(--space-5);
|
|
275
|
+
background: var(--surface);
|
|
276
|
+
border: 1px solid var(--line);
|
|
277
|
+
border-radius: var(--r-md);
|
|
278
|
+
box-shadow: var(--shadow-4);
|
|
279
|
+
opacity: 0;
|
|
280
|
+
translate: 0 -8px;
|
|
281
|
+
transition: opacity var(--dur-2) var(--ease-out),
|
|
282
|
+
translate var(--dur-2) var(--ease-out),
|
|
283
|
+
overlay var(--dur-2) allow-discrete,
|
|
284
|
+
display var(--dur-2) allow-discrete;
|
|
285
|
+
}
|
|
286
|
+
.mega:popover-open { opacity: 1; translate: 0; }
|
|
287
|
+
@starting-style { .mega:popover-open { opacity: 0; translate: 0 -8px; } }
|
|
288
|
+
.mega::backdrop { background: transparent; }
|
|
289
|
+
|
|
290
|
+
.mega-grid {
|
|
291
|
+
display: grid;
|
|
292
|
+
gap: var(--space-5);
|
|
293
|
+
grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
|
|
294
|
+
}
|
|
295
|
+
.mega-col { display: flex; flex-direction: column; gap: var(--space-1); }
|
|
296
|
+
.mega-heading {
|
|
297
|
+
font-size: var(--text-xs);
|
|
298
|
+
font-weight: 700;
|
|
299
|
+
color: var(--text-faint);
|
|
300
|
+
padding: var(--space-2) var(--space-2) var(--space-1);
|
|
301
|
+
}
|
|
302
|
+
.mega-item {
|
|
303
|
+
display: flex;
|
|
304
|
+
align-items: flex-start;
|
|
305
|
+
gap: var(--space-3);
|
|
306
|
+
padding: var(--space-2);
|
|
307
|
+
border-radius: var(--r-sm);
|
|
308
|
+
color: var(--text);
|
|
309
|
+
text-decoration: none;
|
|
310
|
+
transition: background-color var(--dur-1) var(--ease-out);
|
|
311
|
+
}
|
|
312
|
+
.mega-item:hover { background: var(--surface-hover); }
|
|
313
|
+
.mega-item-title { font-weight: 560; font-size: var(--text-sm); }
|
|
314
|
+
.mega-item-note { font-size: var(--text-xs); color: var(--text-muted); }
|
|
315
|
+
.mega-feature {
|
|
316
|
+
grid-column: span 1;
|
|
317
|
+
padding: var(--space-4);
|
|
318
|
+
border-radius: var(--r-sm);
|
|
319
|
+
background: var(--brand-soft);
|
|
320
|
+
color: var(--brand-soft-text);
|
|
321
|
+
}
|
|
322
|
+
.mega-footer {
|
|
323
|
+
display: flex;
|
|
324
|
+
gap: var(--space-4);
|
|
325
|
+
flex-wrap: wrap;
|
|
326
|
+
margin-block-start: var(--space-5);
|
|
327
|
+
padding-block-start: var(--space-4);
|
|
328
|
+
border-block-start: 1px solid var(--line);
|
|
329
|
+
font-size: var(--text-sm);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* --- Speed dial ----------------------------------------------------------
|
|
333
|
+
A FAB that fans out into related actions.
|
|
334
|
+
---------------------------------------------------------------------- */
|
|
335
|
+
.speed-dial {
|
|
336
|
+
position: fixed;
|
|
337
|
+
inset-block-end: calc(var(--space-5) + env(safe-area-inset-bottom));
|
|
338
|
+
inset-inline-end: var(--space-5);
|
|
339
|
+
z-index: var(--z-nav);
|
|
340
|
+
display: flex;
|
|
341
|
+
flex-direction: column-reverse;
|
|
342
|
+
align-items: flex-end;
|
|
343
|
+
gap: var(--space-3);
|
|
344
|
+
}
|
|
345
|
+
body:has(.tabbar) .speed-dial {
|
|
346
|
+
inset-block-end: calc(var(--space-4) + var(--tap) + var(--space-3) + env(safe-area-inset-bottom));
|
|
347
|
+
}
|
|
348
|
+
@media (min-width: 48rem) {
|
|
349
|
+
body:has(.tabbar) .speed-dial { inset-block-end: calc(var(--space-5) + env(safe-area-inset-bottom)); }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.speed-dial-actions {
|
|
353
|
+
display: flex;
|
|
354
|
+
flex-direction: column-reverse;
|
|
355
|
+
align-items: flex-end;
|
|
356
|
+
gap: var(--space-3);
|
|
357
|
+
}
|
|
358
|
+
@supports (animation-delay: calc(sibling-index() * 1ms)) {
|
|
359
|
+
.speed-dial-action { --i: calc(sibling-index() - 1); }
|
|
360
|
+
}
|
|
361
|
+
@supports not (animation-delay: calc(sibling-index() * 1ms)) {
|
|
362
|
+
.speed-dial-action:nth-child(1) { --i: 0; }
|
|
363
|
+
.speed-dial-action:nth-child(2) { --i: 1; }
|
|
364
|
+
.speed-dial-action:nth-child(3) { --i: 2; }
|
|
365
|
+
.speed-dial-action:nth-child(4) { --i: 3; }
|
|
366
|
+
.speed-dial-action:nth-child(5) { --i: 4; }
|
|
367
|
+
.speed-dial-action:nth-child(6) { --i: 5; }
|
|
368
|
+
.speed-dial-action:nth-child(7) { --i: 6; }
|
|
369
|
+
.speed-dial-action:nth-child(8) { --i: 7; }
|
|
370
|
+
}
|
|
371
|
+
.speed-dial-action {
|
|
372
|
+
display: flex;
|
|
373
|
+
align-items: center;
|
|
374
|
+
gap: var(--space-3);
|
|
375
|
+
opacity: 0;
|
|
376
|
+
translate: 0 12px;
|
|
377
|
+
scale: .85;
|
|
378
|
+
pointer-events: none;
|
|
379
|
+
transition: opacity var(--dur-2) var(--ease-out),
|
|
380
|
+
translate var(--dur-2) var(--ease-spring),
|
|
381
|
+
scale var(--dur-2) var(--ease-spring);
|
|
382
|
+
transition-delay: calc(var(--i, 0) * 35ms);
|
|
383
|
+
}
|
|
384
|
+
.speed-dial.is-open .speed-dial-action {
|
|
385
|
+
opacity: 1;
|
|
386
|
+
translate: 0;
|
|
387
|
+
scale: 1;
|
|
388
|
+
pointer-events: auto;
|
|
389
|
+
}
|
|
390
|
+
.speed-dial-label {
|
|
391
|
+
padding: var(--space-1) var(--space-3);
|
|
392
|
+
border-radius: var(--r-sm);
|
|
393
|
+
background: var(--ink-900);
|
|
394
|
+
color: var(--ink-50);
|
|
395
|
+
font-size: var(--text-xs);
|
|
396
|
+
font-weight: 560;
|
|
397
|
+
white-space: nowrap;
|
|
398
|
+
box-shadow: var(--shadow-2);
|
|
399
|
+
}
|
|
400
|
+
.speed-dial-button {
|
|
401
|
+
inline-size: 42px;
|
|
402
|
+
block-size: 42px;
|
|
403
|
+
border-radius: var(--r-full);
|
|
404
|
+
display: grid;
|
|
405
|
+
place-items: center;
|
|
406
|
+
background: var(--surface);
|
|
407
|
+
border: 1px solid var(--line);
|
|
408
|
+
color: var(--text);
|
|
409
|
+
box-shadow: var(--shadow-3);
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
}
|
|
412
|
+
.speed-dial-button:hover { background: var(--surface-hover); }
|
|
413
|
+
.speed-dial > .fab { position: static; }
|
|
414
|
+
.speed-dial > .fab .icon { transition: rotate var(--dur-3) var(--ease-spring); }
|
|
415
|
+
.speed-dial.is-open > .fab .icon { rotate: 135deg; }
|
|
416
|
+
|
|
417
|
+
/* --- Back to top ---------------------------------------------------------
|
|
418
|
+
A link, not a button. #top is a real target at the head of the document,
|
|
419
|
+
so the browser moves focus there along with the scroll. A button calling
|
|
420
|
+
scrollTo() leaves a keyboard user parked at the bottom of the page while
|
|
421
|
+
looking at the top of it, which is the whole bug this component usually
|
|
422
|
+
ships with.
|
|
423
|
+
|
|
424
|
+
Showing and hiding is a scroll-driven animation, so there is no scroll
|
|
425
|
+
listener anywhere -- same mechanism as .scroll-progress in 16-motion.
|
|
426
|
+
visibility is in the keyframe on purpose: it takes the link out of the tab
|
|
427
|
+
order and out of the accessibility tree while it is off screen, which is
|
|
428
|
+
what aria-hidden reaches for and which CSS can actually do by itself.
|
|
429
|
+
deck.js only steps in where animation-timeline is missing, and it uses an
|
|
430
|
+
IntersectionObserver on a sentinel, not a scroll handler.
|
|
431
|
+
---------------------------------------------------------------------- */
|
|
432
|
+
.back-to-top {
|
|
433
|
+
position: fixed;
|
|
434
|
+
inset-block-end: calc(var(--space-5) + env(safe-area-inset-bottom));
|
|
435
|
+
inset-inline-end: var(--space-5);
|
|
436
|
+
/* One above the rest of the bottom chrome, so it stays clickable whatever
|
|
437
|
+
order the markup happens to be in. Same token, same band. */
|
|
438
|
+
z-index: calc(var(--z-nav) + 1);
|
|
439
|
+
inline-size: var(--tap);
|
|
440
|
+
block-size: var(--tap);
|
|
441
|
+
border-radius: var(--r-full);
|
|
442
|
+
display: grid;
|
|
443
|
+
place-items: center;
|
|
444
|
+
background: var(--surface);
|
|
445
|
+
color: var(--text);
|
|
446
|
+
border: 1px solid var(--line);
|
|
447
|
+
box-shadow: var(--shadow-3);
|
|
448
|
+
text-decoration: none;
|
|
449
|
+
transition: opacity var(--dur-2) var(--ease-out),
|
|
450
|
+
visibility var(--dur-2) var(--ease-out),
|
|
451
|
+
background-color var(--dur-2) var(--ease-out),
|
|
452
|
+
border-color var(--dur-2) var(--ease-out),
|
|
453
|
+
scale var(--dur-2) var(--ease-spring);
|
|
454
|
+
}
|
|
455
|
+
.back-to-top:hover {
|
|
456
|
+
background: var(--brand-600);
|
|
457
|
+
color: var(--text-on-brand);
|
|
458
|
+
border-color: transparent;
|
|
459
|
+
scale: 1.06;
|
|
460
|
+
}
|
|
461
|
+
.back-to-top:active { scale: .94; }
|
|
462
|
+
|
|
463
|
+
/* Stack above whatever else is parked in that corner. .fab and .speed-dial
|
|
464
|
+
both sit at inset-block-end: var(--space-5) and both already lift over a
|
|
465
|
+
tab bar, so back-to-top repeats their arithmetic one storey higher. 56px
|
|
466
|
+
is the FAB's own size from 05-buttons; it has no token, so neither does
|
|
467
|
+
this. Every branch keeps env(safe-area-inset-bottom) on the end. */
|
|
468
|
+
body:has(:is(.fab, .speed-dial)) .back-to-top {
|
|
469
|
+
inset-block-end: calc(var(--space-5) + 56px + var(--space-3) + env(safe-area-inset-bottom));
|
|
470
|
+
}
|
|
471
|
+
body:has(.tabbar) .back-to-top {
|
|
472
|
+
inset-block-end: calc(var(--space-4) + var(--tap) + var(--space-3) + env(safe-area-inset-bottom));
|
|
473
|
+
}
|
|
474
|
+
body:has(.tabbar):has(:is(.fab, .speed-dial)) .back-to-top {
|
|
475
|
+
inset-block-end: calc(var(--space-4) + var(--tap) + var(--space-3) + 56px + var(--space-3) + env(safe-area-inset-bottom));
|
|
476
|
+
}
|
|
477
|
+
@media (min-width: 48rem) {
|
|
478
|
+
/* The tab bar is display:none at this width, so the offsets collapse back */
|
|
479
|
+
body:has(.tabbar) .back-to-top {
|
|
480
|
+
inset-block-end: calc(var(--space-5) + env(safe-area-inset-bottom));
|
|
481
|
+
}
|
|
482
|
+
body:has(.tabbar):has(:is(.fab, .speed-dial)) .back-to-top {
|
|
483
|
+
inset-block-end: calc(var(--space-5) + 56px + var(--space-3) + env(safe-area-inset-bottom));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* A speed dial fans out across the band the button sits in -- its collapsed
|
|
488
|
+
actions still take their layout space, so the corner is the dial's while it
|
|
489
|
+
is open. display is the one property the reveal animation does not touch,
|
|
490
|
+
so this wins the cascade without !important. */
|
|
491
|
+
body:has(.speed-dial.is-open) .back-to-top { display: none; }
|
|
492
|
+
|
|
493
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
494
|
+
@supports (animation-timeline: scroll()) {
|
|
495
|
+
.back-to-top {
|
|
496
|
+
animation: dk-btt-in linear both;
|
|
497
|
+
animation-timeline: scroll(root block);
|
|
498
|
+
animation-range: 400px 520px;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/* No scroll timeline: deck.js marks the link with data-deck-btt and toggles
|
|
502
|
+
.is-visible from an IntersectionObserver on a sentinel at the top of the
|
|
503
|
+
page. Unmarked -- no JS -- the link simply stays visible and still works. */
|
|
504
|
+
@supports not (animation-timeline: scroll()) {
|
|
505
|
+
.back-to-top[data-deck-btt] { opacity: 0; visibility: hidden; }
|
|
506
|
+
.back-to-top[data-deck-btt].is-visible { opacity: 1; visibility: visible; }
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* Under prefers-reduced-motion: reduce neither branch above applies, so the
|
|
511
|
+
link is simply always there -- no entrance, and 02-reset has already put
|
|
512
|
+
html back to scroll-behavior: auto, so the jump is instant. */
|
|
513
|
+
@keyframes dk-btt-in {
|
|
514
|
+
from { opacity: 0; visibility: hidden; }
|
|
515
|
+
to { opacity: 1; visibility: visible; }
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* --- Stepper -------------------------------------------------------------
|
|
519
|
+
A wizard. The timeline in 07 records what happened; this one shows where
|
|
520
|
+
you are in a process you are actively moving through.
|
|
521
|
+
---------------------------------------------------------------------- */
|
|
522
|
+
.stepper {
|
|
523
|
+
display: flex;
|
|
524
|
+
gap: var(--space-2);
|
|
525
|
+
overflow-x: auto;
|
|
526
|
+
scrollbar-width: none;
|
|
527
|
+
counter-reset: step;
|
|
528
|
+
}
|
|
529
|
+
.stepper::-webkit-scrollbar { display: none; }
|
|
530
|
+
.step {
|
|
531
|
+
flex: 1 1 0;
|
|
532
|
+
min-inline-size: 7rem;
|
|
533
|
+
display: flex;
|
|
534
|
+
flex-direction: column;
|
|
535
|
+
gap: var(--space-2);
|
|
536
|
+
position: relative;
|
|
537
|
+
counter-increment: step;
|
|
538
|
+
}
|
|
539
|
+
.step-marker {
|
|
540
|
+
display: flex;
|
|
541
|
+
align-items: center;
|
|
542
|
+
gap: var(--space-2);
|
|
543
|
+
}
|
|
544
|
+
.step-marker::before {
|
|
545
|
+
content: counter(step);
|
|
546
|
+
inline-size: 26px;
|
|
547
|
+
block-size: 26px;
|
|
548
|
+
flex: 0 0 auto;
|
|
549
|
+
display: grid;
|
|
550
|
+
place-items: center;
|
|
551
|
+
border-radius: var(--r-full);
|
|
552
|
+
border: 1.5px solid var(--line-strong);
|
|
553
|
+
background: var(--surface);
|
|
554
|
+
font-size: var(--text-xs);
|
|
555
|
+
font-weight: 640;
|
|
556
|
+
color: var(--text-muted);
|
|
557
|
+
transition: background-color var(--dur-2) var(--ease-out),
|
|
558
|
+
border-color var(--dur-2) var(--ease-out),
|
|
559
|
+
color var(--dur-2) var(--ease-out);
|
|
560
|
+
}
|
|
561
|
+
.step-marker::after {
|
|
562
|
+
content: "";
|
|
563
|
+
flex: 1 1 auto;
|
|
564
|
+
block-size: 2px;
|
|
565
|
+
background: var(--line);
|
|
566
|
+
border-radius: var(--r-full);
|
|
567
|
+
}
|
|
568
|
+
.step:last-child .step-marker::after { display: none; }
|
|
569
|
+
.step-label { font-size: var(--text-sm); font-weight: 560; color: var(--text-muted); }
|
|
570
|
+
.step-note { font-size: var(--text-xs); color: var(--text-faint); }
|
|
571
|
+
|
|
572
|
+
.step.is-done .step-marker::before {
|
|
573
|
+
content: "";
|
|
574
|
+
background: var(--brand-600);
|
|
575
|
+
border-color: var(--brand-600);
|
|
576
|
+
background-image: linear-gradient(var(--text-on-brand), var(--text-on-brand));
|
|
577
|
+
background-size: 10px 10px;
|
|
578
|
+
background-position: center;
|
|
579
|
+
background-repeat: no-repeat;
|
|
580
|
+
-webkit-mask: none;
|
|
581
|
+
clip-path: none;
|
|
582
|
+
}
|
|
583
|
+
.step.is-done .step-marker::after { background: var(--brand-500); }
|
|
584
|
+
.step.is-done .step-label { color: var(--text); }
|
|
585
|
+
.step.is-current .step-marker::before {
|
|
586
|
+
border-color: var(--brand-600);
|
|
587
|
+
color: var(--brand);
|
|
588
|
+
box-shadow: 0 0 0 3px var(--brand-soft);
|
|
589
|
+
}
|
|
590
|
+
.step.is-current .step-label { color: var(--text); font-weight: 620; }
|
|
591
|
+
|
|
592
|
+
/* Vertical on a phone, where five steps side by side never fits */
|
|
593
|
+
.stepper-vertical { flex-direction: column; gap: var(--space-5); }
|
|
594
|
+
.stepper-vertical .step { flex-direction: row; gap: var(--space-3); min-inline-size: 0; }
|
|
595
|
+
.stepper-vertical .step-marker { flex-direction: column; align-self: stretch; }
|
|
596
|
+
.stepper-vertical .step-marker::after { inline-size: 2px; block-size: auto; flex: 1 1 auto; }
|
|
597
|
+
@media (max-width: 40rem) {
|
|
598
|
+
/* Horizontal where there is room, vertical below a breakpoint. Prefer this
|
|
599
|
+
over .stepper-vertical: four steps with notes do not fit across a phone,
|
|
600
|
+
and a horizontal stepper that squashes is unreadable. */
|
|
601
|
+
.stepper-auto { flex-direction: column; gap: var(--space-5); }
|
|
602
|
+
.stepper-auto .step { flex-direction: row; gap: var(--space-3); min-inline-size: 0; }
|
|
603
|
+
.stepper-auto .step-marker { flex-direction: column; align-self: stretch; }
|
|
604
|
+
.stepper-auto .step-marker::after { inline-size: 2px; block-size: auto; flex: 1 1 auto; }
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/* --- Sticky banner -------------------------------------------------------
|
|
608
|
+
An announcement strip. Top or bottom, dismissible, out of the way.
|
|
609
|
+
---------------------------------------------------------------------- */
|
|
610
|
+
.banner {
|
|
611
|
+
position: sticky;
|
|
612
|
+
inset-block-start: 0;
|
|
613
|
+
z-index: calc(var(--z-nav) + 1);
|
|
614
|
+
display: flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
gap: var(--space-3);
|
|
617
|
+
padding: var(--space-3) var(--space-gutter);
|
|
618
|
+
padding-block-start: max(var(--space-3), env(safe-area-inset-top));
|
|
619
|
+
background: var(--brand-soft);
|
|
620
|
+
color: var(--brand-soft-text);
|
|
621
|
+
border-block-end: 1px solid color-mix(in oklab, var(--brand-400) 30%, transparent);
|
|
622
|
+
font-size: var(--text-sm);
|
|
623
|
+
}
|
|
624
|
+
.banner-bottom {
|
|
625
|
+
inset-block: auto 0;
|
|
626
|
+
border-block: 1px solid color-mix(in oklab, var(--brand-400) 30%, transparent) 0;
|
|
627
|
+
border-block-start: 1px solid color-mix(in oklab, var(--brand-400) 30%, transparent);
|
|
628
|
+
border-block-end: 0;
|
|
629
|
+
padding-block-end: max(var(--space-3), env(safe-area-inset-bottom));
|
|
630
|
+
}
|
|
631
|
+
.banner-fixed { position: fixed; inset-inline: 0; }
|
|
632
|
+
.banner-dark { background: var(--ink-900); color: var(--ink-100); border-color: transparent; }
|
|
633
|
+
.banner-warn { background: var(--warn-100); color: var(--warn-700); border-color: transparent; }
|
|
634
|
+
.banner-close { margin-inline-start: auto; opacity: .7; cursor: pointer; flex: 0 0 auto; }
|
|
635
|
+
.banner-close:hover { opacity: 1; }
|
|
636
|
+
.banner[hidden] { display: none; }
|
|
637
|
+
|
|
638
|
+
/* --- Jumbotron -----------------------------------------------------------
|
|
639
|
+
A hero block. Kept deliberately plain so the content carries it.
|
|
640
|
+
---------------------------------------------------------------------- */
|
|
641
|
+
.jumbotron {
|
|
642
|
+
display: flex;
|
|
643
|
+
flex-direction: column;
|
|
644
|
+
gap: var(--space-5);
|
|
645
|
+
padding: var(--space-section) var(--space-gutter);
|
|
646
|
+
border-radius: var(--r-lg);
|
|
647
|
+
background: var(--surface);
|
|
648
|
+
border: 1px solid var(--line);
|
|
649
|
+
text-align: start;
|
|
650
|
+
}
|
|
651
|
+
.jumbotron-center { text-align: center; align-items: center; }
|
|
652
|
+
.jumbotron-center .lede { margin-inline: auto; }
|
|
653
|
+
.jumbotron-media {
|
|
654
|
+
display: grid;
|
|
655
|
+
gap: var(--space-8);
|
|
656
|
+
align-items: center;
|
|
657
|
+
grid-template-columns: 1fr;
|
|
658
|
+
}
|
|
659
|
+
@media (min-width: 56rem) {
|
|
660
|
+
.jumbotron-media { grid-template-columns: 1.1fr .9fr; }
|
|
661
|
+
}
|
|
662
|
+
.jumbotron h1 { max-inline-size: 18ch; }
|
|
663
|
+
.jumbotron-center h1 { margin-inline: auto; }
|
|
664
|
+
.jumbotron-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
|
|
665
|
+
@media (max-width: 30rem) {
|
|
666
|
+
.jumbotron-actions > .btn { inline-size: 100%; }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* --- Footer --------------------------------------------------------------
|
|
670
|
+
A real component rather than the demo's ad-hoc one.
|
|
671
|
+
---------------------------------------------------------------------- */
|
|
672
|
+
.footer {
|
|
673
|
+
border-block-start: 1px solid var(--line);
|
|
674
|
+
padding-block: var(--space-10) var(--space-8);
|
|
675
|
+
color: var(--text-muted);
|
|
676
|
+
font-size: var(--text-sm);
|
|
677
|
+
}
|
|
678
|
+
.footer-grid {
|
|
679
|
+
display: grid;
|
|
680
|
+
gap: var(--space-8);
|
|
681
|
+
grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
|
|
682
|
+
padding-block-end: var(--space-8);
|
|
683
|
+
}
|
|
684
|
+
.footer-brand { display: flex; flex-direction: column; gap: var(--space-3); max-inline-size: 26ch; }
|
|
685
|
+
.footer-col { display: flex; flex-direction: column; gap: var(--space-2); }
|
|
686
|
+
.footer-heading {
|
|
687
|
+
font-size: var(--text-xs);
|
|
688
|
+
font-weight: 700;
|
|
689
|
+
color: var(--text);
|
|
690
|
+
margin-block-end: var(--space-1);
|
|
691
|
+
}
|
|
692
|
+
/* FINDINGS 65: this is the same token .footer sets on itself, and the
|
|
693
|
+
underline is removed -- so a link inside a SENTENCE in the footer has no
|
|
694
|
+
colour difference, no weight difference and no underline. Nothing marks it
|
|
695
|
+
until hover, which does not help a keyboard or touch user.
|
|
696
|
+
|
|
697
|
+
It is fine in a .footer-col, which is a stack of links and nothing else.
|
|
698
|
+
It is a WCAG 1.4.1 failure in prose. Scope the underline removal to the
|
|
699
|
+
link containers rather than to every descendant. */
|
|
700
|
+
.footer a { color: var(--text-muted); text-decoration: none; }
|
|
701
|
+
.footer a:hover { color: var(--brand); text-decoration: underline; }
|
|
702
|
+
.footer-bottom {
|
|
703
|
+
display: flex;
|
|
704
|
+
flex-wrap: wrap;
|
|
705
|
+
align-items: center;
|
|
706
|
+
gap: var(--space-3);
|
|
707
|
+
padding-block-start: var(--space-5);
|
|
708
|
+
border-block-start: 1px solid var(--line);
|
|
709
|
+
font-size: var(--text-xs);
|
|
710
|
+
}
|
|
711
|
+
.footer-social { display: flex; gap: var(--space-1); margin-inline-start: auto; }
|
|
712
|
+
.footer-social a {
|
|
713
|
+
inline-size: 34px;
|
|
714
|
+
block-size: 34px;
|
|
715
|
+
display: grid;
|
|
716
|
+
place-items: center;
|
|
717
|
+
border-radius: var(--r-sm);
|
|
718
|
+
}
|
|
719
|
+
.footer-social a:hover { background: var(--surface-hover); }
|
|
720
|
+
|
|
721
|
+
/* --- Indicators ----------------------------------------------------------
|
|
722
|
+
Status dots, counts on an icon, and unread markers.
|
|
723
|
+
---------------------------------------------------------------------- */
|
|
724
|
+
.indicator {
|
|
725
|
+
display: inline-block;
|
|
726
|
+
inline-size: 8px;
|
|
727
|
+
block-size: 8px;
|
|
728
|
+
border-radius: var(--r-full);
|
|
729
|
+
background: var(--ink-400);
|
|
730
|
+
flex: 0 0 auto;
|
|
731
|
+
}
|
|
732
|
+
.indicator-good { background: var(--good-500); }
|
|
733
|
+
.indicator-warn { background: var(--warn-500); }
|
|
734
|
+
.indicator-bad { background: var(--bad-500); }
|
|
735
|
+
.indicator-brand { background: var(--brand-500); }
|
|
736
|
+
.indicator-lg { inline-size: 11px; block-size: 11px; }
|
|
737
|
+
.indicator-ring { box-shadow: 0 0 0 2px var(--surface); }
|
|
738
|
+
|
|
739
|
+
/* A count or dot anchored to a corner of something */
|
|
740
|
+
.with-indicator { position: relative; display: inline-flex; }
|
|
741
|
+
.with-indicator > .indicator-badge {
|
|
742
|
+
position: absolute;
|
|
743
|
+
inset-block-start: -4px;
|
|
744
|
+
inset-inline-end: -6px;
|
|
745
|
+
min-inline-size: 18px;
|
|
746
|
+
block-size: 18px;
|
|
747
|
+
padding-inline: 5px;
|
|
748
|
+
border-radius: var(--r-full);
|
|
749
|
+
background: var(--bad-600);
|
|
750
|
+
color: var(--text-on-bad);
|
|
751
|
+
font-size: var(--text-2xs);
|
|
752
|
+
font-weight: 700;
|
|
753
|
+
line-height: 18px;
|
|
754
|
+
text-align: center;
|
|
755
|
+
box-shadow: 0 0 0 2px var(--surface);
|
|
756
|
+
font-variant-numeric: tabular-nums;
|
|
757
|
+
}
|
|
758
|
+
.with-indicator > .indicator-badge-dot {
|
|
759
|
+
min-inline-size: 9px;
|
|
760
|
+
inline-size: 9px;
|
|
761
|
+
block-size: 9px;
|
|
762
|
+
padding: 0;
|
|
763
|
+
inset-block-start: -1px;
|
|
764
|
+
inset-inline-end: -1px;
|
|
765
|
+
}
|
|
766
|
+
.with-indicator > .indicator-badge-brand { background: var(--brand-600); color: var(--text-on-brand); }
|
|
767
|
+
|
|
768
|
+
/* Live status line: dot plus label, the pattern used in status pages */
|
|
769
|
+
.status-line { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }
|
|
770
|
+
.status-line .indicator { position: relative; }
|
|
771
|
+
}
|