@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,379 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 16 Motion
|
|
3
|
+
Motion answers an action or shows what changed. It is not decoration.
|
|
4
|
+
Nothing in this file runs unless you ask for it by class, except the
|
|
5
|
+
view-transition setup, which you opt into with one at-rule in your app CSS.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
@layer deck.motion {
|
|
9
|
+
|
|
10
|
+
/* FINDINGS 68: these are real tokens and the documentation cannot see them.
|
|
11
|
+
tools/docs/extract.mjs takes tokens from 01-tokens.css only, so all
|
|
12
|
+
thirteen declared below are absent from dist/api.json, API.md and every
|
|
13
|
+
docs_token_table() -- including --dur-4 and --dur-5, which are used nine
|
|
14
|
+
and four times across the stylesheet. Passing one to docs_token_table()
|
|
15
|
+
renders an empty row rather than failing, which is why it went unnoticed.
|
|
16
|
+
Fix belongs in the extractor, not here. */
|
|
17
|
+
|
|
18
|
+
/* --- Extended motion tokens ---------------------------------------------
|
|
19
|
+
The base set lives in tokens. These are the rest of the vocabulary.
|
|
20
|
+
---------------------------------------------------------------------- */
|
|
21
|
+
:root {
|
|
22
|
+
--ease-in: cubic-bezier(.55, 0, 1, .45);
|
|
23
|
+
--ease-emphasized: cubic-bezier(.2, 0, 0, 1);
|
|
24
|
+
--ease-decelerate: cubic-bezier(0, 0, .2, 1);
|
|
25
|
+
--ease-accelerate: cubic-bezier(.4, 0, 1, 1);
|
|
26
|
+
|
|
27
|
+
/* linear() springs — real spring feel with no JS */
|
|
28
|
+
--ease-bounce: linear(0, .004 2%, .016 4%, .062 8%, .137 12%, .5 24%, .68 28%,
|
|
29
|
+
.824 32%, .93 36%, .995 40%, 1.02 44%, 1.01 52%, 1 60%);
|
|
30
|
+
--ease-overshoot: linear(0, .22 8%, .58 16%, .87 24%, 1.06 32%, 1.11 38%,
|
|
31
|
+
1.08 46%, 1.02 58%, 1);
|
|
32
|
+
--ease-snap: linear(0, .48 12%, .82 22%, .97 32%, 1.02 42%, 1);
|
|
33
|
+
|
|
34
|
+
--dur-0: 0ms;
|
|
35
|
+
--dur-4: 420ms;
|
|
36
|
+
--dur-5: 620ms;
|
|
37
|
+
--dur-slow: 900ms;
|
|
38
|
+
|
|
39
|
+
--stagger-step: 45ms;
|
|
40
|
+
--travel: 12px; /* how far entrances move */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* --- Reduced motion ------------------------------------------------------
|
|
44
|
+
Everything below is wrapped in no-preference. A person who asked their OS
|
|
45
|
+
to calm things down gets the end state instantly, with one exception:
|
|
46
|
+
loading indicators keep moving, because a frozen spinner reads as broken.
|
|
47
|
+
---------------------------------------------------------------------- */
|
|
48
|
+
/* --- Transition utilities ------------------------------------------------ */
|
|
49
|
+
.transition { transition: all var(--dur-2) var(--ease-out); }
|
|
50
|
+
.transition-colors { transition: background-color var(--dur-1) var(--ease-out),
|
|
51
|
+
border-color var(--dur-1) var(--ease-out),
|
|
52
|
+
color var(--dur-1) var(--ease-out),
|
|
53
|
+
fill var(--dur-1) var(--ease-out),
|
|
54
|
+
stroke var(--dur-1) var(--ease-out); }
|
|
55
|
+
.transition-move { transition: translate var(--dur-2) var(--ease-out),
|
|
56
|
+
scale var(--dur-2) var(--ease-out),
|
|
57
|
+
rotate var(--dur-2) var(--ease-out); }
|
|
58
|
+
.transition-size { transition: inline-size var(--dur-3) var(--ease-out),
|
|
59
|
+
block-size var(--dur-3) var(--ease-out); }
|
|
60
|
+
.transition-opacity{ transition: opacity var(--dur-2) var(--ease-out); }
|
|
61
|
+
|
|
62
|
+
.dur-1 { transition-duration: var(--dur-1); animation-duration: var(--dur-1); }
|
|
63
|
+
.dur-2 { transition-duration: var(--dur-2); animation-duration: var(--dur-2); }
|
|
64
|
+
.dur-3 { transition-duration: var(--dur-3); animation-duration: var(--dur-3); }
|
|
65
|
+
.dur-4 { transition-duration: var(--dur-4); animation-duration: var(--dur-4); }
|
|
66
|
+
.dur-5 { transition-duration: var(--dur-5); animation-duration: var(--dur-5); }
|
|
67
|
+
|
|
68
|
+
.ease-out { transition-timing-function: var(--ease-out); animation-timing-function: var(--ease-out); }
|
|
69
|
+
.ease-in { transition-timing-function: var(--ease-in); animation-timing-function: var(--ease-in); }
|
|
70
|
+
.ease-spring { transition-timing-function: var(--ease-spring); animation-timing-function: var(--ease-spring); }
|
|
71
|
+
.ease-bounce { transition-timing-function: var(--ease-bounce); animation-timing-function: var(--ease-bounce); }
|
|
72
|
+
.ease-overshoot { transition-timing-function: var(--ease-overshoot); animation-timing-function: var(--ease-overshoot); }
|
|
73
|
+
.ease-linear { transition-timing-function: linear; animation-timing-function: linear; }
|
|
74
|
+
|
|
75
|
+
.delay-1 { transition-delay: 60ms; animation-delay: 60ms; }
|
|
76
|
+
.delay-2 { transition-delay: 140ms; animation-delay: 140ms; }
|
|
77
|
+
.delay-3 { transition-delay: 260ms; animation-delay: 260ms; }
|
|
78
|
+
.no-motion { transition: none !important; animation: none !important; }
|
|
79
|
+
|
|
80
|
+
/* Tell the compositor what is about to move, then let go. Never leave
|
|
81
|
+
will-change on permanently — it costs memory on every element. */
|
|
82
|
+
.will-move:is(:hover, :focus-within, .is-animating) { will-change: translate, scale, opacity; }
|
|
83
|
+
|
|
84
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
85
|
+
|
|
86
|
+
/* --- Entrances --------------------------------------------------------
|
|
87
|
+
One-shot, on load. Use for content that appears, not for every section.
|
|
88
|
+
-------------------------------------------------------------------- */
|
|
89
|
+
.enter { animation: dk-fade var(--dur-3) var(--ease-out) both; }
|
|
90
|
+
.enter-rise { animation: dk-rise var(--dur-3) var(--ease-out) both; }
|
|
91
|
+
.enter-drop { animation: dk-drop var(--dur-3) var(--ease-out) both; }
|
|
92
|
+
.enter-start { animation: dk-in-start var(--dur-3) var(--ease-out) both; }
|
|
93
|
+
.enter-end { animation: dk-in-end var(--dur-3) var(--ease-out) both; }
|
|
94
|
+
.enter-pop { animation: dk-pop var(--dur-3) var(--ease-spring) both; }
|
|
95
|
+
.enter-blur { animation: dk-blur var(--dur-4) var(--ease-out) both; }
|
|
96
|
+
|
|
97
|
+
@keyframes dk-fade { from { opacity: 0; } }
|
|
98
|
+
@keyframes dk-rise { from { opacity: 0; translate: 0 var(--travel); } }
|
|
99
|
+
@keyframes dk-drop { from { opacity: 0; translate: 0 calc(var(--travel) * -1); } }
|
|
100
|
+
@keyframes dk-in-start { from { opacity: 0; translate: calc(var(--travel) * -1.6) 0; } }
|
|
101
|
+
@keyframes dk-in-end { from { opacity: 0; translate: calc(var(--travel) * 1.6) 0; } }
|
|
102
|
+
@keyframes dk-pop { from { opacity: 0; scale: .92; } }
|
|
103
|
+
@keyframes dk-blur { from { opacity: 0; filter: blur(8px); scale: 1.01; } }
|
|
104
|
+
|
|
105
|
+
/* --- Stagger ----------------------------------------------------------
|
|
106
|
+
Put .stagger on the parent. Children animate in sequence. sibling-index()
|
|
107
|
+
counts them, so the sequence has no ceiling and no JavaScript. The twelve
|
|
108
|
+
nth-child rules it replaced are kept below for browsers without it --
|
|
109
|
+
they stop at twelve, which is why deck.js used to patch past that.
|
|
110
|
+
-------------------------------------------------------------------- */
|
|
111
|
+
@supports (animation-delay: calc(sibling-index() * 1ms)) {
|
|
112
|
+
.stagger > * { animation-delay: calc((sibling-index() - 1) * var(--stagger-step)); }
|
|
113
|
+
}
|
|
114
|
+
@supports not (animation-delay: calc(sibling-index() * 1ms)) {
|
|
115
|
+
.stagger > * { animation-delay: calc(var(--n, 0) * var(--stagger-step)); }
|
|
116
|
+
.stagger > :nth-child(1) { --n: 0; }
|
|
117
|
+
.stagger > :nth-child(2) { --n: 1; }
|
|
118
|
+
.stagger > :nth-child(3) { --n: 2; }
|
|
119
|
+
.stagger > :nth-child(4) { --n: 3; }
|
|
120
|
+
.stagger > :nth-child(5) { --n: 4; }
|
|
121
|
+
.stagger > :nth-child(6) { --n: 5; }
|
|
122
|
+
.stagger > :nth-child(7) { --n: 6; }
|
|
123
|
+
.stagger > :nth-child(8) { --n: 7; }
|
|
124
|
+
.stagger > :nth-child(9) { --n: 8; }
|
|
125
|
+
.stagger > :nth-child(10) { --n: 9; }
|
|
126
|
+
.stagger > :nth-child(11) { --n: 10; }
|
|
127
|
+
.stagger > :nth-child(12) { --n: 11; }
|
|
128
|
+
/* Past twelve everything holds the last step rather than snapping back
|
|
129
|
+
to zero. Without sibling-index() that is as far as CSS can count.
|
|
130
|
+
.pile in 21-space3d.css has the same fallback and is MISSING this
|
|
131
|
+
line, so its ninth card stacks on top of the first. */
|
|
132
|
+
.stagger > :nth-child(n+13) { --n: 11; }
|
|
133
|
+
}
|
|
134
|
+
.stagger-fast { --stagger-step: 28ms; }
|
|
135
|
+
.stagger-slow { --stagger-step: 80ms; }
|
|
136
|
+
|
|
137
|
+
/* --- Scroll-driven reveals -------------------------------------------
|
|
138
|
+
No IntersectionObserver, no JS. The animation is tied to the element's
|
|
139
|
+
position in the scrollport. deck.js provides a fallback for browsers
|
|
140
|
+
without animation-timeline by adding .is-revealed.
|
|
141
|
+
-------------------------------------------------------------------- */
|
|
142
|
+
@supports (animation-timeline: view()) {
|
|
143
|
+
.reveal {
|
|
144
|
+
animation: dk-rise linear both;
|
|
145
|
+
animation-timeline: view();
|
|
146
|
+
animation-range: entry 8% cover 26%;
|
|
147
|
+
}
|
|
148
|
+
.reveal-fade { animation-name: dk-fade; }
|
|
149
|
+
.reveal-pop { animation-name: dk-pop; }
|
|
150
|
+
.reveal-late { animation-range: entry 20% cover 42%; }
|
|
151
|
+
|
|
152
|
+
/* A progress bar that fills as the page scrolls — good for long reports */
|
|
153
|
+
.scroll-progress {
|
|
154
|
+
position: fixed;
|
|
155
|
+
inset-block-start: 0;
|
|
156
|
+
inset-inline: 0;
|
|
157
|
+
block-size: 3px;
|
|
158
|
+
background: var(--brand-500);
|
|
159
|
+
transform-origin: left center;
|
|
160
|
+
z-index: var(--z-toast);
|
|
161
|
+
animation: dk-scale-x linear both;
|
|
162
|
+
animation-timeline: scroll(root block);
|
|
163
|
+
}
|
|
164
|
+
@keyframes dk-scale-x { from { scale: 0 1; } to { scale: 1 1; } }
|
|
165
|
+
|
|
166
|
+
/* Sticky header that condenses once you scroll past the top */
|
|
167
|
+
.shrink-on-scroll {
|
|
168
|
+
animation: dk-shrink linear both;
|
|
169
|
+
animation-timeline: scroll(root block);
|
|
170
|
+
animation-range: 0 120px;
|
|
171
|
+
}
|
|
172
|
+
@keyframes dk-shrink {
|
|
173
|
+
to { --control-h-lg: 42px; box-shadow: var(--shadow-2); }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
@supports not (animation-timeline: view()) {
|
|
177
|
+
.reveal, .reveal-fade, .reveal-pop { opacity: 0; }
|
|
178
|
+
.reveal.is-revealed { animation: dk-rise var(--dur-4) var(--ease-out) both; }
|
|
179
|
+
.reveal-fade.is-revealed { animation: dk-fade var(--dur-4) var(--ease-out) both; }
|
|
180
|
+
.reveal-pop.is-revealed { animation: dk-pop var(--dur-4) var(--ease-spring) both; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* --- Attention and feedback ------------------------------------------- */
|
|
184
|
+
.shake { animation: dk-shake 420ms var(--ease-in-out); }
|
|
185
|
+
@keyframes dk-shake {
|
|
186
|
+
10%, 90% { translate: -2px 0; }
|
|
187
|
+
20%, 80% { translate: 4px 0; }
|
|
188
|
+
30%, 50%, 70% { translate: -6px 0; }
|
|
189
|
+
40%, 60% { translate: 6px 0; }
|
|
190
|
+
}
|
|
191
|
+
/* A field that failed validation shakes once, then stops */
|
|
192
|
+
.field.is-invalid:not(.was-shaken) :is(.input, .textarea, .select, .combo-control) {
|
|
193
|
+
animation: dk-shake 420ms var(--ease-in-out);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.flash { animation: dk-flash 1.2s var(--ease-out); }
|
|
197
|
+
@keyframes dk-flash {
|
|
198
|
+
from { background-color: var(--brand-soft); }
|
|
199
|
+
to { background-color: transparent; }
|
|
200
|
+
}
|
|
201
|
+
.flash-good { animation: dk-flash-good 1.4s var(--ease-out); }
|
|
202
|
+
@keyframes dk-flash-good {
|
|
203
|
+
from { background-color: var(--good-100); }
|
|
204
|
+
to { background-color: transparent; }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.pulse { animation: dk-pulse 2s var(--ease-in-out) infinite; }
|
|
208
|
+
@keyframes dk-pulse { 50% { opacity: .55; } }
|
|
209
|
+
|
|
210
|
+
/* Ping ring for a live indicator — a dot with a halo going out */
|
|
211
|
+
.ping { position: relative; }
|
|
212
|
+
.ping::after {
|
|
213
|
+
content: "";
|
|
214
|
+
position: absolute;
|
|
215
|
+
inset: 0;
|
|
216
|
+
border-radius: inherit;
|
|
217
|
+
background: currentColor;
|
|
218
|
+
animation: dk-ping 1.8s var(--ease-out) infinite;
|
|
219
|
+
pointer-events: none;
|
|
220
|
+
}
|
|
221
|
+
@keyframes dk-ping {
|
|
222
|
+
from { opacity: .6; scale: 1; }
|
|
223
|
+
to { opacity: 0; scale: 2.2; }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.nudge { animation: dk-nudge 1.6s var(--ease-in-out) infinite; }
|
|
227
|
+
@keyframes dk-nudge { 50% { translate: 4px 0; } }
|
|
228
|
+
|
|
229
|
+
/* --- Micro-interactions ----------------------------------------------- */
|
|
230
|
+
.lift { transition: translate var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out); }
|
|
231
|
+
.lift:hover { translate: 0 -2px; box-shadow: var(--shadow-3); }
|
|
232
|
+
.lift:active { translate: 0 0; box-shadow: var(--shadow-1); }
|
|
233
|
+
|
|
234
|
+
.press { transition: scale var(--dur-1) var(--ease-out); }
|
|
235
|
+
.press:active { scale: .97; }
|
|
236
|
+
|
|
237
|
+
/* Underline that draws in from the start edge */
|
|
238
|
+
.sweep {
|
|
239
|
+
position: relative;
|
|
240
|
+
text-decoration: none;
|
|
241
|
+
background-image: linear-gradient(currentColor, currentColor);
|
|
242
|
+
background-size: 0 1.5px;
|
|
243
|
+
background-position: 0 100%;
|
|
244
|
+
background-repeat: no-repeat;
|
|
245
|
+
transition: background-size var(--dur-2) var(--ease-out);
|
|
246
|
+
padding-block-end: 1px;
|
|
247
|
+
}
|
|
248
|
+
.sweep:hover, .sweep:focus-visible { background-size: 100% 1.5px; }
|
|
249
|
+
|
|
250
|
+
/* An arrow that steps forward when its button is hovered */
|
|
251
|
+
.btn:hover .icon-follow, .list-row:hover .icon-follow, .card-link:hover .icon-follow {
|
|
252
|
+
translate: 3px 0;
|
|
253
|
+
}
|
|
254
|
+
.icon-follow { transition: translate var(--dur-2) var(--ease-spring); }
|
|
255
|
+
|
|
256
|
+
/* Ripple. deck.js sets --rx/--ry from the pointer position. */
|
|
257
|
+
.ripple { position: relative; overflow: hidden; }
|
|
258
|
+
.ripple > .ripple-ink {
|
|
259
|
+
position: absolute;
|
|
260
|
+
inset-block-start: var(--ry, 50%);
|
|
261
|
+
inset-inline-start: var(--rx, 50%);
|
|
262
|
+
inline-size: 8px;
|
|
263
|
+
block-size: 8px;
|
|
264
|
+
margin: -4px 0 0 -4px;
|
|
265
|
+
border-radius: var(--r-full);
|
|
266
|
+
background: currentColor;
|
|
267
|
+
opacity: .28;
|
|
268
|
+
pointer-events: none;
|
|
269
|
+
animation: dk-ripple 520ms var(--ease-out) forwards;
|
|
270
|
+
}
|
|
271
|
+
@keyframes dk-ripple {
|
|
272
|
+
to { scale: 26; opacity: 0; }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* --- Expand and collapse ----------------------------------------------
|
|
276
|
+
Real height:auto transitions, no measuring in JS. Needs
|
|
277
|
+
interpolate-size, which tokens opts into on :root.
|
|
278
|
+
-------------------------------------------------------------------- */
|
|
279
|
+
.expand {
|
|
280
|
+
block-size: 0;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
transition: block-size var(--dur-3) var(--ease-out),
|
|
283
|
+
opacity var(--dur-2) var(--ease-out);
|
|
284
|
+
opacity: 0;
|
|
285
|
+
}
|
|
286
|
+
.expand.is-open { block-size: auto; opacity: 1; }
|
|
287
|
+
|
|
288
|
+
/* --- Discrete-property transitions ------------------------------------
|
|
289
|
+
Anything that goes display:none can still animate out.
|
|
290
|
+
-------------------------------------------------------------------- */
|
|
291
|
+
.fade-swap {
|
|
292
|
+
transition: opacity var(--dur-2) var(--ease-out),
|
|
293
|
+
display var(--dur-2) allow-discrete;
|
|
294
|
+
}
|
|
295
|
+
.fade-swap[hidden] { opacity: 0; }
|
|
296
|
+
@starting-style { .fade-swap:not([hidden]) { opacity: 0; } }
|
|
297
|
+
|
|
298
|
+
/* --- Marquee / ticker --------------------------------------------------
|
|
299
|
+
For a status strip: recalls, open campaigns, parts on backorder.
|
|
300
|
+
-------------------------------------------------------------------- */
|
|
301
|
+
.marquee {
|
|
302
|
+
display: flex;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
gap: var(--space-8);
|
|
305
|
+
mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
|
|
306
|
+
-webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
|
|
307
|
+
}
|
|
308
|
+
.marquee-track {
|
|
309
|
+
display: flex;
|
|
310
|
+
gap: var(--space-8);
|
|
311
|
+
flex: 0 0 auto;
|
|
312
|
+
padding-inline-end: var(--space-8);
|
|
313
|
+
animation: dk-marquee var(--marquee-dur, 26s) linear infinite;
|
|
314
|
+
white-space: nowrap;
|
|
315
|
+
}
|
|
316
|
+
.marquee:hover .marquee-track { animation-play-state: paused; }
|
|
317
|
+
@keyframes dk-marquee { to { translate: -100% 0; } }
|
|
318
|
+
|
|
319
|
+
/* --- Number and value changes ------------------------------------------ */
|
|
320
|
+
.tick { transition: translate var(--dur-2) var(--ease-spring); }
|
|
321
|
+
.tick.is-up { animation: dk-tick-up var(--dur-3) var(--ease-out); }
|
|
322
|
+
.tick.is-down { animation: dk-tick-down var(--dur-3) var(--ease-out); }
|
|
323
|
+
@keyframes dk-tick-up { from { translate: 0 6px; opacity: 0; color: var(--good-500); } }
|
|
324
|
+
@keyframes dk-tick-down { from { translate: 0 -6px; opacity: 0; color: var(--bad-500); } }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* --- View transitions ----------------------------------------------------
|
|
328
|
+
This is the big one for Keel. Add this to your app CSS:
|
|
329
|
+
|
|
330
|
+
@view-transition { navigation: auto; }
|
|
331
|
+
|
|
332
|
+
and full page loads in a plain PHP multi-page app cross-fade like a single
|
|
333
|
+
page app, with no router and no JavaScript. The rules below style what the
|
|
334
|
+
browser generates. They are inert until you opt in.
|
|
335
|
+
---------------------------------------------------------------------- */
|
|
336
|
+
|
|
337
|
+
::view-transition-group(root) { animation-duration: var(--dur-3); }
|
|
338
|
+
|
|
339
|
+
::view-transition-old(root) {
|
|
340
|
+
animation: dk-vt-out var(--dur-2) var(--ease-in) both;
|
|
341
|
+
}
|
|
342
|
+
::view-transition-new(root) {
|
|
343
|
+
animation: dk-vt-in var(--dur-3) var(--ease-out) both;
|
|
344
|
+
}
|
|
345
|
+
@keyframes dk-vt-out { to { opacity: 0; translate: 0 -6px; } }
|
|
346
|
+
@keyframes dk-vt-in { from { opacity: 0; translate: 0 8px; } }
|
|
347
|
+
|
|
348
|
+
/* Forward and back get direction. Set data-vt="back" on <html> before you
|
|
349
|
+
navigate, or let deck.js do it on popstate. */
|
|
350
|
+
html[data-vt="back"]::view-transition-old(root) { animation-name: dk-vt-out-back; }
|
|
351
|
+
html[data-vt="back"]::view-transition-new(root) { animation-name: dk-vt-in-back; }
|
|
352
|
+
@keyframes dk-vt-out-back { to { opacity: 0; translate: 6% 0; } }
|
|
353
|
+
@keyframes dk-vt-in-back { from { opacity: 0; translate: -6% 0; } }
|
|
354
|
+
|
|
355
|
+
/* Elements that persist across the navigation. Give the same
|
|
356
|
+
view-transition-name to the matching element on both pages and the browser
|
|
357
|
+
tweens between them — a row in a list morphing into a detail header. */
|
|
358
|
+
.vt-hold { view-transition-name: var(--vt); }
|
|
359
|
+
.vt-header { view-transition-name: dk-header; }
|
|
360
|
+
.vt-main { view-transition-name: dk-main; }
|
|
361
|
+
.vt-tabbar { view-transition-name: dk-tabbar; }
|
|
362
|
+
|
|
363
|
+
/* The nav chrome should sit still while the content changes */
|
|
364
|
+
::view-transition-group(dk-header),
|
|
365
|
+
::view-transition-group(dk-tabbar) { animation-duration: var(--dur-1); }
|
|
366
|
+
::view-transition-old(dk-header), ::view-transition-new(dk-header),
|
|
367
|
+
::view-transition-old(dk-tabbar), ::view-transition-new(dk-tabbar) {
|
|
368
|
+
animation: none;
|
|
369
|
+
mix-blend-mode: normal;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@media (prefers-reduced-motion: reduce) {
|
|
373
|
+
::view-transition-group(*),
|
|
374
|
+
::view-transition-old(*),
|
|
375
|
+
::view-transition-new(*) {
|
|
376
|
+
animation-duration: 1ms !important;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 18 Container queries
|
|
3
|
+
A component should respond to the space it was given, not to the size of the
|
|
4
|
+
window. A card in a narrow sidebar and the same card in a wide main column
|
|
5
|
+
are the same markup and should lay out differently without either one
|
|
6
|
+
knowing where it was placed.
|
|
7
|
+
========================================================================== */
|
|
8
|
+
|
|
9
|
+
@layer deck.layout {
|
|
10
|
+
|
|
11
|
+
/* --- Declaring a container ----------------------------------------------
|
|
12
|
+
.cq is the plain one. The named versions let a nested component query a
|
|
13
|
+
specific ancestor instead of the closest one.
|
|
14
|
+
---------------------------------------------------------------------- */
|
|
15
|
+
.cq { container-type: inline-size; }
|
|
16
|
+
.cq-size { container-type: size; }
|
|
17
|
+
.cq-normal { container-type: normal; }
|
|
18
|
+
|
|
19
|
+
.cq-panel { container: panel / inline-size; }
|
|
20
|
+
.cq-pane { container: pane / inline-size; }
|
|
21
|
+
.cq-row { container: row / inline-size; }
|
|
22
|
+
.cq-shell { container: shell / inline-size; }
|
|
23
|
+
|
|
24
|
+
/* Every layout primitive can become a container without extra markup */
|
|
25
|
+
.card.cq, .panel.cq, .list.cq, .split > .cq, .grid > .cq { container-type: inline-size; }
|
|
26
|
+
|
|
27
|
+
/* --- Container query units -----------------------------------------------
|
|
28
|
+
cqi is 1% of the container's inline size. Type that scales with its column
|
|
29
|
+
rather than with the viewport — a heading in a sidebar stays small even on
|
|
30
|
+
a 32 inch monitor.
|
|
31
|
+
---------------------------------------------------------------------- */
|
|
32
|
+
.cq :is(.text-cq) { font-size: clamp(var(--text-sm), 3.4cqi, var(--text-lg)); }
|
|
33
|
+
.cq :is(.display-cq) {
|
|
34
|
+
font-size: clamp(1.5rem, 8cqi, 3.25rem);
|
|
35
|
+
line-height: 1.05;
|
|
36
|
+
letter-spacing: -.03em;
|
|
37
|
+
}
|
|
38
|
+
.cq :is(.pad-cq) { padding: clamp(var(--space-3), 4cqi, var(--space-8)); }
|
|
39
|
+
.cq :is(.gap-cq) { gap: clamp(var(--space-2), 2.5cqi, var(--space-6)); }
|
|
40
|
+
|
|
41
|
+
/* --- Container-driven components ---------------------------------------- */
|
|
42
|
+
|
|
43
|
+
/* A card that turns into a horizontal media object once it has the room */
|
|
44
|
+
.card-flex { display: flex; flex-direction: column; }
|
|
45
|
+
.card-flex .card-media { flex: 0 0 auto; }
|
|
46
|
+
.card-flex .card-media img { inline-size: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
|
|
47
|
+
|
|
48
|
+
@container (min-width: 30rem) {
|
|
49
|
+
.card-flex { flex-direction: row; }
|
|
50
|
+
.card-flex .card-media { inline-size: 40%; max-inline-size: 15rem; }
|
|
51
|
+
.card-flex .card-media img { block-size: 100%; aspect-ratio: auto; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* A stat block that stacks tight, then spreads, then goes inline */
|
|
55
|
+
.stat-cq { display: flex; flex-direction: column; gap: var(--space-1); }
|
|
56
|
+
@container (min-width: 18rem) {
|
|
57
|
+
.stat-cq .stat-value { font-size: var(--text-2xl); }
|
|
58
|
+
}
|
|
59
|
+
@container (min-width: 28rem) {
|
|
60
|
+
.stat-cq {
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
align-items: baseline;
|
|
63
|
+
gap: var(--space-3);
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
}
|
|
66
|
+
.stat-cq .stat-label { order: -1; min-inline-size: 100%; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* List rows that reveal their metadata as space allows */
|
|
70
|
+
.metarow { display: flex; align-items: center; gap: var(--space-3); }
|
|
71
|
+
.metarow .row-meta { display: none; }
|
|
72
|
+
@container (min-width: 26rem) {
|
|
73
|
+
.metarow .row-meta { display: block; color: var(--text-muted); font-size: var(--text-sm); }
|
|
74
|
+
}
|
|
75
|
+
@container (min-width: 38rem) {
|
|
76
|
+
.metarow .row-meta-wide { display: block; }
|
|
77
|
+
}
|
|
78
|
+
.metarow .row-meta-wide { display: none; }
|
|
79
|
+
|
|
80
|
+
/* Form fields that pair up inside their own container, not the viewport.
|
|
81
|
+
This is the one that keeps biting people: a two-up field row inside a
|
|
82
|
+
narrow modal should stay stacked even on a desktop. */
|
|
83
|
+
.field-row-cq { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
|
|
84
|
+
@container (min-width: 32rem) {
|
|
85
|
+
.field-row-cq { grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Button rows that go full width when cramped */
|
|
89
|
+
.actions-cq { display: flex; flex-direction: column; gap: var(--space-2); }
|
|
90
|
+
.actions-cq > .btn { inline-size: 100%; }
|
|
91
|
+
@container (min-width: 24rem) {
|
|
92
|
+
.actions-cq { flex-direction: row; }
|
|
93
|
+
.actions-cq > .btn { inline-size: auto; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* A data grid that drops to cards based on its own width. This is strictly
|
|
97
|
+
better than the media-query version, because a grid inside a sheet is
|
|
98
|
+
narrow no matter how wide the screen is. */
|
|
99
|
+
@container (max-width: 44rem) {
|
|
100
|
+
.dg-cq thead, .dg-cq tfoot { display: none; }
|
|
101
|
+
.dg-cq, .dg-cq tbody, .dg-cq tr, .dg-cq td { display: block; inline-size: 100%; }
|
|
102
|
+
.dg-cq tr {
|
|
103
|
+
background: var(--surface);
|
|
104
|
+
border: 1px solid var(--line);
|
|
105
|
+
border-radius: var(--r-md);
|
|
106
|
+
margin-block-end: var(--space-3);
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
}
|
|
109
|
+
.dg-cq td {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
gap: var(--space-4);
|
|
114
|
+
block-size: auto;
|
|
115
|
+
min-block-size: var(--tap);
|
|
116
|
+
text-align: end;
|
|
117
|
+
white-space: normal;
|
|
118
|
+
position: static !important;
|
|
119
|
+
box-shadow: none !important;
|
|
120
|
+
clip-path: none !important;
|
|
121
|
+
}
|
|
122
|
+
.dg-cq td::before {
|
|
123
|
+
content: attr(data-label);
|
|
124
|
+
font-size: var(--text-xs);
|
|
125
|
+
font-weight: 620;
|
|
126
|
+
color: var(--text-muted);
|
|
127
|
+
text-align: start;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Named-container example: a sidebar nav that collapses to icons when the
|
|
132
|
+
shell is narrow, regardless of what is nested inside it. */
|
|
133
|
+
/* FINDINGS 64: display:none removes the label from the accessibility tree,
|
|
134
|
+
not just from the screen. The icon inside the link is aria-hidden by
|
|
135
|
+
convention, so a collapsed .sidebar-link has NO accessible name -- a
|
|
136
|
+
screen reader announces "link" for every destination in the app's primary
|
|
137
|
+
navigation.
|
|
138
|
+
|
|
139
|
+
Note that .sidebar-group two lines down gets it right: font-size: 0 hides
|
|
140
|
+
the heading visually and keeps it in the tree. The link rule should hide
|
|
141
|
+
the span the same way, or with an .sr-only clip. Until it does, authors
|
|
142
|
+
have to put aria-label on every .sidebar-link by hand. */
|
|
143
|
+
@container shell (max-width: 15rem) {
|
|
144
|
+
.sidebar-link span { display: none; }
|
|
145
|
+
.sidebar-link { justify-content: center; }
|
|
146
|
+
.sidebar-group { text-align: center; font-size: 0; }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* --- Container breakpoint utilities --------------------------------------
|
|
150
|
+
Same idea as the responsive utilities, but keyed to the container.
|
|
151
|
+
---------------------------------------------------------------------- */
|
|
152
|
+
@container (min-width: 24rem) {
|
|
153
|
+
.cq-sm\:row { flex-direction: row; }
|
|
154
|
+
.cq-sm\:flex { display: flex; }
|
|
155
|
+
.cq-sm\:hidden { display: none; }
|
|
156
|
+
.cq-sm\:block { display: block; }
|
|
157
|
+
.cq-sm\:w-auto { inline-size: auto; }
|
|
158
|
+
}
|
|
159
|
+
@container (min-width: 32rem) {
|
|
160
|
+
.cq-md\:row { flex-direction: row; }
|
|
161
|
+
.cq-md\:col { flex-direction: column; }
|
|
162
|
+
.cq-md\:flex { display: flex; }
|
|
163
|
+
.cq-md\:grid { display: grid; }
|
|
164
|
+
.cq-md\:hidden { display: none; }
|
|
165
|
+
.cq-md\:block { display: block; }
|
|
166
|
+
.cq-md\:items-center { align-items: center; }
|
|
167
|
+
.cq-md\:justify-between { justify-content: space-between; }
|
|
168
|
+
}
|
|
169
|
+
@container (min-width: 44rem) {
|
|
170
|
+
.cq-lg\:row { flex-direction: row; }
|
|
171
|
+
.cq-lg\:flex { display: flex; }
|
|
172
|
+
.cq-lg\:hidden { display: none; }
|
|
173
|
+
.cq-lg\:block { display: block; }
|
|
174
|
+
.cq-lg\:grid-2 { display: grid; grid-template-columns: 1fr 1fr; }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* --- Style queries -------------------------------------------------------
|
|
178
|
+
Query a custom property rather than a size. Set --tone on a container and
|
|
179
|
+
everything inside it adapts, with no extra classes on the children.
|
|
180
|
+
Supported in current Chrome and Safari; the fallback is simply no change.
|
|
181
|
+
---------------------------------------------------------------------- */
|
|
182
|
+
.cq-tone { container-type: inline-size; }
|
|
183
|
+
|
|
184
|
+
@container style(--tone: critical) {
|
|
185
|
+
.tone-surface { background: var(--bad-100); border-color: color-mix(in oklab, var(--bad-500) 35%, transparent); }
|
|
186
|
+
.tone-text { color: var(--bad-700); }
|
|
187
|
+
.tone-icon { color: var(--bad-500); }
|
|
188
|
+
}
|
|
189
|
+
@container style(--tone: caution) {
|
|
190
|
+
.tone-surface { background: var(--warn-100); border-color: color-mix(in oklab, var(--warn-500) 35%, transparent); }
|
|
191
|
+
.tone-text { color: var(--warn-700); }
|
|
192
|
+
.tone-icon { color: var(--warn-500); }
|
|
193
|
+
}
|
|
194
|
+
@container style(--tone: clear) {
|
|
195
|
+
.tone-surface { background: var(--good-100); border-color: color-mix(in oklab, var(--good-500) 35%, transparent); }
|
|
196
|
+
.tone-text { color: var(--good-700); }
|
|
197
|
+
.tone-icon { color: var(--good-500); }
|
|
198
|
+
}
|
|
199
|
+
}
|