@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1327 -0
  3. package/bin/deck.mjs +219 -0
  4. package/build.mjs +338 -0
  5. package/dist/api-buckets.json +10486 -0
  6. package/dist/api.json +57896 -0
  7. package/dist/brand/deck-apple-touch-icon.png +0 -0
  8. package/dist/brand/deck-logo-dark.svg +1 -0
  9. package/dist/brand/deck-logo-light.svg +1 -0
  10. package/dist/brand/deck-logo-stacked.svg +1 -0
  11. package/dist/brand/deck-logo.svg +1 -0
  12. package/dist/brand/deck-mark.svg +7 -0
  13. package/dist/brand/deck-og.png +0 -0
  14. package/dist/collisions.json +223 -0
  15. package/dist/deck-adapters.js +482 -0
  16. package/dist/deck-adapters.min.js +23 -0
  17. package/dist/deck-extras.js +823 -0
  18. package/dist/deck-extras.min.js +5 -0
  19. package/dist/deck-icons.svg +184 -0
  20. package/dist/deck.bundle.js +2638 -0
  21. package/dist/deck.bundle.min.js +86 -0
  22. package/dist/deck.css +7904 -0
  23. package/dist/deck.esm.js +2644 -0
  24. package/dist/deck.js +1331 -0
  25. package/dist/deck.min.css +32 -0
  26. package/dist/deck.min.js +60 -0
  27. package/dist/layers/anchor.css +192 -0
  28. package/dist/layers/buttons.css +210 -0
  29. package/dist/layers/charts.css +337 -0
  30. package/dist/layers/combobox.css +248 -0
  31. package/dist/layers/components.css +868 -0
  32. package/dist/layers/container.css +199 -0
  33. package/dist/layers/datagrid.css +335 -0
  34. package/dist/layers/datepicker.css +305 -0
  35. package/dist/layers/forms.css +401 -0
  36. package/dist/layers/gradients.css +302 -0
  37. package/dist/layers/inputs.css +522 -0
  38. package/dist/layers/layers.css +87 -0
  39. package/dist/layers/layout.css +235 -0
  40. package/dist/layers/logical.css +226 -0
  41. package/dist/layers/media.css +400 -0
  42. package/dist/layers/mobile.css +245 -0
  43. package/dist/layers/motion.css +379 -0
  44. package/dist/layers/nav.css +771 -0
  45. package/dist/layers/perf.css +234 -0
  46. package/dist/layers/print.css +198 -0
  47. package/dist/layers/reset.css +103 -0
  48. package/dist/layers/space3d.css +280 -0
  49. package/dist/layers/toasts.css +201 -0
  50. package/dist/layers/tokens.css +229 -0
  51. package/dist/layers/type.css +114 -0
  52. package/dist/layers/utilities.css +241 -0
  53. package/dist/sizes.json +52 -0
  54. package/dist/usage.json +6340 -0
  55. package/package.json +94 -0
  56. package/src/00-layers.css +87 -0
  57. package/src/01-tokens.css +229 -0
  58. package/src/02-reset.css +103 -0
  59. package/src/03-type.css +114 -0
  60. package/src/04-layout.css +235 -0
  61. package/src/05-buttons.css +210 -0
  62. package/src/06-forms.css +401 -0
  63. package/src/07-components.css +868 -0
  64. package/src/08-mobile.css +245 -0
  65. package/src/09-utilities.css +241 -0
  66. package/src/10-datepicker.css +305 -0
  67. package/src/11-combobox.css +248 -0
  68. package/src/12-datagrid.css +335 -0
  69. package/src/13-toasts.css +201 -0
  70. package/src/14-charts.css +337 -0
  71. package/src/16-motion.css +379 -0
  72. package/src/18-container.css +199 -0
  73. package/src/19-logical.css +226 -0
  74. package/src/20-gradients.css +302 -0
  75. package/src/21-space3d.css +280 -0
  76. package/src/22-nav.css +771 -0
  77. package/src/23-inputs.css +522 -0
  78. package/src/24-media.css +400 -0
  79. package/src/25-anchor.css +192 -0
  80. package/src/26-perf.css +234 -0
  81. package/src/99-print.css +198 -0
  82. package/src/brand/deck-apple-touch-icon.png +0 -0
  83. package/src/brand/deck-logo-dark.svg +1 -0
  84. package/src/brand/deck-logo-light.svg +1 -0
  85. package/src/brand/deck-logo-stacked.svg +1 -0
  86. package/src/brand/deck-logo.svg +1 -0
  87. package/src/brand/deck-mark.svg +7 -0
  88. package/src/brand/deck-og.png +0 -0
  89. package/src/brand/sources.json +7 -0
  90. package/src/deck-icons.svg +184 -0
  91. package/src/js/deck-adapters.js +482 -0
  92. package/src/js/deck-extras.js +823 -0
  93. package/src/js/deck.js +1331 -0
