@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,522 @@
1
+ /* ==========================================================================
2
+ Deck — 23 Inputs, extended
3
+ ========================================================================== */
4
+
5
+ @layer deck.components {
6
+
7
+ /* --- Floating label ------------------------------------------------------
8
+ No JavaScript. :placeholder-shown tells us whether the field is empty, so
9
+ the label lifts on focus or on content and never gets out of sync.
10
+ The input needs placeholder=" " for the selector to work.
11
+ ---------------------------------------------------------------------- */
12
+ /* Two things about the markup that this file cannot enforce and that fail
13
+ silently when they are wrong:
14
+
15
+ 1. The label must come AFTER the control. Every lift selector below is
16
+ `~ label`, and the general sibling combinator only looks forward.
17
+ Label first and it simply never moves.
18
+ 2. The control needs placeholder=" " (one space). Without a placeholder
19
+ attribute :placeholder-shown never matches, so the label sits lifted
20
+ over an empty field.
21
+
22
+ FINDINGS 61: .float.is-invalid only colours the label. .field.is-invalid
23
+ in 06-forms.css also reddens the control's border; there is no equivalent
24
+ rule here, so the same class name does two different amounts of work
25
+ depending on which wrapper it is on. :user-invalid is unaffected -- it
26
+ matches the control directly through the 06-forms.css rule. */
27
+ .float {
28
+ position: relative;
29
+ display: flex;
30
+ flex-direction: column;
31
+ }
32
+ .float > :is(.input, .textarea, .select) {
33
+ padding-block: var(--space-5) var(--space-2);
34
+ min-block-size: var(--control-h-lg);
35
+ }
36
+ .float > .textarea { padding-block-start: var(--space-6); }
37
+ .float > label {
38
+ position: absolute;
39
+ inset-block-start: 0;
40
+ inset-inline-start: var(--space-3);
41
+ padding-inline: 2px;
42
+ block-size: var(--control-h-lg);
43
+ display: flex;
44
+ align-items: center;
45
+ color: var(--text-faint);
46
+ font-size: var(--text-base);
47
+ pointer-events: none;
48
+ transform-origin: inline-start center;
49
+ transition: translate var(--dur-2) var(--ease-out),
50
+ scale var(--dur-2) var(--ease-out),
51
+ color var(--dur-2) var(--ease-out);
52
+ }
53
+ .float:focus-within > label,
54
+ .float > .input:not(:placeholder-shown) ~ label,
55
+ .float > .textarea:not(:placeholder-shown) ~ label,
56
+ .float > .select ~ label {
57
+ translate: 0 calc(var(--control-h-lg) * -.28);
58
+ scale: .78;
59
+ color: var(--text-muted);
60
+ }
61
+ .float:focus-within > label { color: var(--brand); }
62
+ .float.is-invalid > label,
63
+ .float > :user-invalid ~ label { color: var(--bad-700); }
64
+
65
+ /* Outlined variant, where the label sits in a notch in the border */
66
+ .float-outline > label {
67
+ background: var(--surface);
68
+ block-size: auto;
69
+ inset-block-start: 50%;
70
+ translate: 0 -50%;
71
+ padding-inline: var(--space-1);
72
+ }
73
+ .float-outline:focus-within > label,
74
+ .float-outline > .input:not(:placeholder-shown) ~ label,
75
+ .float-outline > .textarea:not(:placeholder-shown) ~ label,
76
+ .float-outline > .select ~ label {
77
+ translate: 0 calc(var(--control-h-lg) * -.5 - 50%);
78
+ scale: .8;
79
+ }
80
+ .float-outline > :is(.input, .textarea, .select) { padding-block: var(--space-3); }
81
+
82
+ /* --- Number input --------------------------------------------------------
83
+ Native spinners are tiny and inconsistent across browsers. These are real
84
+ buttons that clear the touch target.
85
+ ---------------------------------------------------------------------- */
86
+ .number {
87
+ display: flex;
88
+ align-items: stretch;
89
+ inline-size: 100%;
90
+ max-inline-size: var(--number-width, 11rem);
91
+ border: 1px solid var(--line-strong);
92
+ border-radius: var(--r-sm);
93
+ background: var(--surface);
94
+ box-shadow: var(--shadow-1);
95
+ overflow: hidden;
96
+ }
97
+ .number:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
98
+ .number > input {
99
+ flex: 1 1 auto;
100
+ min-inline-size: 0;
101
+ border: 0;
102
+ outline: none;
103
+ background: none;
104
+ text-align: center;
105
+ font-variant-numeric: tabular-nums;
106
+ font-size: var(--text-base);
107
+ color: inherit;
108
+ padding-inline: var(--space-1);
109
+ }
110
+ @media (pointer: coarse) { .number > input { font-size: 16px; } }
111
+ .number > input::-webkit-outer-spin-button,
112
+ .number > input::-webkit-inner-spin-button { appearance: none; margin: 0; }
113
+ .number > input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
114
+ .number > button {
115
+ inline-size: var(--control-h);
116
+ min-block-size: var(--control-h);
117
+ display: grid;
118
+ place-items: center;
119
+ background: var(--surface-2);
120
+ color: var(--text-muted);
121
+ cursor: pointer;
122
+ flex: 0 0 auto;
123
+ user-select: none;
124
+ transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
125
+ }
126
+ .number > button:hover { background: var(--surface-hover); color: var(--text); }
127
+ .number > button:active { background: var(--brand-soft); color: var(--brand-soft-text); }
128
+ .number > button[disabled] { opacity: .4; pointer-events: none; }
129
+ .number > button:first-child { border-inline-end: 1px solid var(--line); }
130
+ .number > button:last-child { border-inline-start: 1px solid var(--line); }
131
+ .number-unit {
132
+ display: grid;
133
+ place-items: center;
134
+ padding-inline: var(--space-3);
135
+ background: var(--surface-2);
136
+ border-inline-start: 1px solid var(--line);
137
+ font-size: var(--text-sm);
138
+ color: var(--text-muted);
139
+ white-space: nowrap;
140
+ }
141
+ .number-sm { --number-width: 8.5rem; }
142
+ .number-sm > button { inline-size: var(--control-h-sm); min-block-size: var(--control-h-sm); }
143
+
144
+ /* --- Phone input ---------------------------------------------------------
145
+ Country select welded to the number. The number stays LTR even in an RTL
146
+ document, because a phone number always reads left to right.
147
+ ---------------------------------------------------------------------- */
148
+ .phone {
149
+ display: flex;
150
+ align-items: stretch;
151
+ inline-size: 100%;
152
+ border: 1px solid var(--line-strong);
153
+ border-radius: var(--r-sm);
154
+ background: var(--surface);
155
+ box-shadow: var(--shadow-1);
156
+ overflow: hidden;
157
+ }
158
+ .phone:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
159
+ .phone > .phone-country {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: var(--space-2);
163
+ padding-inline: var(--space-3);
164
+ background: var(--surface-2);
165
+ border-inline-end: 1px solid var(--line);
166
+ cursor: pointer;
167
+ font-size: var(--text-sm);
168
+ color: var(--text-muted);
169
+ white-space: nowrap;
170
+ flex: 0 0 auto;
171
+ }
172
+ .phone > .phone-country:hover { background: var(--surface-hover); }
173
+ .phone > .phone-country select {
174
+ appearance: none;
175
+ border: 0;
176
+ background: none;
177
+ outline: none;
178
+ font: inherit;
179
+ color: inherit;
180
+ cursor: pointer;
181
+ max-inline-size: 4.5rem;
182
+ }
183
+ .phone-flag { font-size: 1.1em; line-height: 1; }
184
+ .phone-code { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 560; }
185
+ .phone > input {
186
+ flex: 1 1 auto;
187
+ min-inline-size: 0;
188
+ border: 0;
189
+ outline: none;
190
+ background: none;
191
+ padding: var(--space-2) var(--space-3);
192
+ font-size: var(--text-base);
193
+ font-variant-numeric: tabular-nums;
194
+ letter-spacing: .02em;
195
+ color: inherit;
196
+ direction: ltr;
197
+ unicode-bidi: isolate;
198
+ text-align: start;
199
+ }
200
+ @media (pointer: coarse) { .phone > input { font-size: 16px; } }
201
+ .phone.is-valid { border-color: color-mix(in oklab, var(--good-500) 55%, transparent); }
202
+ .phone-status { display: grid; place-items: center; padding-inline: var(--space-3); flex: 0 0 auto; }
203
+
204
+ /* --- Rating --------------------------------------------------------------
205
+ Radio inputs underneath, so it posts a real value and works with the
206
+ keyboard. Reversed flex order makes the CSS-only hover fill work.
207
+ ---------------------------------------------------------------------- */
208
+ /* The inputs are written HIGHEST value first -- 5, 4, 3, 2, 1 -- and
209
+ row-reverse draws them 1 to 5. That is not a quirk, it is the whole
210
+ mechanism: to light a star and everything below it, the selector has to
211
+ reach forward from the chosen one, and CSS has no previous-sibling
212
+ combinator. Reversing the row turns "everything below" into "everything
213
+ after".
214
+
215
+ Get the order wrong and it fails recognisably: the stars fill from the
216
+ wrong end and the posted value is the mirror of the one clicked.
217
+
218
+ :focus-visible + label uses the ADJACENT sibling, so each label must come
219
+ immediately after its own input. */
220
+ .rating {
221
+ display: inline-flex;
222
+ flex-direction: row-reverse;
223
+ justify-content: flex-end;
224
+ gap: 2px;
225
+ }
226
+ .rating > input { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; }
227
+ .rating > label {
228
+ inline-size: var(--star, 26px);
229
+ block-size: var(--star, 26px);
230
+ display: grid;
231
+ place-items: center;
232
+ color: var(--ink-300);
233
+ cursor: pointer;
234
+ transition: color var(--dur-1) var(--ease-out), scale var(--dur-1) var(--ease-spring);
235
+ }
236
+ .rating > label .icon { inline-size: 100%; block-size: 100%; fill: currentColor; stroke: none; }
237
+ .rating > label:hover { scale: 1.14; }
238
+ /* the checked star and everything visually before it (after it in the DOM) */
239
+ .rating > input:checked ~ label,
240
+ .rating > label:hover,
241
+ .rating > label:hover ~ label {
242
+ color: var(--accent-500);
243
+ }
244
+ .rating > input:focus-visible + label { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-xs); }
245
+ .rating-sm { --star: 18px; }
246
+ .rating-lg { --star: 36px; }
247
+
248
+ /* Read-only display: a fill percentage rather than whole stars */
249
+ .rating-static {
250
+ display: inline-flex;
251
+ gap: 2px;
252
+ color: var(--ink-300);
253
+ position: relative;
254
+ }
255
+ .rating-static > .rating-fill {
256
+ position: absolute;
257
+ inset-block: 0;
258
+ inset-inline-start: 0;
259
+ inline-size: calc(var(--value) * 1%);
260
+ overflow: hidden;
261
+ display: flex;
262
+ gap: 2px;
263
+ color: var(--accent-500);
264
+ white-space: nowrap;
265
+ }
266
+ .rating-static .icon { fill: currentColor; stroke: none; inline-size: var(--star, 18px); block-size: var(--star, 18px); flex: 0 0 auto; }
267
+
268
+ /* --- Range selector (two handles) ---------------------------------------
269
+ Two stacked native range inputs. Real inputs mean real keyboard support
270
+ and a real form post; the track between them is drawn from --lo and --hi.
271
+ ---------------------------------------------------------------------- */
272
+ .range-pair {
273
+ position: relative;
274
+ display: grid;
275
+ block-size: var(--tap);
276
+ align-items: center;
277
+ }
278
+ .range-pair::before {
279
+ content: "";
280
+ grid-area: 1 / 1;
281
+ block-size: 6px;
282
+ border-radius: var(--r-full);
283
+ background: var(--ink-200);
284
+ }
285
+ .range-pair::after {
286
+ content: "";
287
+ grid-area: 1 / 1;
288
+ block-size: 6px;
289
+ border-radius: var(--r-full);
290
+ background: var(--brand-500);
291
+ margin-inline-start: calc(var(--lo, 0) * 1%);
292
+ inline-size: calc((var(--hi, 100) - var(--lo, 0)) * 1%);
293
+ }
294
+ .range-pair > input[type="range"] {
295
+ grid-area: 1 / 1;
296
+ inline-size: 100%;
297
+ background: none;
298
+ appearance: none;
299
+ block-size: var(--tap);
300
+ pointer-events: none;
301
+ margin: 0;
302
+ }
303
+ .range-pair > input[type="range"]::-webkit-slider-runnable-track { background: transparent; block-size: 6px; }
304
+ .range-pair > input[type="range"]::-moz-range-track { background: transparent; block-size: 6px; }
305
+ .range-pair > input[type="range"]::-webkit-slider-thumb {
306
+ appearance: none;
307
+ pointer-events: auto;
308
+ margin-block-start: -8px;
309
+ inline-size: 22px;
310
+ block-size: 22px;
311
+ border-radius: var(--r-full);
312
+ background: var(--brand-600);
313
+ border: 3px solid var(--ink-0);
314
+ box-shadow: var(--shadow-2);
315
+ cursor: grab;
316
+ }
317
+ .range-pair > input[type="range"]::-moz-range-thumb {
318
+ pointer-events: auto;
319
+ inline-size: 22px;
320
+ block-size: 22px;
321
+ border-radius: var(--r-full);
322
+ background: var(--brand-600);
323
+ border: 3px solid var(--ink-0);
324
+ box-shadow: var(--shadow-2);
325
+ cursor: grab;
326
+ }
327
+ .range-pair > input[type="range"]:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--focus); outline-offset: 2px; }
328
+ /* The current value of a slider. Put it on an <output>, which is an
329
+ implicit live region, so the value is announced as the slider moves. */
330
+ .range-readout {
331
+ display: flex;
332
+ justify-content: space-between;
333
+ font-size: var(--text-sm);
334
+ font-variant-numeric: tabular-nums;
335
+ color: var(--text-muted);
336
+ }
337
+
338
+ /* Ticks under a single or paired range */
339
+ .range-ticks {
340
+ display: flex;
341
+ justify-content: space-between;
342
+ font-size: var(--text-2xs);
343
+ color: var(--text-faint);
344
+ font-variant-numeric: tabular-nums;
345
+ padding-inline: 11px;
346
+ }
347
+
348
+ /* --- Copy to clipboard ----------------------------------------------------
349
+ The behaviour hangs off [data-deck-copy], not off .copy-btn. The class is
350
+ the look and nothing else, so a button with the class and no attribute
351
+ looks exactly right and does nothing at all -- the single most common way
352
+ to get this component wrong.
353
+
354
+ Whether a successful copy raises a toast depends on what is inside the
355
+ button: a .copy-done element swaps in place and no toast fires; without
356
+ one, deck-extras.js toasts instead. .copy-inline never toasts.
357
+ ---------------------------------------------------------------------- */
358
+ .copy {
359
+ display: flex;
360
+ align-items: stretch;
361
+ inline-size: 100%;
362
+ border: 1px solid var(--line);
363
+ border-radius: var(--r-sm);
364
+ background: var(--surface-2);
365
+ overflow: hidden;
366
+ }
367
+ .copy > code, .copy > .copy-value {
368
+ flex: 1 1 auto;
369
+ min-inline-size: 0;
370
+ padding: var(--space-2) var(--space-3);
371
+ background: none;
372
+ border: 0;
373
+ border-radius: 0;
374
+ font-family: var(--font-mono);
375
+ font-size: var(--text-sm);
376
+ color: var(--text);
377
+ overflow-x: auto;
378
+ white-space: nowrap;
379
+ scrollbar-width: none;
380
+ direction: ltr;
381
+ unicode-bidi: isolate;
382
+ display: flex;
383
+ align-items: center;
384
+ }
385
+ .copy > :is(code, .copy-value)::-webkit-scrollbar { display: none; }
386
+ .copy-btn {
387
+ flex: 0 0 auto;
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: var(--space-2);
391
+ padding-inline: var(--space-3);
392
+ min-inline-size: var(--tap);
393
+ background: var(--surface);
394
+ border-inline-start: 1px solid var(--line);
395
+ color: var(--text-muted);
396
+ font-size: var(--text-sm);
397
+ font-weight: 560;
398
+ cursor: pointer;
399
+ transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
400
+ }
401
+ .copy-btn:hover { background: var(--surface-hover); color: var(--text); }
402
+ .copy-btn.is-copied { color: var(--good-700); background: var(--good-100); }
403
+ .copy-btn .copy-done { display: none; }
404
+ .copy-btn.is-copied .copy-done { display: inline-flex; }
405
+ .copy-btn.is-copied .copy-idle { display: none; }
406
+
407
+ /* Inline icon-only variant, for an order number or an API key in a table */
408
+ .copy-inline {
409
+ display: inline-flex;
410
+ align-items: center;
411
+ gap: var(--space-1);
412
+ color: var(--text-faint);
413
+ cursor: pointer;
414
+ border-radius: var(--r-xs);
415
+ padding: 2px;
416
+ }
417
+ .copy-inline:hover { color: var(--brand); background: var(--surface-hover); }
418
+ .copy-inline.is-copied { color: var(--good-700); }
419
+
420
+ /* --- WYSIWYG editor -------------------------------------------------------
421
+ A toolbar over a contenteditable region. Deck styles the chrome; deck.js
422
+ wires the commands. The content area inherits .prose, so what you type
423
+ looks the way it will look when it is published.
424
+ ---------------------------------------------------------------------- */
425
+ .editor {
426
+ display: flex;
427
+ flex-direction: column;
428
+ border: 1px solid var(--line-strong);
429
+ border-radius: var(--r-sm);
430
+ background: var(--surface);
431
+ box-shadow: var(--shadow-1);
432
+ overflow: clip;
433
+ }
434
+ .editor:focus-within { border-color: var(--focus); box-shadow: var(--ring); }
435
+
436
+ /* The tool row. Pressed state is [aria-pressed="true"], which deck-extras.js
437
+ rewrites from queryCommandState after every command and selection change,
438
+ so the toolbar reflects the caret rather than the last button pressed. */
439
+ .editor-toolbar {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 2px;
443
+ flex-wrap: wrap;
444
+ padding: var(--space-1);
445
+ background: var(--surface-2);
446
+ border-block-end: 1px solid var(--line);
447
+ position: sticky;
448
+ inset-block-start: 0;
449
+ z-index: 1;
450
+ }
451
+ .editor-tool {
452
+ inline-size: 32px;
453
+ block-size: 32px;
454
+ display: grid;
455
+ place-items: center;
456
+ border-radius: var(--r-xs);
457
+ color: var(--text-muted);
458
+ cursor: pointer;
459
+ font-size: var(--text-sm);
460
+ font-weight: 620;
461
+ transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
462
+ }
463
+ .editor-tool:hover { background: var(--surface-hover); color: var(--text); }
464
+ .editor-tool[aria-pressed="true"] { background: var(--brand-soft); color: var(--brand-soft-text); }
465
+ .editor-tool-wide { inline-size: auto; padding-inline: var(--space-2); }
466
+ .editor-sep { inline-size: 1px; block-size: 20px; background: var(--line); margin-inline: var(--space-1); }
467
+ .editor-select {
468
+ block-size: 32px;
469
+ padding-inline: var(--space-2);
470
+ border: 0;
471
+ border-radius: var(--r-xs);
472
+ background: none;
473
+ font-size: var(--text-sm);
474
+ color: var(--text-muted);
475
+ cursor: pointer;
476
+ }
477
+ .editor-select:hover { background: var(--surface-hover); }
478
+
479
+ /* The editable region. Its placeholder comes from data-placeholder through
480
+ :empty::before, so it needs no element and no script to hide it. */
481
+ .editor-content {
482
+ min-block-size: var(--editor-height, 12rem);
483
+ max-block-size: var(--editor-max, 30rem);
484
+ overflow-y: auto;
485
+ padding: var(--space-4);
486
+ outline: none;
487
+ line-height: var(--leading-normal);
488
+ }
489
+ .editor-content:empty::before {
490
+ content: attr(data-placeholder);
491
+ color: var(--text-faint);
492
+ pointer-events: none;
493
+ }
494
+ .editor-content > * + * { margin-block-start: var(--space-3); }
495
+ .editor-content :is(h1, h2, h3) { margin-block-start: var(--space-5); }
496
+ .editor-content :is(ul, ol) { padding-inline-start: var(--space-6); }
497
+ .editor-content ul { list-style: disc; }
498
+ .editor-content ol { list-style: decimal; }
499
+ .editor-content blockquote {
500
+ border-inline-start: 3px solid var(--brand-300);
501
+ padding-inline-start: var(--space-4);
502
+ color: var(--text-muted);
503
+ }
504
+ .editor-content a { color: var(--brand); }
505
+ .editor-content img { max-inline-size: 100%; border-radius: var(--r-sm); }
506
+
507
+ .editor-footer {
508
+ display: flex;
509
+ align-items: center;
510
+ gap: var(--space-3);
511
+ padding: var(--space-2) var(--space-3);
512
+ border-block-start: 1px solid var(--line);
513
+ background: var(--surface-2);
514
+ font-size: var(--text-xs);
515
+ color: var(--text-faint);
516
+ }
517
+ /* The character count. .is-over turns it red past data-limit — a soft
518
+ limit, since silently refusing keystrokes reads as a broken keyboard. The
519
+ server has to enforce it, and role="status" makes it audible. */
520
+ .editor-count { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
521
+ .editor-count.is-over { color: var(--bad-700); font-weight: 620; }
522
+ }
@@ -0,0 +1,87 @@
1
+ /* ==========================================================================
2
+ Deck — 00 Layers
3
+ The whole cascade contract in one place, declared before any rule exists.
4
+ Order is decided here, not by file order, not by specificity, and never by
5
+ !important.
6
+
7
+ Later layers win. Anything you write OUTSIDE a layer beats every layer, so
8
+ a plain rule in a page template always wins with no escalation.
9
+
10
+ deck.* the framework
11
+ app.* your slots, in the order you almost certainly want them
12
+
13
+ In a Keel view you can write:
14
+
15
+ @layer app.pages {
16
+ .page-header { padding-block: var(--space-8); }
17
+ }
18
+
19
+ and it beats every deck rule without a single !important, while still being
20
+ overridable by app.overrides and by unlayered CSS.
21
+ ========================================================================== */
22
+
23
+ @layer
24
+ deck.reset, /* normalize and element defaults */
25
+ deck.tokens, /* custom properties only */
26
+ deck.type, /* headings, prose, links */
27
+ deck.layout, /* containers, stack, grid, containers */
28
+ deck.components, /* buttons, forms, cards, grid, charts */
29
+ deck.mobile, /* sheets, tab bar, icons, emoji */
30
+ deck.motion, /* transitions, entrances, view trans. */
31
+ deck.effects, /* gradients, 3D, surface treatments */
32
+ deck.utilities, /* single-purpose classes */
33
+ deck.rtl, /* direction fixes, must beat utilities */
34
+ deck.print, /* paper */
35
+
36
+ app.base, /* your resets and element styles */
37
+ app.components, /* your components and deck overrides */
38
+ app.pages, /* per-page and per-view tweaks */
39
+ app.overrides; /* the escape hatch, still not !important */
40
+
41
+ /* --------------------------------------------------------------------------
42
+ Registered custom properties.
43
+ @property is not affected by layers. Registering a property gives it a type,
44
+ which is what lets the browser interpolate it — an angle or a color in a
45
+ custom property can now animate, which plain custom properties cannot do.
46
+ -------------------------------------------------------------------------- */
47
+
48
+ @property --g-angle {
49
+ syntax: "<angle>";
50
+ initial-value: 180deg;
51
+ inherits: false;
52
+ }
53
+ @property --g-from {
54
+ syntax: "<color>";
55
+ initial-value: transparent;
56
+ inherits: false;
57
+ }
58
+ @property --g-to {
59
+ syntax: "<color>";
60
+ initial-value: transparent;
61
+ inherits: false;
62
+ }
63
+ @property --g-stop {
64
+ syntax: "<percentage>";
65
+ initial-value: 50%;
66
+ inherits: false;
67
+ }
68
+ @property --sheen {
69
+ syntax: "<percentage>";
70
+ initial-value: -40%;
71
+ inherits: false;
72
+ }
73
+ @property --tilt-x {
74
+ syntax: "<angle>";
75
+ initial-value: 0deg;
76
+ inherits: false;
77
+ }
78
+ @property --tilt-y {
79
+ syntax: "<angle>";
80
+ initial-value: 0deg;
81
+ inherits: false;
82
+ }
83
+ @property --depth {
84
+ syntax: "<length>";
85
+ initial-value: 0px;
86
+ inherits: false;
87
+ }