@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,305 @@
1
+ /* ==========================================================================
2
+ Deck — 10 Date picker
3
+ Popover calendar on desktop, bottom sheet on a phone. Single date, range,
4
+ and month/year jump. Behaviour lives in deck.js; this file is the skin.
5
+ ========================================================================== */
6
+
7
+ @layer deck.components {
8
+
9
+ /* --- Trigger field ------------------------------------------------------ */
10
+ /* The trigger for a date picker: an input with a calendar icon inside its
11
+ starting edge and a clear button at the end. The input is readonly and
12
+ cursor: pointer, because the value comes from the calendar — which also
13
+ means a reader who knows the date cannot type it. */
14
+ .datefield { position: relative; display: flex; align-items: center; }
15
+ .datefield > .icon {
16
+ position: absolute;
17
+ inset-inline-start: var(--space-3);
18
+ color: var(--text-faint);
19
+ pointer-events: none;
20
+ }
21
+ .datefield > .input { padding-inline-start: var(--space-10); cursor: pointer; }
22
+ /* Clears the field. Hidden until .has-value is set by deck.js. */
23
+ .datefield > .datefield-clear {
24
+ position: absolute;
25
+ inset-inline-end: var(--space-2);
26
+ inline-size: 28px;
27
+ block-size: 28px;
28
+ display: grid;
29
+ place-items: center;
30
+ border-radius: var(--r-full);
31
+ color: var(--text-faint);
32
+ cursor: pointer;
33
+ }
34
+ .datefield > .datefield-clear:hover { background: var(--surface-hover); color: var(--text); }
35
+ .datefield:not(.has-value) .datefield-clear { display: none; }
36
+
37
+ /* --- Panel -------------------------------------------------------------- */
38
+ /* The calendar panel: a native popover, anchored to its field by
39
+ 25-anchor.css with the same fallback chain as a menu. Where there is no
40
+ room below it opens upward, and position-try-order: most-block-size picks
41
+ the direction with the most room rather than the first that fits — which
42
+ matters more for a calendar than for a menu. */
43
+ .datepicker {
44
+ position: absolute;
45
+ margin: 0;
46
+ inset: auto;
47
+ padding: 0;
48
+ inline-size: max-content;
49
+ max-inline-size: calc(100vw - var(--space-6));
50
+ background: var(--surface);
51
+ border: 1px solid var(--line);
52
+ border-radius: var(--r-md);
53
+ box-shadow: var(--shadow-4);
54
+ color: var(--text);
55
+ opacity: 0;
56
+ translate: 0 -6px;
57
+ transition: opacity var(--dur-2) var(--ease-out),
58
+ translate var(--dur-2) var(--ease-out),
59
+ overlay var(--dur-2) allow-discrete,
60
+ display var(--dur-2) allow-discrete;
61
+ }
62
+ .datepicker:popover-open { opacity: 1; translate: 0; }
63
+ @starting-style { .datepicker:popover-open { opacity: 0; translate: 0 -6px; } }
64
+ .datepicker::backdrop { background: transparent; }
65
+
66
+ .datepicker-layout { display: flex; flex-direction: column; }
67
+ @media (min-width: 40rem) {
68
+ .datepicker-layout:has(.datepicker-presets) { flex-direction: row; }
69
+ }
70
+
71
+ /* --- Preset rail --------------------------------------------------------- */
72
+ /* The shortcut rail — "Last 7 days", "This month". A horizontal scroller
73
+ below 40rem and a column beside the calendar above it. */
74
+ .datepicker-presets {
75
+ display: flex;
76
+ gap: var(--space-1);
77
+ padding: var(--space-2);
78
+ overflow-x: auto;
79
+ scrollbar-width: none;
80
+ border-block-end: 1px solid var(--line);
81
+ }
82
+ .datepicker-presets::-webkit-scrollbar { display: none; }
83
+ @media (min-width: 40rem) {
84
+ .datepicker-presets {
85
+ flex-direction: column;
86
+ inline-size: 10.5rem;
87
+ overflow-x: visible;
88
+ border-block-end: 0;
89
+ border-inline-end: 1px solid var(--line);
90
+ background: var(--surface-2);
91
+ }
92
+ }
93
+ .datepicker-preset {
94
+ padding: var(--space-2) var(--space-3);
95
+ min-block-size: 36px;
96
+ border-radius: var(--r-xs);
97
+ font-size: var(--text-sm);
98
+ font-weight: 520;
99
+ color: var(--text-muted);
100
+ text-align: start;
101
+ white-space: nowrap;
102
+ cursor: pointer;
103
+ transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
104
+ }
105
+ .datepicker-preset:hover { background: var(--surface-hover); color: var(--text); }
106
+ .datepicker-preset[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-soft-text); font-weight: 600; }
107
+
108
+ /* --- Calendar ----------------------------------------------------------- */
109
+ .datepicker-main { padding: var(--space-3); }
110
+
111
+ .datepicker-head {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: var(--space-1);
115
+ padding-block-end: var(--space-2);
116
+ }
117
+ .datepicker-title {
118
+ flex: 1 1 auto;
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ gap: var(--space-2);
123
+ min-block-size: 36px;
124
+ padding-inline: var(--space-3);
125
+ border-radius: var(--r-sm);
126
+ font-weight: 620;
127
+ font-size: var(--text-sm);
128
+ cursor: pointer;
129
+ letter-spacing: -.01em;
130
+ }
131
+ .datepicker-title:hover { background: var(--surface-hover); }
132
+ .datepicker-nav {
133
+ inline-size: 36px;
134
+ block-size: 36px;
135
+ display: grid;
136
+ place-items: center;
137
+ border-radius: var(--r-sm);
138
+ color: var(--text-muted);
139
+ cursor: pointer;
140
+ }
141
+ .datepicker-nav:hover { background: var(--surface-hover); color: var(--text); }
142
+ .datepicker-nav[disabled] { opacity: .35; pointer-events: none; }
143
+
144
+ .datepicker-months { display: flex; gap: var(--space-5); }
145
+ .datepicker-month { display: flex; flex-direction: column; }
146
+
147
+ .datepicker-grid {
148
+ display: grid;
149
+ grid-template-columns: repeat(7, 1fr);
150
+ gap: 1px;
151
+ }
152
+ .datepicker-dow {
153
+ text-align: center;
154
+ font-size: var(--text-2xs);
155
+ font-weight: 620;
156
+ color: var(--text-faint);
157
+ padding-block: var(--space-2);
158
+ }
159
+
160
+ /* One day cell. 38px, raised to 42px on a coarse pointer so it approaches
161
+ the touch target. Eight is- states compose on it, and the range band is
162
+ built from is-in-range plus logical corner radii on the two ends. */
163
+ .datepicker-day {
164
+ position: relative;
165
+ inline-size: 38px;
166
+ block-size: 38px;
167
+ display: grid;
168
+ place-items: center;
169
+ border-radius: var(--r-sm);
170
+ font-size: var(--text-sm);
171
+ font-variant-numeric: tabular-nums;
172
+ color: var(--text);
173
+ cursor: pointer;
174
+ -webkit-tap-highlight-color: transparent;
175
+ transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
176
+ }
177
+ @media (pointer: coarse) { .datepicker-day { inline-size: 42px; block-size: 42px; } }
178
+
179
+ .datepicker-day:hover { background: var(--surface-hover); }
180
+ .datepicker-day.is-outside { color: var(--text-faint); opacity: .55; }
181
+ .datepicker-day[disabled], .datepicker-day.is-blocked {
182
+ color: var(--text-faint);
183
+ opacity: .4;
184
+ pointer-events: none;
185
+ text-decoration: line-through;
186
+ }
187
+
188
+ /* Today gets a marker, not a fill, so it never competes with the selection */
189
+ .datepicker-day.is-today::after {
190
+ content: "";
191
+ position: absolute;
192
+ inset-block-end: 5px;
193
+ inline-size: 4px;
194
+ block-size: 4px;
195
+ border-radius: var(--r-full);
196
+ background: var(--brand);
197
+ }
198
+
199
+ .datepicker-day.is-selected {
200
+ background: var(--brand-600);
201
+ color: var(--text-on-brand);
202
+ font-weight: 620;
203
+ }
204
+ .datepicker-day.is-selected::after { background: var(--text-on-brand); }
205
+
206
+ /* Range: the middle days get a continuous band, the ends get the caps */
207
+ .datepicker-day.is-in-range {
208
+ background: var(--brand-soft);
209
+ color: var(--brand-soft-text);
210
+ border-radius: 0;
211
+ }
212
+ .datepicker-day.is-range-start {
213
+ border-start-end-radius: 0;
214
+ border-end-end-radius: 0;
215
+ }
216
+ .datepicker-day.is-range-end {
217
+ border-start-start-radius: 0;
218
+ border-end-start-radius: 0;
219
+ }
220
+ .datepicker-day.is-range-start.is-range-end { border-radius: var(--r-sm); }
221
+ .datepicker-grid:has(.is-in-range) .datepicker-day.is-in-range:first-child {
222
+ border-start-start-radius: var(--r-sm); border-end-start-radius: var(--r-sm);
223
+ }
224
+ .datepicker-grid:has(.is-in-range) .datepicker-day.is-in-range:nth-child(7n) {
225
+ border-start-end-radius: var(--r-sm); border-end-end-radius: var(--r-sm);
226
+ }
227
+ .datepicker-day.is-preview { background: color-mix(in oklab, var(--brand-soft) 60%, transparent); }
228
+ .datepicker-day:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; z-index: 1; }
229
+
230
+ /* --- Month and year jump grid ------------------------------------------- */
231
+ /* Opens the month and year grid. A picker without one makes a reader press
232
+ the next-month arrow twenty-four times to reach a date two years out. */
233
+ .datepicker-jump {
234
+ display: grid;
235
+ grid-template-columns: repeat(3, 1fr);
236
+ gap: var(--space-1);
237
+ padding-block: var(--space-2);
238
+ }
239
+ .datepicker-jump > button {
240
+ min-block-size: 40px;
241
+ padding-inline: var(--space-2);
242
+ border-radius: var(--r-sm);
243
+ font-size: var(--text-sm);
244
+ font-variant-numeric: tabular-nums;
245
+ color: var(--text-muted);
246
+ cursor: pointer;
247
+ }
248
+ .datepicker-jump > button:hover { background: var(--surface-hover); color: var(--text); }
249
+ .datepicker-jump > button[aria-selected="true"] {
250
+ background: var(--brand-600);
251
+ color: var(--text-on-brand);
252
+ font-weight: 620;
253
+ }
254
+
255
+ /* --- Time row and footer ------------------------------------------------- */
256
+ .datepicker-time {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: var(--space-2);
260
+ padding: var(--space-3);
261
+ border-block-start: 1px solid var(--line);
262
+ }
263
+ .datepicker-time .input { inline-size: auto; min-inline-size: 7.5rem; }
264
+
265
+ .datepicker-foot {
266
+ display: flex;
267
+ align-items: center;
268
+ gap: var(--space-2);
269
+ padding: var(--space-3);
270
+ border-block-start: 1px solid var(--line);
271
+ background: var(--surface-2);
272
+ }
273
+ .datepicker-readout {
274
+ flex: 1 1 auto;
275
+ font-size: var(--text-sm);
276
+ color: var(--text-muted);
277
+ font-variant-numeric: tabular-nums;
278
+ min-inline-size: 0;
279
+ }
280
+
281
+ /* --- Phone: the picker becomes a sheet ---------------------------------- */
282
+ @media (max-width: 30rem) {
283
+ .datepicker {
284
+ position: fixed;
285
+ inset: auto 0 0 0;
286
+ inline-size: 100%;
287
+ max-inline-size: 100%;
288
+ border-radius: var(--r-lg) var(--r-lg) 0 0;
289
+ border-inline: 0;
290
+ border-block-end: 0;
291
+ translate: 0 100%;
292
+ padding-block-end: env(safe-area-inset-bottom);
293
+ transition: translate var(--dur-3) var(--ease-out),
294
+ overlay var(--dur-3) allow-discrete,
295
+ display var(--dur-3) allow-discrete;
296
+ opacity: 1;
297
+ }
298
+ .datepicker:popover-open { translate: 0; }
299
+ @starting-style { .datepicker:popover-open { translate: 0 100%; } }
300
+ .datepicker::backdrop { background: oklch(18% .02 var(--hue-neutral) / .45); }
301
+ .datepicker-months { flex-direction: column; }
302
+ .datepicker-day { inline-size: 100%; }
303
+ .datepicker-main { padding-inline: var(--space-4); }
304
+ }
305
+ }
@@ -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
+ }