@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
package/src/06-forms.css
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Deck — 06 Forms
|
|
3
|
+
Inputs are 16px on touch devices so iOS never zooms on focus.
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
@layer deck.components {
|
|
7
|
+
|
|
8
|
+
/* One form control and everything attached to it: label, input, help
|
|
9
|
+
text, error. The vertical gap between them comes from here, so a field
|
|
10
|
+
never needs margins. */
|
|
11
|
+
.field {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: var(--space-2);
|
|
15
|
+
min-inline-size: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* The label for a control. A flex row, so a "required" or "optional"
|
|
19
|
+
marker sits beside the text without extra markup. */
|
|
20
|
+
.label {
|
|
21
|
+
font-size: var(--text-sm);
|
|
22
|
+
font-weight: 580;
|
|
23
|
+
color: var(--text);
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: var(--space-2);
|
|
27
|
+
}
|
|
28
|
+
/* The marker for an optional field. Where most fields are required this
|
|
29
|
+
reads better than an asterisk on nearly every label. */
|
|
30
|
+
.label .optional { color: var(--text-faint); font-weight: 400; }
|
|
31
|
+
/* The marker beside a required field's label. Visual only — a screen
|
|
32
|
+
reader announces the required attribute, not the asterisk. */
|
|
33
|
+
.label .required { color: var(--bad-500); }
|
|
34
|
+
|
|
35
|
+
/* A hint below a control. Needs aria-describedby on the control to reach
|
|
36
|
+
a screen reader — sitting next to it is not an association. */
|
|
37
|
+
.help { font-size: var(--text-sm); color: var(--text-muted); }
|
|
38
|
+
/* A validation message below a control. Lightened for dark mode with
|
|
39
|
+
light-dark(), and aligned to flex-start so an icon stays on the first
|
|
40
|
+
line of a two-line message. */
|
|
41
|
+
.error {
|
|
42
|
+
font-size: var(--text-sm);
|
|
43
|
+
color: light-dark(var(--bad-700), oklch(76% .13 var(--hue-bad)));
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: var(--space-2);
|
|
46
|
+
align-items: flex-start;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.input, .textarea, .select {
|
|
50
|
+
inline-size: 100%;
|
|
51
|
+
min-block-size: var(--control-h);
|
|
52
|
+
padding: var(--space-2) var(--space-3);
|
|
53
|
+
background: var(--surface);
|
|
54
|
+
color: var(--text);
|
|
55
|
+
border: 1px solid var(--line-strong);
|
|
56
|
+
border-radius: var(--r-sm);
|
|
57
|
+
box-shadow: var(--shadow-1);
|
|
58
|
+
font-size: var(--text-base);
|
|
59
|
+
line-height: 1.45;
|
|
60
|
+
transition: border-color var(--dur-1) var(--ease-out),
|
|
61
|
+
box-shadow var(--dur-1) var(--ease-out),
|
|
62
|
+
background-color var(--dur-1) var(--ease-out);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (pointer: coarse) {
|
|
66
|
+
.input, .textarea, .select { font-size: 16px; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }
|
|
70
|
+
|
|
71
|
+
.input:hover, .textarea:hover, .select:hover { border-color: var(--ink-400); }
|
|
72
|
+
|
|
73
|
+
.input:focus, .textarea:focus, .select:focus {
|
|
74
|
+
outline: none;
|
|
75
|
+
border-color: var(--focus);
|
|
76
|
+
box-shadow: var(--ring);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.input[disabled], .textarea[disabled], .select[disabled] {
|
|
80
|
+
background: var(--bg-sunken);
|
|
81
|
+
color: var(--text-faint);
|
|
82
|
+
cursor: not-allowed;
|
|
83
|
+
box-shadow: none;
|
|
84
|
+
}
|
|
85
|
+
.input[readonly] { background: var(--surface-2); }
|
|
86
|
+
|
|
87
|
+
/* Grows with its content instead of scrolling in a 4-row box */
|
|
88
|
+
.textarea {
|
|
89
|
+
min-block-size: calc(var(--control-h) * 2);
|
|
90
|
+
resize: vertical;
|
|
91
|
+
field-sizing: content;
|
|
92
|
+
max-block-size: 22rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.select {
|
|
96
|
+
appearance: none;
|
|
97
|
+
padding-inline-end: var(--space-10);
|
|
98
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
99
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
100
|
+
background-position: calc(100% - 19px) calc(50% + 1px), calc(100% - 14px) calc(50% + 1px);
|
|
101
|
+
background-size: 5px 5px, 5px 5px;
|
|
102
|
+
background-repeat: no-repeat;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Validation only after the user has actually interacted */
|
|
107
|
+
/* Marks a field invalid from your own validation, producing the same
|
|
108
|
+
border and ring as :user-invalid without needing the browser to agree.
|
|
109
|
+
One of the few states an author writes by hand. */
|
|
110
|
+
.input:user-invalid, .textarea:user-invalid, .select:user-invalid,
|
|
111
|
+
.field.is-invalid :is(.input, .textarea, .select) {
|
|
112
|
+
border-color: var(--bad-500);
|
|
113
|
+
}
|
|
114
|
+
.input:user-invalid:focus, .field.is-invalid :is(.input, .textarea, .select):focus {
|
|
115
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--bad-500) 26%, transparent);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* --- Input group: prefix / suffix / attached button --------------------- */
|
|
119
|
+
/* A prefix, suffix or attached button joined to a control. The group
|
|
120
|
+
takes over the border, radius and shadow, and the input inside strips
|
|
121
|
+
its own, so the seam does not double up. :focus-within puts the ring
|
|
122
|
+
around the whole assembly. */
|
|
123
|
+
.input-group {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: stretch;
|
|
126
|
+
inline-size: 100%;
|
|
127
|
+
border: 1px solid var(--line-strong);
|
|
128
|
+
border-radius: var(--r-sm);
|
|
129
|
+
background: var(--surface);
|
|
130
|
+
box-shadow: var(--shadow-1);
|
|
131
|
+
overflow: hidden;
|
|
132
|
+
}
|
|
133
|
+
.input-group:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
|
|
134
|
+
.input-group > .input {
|
|
135
|
+
border: 0;
|
|
136
|
+
border-radius: 0;
|
|
137
|
+
box-shadow: none;
|
|
138
|
+
background: transparent;
|
|
139
|
+
}
|
|
140
|
+
.input-group > .input:focus { box-shadow: none; }
|
|
141
|
+
/* A prefix or suffix attached to a control inside .input-group. Its
|
|
142
|
+
divider is border-inline-end normally and border-inline-start on
|
|
143
|
+
:last-child, so a leading and a trailing addon each get their rule on
|
|
144
|
+
the correct side in both writing directions. */
|
|
145
|
+
.input-group > .addon {
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: var(--space-2);
|
|
149
|
+
padding-inline: var(--space-3);
|
|
150
|
+
background: var(--surface-2);
|
|
151
|
+
color: var(--text-muted);
|
|
152
|
+
font-size: var(--text-sm);
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
border-inline-end: 1px solid var(--line);
|
|
155
|
+
}
|
|
156
|
+
.input-group > .addon:last-child { border-inline-start: 1px solid var(--line); border-inline-end: 0; }
|
|
157
|
+
.input-group > .btn { border-radius: 0; border-block: 0; border-inline-end: 0; box-shadow: none; }
|
|
158
|
+
|
|
159
|
+
/* --- Search field ------------------------------------------------------- */
|
|
160
|
+
/* A control with an icon inside its starting edge. The icon is
|
|
161
|
+
pointer-events: none, so clicking it focuses the field underneath
|
|
162
|
+
rather than doing nothing. */
|
|
163
|
+
.search { position: relative; display: flex; align-items: center; }
|
|
164
|
+
.search > .icon {
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset-inline-start: var(--space-3);
|
|
167
|
+
color: var(--text-faint);
|
|
168
|
+
pointer-events: none;
|
|
169
|
+
}
|
|
170
|
+
.search > .input { padding-inline-start: var(--space-10); border-radius: var(--r-full); }
|
|
171
|
+
|
|
172
|
+
/* --- Checkbox and radio ------------------------------------------------- */
|
|
173
|
+
/* A checkbox or radio and its text, laid out as a row that clears the
|
|
174
|
+
44px touch target. Wrap the text in .check-text to add a second line. */
|
|
175
|
+
.check {
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: flex-start;
|
|
178
|
+
gap: var(--space-3);
|
|
179
|
+
min-block-size: var(--tap);
|
|
180
|
+
padding-block: var(--space-2);
|
|
181
|
+
cursor: pointer;
|
|
182
|
+
line-height: 1.4;
|
|
183
|
+
}
|
|
184
|
+
.check input[type="checkbox"], .check input[type="radio"] {
|
|
185
|
+
appearance: none;
|
|
186
|
+
flex: 0 0 auto;
|
|
187
|
+
inline-size: 20px;
|
|
188
|
+
block-size: 20px;
|
|
189
|
+
margin-block-start: 1px;
|
|
190
|
+
border: 1.5px solid var(--line-strong);
|
|
191
|
+
background: var(--surface);
|
|
192
|
+
border-radius: var(--r-xs);
|
|
193
|
+
display: grid;
|
|
194
|
+
place-content: center;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: background-color var(--dur-1) var(--ease-out),
|
|
197
|
+
border-color var(--dur-1) var(--ease-out);
|
|
198
|
+
}
|
|
199
|
+
.check input[type="radio"] { border-radius: var(--r-full); }
|
|
200
|
+
.check input:hover { border-color: var(--brand-400); }
|
|
201
|
+
.check input:checked { background: var(--brand-600); border-color: var(--brand-600); }
|
|
202
|
+
.check input[type="checkbox"]::before {
|
|
203
|
+
content: "";
|
|
204
|
+
inline-size: 11px;
|
|
205
|
+
block-size: 11px;
|
|
206
|
+
scale: 0;
|
|
207
|
+
transition: scale var(--dur-2) var(--ease-spring);
|
|
208
|
+
box-shadow: inset 1em 1em var(--text-on-brand);
|
|
209
|
+
clip-path: polygon(14% 44%, 0 65%, 41% 100%, 100% 16%, 84% 0, 39% 68%);
|
|
210
|
+
}
|
|
211
|
+
.check input[type="radio"]::before {
|
|
212
|
+
content: "";
|
|
213
|
+
inline-size: 9px;
|
|
214
|
+
block-size: 9px;
|
|
215
|
+
border-radius: var(--r-full);
|
|
216
|
+
background: var(--text-on-brand);
|
|
217
|
+
scale: 0;
|
|
218
|
+
transition: scale var(--dur-2) var(--ease-spring);
|
|
219
|
+
}
|
|
220
|
+
.check input:checked::before { scale: 1; }
|
|
221
|
+
.check input:indeterminate { background: var(--brand-600); border-color: var(--brand-600); }
|
|
222
|
+
.check input:indeterminate::before {
|
|
223
|
+
scale: 1;
|
|
224
|
+
clip-path: polygon(10% 42%, 90% 42%, 90% 58%, 10% 58%);
|
|
225
|
+
}
|
|
226
|
+
.check input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
227
|
+
/* The text beside a checkbox or radio. A column, so a .check-note can sit
|
|
228
|
+
under the label as a second line. */
|
|
229
|
+
/* The text beside a checkbox, as a column, so a .check-note can sit
|
|
230
|
+
beneath the label as a second line. */
|
|
231
|
+
.check .check-text { display: flex; flex-direction: column; gap: 2px; }
|
|
232
|
+
/* A muted second line inside .check-text. It is inside the label, so it
|
|
233
|
+
becomes part of the accessible name — move it out and use
|
|
234
|
+
aria-describedby if that name gets too long. */
|
|
235
|
+
.check .check-note { font-size: var(--text-sm); color: var(--text-muted); }
|
|
236
|
+
|
|
237
|
+
/* Card-style choice, good for pricing and plan pickers */
|
|
238
|
+
.check-card {
|
|
239
|
+
border: 1px solid var(--line);
|
|
240
|
+
border-radius: var(--r-md);
|
|
241
|
+
padding: var(--space-4);
|
|
242
|
+
background: var(--surface);
|
|
243
|
+
transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
|
|
244
|
+
}
|
|
245
|
+
.check-card:has(input:checked) {
|
|
246
|
+
border-color: var(--brand-500);
|
|
247
|
+
background: var(--brand-soft);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* --- Switch ------------------------------------------------------------- */
|
|
251
|
+
/* An on/off control that takes effect immediately, drawn over a real
|
|
252
|
+
checkbox. If the reader has to press Save afterwards, use .check
|
|
253
|
+
instead. Add role="switch" when "on"/"off" reads better than
|
|
254
|
+
"checked"/"unchecked". */
|
|
255
|
+
.switch { display: flex; align-items: center; gap: var(--space-3); min-block-size: var(--tap); cursor: pointer; }
|
|
256
|
+
.switch input {
|
|
257
|
+
appearance: none;
|
|
258
|
+
flex: 0 0 auto;
|
|
259
|
+
inline-size: 46px;
|
|
260
|
+
block-size: 28px;
|
|
261
|
+
border-radius: var(--r-full);
|
|
262
|
+
background: var(--ink-300);
|
|
263
|
+
position: relative;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
transition: background-color var(--dur-2) var(--ease-out);
|
|
266
|
+
}
|
|
267
|
+
.switch input::after {
|
|
268
|
+
content: "";
|
|
269
|
+
position: absolute;
|
|
270
|
+
inset-block-start: 3px;
|
|
271
|
+
inset-inline-start: 3px;
|
|
272
|
+
inline-size: 22px;
|
|
273
|
+
block-size: 22px;
|
|
274
|
+
border-radius: var(--r-full);
|
|
275
|
+
background: var(--ink-0);
|
|
276
|
+
box-shadow: var(--shadow-2);
|
|
277
|
+
transition: translate var(--dur-2) var(--ease-spring);
|
|
278
|
+
}
|
|
279
|
+
.switch input:checked { background: var(--brand-600); }
|
|
280
|
+
.switch input:checked::after { translate: 18px 0; }
|
|
281
|
+
.switch input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
282
|
+
|
|
283
|
+
/* --- Range -------------------------------------------------------------- */
|
|
284
|
+
/* A slider. The track and thumb are vendor pseudo-elements with
|
|
285
|
+
different names and different box models per engine, so every
|
|
286
|
+
declaration is written twice — WebKit needs a negative block-start
|
|
287
|
+
margin to centre the thumb and Firefox does not. */
|
|
288
|
+
.range {
|
|
289
|
+
inline-size: 100%;
|
|
290
|
+
appearance: none;
|
|
291
|
+
background: transparent;
|
|
292
|
+
block-size: var(--tap);
|
|
293
|
+
}
|
|
294
|
+
.range::-webkit-slider-runnable-track {
|
|
295
|
+
block-size: 6px;
|
|
296
|
+
border-radius: var(--r-full);
|
|
297
|
+
background: var(--ink-200);
|
|
298
|
+
}
|
|
299
|
+
.range::-moz-range-track {
|
|
300
|
+
block-size: 6px;
|
|
301
|
+
border-radius: var(--r-full);
|
|
302
|
+
background: var(--ink-200);
|
|
303
|
+
}
|
|
304
|
+
.range::-webkit-slider-thumb {
|
|
305
|
+
appearance: none;
|
|
306
|
+
margin-block-start: -8px;
|
|
307
|
+
inline-size: 22px;
|
|
308
|
+
block-size: 22px;
|
|
309
|
+
border-radius: var(--r-full);
|
|
310
|
+
background: var(--brand-600);
|
|
311
|
+
border: 3px solid var(--ink-0);
|
|
312
|
+
box-shadow: var(--shadow-2);
|
|
313
|
+
cursor: grab;
|
|
314
|
+
}
|
|
315
|
+
.range::-moz-range-thumb {
|
|
316
|
+
inline-size: 22px;
|
|
317
|
+
block-size: 22px;
|
|
318
|
+
border-radius: var(--r-full);
|
|
319
|
+
background: var(--brand-600);
|
|
320
|
+
border: 3px solid var(--ink-0);
|
|
321
|
+
box-shadow: var(--shadow-2);
|
|
322
|
+
cursor: grab;
|
|
323
|
+
}
|
|
324
|
+
.range:active::-webkit-slider-thumb { cursor: grabbing; scale: 1.08; }
|
|
325
|
+
|
|
326
|
+
/* --- File --------------------------------------------------------------- */
|
|
327
|
+
/* A dashed drop zone wrapping a real file input. The input is 1px and
|
|
328
|
+
transparent rather than display: none, so it keeps its place in the
|
|
329
|
+
tab order, and :focus-within is what makes that focus visible. Deck
|
|
330
|
+
styles the zone; dropping needs a handler of your own. */
|
|
331
|
+
.file {
|
|
332
|
+
display: grid;
|
|
333
|
+
place-items: center;
|
|
334
|
+
gap: var(--space-2);
|
|
335
|
+
padding: var(--space-6);
|
|
336
|
+
border: 1.5px dashed var(--line-strong);
|
|
337
|
+
border-radius: var(--r-md);
|
|
338
|
+
background: var(--surface-2);
|
|
339
|
+
text-align: center;
|
|
340
|
+
color: var(--text-muted);
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
|
|
343
|
+
}
|
|
344
|
+
.file:hover, .file:focus-within { border-color: var(--brand-500); background: var(--brand-soft); }
|
|
345
|
+
.file input[type="file"] { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; }
|
|
346
|
+
|
|
347
|
+
/* --- Fieldset ----------------------------------------------------------- */
|
|
348
|
+
/* A bordered group of controls that answer one question. Its legend is
|
|
349
|
+
the group's accessible name, which is what a radio group needs and
|
|
350
|
+
what a .label above one does not provide. */
|
|
351
|
+
.fieldset {
|
|
352
|
+
border: 1px solid var(--line);
|
|
353
|
+
border-radius: var(--r-md);
|
|
354
|
+
padding: var(--space-5);
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
gap: var(--space-4);
|
|
358
|
+
min-inline-size: 0;
|
|
359
|
+
}
|
|
360
|
+
.fieldset > legend {
|
|
361
|
+
padding-inline: var(--space-2);
|
|
362
|
+
font-size: var(--text-sm);
|
|
363
|
+
font-weight: 600;
|
|
364
|
+
color: var(--text-muted);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Two fields side by side on anything wider than a phone */
|
|
368
|
+
.field-row {
|
|
369
|
+
display: grid;
|
|
370
|
+
gap: var(--space-4);
|
|
371
|
+
grid-template-columns: 1fr;
|
|
372
|
+
}
|
|
373
|
+
@media (min-width: 40rem) {
|
|
374
|
+
.field-row { grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Sticky save bar for long forms on mobile */
|
|
378
|
+
.form-actions {
|
|
379
|
+
display: flex;
|
|
380
|
+
gap: var(--space-3);
|
|
381
|
+
flex-wrap: wrap;
|
|
382
|
+
}
|
|
383
|
+
/* Pins the action row to the bottom of the viewport with a blur behind
|
|
384
|
+
it and a safe-area inset for the home indicator. Add matching bottom
|
|
385
|
+
padding to the form, or the last field sits underneath it. */
|
|
386
|
+
.form-actions-sticky {
|
|
387
|
+
position: sticky;
|
|
388
|
+
inset-block-end: 0;
|
|
389
|
+
z-index: var(--z-sticky);
|
|
390
|
+
background: color-mix(in oklab, var(--bg) 88%, transparent);
|
|
391
|
+
backdrop-filter: blur(12px);
|
|
392
|
+
-webkit-backdrop-filter: blur(12px);
|
|
393
|
+
border-block-start: 1px solid var(--line);
|
|
394
|
+
padding-block: var(--space-3);
|
|
395
|
+
padding-block-end: calc(var(--space-3) + env(safe-area-inset-bottom));
|
|
396
|
+
}
|
|
397
|
+
.form-actions-sticky > .btn { flex: 1 1 auto; }
|
|
398
|
+
@media (min-width: 48rem) {
|
|
399
|
+
.form-actions-sticky > .btn { flex: 0 0 auto; }
|
|
400
|
+
}
|
|
401
|
+
}
|