@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,400 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 24 Media and messaging
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
@layer deck.components {
|
|
6
|
+
|
|
7
|
+
/* --- Video ---------------------------------------------------------------
|
|
8
|
+
A responsive frame for a native video or an embed, plus styling for the
|
|
9
|
+
native controls where the browser allows it.
|
|
10
|
+
---------------------------------------------------------------------- */
|
|
11
|
+
.video {
|
|
12
|
+
position: relative;
|
|
13
|
+
inline-size: 100%;
|
|
14
|
+
aspect-ratio: var(--ratio, 16 / 9);
|
|
15
|
+
background: var(--ink-950);
|
|
16
|
+
border-radius: var(--r-md);
|
|
17
|
+
overflow: clip;
|
|
18
|
+
isolation: isolate;
|
|
19
|
+
}
|
|
20
|
+
.video > :is(video, iframe, embed, object) {
|
|
21
|
+
position: absolute;
|
|
22
|
+
inset: 0;
|
|
23
|
+
inline-size: 100%;
|
|
24
|
+
block-size: 100%;
|
|
25
|
+
border: 0;
|
|
26
|
+
object-fit: contain;
|
|
27
|
+
}
|
|
28
|
+
.video-cover > video { object-fit: cover; }
|
|
29
|
+
.video-square { --ratio: 1; }
|
|
30
|
+
.video-portrait { --ratio: 9 / 16; }
|
|
31
|
+
.video-wide { --ratio: 21 / 9; }
|
|
32
|
+
|
|
33
|
+
.video video::-webkit-media-controls-panel { background-image: none; }
|
|
34
|
+
.video video { accent-color: var(--brand-500); }
|
|
35
|
+
|
|
36
|
+
/* Poster overlay with a play button, for a click-to-load embed */
|
|
37
|
+
.video-poster {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset: 0;
|
|
40
|
+
z-index: 1;
|
|
41
|
+
display: grid;
|
|
42
|
+
place-items: center;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
border: 0;
|
|
45
|
+
padding: 0;
|
|
46
|
+
background: none;
|
|
47
|
+
}
|
|
48
|
+
.video-poster img { inline-size: 100%; block-size: 100%; object-fit: cover; }
|
|
49
|
+
.video-poster::after {
|
|
50
|
+
content: "";
|
|
51
|
+
position: absolute;
|
|
52
|
+
inset: 0;
|
|
53
|
+
background: linear-gradient(to top, oklch(12% .02 var(--hue-neutral) / .55), transparent 60%);
|
|
54
|
+
}
|
|
55
|
+
.video-play {
|
|
56
|
+
position: relative;
|
|
57
|
+
z-index: 1;
|
|
58
|
+
inline-size: 64px;
|
|
59
|
+
block-size: 64px;
|
|
60
|
+
border-radius: var(--r-full);
|
|
61
|
+
display: grid;
|
|
62
|
+
place-items: center;
|
|
63
|
+
background: color-mix(in oklab, var(--surface) 90%, transparent);
|
|
64
|
+
backdrop-filter: blur(8px);
|
|
65
|
+
-webkit-backdrop-filter: blur(8px);
|
|
66
|
+
color: var(--text);
|
|
67
|
+
box-shadow: var(--shadow-3);
|
|
68
|
+
transition: scale var(--dur-2) var(--ease-spring), background-color var(--dur-1) var(--ease-out);
|
|
69
|
+
}
|
|
70
|
+
.video-poster:hover .video-play { scale: 1.08; background: var(--surface); }
|
|
71
|
+
.video-meta {
|
|
72
|
+
position: absolute;
|
|
73
|
+
inset-block-end: var(--space-3);
|
|
74
|
+
inset-inline-start: var(--space-3);
|
|
75
|
+
z-index: 2;
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: var(--space-2);
|
|
78
|
+
color: oklch(98% 0 0);
|
|
79
|
+
font-size: var(--text-xs);
|
|
80
|
+
text-shadow: 0 1px 3px oklch(0% 0 0 / .6);
|
|
81
|
+
}
|
|
82
|
+
.video-duration {
|
|
83
|
+
padding: 2px 6px;
|
|
84
|
+
border-radius: var(--r-xs);
|
|
85
|
+
background: oklch(12% 0 0 / .72);
|
|
86
|
+
font-variant-numeric: tabular-nums;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* --- Masonry gallery -----------------------------------------------------
|
|
90
|
+
Two implementations. Columns is universal and good for photos where the
|
|
91
|
+
reading order down each column is fine. Grid masonry is the real thing
|
|
92
|
+
and preserves row order; it is used automatically where supported.
|
|
93
|
+
---------------------------------------------------------------------- */
|
|
94
|
+
.masonry {
|
|
95
|
+
columns: var(--cols, 1);
|
|
96
|
+
column-gap: var(--gap, var(--space-3));
|
|
97
|
+
}
|
|
98
|
+
@media (min-width: 30rem) { .masonry { --cols: 2; } }
|
|
99
|
+
@media (min-width: 48rem) { .masonry { --cols: 3; } }
|
|
100
|
+
@media (min-width: 72rem) { .masonry { --cols: 4; } }
|
|
101
|
+
.masonry-2 { --cols: 2; }
|
|
102
|
+
.masonry-3 { --cols: 3; }
|
|
103
|
+
|
|
104
|
+
.masonry > * {
|
|
105
|
+
break-inside: avoid;
|
|
106
|
+
margin-block-end: var(--gap, var(--space-3));
|
|
107
|
+
border-radius: var(--r-md);
|
|
108
|
+
overflow: clip;
|
|
109
|
+
display: block;
|
|
110
|
+
}
|
|
111
|
+
.masonry img { inline-size: 100%; block-size: auto; display: block; }
|
|
112
|
+
|
|
113
|
+
@supports (grid-template-rows: masonry) {
|
|
114
|
+
.masonry {
|
|
115
|
+
columns: auto;
|
|
116
|
+
display: grid;
|
|
117
|
+
grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr));
|
|
118
|
+
grid-template-rows: masonry;
|
|
119
|
+
gap: var(--gap, var(--space-3));
|
|
120
|
+
}
|
|
121
|
+
.masonry > * { margin-block-end: 0; }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* A simple gallery grid, when equal tiles are wanted instead */
|
|
125
|
+
.gallery {
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr));
|
|
128
|
+
gap: var(--space-2);
|
|
129
|
+
}
|
|
130
|
+
.gallery > * {
|
|
131
|
+
aspect-ratio: 1;
|
|
132
|
+
border-radius: var(--r-sm);
|
|
133
|
+
overflow: clip;
|
|
134
|
+
position: relative;
|
|
135
|
+
}
|
|
136
|
+
.gallery img { inline-size: 100%; block-size: 100%; object-fit: cover; transition: scale var(--dur-3) var(--ease-out); }
|
|
137
|
+
.gallery a:hover img { scale: 1.05; }
|
|
138
|
+
/* Let one tile take a bigger cell */
|
|
139
|
+
.gallery .span-2 { grid-column: span 2; grid-row: span 2; aspect-ratio: 1; }
|
|
140
|
+
.gallery .span-wide { grid-column: span 2; aspect-ratio: 2; }
|
|
141
|
+
|
|
142
|
+
/* --- Lazy loading --------------------------------------------------------
|
|
143
|
+
A placeholder that holds the space so nothing shifts, and fades the image
|
|
144
|
+
in once it decodes. Set the aspect ratio on the frame and the layout is
|
|
145
|
+
stable before a byte arrives.
|
|
146
|
+
---------------------------------------------------------------------- */
|
|
147
|
+
.lazy {
|
|
148
|
+
position: relative;
|
|
149
|
+
display: block;
|
|
150
|
+
background: var(--bg-sunken);
|
|
151
|
+
overflow: clip;
|
|
152
|
+
aspect-ratio: var(--ratio, 3 / 2);
|
|
153
|
+
}
|
|
154
|
+
.lazy > img {
|
|
155
|
+
inline-size: 100%;
|
|
156
|
+
block-size: 100%;
|
|
157
|
+
object-fit: cover;
|
|
158
|
+
opacity: 0;
|
|
159
|
+
transition: opacity var(--dur-3) var(--ease-out);
|
|
160
|
+
}
|
|
161
|
+
.lazy > img.is-loaded, .lazy > img[data-loaded] { opacity: 1; }
|
|
162
|
+
/* Two things about this shimmer are recorded in FINDINGS.md:
|
|
163
|
+
|
|
164
|
+
67: it runs the same dk-shimmer keyframes as .skeleton, for the same
|
|
165
|
+
purpose, but .lazy::after is NOT in 02-reset.css's reduced-motion
|
|
166
|
+
exemption list -- so it freezes where .skeleton slows to 2.4s. The
|
|
167
|
+
durations differ too (1.6s here, 1.4s there). One of the two is wrong.
|
|
168
|
+
|
|
169
|
+
66: deck-extras.js adds .is-error to the wrapper when an image fails to
|
|
170
|
+
load, and nothing in src/ styles that class. So the shimmer carries on
|
|
171
|
+
forever and a broken image is indistinguishable from a loading one. */
|
|
172
|
+
.lazy::after {
|
|
173
|
+
content: "";
|
|
174
|
+
position: absolute;
|
|
175
|
+
inset: 0;
|
|
176
|
+
background: linear-gradient(100deg, transparent 20%,
|
|
177
|
+
color-mix(in oklab, var(--ink-300) 40%, transparent) 45%, transparent 70%);
|
|
178
|
+
background-size: 250% 100%;
|
|
179
|
+
background-repeat: no-repeat;
|
|
180
|
+
animation: dk-shimmer 1.6s var(--ease-in-out) infinite;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
}
|
|
183
|
+
.lazy:has(img.is-loaded)::after, .lazy:has(img[data-loaded])::after { display: none; }
|
|
184
|
+
|
|
185
|
+
/* A blurred low-res preview underneath, if you have one */
|
|
186
|
+
.lazy-blur { background-size: cover; background-position: center; }
|
|
187
|
+
.lazy-blur::before {
|
|
188
|
+
content: "";
|
|
189
|
+
position: absolute;
|
|
190
|
+
inset: -8px;
|
|
191
|
+
background: inherit;
|
|
192
|
+
filter: blur(14px);
|
|
193
|
+
transition: opacity var(--dur-3) var(--ease-out);
|
|
194
|
+
}
|
|
195
|
+
.lazy-blur:has(img.is-loaded)::before { opacity: 0; }
|
|
196
|
+
|
|
197
|
+
/* Generic content placeholder while a region streams in */
|
|
198
|
+
.lazy-block {
|
|
199
|
+
display: grid;
|
|
200
|
+
place-items: center;
|
|
201
|
+
min-block-size: 8rem;
|
|
202
|
+
border: 1px dashed var(--line);
|
|
203
|
+
border-radius: var(--r-md);
|
|
204
|
+
color: var(--text-faint);
|
|
205
|
+
gap: var(--space-2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* --- Chat bubbles --------------------------------------------------------
|
|
209
|
+
Conversation view. Consecutive messages from one side group together and
|
|
210
|
+
only the last one in a run gets the tail.
|
|
211
|
+
---------------------------------------------------------------------- */
|
|
212
|
+
.chat {
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-direction: column;
|
|
215
|
+
gap: var(--space-1);
|
|
216
|
+
padding: var(--space-4);
|
|
217
|
+
overflow-y: auto;
|
|
218
|
+
overscroll-behavior: contain;
|
|
219
|
+
}
|
|
220
|
+
/* A centred date pill between days. Styling, not structure — a screen
|
|
221
|
+
reader hears ordinary text. */
|
|
222
|
+
.chat-day {
|
|
223
|
+
align-self: center;
|
|
224
|
+
margin-block: var(--space-4) var(--space-2);
|
|
225
|
+
padding: var(--space-1) var(--space-3);
|
|
226
|
+
border-radius: var(--r-full);
|
|
227
|
+
background: var(--surface-2);
|
|
228
|
+
color: var(--text-faint);
|
|
229
|
+
font-size: var(--text-xs);
|
|
230
|
+
font-weight: 560;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* One message row. .msg-out flips it to the other edge and reverses the row
|
|
234
|
+
so the avatar swaps sides. The incoming/outgoing distinction is position
|
|
235
|
+
and colour only, so name the sender in text for anyone not seeing it. */
|
|
236
|
+
.msg {
|
|
237
|
+
display: flex;
|
|
238
|
+
gap: var(--space-2);
|
|
239
|
+
align-items: flex-end;
|
|
240
|
+
max-inline-size: min(80%, 34rem);
|
|
241
|
+
align-self: flex-start;
|
|
242
|
+
}
|
|
243
|
+
.msg-out { align-self: flex-end; flex-direction: row-reverse; }
|
|
244
|
+
|
|
245
|
+
/* The message body. Its corner radii are logical, which is what lets the
|
|
246
|
+
grouping flatten the correct side in either writing direction. */
|
|
247
|
+
.bubble {
|
|
248
|
+
position: relative;
|
|
249
|
+
padding: var(--space-2) var(--space-3);
|
|
250
|
+
border-radius: var(--r-md);
|
|
251
|
+
background: var(--surface-2);
|
|
252
|
+
border: 1px solid var(--line);
|
|
253
|
+
font-size: var(--text-sm);
|
|
254
|
+
line-height: 1.45;
|
|
255
|
+
overflow-wrap: anywhere;
|
|
256
|
+
border-end-start-radius: var(--r-xs);
|
|
257
|
+
}
|
|
258
|
+
.msg-out .bubble {
|
|
259
|
+
background: var(--brand-600);
|
|
260
|
+
color: var(--text-on-brand);
|
|
261
|
+
border-color: transparent;
|
|
262
|
+
border-end-start-radius: var(--r-md);
|
|
263
|
+
border-end-end-radius: var(--r-xs);
|
|
264
|
+
}
|
|
265
|
+
/* Grouping: only the last bubble of a run keeps its tail */
|
|
266
|
+
.msg:not(:last-of-type) + .msg .bubble { border-start-start-radius: var(--r-xs); }
|
|
267
|
+
.msg-out:not(:last-of-type) + .msg-out .bubble { border-start-end-radius: var(--r-xs); }
|
|
268
|
+
|
|
269
|
+
.bubble-meta {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: var(--space-1);
|
|
273
|
+
margin-block-start: 2px;
|
|
274
|
+
font-size: var(--text-2xs);
|
|
275
|
+
opacity: .7;
|
|
276
|
+
justify-content: flex-end;
|
|
277
|
+
font-variant-numeric: tabular-nums;
|
|
278
|
+
}
|
|
279
|
+
.msg:not(.msg-out) .bubble-meta { justify-content: flex-start; }
|
|
280
|
+
.bubble-name { font-weight: 620; font-size: var(--text-xs); margin-block-end: 2px; color: var(--brand); }
|
|
281
|
+
.msg-out .bubble-name { color: inherit; }
|
|
282
|
+
|
|
283
|
+
.bubble-attachment {
|
|
284
|
+
margin: calc(var(--space-2) * -1) calc(var(--space-3) * -1) var(--space-2);
|
|
285
|
+
border-start-start-radius: var(--r-md);
|
|
286
|
+
border-start-end-radius: var(--r-md);
|
|
287
|
+
overflow: clip;
|
|
288
|
+
}
|
|
289
|
+
.bubble-attachment img { inline-size: 100%; display: block; }
|
|
290
|
+
|
|
291
|
+
.msg .avatar { --size: 28px; align-self: flex-end; margin-block-end: 2px; }
|
|
292
|
+
.msg:not(:last-of-type) + .msg .avatar { visibility: hidden; }
|
|
293
|
+
|
|
294
|
+
.bubble-system {
|
|
295
|
+
align-self: center;
|
|
296
|
+
background: none;
|
|
297
|
+
border: 0;
|
|
298
|
+
color: var(--text-faint);
|
|
299
|
+
font-size: var(--text-xs);
|
|
300
|
+
text-align: center;
|
|
301
|
+
max-inline-size: 42ch;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Typing indicator */
|
|
305
|
+
.bubble-typing { display: inline-flex; gap: 4px; padding-block: var(--space-3); }
|
|
306
|
+
.bubble-typing > span {
|
|
307
|
+
inline-size: 6px;
|
|
308
|
+
block-size: 6px;
|
|
309
|
+
border-radius: var(--r-full);
|
|
310
|
+
background: var(--text-faint);
|
|
311
|
+
}
|
|
312
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
313
|
+
.bubble-typing > span { animation: dk-typing 1.2s var(--ease-in-out) infinite; }
|
|
314
|
+
.bubble-typing > span:nth-child(2) { animation-delay: .16s; }
|
|
315
|
+
.bubble-typing > span:nth-child(3) { animation-delay: .32s; }
|
|
316
|
+
@keyframes dk-typing {
|
|
317
|
+
0%, 60%, 100% { translate: 0 0; opacity: .45; }
|
|
318
|
+
30% { translate: 0 -4px; opacity: 1; }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Composer pinned to the bottom */
|
|
323
|
+
.chat-composer {
|
|
324
|
+
display: flex;
|
|
325
|
+
align-items: flex-end;
|
|
326
|
+
gap: var(--space-2);
|
|
327
|
+
padding: var(--space-3) var(--space-4);
|
|
328
|
+
padding-block-end: max(var(--space-3), env(safe-area-inset-bottom));
|
|
329
|
+
border-block-start: 1px solid var(--line);
|
|
330
|
+
background: var(--surface);
|
|
331
|
+
}
|
|
332
|
+
.chat-composer .textarea {
|
|
333
|
+
min-block-size: var(--control-h);
|
|
334
|
+
max-block-size: 8rem;
|
|
335
|
+
field-sizing: content;
|
|
336
|
+
border-radius: var(--r-lg);
|
|
337
|
+
resize: none;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* --- QR code -------------------------------------------------------------
|
|
341
|
+
deck.js renders the modules into this frame. Kept square, with a quiet
|
|
342
|
+
zone, so it stays scannable at any size.
|
|
343
|
+
---------------------------------------------------------------------- */
|
|
344
|
+
.qr {
|
|
345
|
+
display: inline-grid;
|
|
346
|
+
place-items: center;
|
|
347
|
+
padding: var(--qr-quiet, 12px);
|
|
348
|
+
background: var(--qr-bg, #fff);
|
|
349
|
+
border-radius: var(--r-sm);
|
|
350
|
+
border: 1px solid var(--line);
|
|
351
|
+
inline-size: fit-content;
|
|
352
|
+
-webkit-print-color-adjust: exact;
|
|
353
|
+
print-color-adjust: exact;
|
|
354
|
+
}
|
|
355
|
+
.qr svg { display: block; inline-size: var(--qr-size, 160px); block-size: var(--qr-size, 160px); }
|
|
356
|
+
.qr svg rect { fill: var(--qr-fg, #000); shape-rendering: crispEdges; }
|
|
357
|
+
.qr-lg { --qr-size: 240px; }
|
|
358
|
+
.qr-sm { --qr-size: 104px; }
|
|
359
|
+
/* A logo punched out of the middle. Only safe at error correction Q or H. */
|
|
360
|
+
.qr-logo {
|
|
361
|
+
position: relative;
|
|
362
|
+
}
|
|
363
|
+
.qr-logo > .qr-logo-mark {
|
|
364
|
+
position: absolute;
|
|
365
|
+
inset: 50%;
|
|
366
|
+
translate: -50% -50%;
|
|
367
|
+
inline-size: 22%;
|
|
368
|
+
block-size: 22%;
|
|
369
|
+
display: grid;
|
|
370
|
+
place-items: center;
|
|
371
|
+
background: var(--qr-bg, #fff);
|
|
372
|
+
border-radius: var(--r-xs);
|
|
373
|
+
padding: 4px;
|
|
374
|
+
}
|
|
375
|
+
.qr-caption {
|
|
376
|
+
margin-block-start: var(--space-2);
|
|
377
|
+
font-size: var(--text-xs);
|
|
378
|
+
color: var(--text-muted);
|
|
379
|
+
text-align: center;
|
|
380
|
+
max-inline-size: var(--qr-size, 160px);
|
|
381
|
+
overflow-wrap: anywhere;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@layer deck.print {
|
|
386
|
+
@media print {
|
|
387
|
+
/* A QR code is often the whole point of printing the page */
|
|
388
|
+
.qr { border: 0; break-inside: avoid; }
|
|
389
|
+
.video, .chat-composer, .carousel-arrow, .carousel-dots,
|
|
390
|
+
.speed-dial, .drawer, .mega, .banner { display: none !important; }
|
|
391
|
+
.masonry { columns: 2; }
|
|
392
|
+
.chat { overflow: visible; max-block-size: none; }
|
|
393
|
+
.msg { break-inside: avoid; }
|
|
394
|
+
.bubble { border: 1px solid #bbb !important; background: #fff !important; color: #000 !important; }
|
|
395
|
+
.msg-out .bubble { background: #f2f2f2 !important; }
|
|
396
|
+
.lazy::after { display: none !important; }
|
|
397
|
+
.editor-toolbar, .editor-footer { display: none !important; }
|
|
398
|
+
.editor-content { max-block-size: none !important; overflow: visible !important; }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 08 Mobile
|
|
3
|
+
The parts Tailwind makes you build yourself every time.
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
@layer deck.mobile {
|
|
7
|
+
|
|
8
|
+
/* --- Bottom sheet -------------------------------------------------------
|
|
9
|
+
<dialog class="sheet"> on a phone, a centred modal from 40rem up.
|
|
10
|
+
---------------------------------------------------------------------- */
|
|
11
|
+
.sheet {
|
|
12
|
+
inline-size: 100%;
|
|
13
|
+
max-inline-size: 100%;
|
|
14
|
+
max-block-size: 88dvh;
|
|
15
|
+
margin: 0;
|
|
16
|
+
margin-block-start: auto;
|
|
17
|
+
padding: 0;
|
|
18
|
+
border: 0;
|
|
19
|
+
border-start-start-radius: var(--r-lg);
|
|
20
|
+
border-start-end-radius: var(--r-lg);
|
|
21
|
+
background: var(--surface);
|
|
22
|
+
color: var(--text);
|
|
23
|
+
box-shadow: var(--shadow-4);
|
|
24
|
+
overflow: clip;
|
|
25
|
+
translate: 0 100%;
|
|
26
|
+
transition: translate var(--dur-3) var(--ease-out),
|
|
27
|
+
display var(--dur-3) allow-discrete,
|
|
28
|
+
overlay var(--dur-3) allow-discrete;
|
|
29
|
+
}
|
|
30
|
+
.sheet[open] { translate: 0; }
|
|
31
|
+
@starting-style { .sheet[open] { translate: 0 100%; } }
|
|
32
|
+
|
|
33
|
+
.sheet::backdrop {
|
|
34
|
+
background: oklch(18% .02 var(--hue-neutral) / .5);
|
|
35
|
+
opacity: 0;
|
|
36
|
+
transition: opacity var(--dur-3) var(--ease-out),
|
|
37
|
+
display var(--dur-3) allow-discrete,
|
|
38
|
+
overlay var(--dur-3) allow-discrete;
|
|
39
|
+
}
|
|
40
|
+
.sheet[open]::backdrop { opacity: 1; }
|
|
41
|
+
@starting-style { .sheet[open]::backdrop { opacity: 0; } }
|
|
42
|
+
|
|
43
|
+
/* The drag handle at the top of a bottom sheet. Deck draws it and does not
|
|
44
|
+
implement the drag — swiping down does nothing — so always ship a real
|
|
45
|
+
close button as well. */
|
|
46
|
+
.sheet-grip {
|
|
47
|
+
inline-size: 38px;
|
|
48
|
+
block-size: 4px;
|
|
49
|
+
border-radius: var(--r-full);
|
|
50
|
+
background: var(--ink-300);
|
|
51
|
+
margin: var(--space-3) auto var(--space-1);
|
|
52
|
+
}
|
|
53
|
+
/* The title row of a sheet. */
|
|
54
|
+
.sheet-header { padding: var(--space-3) var(--space-5); display: flex; align-items: center; gap: var(--space-3); }
|
|
55
|
+
/* The sheet's heading. Point aria-labelledby at it. */
|
|
56
|
+
.sheet-title { font-size: var(--text-md); font-weight: 640; }
|
|
57
|
+
/* The scrolling region. Pads for the home indicator, and contains its
|
|
58
|
+
overscroll so reaching the end does not scroll the page behind. */
|
|
59
|
+
.sheet-body {
|
|
60
|
+
padding: var(--space-2) var(--space-5) calc(var(--space-6) + env(safe-area-inset-bottom));
|
|
61
|
+
overflow-y: auto;
|
|
62
|
+
overscroll-behavior: contain;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: var(--space-4);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (min-width: 40rem) {
|
|
69
|
+
.sheet {
|
|
70
|
+
inline-size: min(30rem, calc(100vw - var(--space-8)));
|
|
71
|
+
margin: auto;
|
|
72
|
+
border-radius: var(--r-lg);
|
|
73
|
+
translate: 0;
|
|
74
|
+
opacity: 0;
|
|
75
|
+
scale: .97;
|
|
76
|
+
transition: opacity var(--dur-2) var(--ease-out), scale var(--dur-2) var(--ease-spring),
|
|
77
|
+
display var(--dur-2) allow-discrete, overlay var(--dur-2) allow-discrete;
|
|
78
|
+
}
|
|
79
|
+
.sheet[open] { opacity: 1; scale: 1; }
|
|
80
|
+
@starting-style { .sheet[open] { opacity: 0; scale: .97; } }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* --- Bottom tab bar ----------------------------------------------------- */
|
|
84
|
+
.tabbar {
|
|
85
|
+
position: fixed;
|
|
86
|
+
inset-inline: 0;
|
|
87
|
+
inset-block-end: 0;
|
|
88
|
+
z-index: var(--z-nav);
|
|
89
|
+
display: flex;
|
|
90
|
+
background: color-mix(in oklab, var(--surface) 88%, transparent);
|
|
91
|
+
backdrop-filter: saturate(1.8) blur(16px);
|
|
92
|
+
-webkit-backdrop-filter: saturate(1.8) blur(16px);
|
|
93
|
+
border-block-start: 1px solid var(--line);
|
|
94
|
+
padding-block-end: env(safe-area-inset-bottom);
|
|
95
|
+
}
|
|
96
|
+
@media (min-width: 48rem) { .tabbar { display: none; } }
|
|
97
|
+
|
|
98
|
+
.tabbar-item {
|
|
99
|
+
flex: 1 1 0;
|
|
100
|
+
min-block-size: var(--tap);
|
|
101
|
+
padding-block: var(--space-2);
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
gap: 3px;
|
|
107
|
+
color: var(--text-faint);
|
|
108
|
+
font-size: var(--text-2xs);
|
|
109
|
+
font-weight: 560;
|
|
110
|
+
text-decoration: none;
|
|
111
|
+
-webkit-tap-highlight-color: transparent;
|
|
112
|
+
transition: color var(--dur-1) var(--ease-out);
|
|
113
|
+
}
|
|
114
|
+
.tabbar-item[aria-current] { color: var(--brand); }
|
|
115
|
+
.tabbar-item[aria-current] .icon { scale: 1.06; }
|
|
116
|
+
.tabbar-item .icon { transition: scale var(--dur-2) var(--ease-spring); }
|
|
117
|
+
|
|
118
|
+
/* Give the page room so the tab bar never covers the last row */
|
|
119
|
+
body:has(.tabbar) { padding-block-end: calc(var(--tap) + var(--space-4) + env(safe-area-inset-bottom)); }
|
|
120
|
+
@media (min-width: 48rem) { body:has(.tabbar) { padding-block-end: 0; } }
|
|
121
|
+
|
|
122
|
+
/* --- Safe area helpers --------------------------------------------------- */
|
|
123
|
+
.safe-top { padding-block-start: env(safe-area-inset-top); }
|
|
124
|
+
.safe-bottom { padding-block-end: env(safe-area-inset-bottom); }
|
|
125
|
+
.safe-x { padding-inline: max(var(--space-gutter), env(safe-area-inset-left)) max(var(--space-gutter), env(safe-area-inset-right)); }
|
|
126
|
+
|
|
127
|
+
/* --- Icons --------------------------------------------------------------
|
|
128
|
+
<svg class="icon"><use href="deck-icons.svg#check"></use></svg>
|
|
129
|
+
Icons inherit color and font-size, so they line up with text by default.
|
|
130
|
+
---------------------------------------------------------------------- */
|
|
131
|
+
/* An SVG icon sized in em, so it scales with the text it sits beside and
|
|
132
|
+
lands on the baseline. Icons are filled with currentColor and inherit
|
|
133
|
+
the surrounding colour. */
|
|
134
|
+
.icon {
|
|
135
|
+
inline-size: 1.25em;
|
|
136
|
+
block-size: 1.25em;
|
|
137
|
+
flex: 0 0 auto;
|
|
138
|
+
fill: none;
|
|
139
|
+
stroke: currentColor;
|
|
140
|
+
stroke-width: 1.75;
|
|
141
|
+
stroke-linecap: round;
|
|
142
|
+
stroke-linejoin: round;
|
|
143
|
+
vertical-align: -.22em;
|
|
144
|
+
}
|
|
145
|
+
/* Small icons reference the heavier cut of the sprite, not a thicker stroke:
|
|
146
|
+
the symbols are filled outlines, so stroke-width does nothing to them.
|
|
147
|
+
Every icon ships twice, #name at wght 400 and #name-sm at wght 500, and
|
|
148
|
+
.icon-sm goes with the -sm symbol. CSS cannot rewrite a <use href>, so the
|
|
149
|
+
pairing lives in the markup:
|
|
150
|
+
<svg class="icon icon-sm"><use href="deck-icons.svg#check-sm"></use></svg> */
|
|
151
|
+
.icon-sm { inline-size: 1em; block-size: 1em; }
|
|
152
|
+
/* A 1.6em icon, for icon tiles and empty states where the default would
|
|
153
|
+
look undersized. */
|
|
154
|
+
.icon-lg { inline-size: 1.6em; block-size: 1.6em; stroke-width: 1.6; }
|
|
155
|
+
.icon-xl { inline-size: 2.25em; block-size: 2.25em; stroke-width: 1.4; }
|
|
156
|
+
.icon-fill { fill: currentColor; stroke: none; }
|
|
157
|
+
.icon-muted { color: var(--text-faint); }
|
|
158
|
+
.icon-spin { animation: dk-spin 1.1s linear infinite; }
|
|
159
|
+
|
|
160
|
+
/* Icon in a tinted tile — for feature rows and empty states */
|
|
161
|
+
.icon-tile {
|
|
162
|
+
inline-size: 40px;
|
|
163
|
+
block-size: 40px;
|
|
164
|
+
border-radius: var(--r-sm);
|
|
165
|
+
display: grid;
|
|
166
|
+
place-items: center;
|
|
167
|
+
background: var(--brand-soft);
|
|
168
|
+
color: var(--brand);
|
|
169
|
+
flex: 0 0 auto;
|
|
170
|
+
}
|
|
171
|
+
.icon-tile-good { background: var(--good-100); color: var(--good-700); }
|
|
172
|
+
.icon-tile-warn { background: var(--warn-100); color: var(--warn-700); }
|
|
173
|
+
.icon-tile-bad { background: var(--bad-100); color: var(--bad-700); }
|
|
174
|
+
.icon-tile-lg { inline-size: 52px; block-size: 52px; border-radius: var(--r-md); }
|
|
175
|
+
|
|
176
|
+
/* --- Emoji --------------------------------------------------------------
|
|
177
|
+
Emoji render at wildly different sizes and baselines across platforms.
|
|
178
|
+
.emoji pins the font stack and the metrics so they sit on the line.
|
|
179
|
+
---------------------------------------------------------------------- */
|
|
180
|
+
/* Pins the emoji font stack and metrics so an emoji renders at the same
|
|
181
|
+
size and baseline on Windows, iOS and Android instead of jumping
|
|
182
|
+
between them. */
|
|
183
|
+
.emoji {
|
|
184
|
+
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
|
|
185
|
+
"Twemoji Mozilla", "Android Emoji", sans-serif;
|
|
186
|
+
font-size: 1.05em;
|
|
187
|
+
line-height: 1;
|
|
188
|
+
font-style: normal;
|
|
189
|
+
font-weight: 400;
|
|
190
|
+
vertical-align: -.12em;
|
|
191
|
+
display: inline-block;
|
|
192
|
+
text-rendering: auto;
|
|
193
|
+
-webkit-font-smoothing: auto;
|
|
194
|
+
}
|
|
195
|
+
.emoji-lg { font-size: 1.6em; vertical-align: -.2em; }
|
|
196
|
+
.emoji-xl { font-size: 2.5em; vertical-align: -.24em; }
|
|
197
|
+
.emoji-hero { font-size: 3.5rem; line-height: 1; }
|
|
198
|
+
|
|
199
|
+
/* Emoji as an avatar or a status marker */
|
|
200
|
+
.emoji-tile {
|
|
201
|
+
inline-size: 40px;
|
|
202
|
+
block-size: 40px;
|
|
203
|
+
border-radius: var(--r-sm);
|
|
204
|
+
background: var(--surface-2);
|
|
205
|
+
border: 1px solid var(--line);
|
|
206
|
+
display: grid;
|
|
207
|
+
place-items: center;
|
|
208
|
+
font-size: 20px;
|
|
209
|
+
flex: 0 0 auto;
|
|
210
|
+
}
|
|
211
|
+
.emoji-tile-round { border-radius: var(--r-full); }
|
|
212
|
+
|
|
213
|
+
/* Emoji picker grid — drop your own set in */
|
|
214
|
+
.emoji-grid {
|
|
215
|
+
display: grid;
|
|
216
|
+
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
|
|
217
|
+
gap: 2px;
|
|
218
|
+
}
|
|
219
|
+
.emoji-grid > button {
|
|
220
|
+
aspect-ratio: 1;
|
|
221
|
+
border-radius: var(--r-xs);
|
|
222
|
+
font-size: 21px;
|
|
223
|
+
line-height: 1;
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
transition: background-color var(--dur-1) var(--ease-out), scale var(--dur-1) var(--ease-spring);
|
|
226
|
+
}
|
|
227
|
+
.emoji-grid > button:hover { background: var(--surface-hover); scale: 1.16; }
|
|
228
|
+
|
|
229
|
+
/* Reaction pill, like a Slack or Teams reaction */
|
|
230
|
+
.reaction {
|
|
231
|
+
display: inline-flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: var(--space-1);
|
|
234
|
+
min-block-size: 28px;
|
|
235
|
+
padding-inline: var(--space-2);
|
|
236
|
+
border-radius: var(--r-full);
|
|
237
|
+
border: 1px solid var(--line);
|
|
238
|
+
background: var(--surface);
|
|
239
|
+
font-size: var(--text-xs);
|
|
240
|
+
font-variant-numeric: tabular-nums;
|
|
241
|
+
color: var(--text-muted);
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
}
|
|
244
|
+
.reaction[aria-pressed="true"] { background: var(--brand-soft); border-color: var(--brand-300); color: var(--brand-soft-text); }
|
|
245
|
+
}
|