@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,337 @@
1
+ /* ==========================================================================
2
+ Deck — 14 Charts
3
+ No charting library. Bars and donuts are plain CSS driven by a --value
4
+ custom property. Lines and areas are inline SVG that you style with these
5
+ classes. Everything reads the brand palette, so charts retheme with the
6
+ rest of the app.
7
+ ========================================================================== */
8
+
9
+ @layer deck.components {
10
+
11
+ /* The frame, and the palette. Six series colours are computed by rotating
12
+ --hue-brand, so charts retheme with the rest of the app and stay in one
13
+ family. That also makes them hard to tell apart for a colour-blind
14
+ reader — label the marks directly rather than relying on hue. */
15
+ .chart {
16
+ --c1: var(--brand-500);
17
+ --c2: oklch(64% .13 calc(var(--hue-brand) + 48));
18
+ --c3: oklch(70% .14 calc(var(--hue-brand) + 96));
19
+ --c4: oklch(62% .13 calc(var(--hue-brand) - 52));
20
+ --c5: oklch(74% .12 calc(var(--hue-brand) + 148));
21
+ --c6: oklch(56% .10 calc(var(--hue-brand) + 200));
22
+ --chart-h: 240px;
23
+ --chart-grid: color-mix(in oklab, var(--line) 90%, transparent);
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--space-3);
27
+ min-inline-size: 0;
28
+ }
29
+ .chart-sm { --chart-h: 150px; }
30
+ .chart-lg { --chart-h: 340px; }
31
+
32
+ .chart-head { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
33
+ .chart-title { font-size: var(--text-sm); font-weight: 620; }
34
+ .chart-note { font-size: var(--text-xs); color: var(--text-muted); }
35
+
36
+ /* --- Legend -------------------------------------------------------------- */
37
+ /* The key. Built from real buttons it gets a working toggle state for free,
38
+ because the faded style keys off [aria-pressed="false"]. */
39
+ .chart-legend { display: flex; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-xs); color: var(--text-muted); }
40
+ .chart-legend > * { display: inline-flex; align-items: center; gap: var(--space-2); }
41
+ .chart-legend > *::before {
42
+ content: "";
43
+ inline-size: 10px;
44
+ block-size: 10px;
45
+ border-radius: 3px;
46
+ background: var(--series, var(--c1));
47
+ flex: 0 0 auto;
48
+ }
49
+ .chart-legend [aria-pressed="false"] { opacity: .4; }
50
+ .chart-legend button { cursor: pointer; }
51
+
52
+ /* --- Columns (vertical bars) --------------------------------------------
53
+ <div class="chart-columns">
54
+ <div class="chart-col" style="--value:68" data-label="Mar" data-value="68"></div>
55
+ ---------------------------------------------------------------------- */
56
+ .chart-columns {
57
+ display: flex;
58
+ align-items: flex-end;
59
+ gap: var(--gap, 6px);
60
+ block-size: var(--chart-h);
61
+ padding-block-end: 22px;
62
+ position: relative;
63
+ background:
64
+ repeating-linear-gradient(to top,
65
+ var(--chart-grid) 0 1px,
66
+ transparent 1px calc((100% - 22px) / 4));
67
+ background-position: 0 -22px;
68
+ background-size: 100% calc(100% - 22px);
69
+ background-repeat: no-repeat;
70
+ }
71
+ /* A column. Its height is calc(var(--value) * 1%), so rendering a chart is
72
+ writing numbers into style attributes. data-label and data-value are drawn
73
+ as generated content, which means they are for sighted readers only. */
74
+ .chart-col {
75
+ flex: 1 1 0;
76
+ min-inline-size: 6px;
77
+ block-size: calc(var(--value) * 1%);
78
+ background: var(--series, var(--c1));
79
+ border-radius: var(--r-xs) var(--r-xs) 2px 2px;
80
+ position: relative;
81
+ transform-origin: bottom;
82
+ animation: chart-rise var(--dur-3) var(--ease-out);
83
+ transition: filter var(--dur-1) var(--ease-out);
84
+ }
85
+ .chart-col:hover { filter: brightness(1.08) saturate(1.1); }
86
+
87
+ /* A label placed inside a bar gets ink chosen for that series rather than
88
+ the page's --text. contrast-color() is written directly on the elements
89
+ that carry the fill, not hoisted into a custom property: var() is
90
+ substituted where a property is declared, so a shared --text-on-series
91
+ token would read --series from .chart and get it wrong for the usual
92
+ markup, which puts .s1 .. .s6 on the bar itself.
93
+
94
+ Only inside @supports: with six hue-derived series there is no single
95
+ static answer, and guessing one would be worse than the inheritance it
96
+ replaced. Series colours are chosen for discriminability and some sit in
97
+ the 55-65% band, so this raises the floor rather than guaranteeing AA. */
98
+ @supports (color: contrast-color(red)) {
99
+ .chart-col, .chart-col-stack > span, .chart-bar-fill, .chart-meter > span {
100
+ color: contrast-color(var(--series, var(--c1)));
101
+ }
102
+ }
103
+ .chart-col[data-label]::after {
104
+ content: attr(data-label);
105
+ position: absolute;
106
+ inset-block-start: 100%;
107
+ inset-inline: 0;
108
+ padding-block-start: 6px;
109
+ text-align: center;
110
+ font-size: var(--text-2xs);
111
+ color: var(--text-faint);
112
+ white-space: nowrap;
113
+ }
114
+ .chart-col[data-value]::before {
115
+ content: attr(data-value);
116
+ position: absolute;
117
+ inset-block-end: calc(100% + 6px);
118
+ inset-inline-start: 50%;
119
+ translate: -50% 0;
120
+ padding: 3px 7px;
121
+ background: var(--ink-900);
122
+ color: var(--ink-50);
123
+ border-radius: var(--r-xs);
124
+ font-size: var(--text-2xs);
125
+ font-weight: 620;
126
+ white-space: nowrap;
127
+ opacity: 0;
128
+ pointer-events: none;
129
+ transition: opacity var(--dur-1) var(--ease-out);
130
+ z-index: 2;
131
+ }
132
+ .chart-col:hover::before { opacity: 1; }
133
+ @keyframes chart-rise { from { scale: 1 0; } }
134
+
135
+ /* Stacked columns: nest segments inside a .chart-col-stack */
136
+ .chart-col-stack {
137
+ flex: 1 1 0;
138
+ display: flex;
139
+ flex-direction: column-reverse;
140
+ block-size: calc(var(--value) * 1%);
141
+ border-radius: var(--r-xs) var(--r-xs) 2px 2px;
142
+ overflow: hidden;
143
+ position: relative;
144
+ animation: chart-rise var(--dur-3) var(--ease-out);
145
+ }
146
+ .chart-col-stack > span { block-size: calc(var(--part) * 1%); background: var(--series, var(--c1)); }
147
+
148
+ /* Grouped columns */
149
+ .chart-group { flex: 1 1 0; display: flex; align-items: flex-end; gap: 2px; block-size: 100%; position: relative; }
150
+ .chart-group > .chart-col { border-radius: 2px 2px 1px 1px; }
151
+
152
+ /* --- Bars (horizontal) --------------------------------------------------- */
153
+ .chart-bars { display: flex; flex-direction: column; gap: var(--space-3); }
154
+ .chart-bar {
155
+ display: grid;
156
+ grid-template-columns: minmax(4.5rem, auto) 1fr auto;
157
+ align-items: center;
158
+ gap: var(--space-3);
159
+ font-size: var(--text-sm);
160
+ }
161
+ .chart-bar-label { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
162
+ .chart-bar-track {
163
+ block-size: 10px;
164
+ border-radius: var(--r-full);
165
+ background: var(--bg-sunken);
166
+ overflow: hidden;
167
+ }
168
+ /* The filled part of a horizontal bar, sized from --value. It grows from
169
+ the starting edge, which needs transform-origin — a physical property —
170
+ flipped for RTL in 19-logical.css. */
171
+ .chart-bar-fill {
172
+ display: block;
173
+ block-size: 100%;
174
+ inline-size: calc(var(--value) * 1%);
175
+ border-radius: var(--r-full);
176
+ background: var(--series, var(--c1));
177
+ transform-origin: left;
178
+ animation: chart-grow var(--dur-3) var(--ease-out);
179
+ }
180
+ /* The value printed beside a bar rather than inside it. Prefer this over
181
+ text on the fill: contrast-color() raises the floor for text on a series
182
+ colour but does not guarantee AA. */
183
+ .chart-bar-value { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
184
+ @keyframes chart-grow { from { scale: 0 1; } }
185
+
186
+ /* --- Line and area (inline SVG) ------------------------------------------
187
+ <svg class="chart-svg" viewBox="0 0 300 100" preserveAspectRatio="none">
188
+ <path class="chart-area" d="..."/>
189
+ <path class="chart-line" d="..."/>
190
+ ---------------------------------------------------------------------- */
191
+ .chart-svg {
192
+ inline-size: 100%;
193
+ block-size: var(--chart-h);
194
+ overflow: visible;
195
+ }
196
+ .chart-line {
197
+ fill: none;
198
+ stroke: var(--series, var(--c1));
199
+ stroke-width: var(--stroke, 2);
200
+ stroke-linecap: round;
201
+ stroke-linejoin: round;
202
+ vector-effect: non-scaling-stroke;
203
+ stroke-dasharray: 1200;
204
+ stroke-dashoffset: 1200;
205
+ animation: chart-draw 1s var(--ease-out) forwards;
206
+ }
207
+ @keyframes chart-draw { to { stroke-dashoffset: 0; } }
208
+ .chart-line-dashed { stroke-dasharray: 5 4; animation: none; stroke-dashoffset: 0; opacity: .6; }
209
+
210
+ .chart-area {
211
+ fill: var(--series, var(--c1));
212
+ opacity: .14;
213
+ stroke: none;
214
+ animation: chart-fade var(--dur-3) var(--ease-out);
215
+ }
216
+ @keyframes chart-fade { from { opacity: 0; } }
217
+
218
+ .chart-dot {
219
+ fill: var(--surface);
220
+ stroke: var(--series, var(--c1));
221
+ stroke-width: 2;
222
+ vector-effect: non-scaling-stroke;
223
+ }
224
+ .chart-dot-active { fill: var(--series, var(--c1)); }
225
+
226
+ .chart-gridline {
227
+ stroke: var(--chart-grid);
228
+ stroke-width: 1;
229
+ vector-effect: non-scaling-stroke;
230
+ }
231
+ .chart-baseline { stroke: var(--line-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }
232
+
233
+ /* --- Axis labels --------------------------------------------------------- */
234
+ .chart-x {
235
+ display: flex;
236
+ justify-content: space-between;
237
+ font-size: var(--text-2xs);
238
+ color: var(--text-faint);
239
+ padding-block-start: var(--space-2);
240
+ }
241
+ .chart-y {
242
+ display: flex;
243
+ flex-direction: column-reverse;
244
+ justify-content: space-between;
245
+ font-size: var(--text-2xs);
246
+ color: var(--text-faint);
247
+ font-variant-numeric: tabular-nums;
248
+ text-align: end;
249
+ padding-inline-end: var(--space-2);
250
+ min-inline-size: 2.5rem;
251
+ }
252
+ .chart-plot { display: grid; grid-template-columns: auto 1fr; }
253
+
254
+ /* --- Sparkline ----------------------------------------------------------- */
255
+ /* A tiny inline chart for a table cell or a stat block. */
256
+ .sparkline {
257
+ inline-size: var(--spark-w, 88px);
258
+ block-size: var(--spark-h, 28px);
259
+ overflow: visible;
260
+ vertical-align: middle;
261
+ }
262
+ .sparkline .chart-line { stroke-width: 1.75; animation: none; stroke-dashoffset: 0; }
263
+ .sparkline-good .chart-line { stroke: var(--good-500); }
264
+ .sparkline-bad .chart-line { stroke: var(--bad-500); }
265
+
266
+ /* --- Donut --------------------------------------------------------------- */
267
+ /* A conic gradient driven by --value, with .donut-center holding a figure
268
+ in the hole. One element, no SVG. Give it role="img" and a label — the
269
+ shape carries the meaning and nothing else conveys it. */
270
+ .donut {
271
+ --size: 128px;
272
+ --thickness: 18px;
273
+ inline-size: var(--size);
274
+ block-size: var(--size);
275
+ border-radius: var(--r-full);
276
+ background: conic-gradient(var(--stops, var(--c1) 0 100%));
277
+ mask: radial-gradient(farthest-side, transparent calc(100% - var(--thickness)), #000 calc(100% - var(--thickness) + 1px));
278
+ -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--thickness)), #000 calc(100% - var(--thickness) + 1px));
279
+ animation: dk-spin-in var(--dur-3) var(--ease-out);
280
+ }
281
+ @keyframes dk-spin-in { from { rotate: -40deg; opacity: 0; } }
282
+ .donut-wrap { position: relative; display: inline-grid; place-items: center; }
283
+ .donut-center {
284
+ position: absolute;
285
+ display: grid;
286
+ place-items: center;
287
+ text-align: center;
288
+ line-height: 1.15;
289
+ }
290
+ .donut-center .donut-value { font-size: var(--text-xl); font-weight: 660; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
291
+ .donut-center .donut-label { font-size: var(--text-2xs); color: var(--text-muted); }
292
+
293
+ /* --- Progress-style comparison ------------------------------------------- */
294
+ .chart-meter {
295
+ display: flex;
296
+ block-size: 12px;
297
+ border-radius: var(--r-full);
298
+ overflow: hidden;
299
+ background: var(--bg-sunken);
300
+ }
301
+ .chart-meter > span {
302
+ inline-size: calc(var(--value) * 1%);
303
+ background: var(--series, var(--c1));
304
+ transition: inline-size var(--dur-3) var(--ease-out);
305
+ }
306
+ .chart-meter > span + span { border-inline-start: 1px solid var(--surface); }
307
+
308
+ /* --- Heat cells ---------------------------------------------------------- */
309
+ /* A grid of --cols columns whose cells mix the series colour with the
310
+ sunken background by --value, interpolated in oklab. */
311
+ .chart-heat { display: grid; grid-template-columns: repeat(var(--cols, 7), 1fr); gap: 3px; }
312
+ .chart-heat > * {
313
+ aspect-ratio: 1;
314
+ border-radius: 3px;
315
+ background: color-mix(in oklab, var(--c1) calc(var(--value) * 1%), var(--bg-sunken));
316
+ }
317
+ .chart-heat > *:hover { outline: 2px solid var(--brand); outline-offset: 1px; }
318
+
319
+ /* --- Series color helpers ------------------------------------------------ */
320
+ /* Series 1. Every chart mark reads --series, so a colour is set once on
321
+ the element and the fill, stroke and legend swatch all follow. */
322
+ .s1 { --series: var(--c1); }
323
+ .s2 { --series: var(--c2); }
324
+ .s3 { --series: var(--c3); }
325
+ .s4 { --series: var(--c4); }
326
+ .s5 { --series: var(--c5); }
327
+ .s6 { --series: var(--c6); }
328
+ .s-good { --series: var(--good-500); }
329
+ .s-warn { --series: var(--warn-500); }
330
+ .s-bad { --series: var(--bad-500); }
331
+ .s-muted { --series: var(--ink-300); }
332
+
333
+ @media (prefers-reduced-motion: reduce) {
334
+ .chart-col, .chart-col-stack, .chart-bar-fill, .chart-line, .chart-area, .donut { animation: none; }
335
+ .chart-line { stroke-dashoffset: 0; }
336
+ }
337
+ }
@@ -0,0 +1,248 @@
1
+ /* ==========================================================================
2
+ Deck — 11 Combobox / autocomplete
3
+ Type to filter, arrow keys to move, enter to pick. Single and multi select.
4
+ ========================================================================== */
5
+
6
+ @layer deck.components {
7
+
8
+ /* A filtering select. It wraps a real <select> that stays in the DOM and
9
+ stays in sync, so an ordinary form post works with no server-side
10
+ handling, server-side rendering is just marking options selected, and
11
+ without JavaScript the reader still gets a working control. */
12
+ .combo { position: relative; display: flex; flex-direction: column; }
13
+
14
+ /* --- Control ------------------------------------------------------------ */
15
+ /* The box, styled to match .input so a combobox in a form does not look
16
+ like a different species. Focus is handled twice — :has(.combo-input:focus)
17
+ and :focus-within — so a token's remove button lights the box too. */
18
+ .combo-control {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: var(--space-2);
22
+ inline-size: 100%;
23
+ min-block-size: var(--control-h);
24
+ padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
25
+ background: var(--surface);
26
+ border: 1px solid var(--line-strong);
27
+ border-radius: var(--r-sm);
28
+ box-shadow: var(--shadow-1);
29
+ cursor: text;
30
+ transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
31
+ }
32
+ .combo-control:hover { border-color: var(--ink-400); }
33
+ .combo:has(.combo-input:focus) .combo-control,
34
+ .combo-control:focus-within {
35
+ border-color: var(--focus);
36
+ box-shadow: var(--ring);
37
+ }
38
+ .combo.is-invalid .combo-control { border-color: var(--bad-500); }
39
+ .combo.is-disabled .combo-control {
40
+ background: var(--bg-sunken);
41
+ color: var(--text-faint);
42
+ pointer-events: none;
43
+ }
44
+
45
+ /* The text field. deck.js gives it role="combobox" and keeps aria-expanded
46
+ in step; arrow keys move .is-active rather than focus, so the reader can
47
+ keep typing while the highlight moves. */
48
+ .combo-input {
49
+ flex: 1 1 6rem;
50
+ min-inline-size: 4rem;
51
+ border: 0;
52
+ outline: none;
53
+ background: none;
54
+ padding: var(--space-2) 0;
55
+ font-size: var(--text-base);
56
+ color: inherit;
57
+ }
58
+ @media (pointer: coarse) { .combo-input { font-size: 16px; } }
59
+ .combo-input::placeholder { color: var(--text-faint); }
60
+
61
+ .combo-toggle {
62
+ inline-size: 30px;
63
+ block-size: 30px;
64
+ display: grid;
65
+ place-items: center;
66
+ border-radius: var(--r-xs);
67
+ color: var(--text-faint);
68
+ cursor: pointer;
69
+ flex: 0 0 auto;
70
+ }
71
+ .combo-toggle:hover { background: var(--surface-hover); color: var(--text); }
72
+ .combo-toggle .icon { transition: rotate var(--dur-2) var(--ease-out); }
73
+ .combo.is-open .combo-toggle .icon { rotate: 180deg; }
74
+ .combo-clear { color: var(--text-faint); }
75
+
76
+ /* --- Selected tokens (multi select) -------------------------------------- */
77
+ .combo-tokens { display: contents; }
78
+ .combo-token {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ gap: var(--space-1);
82
+ min-block-size: 26px;
83
+ padding-inline: var(--space-1) var(--space-2);
84
+ padding-inline-start: var(--space-2);
85
+ border-radius: var(--r-xs);
86
+ background: var(--brand-soft);
87
+ color: var(--brand-soft-text);
88
+ font-size: var(--text-sm);
89
+ font-weight: 540;
90
+ max-inline-size: 14rem;
91
+ flex: 0 0 auto;
92
+ }
93
+ .combo-token > span {
94
+ overflow: hidden;
95
+ text-overflow: ellipsis;
96
+ white-space: nowrap;
97
+ }
98
+ .combo-token > button {
99
+ display: grid;
100
+ place-items: center;
101
+ inline-size: 18px;
102
+ block-size: 18px;
103
+ border-radius: var(--r-full);
104
+ opacity: .65;
105
+ cursor: pointer;
106
+ flex: 0 0 auto;
107
+ }
108
+ .combo-token > button:hover { opacity: 1; background: color-mix(in oklab, currentColor 16%, transparent); }
109
+ .combo-count {
110
+ font-size: var(--text-xs);
111
+ font-weight: 620;
112
+ color: var(--text-muted);
113
+ flex: 0 0 auto;
114
+ }
115
+
116
+ /* --- Listbox ------------------------------------------------------------- */
117
+ /* The option list. A positioned sibling by default, which a clipping
118
+ ancestor can cut off; 25-anchor.css moves it to the top layer where
119
+ anchor positioning exists, which is the fix for the combobox-inside-a-modal
120
+ bug. */
121
+ .combo-list {
122
+ position: absolute;
123
+ inset-block-start: calc(100% + 4px);
124
+ inset-inline: 0;
125
+ z-index: var(--z-modal);
126
+ margin: 0;
127
+ padding: var(--space-1);
128
+ max-block-size: min(20rem, 50dvh);
129
+ overflow-y: auto;
130
+ overscroll-behavior: contain;
131
+ background: var(--surface);
132
+ border: 1px solid var(--line);
133
+ border-radius: var(--r-md);
134
+ box-shadow: var(--shadow-4);
135
+ display: none;
136
+ }
137
+ .combo.is-open .combo-list { display: block; animation: dk-combo-in var(--dur-2) var(--ease-out); }
138
+ @keyframes dk-combo-in { from { opacity: 0; translate: 0 -4px; } }
139
+
140
+ /* Open upward when there is no room below */
141
+ .combo-list.is-above { inset-block: auto calc(100% + 4px); }
142
+
143
+ .combo-option {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: var(--space-3);
147
+ inline-size: 100%;
148
+ min-block-size: 40px;
149
+ padding: var(--space-2) var(--space-3);
150
+ border-radius: var(--r-xs);
151
+ font-size: var(--text-sm);
152
+ color: var(--text);
153
+ text-align: start;
154
+ cursor: pointer;
155
+ scroll-margin-block: var(--space-1);
156
+ }
157
+ /* The text column of an option. min-inline-size: 0 so a long value cannot
158
+ push the tick off the end. */
159
+ .combo-option .combo-option-main { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 1px; }
160
+ .combo-option .combo-option-sub { font-size: var(--text-xs); color: var(--text-muted); }
161
+ .combo-option .combo-option-trail { color: var(--text-faint); font-size: var(--text-xs); flex: 0 0 auto; }
162
+
163
+ /* Keyboard highlight and mouse hover use the same state, set by deck.js */
164
+ .combo-option.is-active { background: var(--surface-hover); }
165
+ .combo-option[aria-selected="true"] {
166
+ background: var(--brand-soft);
167
+ color: var(--brand-soft-text);
168
+ font-weight: 560;
169
+ }
170
+ .combo-option[aria-selected="true"].is-active { background: color-mix(in oklab, var(--brand-soft) 78%, var(--brand-300)); }
171
+ .combo-option[aria-disabled="true"] { opacity: .45; pointer-events: none; }
172
+ /* The tick on a selected option, revealed by [aria-selected="true"]. */
173
+ .combo-option .combo-check { margin-inline-start: auto; opacity: 0; flex: 0 0 auto; }
174
+ .combo-option[aria-selected="true"] .combo-check { opacity: 1; }
175
+
176
+ /* The matched substring, wrapped by deck.js */
177
+ .combo-option mark {
178
+ background: none;
179
+ color: inherit;
180
+ font-weight: 700;
181
+ padding: 0;
182
+ text-decoration: underline;
183
+ text-decoration-color: color-mix(in oklab, var(--brand) 55%, transparent);
184
+ text-underline-offset: .16em;
185
+ }
186
+
187
+ /* A sticky heading above a group of options. */
188
+ .combo-group {
189
+ padding: var(--space-3) var(--space-3) var(--space-1);
190
+ font-size: var(--text-2xs);
191
+ font-weight: 700;
192
+ color: var(--text-faint);
193
+ position: sticky;
194
+ inset-block-start: calc(var(--space-1) * -1);
195
+ background: var(--surface);
196
+ }
197
+ .combo-sep { block-size: 1px; background: var(--line); margin: var(--space-1) 0; }
198
+
199
+ /* The no-matches row. Name what was searched for — "No repositories match
200
+ 'deckk'" tells a reader who mistyped what they actually typed. */
201
+ .combo-empty, .combo-loading {
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ gap: var(--space-2);
206
+ padding: var(--space-6) var(--space-4);
207
+ font-size: var(--text-sm);
208
+ color: var(--text-muted);
209
+ text-align: center;
210
+ }
211
+
212
+ /* Sticky "create new" row for tag-style inputs */
213
+ .combo-create {
214
+ position: sticky;
215
+ inset-block-end: calc(var(--space-1) * -1);
216
+ background: var(--surface);
217
+ border-block-start: 1px solid var(--line);
218
+ margin: var(--space-1) calc(var(--space-1) * -1) calc(var(--space-1) * -1);
219
+ padding: var(--space-1);
220
+ }
221
+ .combo-create .combo-option { color: var(--brand); font-weight: 560; }
222
+
223
+ .combo-hint {
224
+ display: none;
225
+ gap: var(--space-3);
226
+ padding: var(--space-2) var(--space-3);
227
+ border-block-start: 1px solid var(--line);
228
+ font-size: var(--text-2xs);
229
+ color: var(--text-faint);
230
+ }
231
+ @media (pointer: fine) { .combo.is-open .combo-hint { display: flex; } }
232
+
233
+ /* --- Phone: the list takes over the screen ------------------------------- */
234
+ @media (max-width: 30rem) {
235
+ .combo.is-open .combo-list {
236
+ position: fixed;
237
+ inset: auto 0 0 0;
238
+ max-block-size: 70dvh;
239
+ border-radius: var(--r-lg) var(--r-lg) 0 0;
240
+ border-inline: 0;
241
+ border-block-end: 0;
242
+ padding-block-end: calc(var(--space-2) + env(safe-area-inset-bottom));
243
+ animation: dk-combo-up var(--dur-3) var(--ease-out);
244
+ }
245
+ .combo-option { min-block-size: var(--tap); }
246
+ @keyframes dk-combo-up { from { translate: 0 100%; } }
247
+ }
248
+ }