@@ -0,0 +1,280 @@
1
+ /* ==========================================================================
2
+ Deck — 21 Space (3D transforms)
3
+ 3D earns its place when depth carries meaning: a card that has two sides, a
4
+ stack you are working down through, a control that physically depresses.
5
+ It does not earn its place as decoration on a landing page.
6
+
7
+ Every transform here uses translate / rotate / scale as individual
8
+ properties rather than the transform shorthand, so two effects on the same
9
+ element compose instead of overwriting each other.
10
+ ========================================================================== */
11
+
12
+ @layer deck.effects {
13
+
14
+ /* --- Scene ---------------------------------------------------------------
15
+ A scene establishes the vanishing point. Children need preserve-3d to
16
+ stay in the same space instead of being flattened.
17
+ ---------------------------------------------------------------------- */
18
+ .scene {
19
+ perspective: var(--perspective, 1000px);
20
+ perspective-origin: var(--vanish, 50% 50%);
21
+ }
22
+ .scene-near { --perspective: 560px; }
23
+ .scene-far { --perspective: 2000px; }
24
+ .space { transform-style: preserve-3d; }
25
+ .flat { transform-style: flat; }
26
+ .backface-hidden { backface-visibility: hidden; }
27
+
28
+ /* --- Flip card -----------------------------------------------------------
29
+ Two faces, one footprint. Front and back are stacked in the same grid
30
+ cell so the card is exactly as tall as its taller side.
31
+ ---------------------------------------------------------------------- */
32
+ .flip {
33
+ display: grid;
34
+ transform-style: preserve-3d;
35
+ transition: rotate var(--dur-4) var(--ease-spring);
36
+ }
37
+ .flip > .flip-front, .flip > .flip-back {
38
+ grid-area: 1 / 1;
39
+ backface-visibility: hidden;
40
+ -webkit-backface-visibility: hidden;
41
+ }
42
+ .flip > .flip-back { rotate: y 180deg; }
43
+ .flip.is-flipped { rotate: y 180deg; }
44
+ .flip-x { transition: rotate var(--dur-4) var(--ease-spring); }
45
+ .flip-x > .flip-back { rotate: x 180deg; }
46
+ .flip-x.is-flipped { rotate: x 180deg; }
47
+
48
+ /* Hover flip for pointer devices only — on touch it needs a real tap */
49
+ @media (hover: hover) and (prefers-reduced-motion: no-preference) {
50
+ .flip-hover:hover { rotate: y 180deg; }
51
+ }
52
+
53
+ /* --- Tilt ----------------------------------------------------------------
54
+ deck.js writes --tilt-x and --tilt-y from the pointer position. Both are
55
+ registered angles, so they interpolate smoothly back to rest.
56
+ ---------------------------------------------------------------------- */
57
+ .tilt {
58
+ transform-style: preserve-3d;
59
+ rotate: var(--tilt-axis, 1 1 0) var(--tilt-amount, 0deg);
60
+ transition: rotate var(--dur-3) var(--ease-out), scale var(--dur-2) var(--ease-out);
61
+ will-change: auto;
62
+ }
63
+ @media (hover: hover) and (prefers-reduced-motion: no-preference) {
64
+ .tilt.is-tilting { transition: rotate 80ms linear; }
65
+ .tilt:hover { scale: 1.015; }
66
+ /* Content that should float above the card face while it tilts */
67
+ .tilt .tilt-lift { transform: translateZ(var(--depth, 28px)); }
68
+ .tilt .tilt-lift-sm { transform: translateZ(12px); }
69
+ }
70
+
71
+ /* --- Depth stack ---------------------------------------------------------
72
+ A pile of records you are working through. The one on top is live, the
73
+ ones behind are context. --i is the child's own position, 0 for the top
74
+ card; deck.js parks a dismissed card at the end so the rest close up.
75
+ ---------------------------------------------------------------------- */
76
+ @supports (animation-delay: calc(sibling-index() * 1ms)) {
77
+ .pile > * { --i: calc(sibling-index() - 1); }
78
+ }
79
+ @supports not (animation-delay: calc(sibling-index() * 1ms)) {
80
+ .pile > :nth-child(1) { --i: 0; }
81
+ .pile > :nth-child(2) { --i: 1; }
82
+ .pile > :nth-child(3) { --i: 2; }
83
+ .pile > :nth-child(4) { --i: 3; }
84
+ .pile > :nth-child(5) { --i: 4; }
85
+ .pile > :nth-child(6) { --i: 5; }
86
+ .pile > :nth-child(7) { --i: 6; }
87
+ .pile > :nth-child(8) { --i: 7; }
88
+ }
89
+ .pile {
90
+ display: grid;
91
+ transform-style: preserve-3d;
92
+ perspective: 1200px;
93
+ }
94
+ .pile > * {
95
+ grid-area: 1 / 1;
96
+ translate: 0 calc(var(--i, 0) * 10px);
97
+ scale: calc(1 - var(--i, 0) * .045);
98
+ z-index: calc(10 - var(--i, 0));
99
+ filter: brightness(calc(1 - var(--i, 0) * .04));
100
+ transition: translate var(--dur-3) var(--ease-out),
101
+ scale var(--dur-3) var(--ease-out),
102
+ rotate var(--dur-3) var(--ease-out),
103
+ opacity var(--dur-2) var(--ease-out);
104
+ }
105
+ .pile > :nth-child(n+4) { opacity: 0; pointer-events: none; }
106
+
107
+ /* Fan the pile out to see what is in it */
108
+ .pile.is-fanned > * {
109
+ translate: calc(var(--i, 0) * 14px) calc(var(--i, 0) * 22px);
110
+ rotate: calc(var(--i, 0) * 2.5deg);
111
+ scale: 1;
112
+ filter: none;
113
+ }
114
+ .pile.is-fanned > :nth-child(n+4):not(.is-dismissed) { opacity: 1; pointer-events: auto; }
115
+
116
+ /* Dismiss the top card away from the pile */
117
+ .pile > .is-dismissed {
118
+ translate: 120% -20px;
119
+ rotate: 14deg;
120
+ opacity: 0;
121
+ }
122
+
123
+ /* --- Coverflow -----------------------------------------------------------
124
+ A horizontal scroller where the off-center items rotate away. Uses scroll
125
+ snap for the mechanics and 3D only for the read.
126
+ ---------------------------------------------------------------------- */
127
+ .coverflow {
128
+ display: flex;
129
+ gap: var(--space-4);
130
+ overflow-x: auto;
131
+ scroll-snap-type: x mandatory;
132
+ perspective: 1200px;
133
+ padding-block: var(--space-6);
134
+ padding-inline: max(var(--space-gutter), 20%);
135
+ scrollbar-width: none;
136
+ overscroll-behavior-inline: contain;
137
+ }
138
+ .coverflow::-webkit-scrollbar { display: none; }
139
+ .coverflow > * {
140
+ flex: 0 0 auto;
141
+ scroll-snap-align: center;
142
+ transform-style: preserve-3d;
143
+ transition: rotate var(--dur-3) var(--ease-out), scale var(--dur-3) var(--ease-out);
144
+ }
145
+ /* deck.js marks the centered item; without JS everything simply sits flat */
146
+ .coverflow > .is-behind-start { rotate: y 32deg; scale: .88; }
147
+ .coverflow > .is-behind-end { rotate: y -32deg; scale: .88; }
148
+ .coverflow > .is-front { rotate: y 0deg; scale: 1; }
149
+
150
+ /* --- Cube ----------------------------------------------------------------
151
+ Six faces for a rotating status block or an empty-state illustration.
152
+ ---------------------------------------------------------------------- */
153
+ .cube {
154
+ --size: 120px;
155
+ position: relative;
156
+ inline-size: var(--size);
157
+ block-size: var(--size);
158
+ transform-style: preserve-3d;
159
+ transition: rotate var(--dur-5) var(--ease-spring);
160
+ }
161
+ .cube > * {
162
+ position: absolute;
163
+ inset: 0;
164
+ display: grid;
165
+ place-items: center;
166
+ border: 1px solid var(--line);
167
+ background: color-mix(in oklab, var(--surface) 88%, transparent);
168
+ backface-visibility: visible;
169
+ }
170
+ .cube > .face-front { transform: translateZ(calc(var(--size) / 2)); }
171
+ .cube > .face-back { transform: rotateY(180deg) translateZ(calc(var(--size) / 2)); }
172
+ .cube > .face-end { transform: rotateY(90deg) translateZ(calc(var(--size) / 2)); }
173
+ .cube > .face-start { transform: rotateY(-90deg) translateZ(calc(var(--size) / 2)); }
174
+ .cube > .face-top { transform: rotateX(90deg) translateZ(calc(var(--size) / 2)); }
175
+ .cube > .face-bottom { transform: rotateX(-90deg) translateZ(calc(var(--size) / 2)); }
176
+
177
+ .cube[data-face="front"] { rotate: 0deg; }
178
+ .cube[data-face="end"] { rotate: y -90deg; }
179
+ .cube[data-face="back"] { rotate: y 180deg; }
180
+ .cube[data-face="start"] { rotate: y 90deg; }
181
+ .cube[data-face="top"] { rotate: x -90deg; }
182
+ .cube[data-face="bottom"] { rotate: x 90deg; }
183
+
184
+ @media (prefers-reduced-motion: no-preference) {
185
+ .cube-spin { animation: dk-cube 14s linear infinite; }
186
+ @keyframes dk-cube {
187
+ from { transform: rotateX(-18deg) rotateY(0deg); }
188
+ to { transform: rotateX(-18deg) rotateY(360deg); }
189
+ }
190
+ }
191
+
192
+ /* --- Depressible controls ------------------------------------------------
193
+ A button with real thickness. The face moves down into the shadow instead
194
+ of just changing color.
195
+ ---------------------------------------------------------------------- */
196
+ .btn-3d {
197
+ --lift: 3px;
198
+ position: relative;
199
+ border: 0;
200
+ box-shadow:
201
+ 0 var(--lift) 0 0 color-mix(in oklab, var(--brand-600) 62%, var(--ink-900)),
202
+ 0 calc(var(--lift) + 3px) 8px oklch(20% .03 var(--hue-neutral) / .22);
203
+ translate: 0 0;
204
+ transition: translate 70ms var(--ease-out), box-shadow 70ms var(--ease-out);
205
+ }
206
+ .btn-3d:active {
207
+ translate: 0 var(--lift);
208
+ box-shadow: 0 0 0 0 color-mix(in oklab, var(--brand-600) 62%, var(--ink-900));
209
+ }
210
+
211
+ /* --- Parallax layers -----------------------------------------------------
212
+ True 3D parallax: layers sit at different Z depths inside a perspective
213
+ container, so the browser does the math on the compositor. No scroll
214
+ handler, no jank.
215
+ ---------------------------------------------------------------------- */
216
+ .parallax {
217
+ block-size: 100%;
218
+ overflow-y: auto;
219
+ overflow-x: hidden;
220
+ perspective: 8px;
221
+ perspective-origin: 0 0;
222
+ }
223
+ .parallax-layer {
224
+ position: absolute;
225
+ inset: 0;
226
+ transform-origin: 0 0;
227
+ }
228
+ .parallax-back { transform: translateZ(-6px) scale(1.75); }
229
+ .parallax-mid { transform: translateZ(-3px) scale(1.375); }
230
+ .parallax-front { transform: translateZ(0); }
231
+
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .parallax { perspective: none; }
234
+ .parallax-layer { transform: none; position: relative; }
235
+ }
236
+
237
+ /* --- Page turn -----------------------------------------------------------
238
+ For stepping through a multi-page form or a wizard. Pairs with
239
+ Deck.transition() so the DOM swap and the motion happen together.
240
+ ---------------------------------------------------------------------- */
241
+ @media (prefers-reduced-motion: no-preference) {
242
+ .turn-out { animation: dk-turn-out var(--dur-3) var(--ease-in) both; transform-origin: left center; }
243
+ .turn-in { animation: dk-turn-in var(--dur-4) var(--ease-out) both; transform-origin: left center; }
244
+ @keyframes dk-turn-out {
245
+ to { transform: perspective(1400px) rotateY(-22deg); opacity: 0; }
246
+ }
247
+ @keyframes dk-turn-in {
248
+ from { transform: perspective(1400px) rotateY(24deg); opacity: 0; }
249
+ }
250
+ [dir="rtl"] :is(.turn-out, .turn-in) { transform-origin: right center; }
251
+ }
252
+
253
+ /* --- Layered elevation ---------------------------------------------------
254
+ Lift an element toward the viewer inside a scene, rather than faking it
255
+ with a bigger shadow.
256
+ ---------------------------------------------------------------------- */
257
+ .z-lift-1 { transform: translateZ(8px); }
258
+ .z-lift-2 { transform: translateZ(20px); }
259
+ .z-lift-3 { transform: translateZ(40px); }
260
+ .z-sink { transform: translateZ(-16px); }
261
+
262
+ /* --- Performance guard ---------------------------------------------------
263
+ 3D forces layers onto the GPU. Promote only while something is actually
264
+ moving, and hand the memory back when it stops.
265
+ ---------------------------------------------------------------------- */
266
+ .tilt.is-tilting, .flip.is-flipped, .pile.is-fanned > *,
267
+ .coverflow > *, .cube {
268
+ will-change: transform, rotate, scale;
269
+ }
270
+ .tilt:not(.is-tilting), .flip:not(.is-flipped) { will-change: auto; }
271
+
272
+ @media (prefers-reduced-motion: reduce) {
273
+ .flip, .flip-x, .tilt, .cube, .pile > *, .coverflow > * {
274
+ transition-duration: 1ms;
275
+ rotate: none;
276
+ }
277
+ .flip.is-flipped, .flip-x.is-flipped { rotate: y 180deg; }
278
+ .coverflow > * { rotate: none; scale: 1; }
279
+ }
280
+ }
@@ -0,0 +1,201 @@
1
+ /* ==========================================================================
2
+ Deck — 13 Toasts
3
+ A stack, not a list. Collapsed toasts sit behind the newest one and fan out
4
+ on hover or focus. Queue logic is in deck.js (Deck.toast).
5
+ ========================================================================== */
6
+
7
+ @layer deck.components {
8
+
9
+ /* The fixed container. pointer-events: none on the region and auto on each
10
+ toast, so the empty area does not swallow clicks on the page beneath. Sits
11
+ above a .tabbar when the page has one, and clears the home indicator. */
12
+ .toast-region {
13
+ position: fixed;
14
+ z-index: var(--z-toast);
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: var(--space-2);
18
+ inline-size: min(23rem, calc(100vw - var(--space-6)));
19
+ pointer-events: none;
20
+ /* the stack grows upward from the bottom by default */
21
+ inset-block-end: calc(var(--space-4) + env(safe-area-inset-bottom));
22
+ inset-inline-end: var(--space-4);
23
+ }
24
+ body:has(.tabbar) .toast-region {
25
+ inset-block-end: calc(var(--space-4) + var(--tap) + env(safe-area-inset-bottom));
26
+ }
27
+ @media (min-width: 48rem) {
28
+ body:has(.tabbar) .toast-region { inset-block-end: calc(var(--space-4) + env(safe-area-inset-bottom)); }
29
+ }
30
+
31
+ .toast-region-start { inset-inline: var(--space-4) auto; }
32
+ .toast-region-center { inset-inline: 0; margin-inline: auto; }
33
+ .toast-region-top {
34
+ inset-block: calc(var(--space-4) + env(safe-area-inset-top)) auto;
35
+ flex-direction: column-reverse;
36
+ }
37
+
38
+ /* On a phone the stack spans the width and sits above the thumb */
39
+ @media (max-width: 30rem) {
40
+ .toast-region { inset-inline: var(--space-3); inline-size: auto; }
41
+ }
42
+
43
+ /* --- The toast ----------------------------------------------------------- */
44
+ /* One notification. The accent colour is a single custom property,
45
+ --toast-accent, which paints the edge, the icon and the timer together, so
46
+ a kind is one declaration. Built by Deck.toast() rather than written by
47
+ hand; touch-action: pan-y lets a horizontal swipe dismiss it while a
48
+ vertical drag still scrolls the page. */
49
+ .toast {
50
+ --toast-accent: var(--ink-400);
51
+ position: relative;
52
+ display: flex;
53
+ align-items: flex-start;
54
+ gap: var(--space-3);
55
+ padding: var(--space-3) var(--space-4);
56
+ background: var(--surface);
57
+ color: var(--text);
58
+ border: 1px solid var(--line);
59
+ border-radius: var(--r-md);
60
+ box-shadow: var(--shadow-4);
61
+ font-size: var(--text-sm);
62
+ pointer-events: auto;
63
+ overflow: hidden;
64
+ touch-action: pan-y;
65
+ transform-origin: bottom center;
66
+ transition: translate var(--dur-3) var(--ease-out),
67
+ scale var(--dur-3) var(--ease-out),
68
+ opacity var(--dur-2) var(--ease-out),
69
+ margin var(--dur-3) var(--ease-out);
70
+ }
71
+ .toast::before {
72
+ content: "";
73
+ position: absolute;
74
+ inset-block: 0;
75
+ inset-inline-start: 0;
76
+ inline-size: 3px;
77
+ background: var(--toast-accent);
78
+ }
79
+
80
+ /* The leading glyph, coloured by --toast-accent so it matches the kind
81
+ without a rule of its own. */
82
+ .toast-icon { color: var(--toast-accent); margin-block-start: 1px; flex: 0 0 auto; }
83
+ /* The text column of a toast. min-inline-size: 0 so a long unbroken string
84
+ cannot push the close button off the end. */
85
+ .toast-main { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 2px; }
86
+ .toast-title { font-weight: 620; }
87
+ .toast-text { color: var(--text-muted); }
88
+ .toast-close {
89
+ flex: 0 0 auto;
90
+ inline-size: 26px;
91
+ block-size: 26px;
92
+ display: grid;
93
+ place-items: center;
94
+ border-radius: var(--r-full);
95
+ color: var(--text-faint);
96
+ cursor: pointer;
97
+ margin-block-start: -2px;
98
+ margin-inline-end: -4px;
99
+ }
100
+ .toast-close:hover { background: var(--surface-hover); color: var(--text); }
101
+ .toast-actions { display: flex; gap: var(--space-2); margin-block-start: var(--space-2); }
102
+ .toast-action {
103
+ font-size: var(--text-sm);
104
+ font-weight: 620;
105
+ color: var(--brand);
106
+ cursor: pointer;
107
+ padding: var(--space-1) 0;
108
+ }
109
+ .toast-action:hover { text-decoration: underline; }
110
+
111
+ /* --- Kinds --------------------------------------------------------------- */
112
+ .toast-good { --toast-accent: var(--good-500); }
113
+ .toast-warn { --toast-accent: var(--warn-500); }
114
+ .toast-bad { --toast-accent: var(--bad-500); }
115
+ .toast-info { --toast-accent: var(--brand-500); }
116
+ .toast-loading { --toast-accent: var(--brand-500); }
117
+
118
+ /* --- Auto-dismiss timer -------------------------------------------------- */
119
+ /* The auto-dismiss bar. A CSS animation rather than a JavaScript countdown,
120
+ which is what allows hovering or focusing the region to pause every timer
121
+ in it with one declaration — so a reader moving toward an Undo button does
122
+ not have it vanish as they arrive. */
123
+ .toast-timer {
124
+ position: absolute;
125
+ inset-block-end: 0;
126
+ inset-inline-start: 0;
127
+ block-size: 2px;
128
+ inline-size: 100%;
129
+ background: var(--toast-accent);
130
+ opacity: .5;
131
+ transform-origin: left center;
132
+ animation: dk-toast-timer linear forwards;
133
+ animation-duration: var(--toast-duration, 5s);
134
+ }
135
+ @keyframes dk-toast-timer { to { scale: 0 1; } }
136
+ /* Hovering the stack pauses every timer in it */
137
+ .toast-region:is(:hover, :focus-within) .toast-timer { animation-play-state: paused; }
138
+
139
+ /* --- Enter and exit ------------------------------------------------------ */
140
+ .toast { animation: dk-toast-in var(--dur-3) var(--ease-spring); }
141
+ @keyframes dk-toast-in {
142
+ from { opacity: 0; translate: 0 16px; scale: .94; }
143
+ }
144
+ .toast-region-top .toast { animation-name: dk-toast-in-top; }
145
+ @keyframes dk-toast-in-top {
146
+ from { opacity: 0; translate: 0 -16px; scale: .94; }
147
+ }
148
+ .toast.is-leaving {
149
+ opacity: 0;
150
+ scale: .94;
151
+ translate: var(--toast-exit-x, 0) 8px;
152
+ margin-block-end: calc((var(--toast-h, 60px) + var(--space-2)) * -1);
153
+ pointer-events: none;
154
+ }
155
+
156
+ /* --- Collapsed stack -----------------------------------------------------
157
+ --i counts from the newest, which is the first child because push()
158
+ prepends. Beyond the third toast they hide behind.
159
+ ---------------------------------------------------------------------- */
160
+ @supports (animation-delay: calc(sibling-index() * 1ms)) {
161
+ .toast-region-stacked .toast { --i: calc(sibling-index() - 1); }
162
+ }
163
+ @supports not (animation-delay: calc(sibling-index() * 1ms)) {
164
+ .toast-region-stacked .toast:nth-child(1) { --i: 0; }
165
+ .toast-region-stacked .toast:nth-child(2) { --i: 1; }
166
+ .toast-region-stacked .toast:nth-child(3) { --i: 2; }
167
+ .toast-region-stacked .toast:nth-child(4) { --i: 3; }
168
+ .toast-region-stacked .toast:nth-child(5) { --i: 4; }
169
+ .toast-region-stacked .toast:nth-child(6) { --i: 5; }
170
+ .toast-region-stacked .toast:nth-child(7) { --i: 6; }
171
+ .toast-region-stacked .toast:nth-child(8) { --i: 7; }
172
+ }
173
+ .toast-region-stacked .toast {
174
+ position: absolute;
175
+ inset-inline: 0;
176
+ inset-block-end: 0;
177
+ translate: 0 calc(var(--i) * -8px);
178
+ scale: calc(1 - var(--i) * .04);
179
+ opacity: calc(1 - var(--i) * .18);
180
+ z-index: calc(100 - var(--i));
181
+ }
182
+ .toast-region-stacked .toast:nth-child(n+4) { opacity: 0; pointer-events: none; }
183
+
184
+ .toast-region-stacked:is(:hover, :focus-within) .toast {
185
+ translate: 0 calc(var(--i) * (var(--toast-h, 68px) + var(--space-2)) * -1);
186
+ scale: 1;
187
+ opacity: 1;
188
+ }
189
+
190
+ /* --- Swipe to dismiss ---------------------------------------------------- */
191
+ .toast.is-dragging { transition: none; cursor: grabbing; user-select: none; }
192
+
193
+ /* --- Undo pattern -------------------------------------------------------- */
194
+ .toast-undo { --toast-accent: var(--ink-500); }
195
+ .toast-undo .toast-action { color: var(--text); }
196
+
197
+ @media (prefers-reduced-motion: reduce) {
198
+ .toast, .toast-region-stacked .toast { animation: none; transition: opacity var(--dur-1) linear; }
199
+ .toast-timer { animation: none; display: none; }
200
+ }
201
+ }