@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,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,192 @@
1
+ /* ==========================================================================
2
+ Deck — 25 Anchor positioning
3
+ Every floating thing in Deck was positioned by hand with
4
+ getBoundingClientRect, which does not flip when it hits the bottom of the
5
+ window, does not shift back inside when it hits an edge, and does not
6
+ follow its anchor inside a scrolling container. Those are exactly the
7
+ problems Floating UI solves, and they are also exactly what CSS anchor
8
+ positioning solves natively — with no JavaScript, on the compositor, and
9
+ without a resize or scroll listener.
10
+
11
+ So Deck uses anchor positioning where the browser has it, and hands off to
12
+ Floating UI where it does not, if you have loaded it. Neither is required.
13
+ ========================================================================== */
14
+
15
+ @layer deck.components {
16
+
17
+ @supports (anchor-name: --a) {
18
+
19
+ /* --- Naming an anchor --------------------------------------------------
20
+ Put .anchor on the trigger and --anchor on both trigger and panel.
21
+ Each pair needs a unique name, so generate it server-side in Keel:
22
+ <button class="anchor" style="--anchor:--menu-<?= $id ?>">
23
+ ---------------------------------------------------------------------- */
24
+ .anchor { anchor-name: var(--anchor); }
25
+
26
+ /* --- Menus and mega menus ---------------------------------------------- */
27
+ .menu, .mega, .datepicker {
28
+ position-anchor: var(--anchor);
29
+ position-area: block-end span-inline-end;
30
+ position-try-fallbacks:
31
+ block-end span-inline-start,
32
+ block-start span-inline-end,
33
+ block-start span-inline-start,
34
+ flip-block, flip-inline;
35
+ position-try-order: most-block-size;
36
+ margin-block: var(--space-2);
37
+ /* stay inside the viewport no matter which fallback wins */
38
+ max-block-size: calc(100dvh - var(--space-8));
39
+ max-inline-size: calc(100vw - var(--space-6));
40
+ inset: auto;
41
+ }
42
+ .menu { min-inline-size: max(12rem, anchor-size(inline)); }
43
+
44
+ .mega {
45
+ position-area: block-end;
46
+ justify-self: anchor-center;
47
+ }
48
+
49
+ /* A dropdown that should match the width of the field it belongs to */
50
+ .menu-match { inline-size: anchor-size(inline); min-inline-size: 0; }
51
+
52
+ /* --- Combobox list ------------------------------------------------------
53
+ The listbox is a sibling today, positioned with absolute. With anchor
54
+ positioning it can go in the top layer and escape overflow:hidden
55
+ ancestors, which is the bug that bites every combobox inside a modal.
56
+ ---------------------------------------------------------------------- */
57
+ .combo-control { anchor-name: var(--anchor, --combo); }
58
+ .combo-list {
59
+ position-anchor: var(--anchor, --combo);
60
+ position-area: block-end;
61
+ position-try-fallbacks: flip-block;
62
+ inline-size: anchor-size(inline);
63
+ inset: auto;
64
+ margin-block: 4px;
65
+ }
66
+ .combo-list.is-above { inset: auto; }
67
+
68
+ /* --- Tooltips -----------------------------------------------------------
69
+ The ::after tooltip could never flip. This one can, and it can carry a
70
+ real arrow that stays pointed at the anchor.
71
+ ---------------------------------------------------------------------- */
72
+ .tip {
73
+ position: fixed;
74
+ position-anchor: var(--anchor);
75
+ position-area: block-start;
76
+ position-try-fallbacks: flip-block, flip-inline;
77
+ justify-self: anchor-center;
78
+ margin: var(--space-2);
79
+ inline-size: max-content;
80
+ max-inline-size: 16rem;
81
+ padding: var(--space-2) var(--space-3);
82
+ border: 0;
83
+ border-radius: var(--r-xs);
84
+ background: var(--ink-900);
85
+ color: var(--ink-50);
86
+ font-size: var(--text-xs);
87
+ line-height: 1.4;
88
+ box-shadow: var(--shadow-3);
89
+ opacity: 0;
90
+ transition: opacity var(--dur-2) var(--ease-out),
91
+ overlay var(--dur-2) allow-discrete,
92
+ display var(--dur-2) allow-discrete;
93
+ }
94
+ .tip:popover-open { opacity: 1; }
95
+ @starting-style { .tip:popover-open { opacity: 0; } }
96
+ .tip::backdrop { background: transparent; }
97
+
98
+ /* The arrow on a .tip. background: inherit takes the tip's colour, and
99
+ justify-self: anchor-center keeps it pointed at the trigger whichever way
100
+ the tip has flipped. */
101
+ .tip-arrow {
102
+ position: absolute;
103
+ inline-size: 8px;
104
+ block-size: 8px;
105
+ rotate: 45deg;
106
+ background: inherit;
107
+ position-anchor: var(--anchor);
108
+ justify-self: anchor-center;
109
+ }
110
+
111
+ /* --- Popover card -------------------------------------------------------
112
+ A richer tooltip: a heading, body copy, and actions. Used for the
113
+ "what does this labor code mean" pattern.
114
+ ---------------------------------------------------------------------- */
115
+ .pop {
116
+ position: fixed;
117
+ position-anchor: var(--anchor);
118
+ position-area: block-end;
119
+ position-try-fallbacks: flip-block, flip-inline, block-start span-inline-start;
120
+ justify-self: anchor-center;
121
+ margin: var(--space-2);
122
+ inline-size: min(20rem, calc(100vw - var(--space-6)));
123
+ padding: var(--space-4);
124
+ background: var(--surface);
125
+ color: var(--text);
126
+ border: 1px solid var(--line);
127
+ border-radius: var(--r-md);
128
+ box-shadow: var(--shadow-4);
129
+ opacity: 0;
130
+ translate: 0 -4px;
131
+ transition: opacity var(--dur-2) var(--ease-out),
132
+ translate var(--dur-2) var(--ease-out),
133
+ overlay var(--dur-2) allow-discrete,
134
+ display var(--dur-2) allow-discrete;
135
+ }
136
+ .pop:popover-open { opacity: 1; translate: 0; }
137
+ @starting-style { .pop:popover-open { opacity: 0; translate: 0 -4px; } }
138
+ .pop::backdrop { background: transparent; }
139
+ /* The heading inside a popover card. */
140
+ .pop-title { font-weight: 620; margin-block-end: var(--space-1); }
141
+ /* The explanatory text inside a popover card. */
142
+ .pop-body { font-size: var(--text-sm); color: var(--text-muted); }
143
+ /* The action row at the bottom of a popover card. A button here can close
144
+ the panel with popovertargetaction="hide" and no script. */
145
+ .pop-actions { display: flex; gap: var(--space-2); margin-block-start: var(--space-3); }
146
+
147
+ /* --- Hide the anchored element when its anchor scrolls out of view ------ */
148
+ .menu, .mega, .tip, .pop, .datepicker, .combo-list {
149
+ position-visibility: anchors-visible;
150
+ }
151
+ }
152
+
153
+ /* --- Floating UI handoff --------------------------------------------------
154
+ Where anchor positioning is missing, deck-adapters.js uses Floating UI if
155
+ it is on the page and falls back to Deck's own placement if it is not.
156
+ These rules just get out of the way so the adapter can set inset.
157
+ ---------------------------------------------------------------------- */
158
+ @supports not (anchor-name: --a) {
159
+ .tip, .pop {
160
+ position: fixed;
161
+ inset: auto;
162
+ margin: 0;
163
+ inline-size: max-content;
164
+ max-inline-size: 16rem;
165
+ border: 0;
166
+ border-radius: var(--r-xs);
167
+ background: var(--ink-900);
168
+ color: var(--ink-50);
169
+ font-size: var(--text-xs);
170
+ padding: var(--space-2) var(--space-3);
171
+ box-shadow: var(--shadow-3);
172
+ opacity: 0;
173
+ transition: opacity var(--dur-2) var(--ease-out),
174
+ overlay var(--dur-2) allow-discrete,
175
+ display var(--dur-2) allow-discrete;
176
+ }
177
+ .pop {
178
+ inline-size: min(20rem, calc(100vw - var(--space-6)));
179
+ max-inline-size: none;
180
+ background: var(--surface);
181
+ color: var(--text);
182
+ border: 1px solid var(--line);
183
+ border-radius: var(--r-md);
184
+ box-shadow: var(--shadow-4);
185
+ padding: var(--space-4);
186
+ }
187
+ .tip:popover-open, .pop:popover-open { opacity: 1; }
188
+ @starting-style { .tip:popover-open, .pop:popover-open { opacity: 0; } }
189
+ .tip::backdrop, .pop::backdrop { background: transparent; }
190
+ .tip-arrow { position: absolute; inline-size: 8px; block-size: 8px; rotate: 45deg; background: inherit; }
191
+ }
192
+ }