@specbook/ui 0.0.0-stage → 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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/dist-viewer/apple-touch-icon.png +0 -0
  4. package/dist-viewer/assets/Host-DdTTHeXU.js +37 -0
  5. package/dist-viewer/assets/faker-B3c03STC.js +11 -0
  6. package/dist-viewer/assets/index-DnYdlDXQ.js +1 -0
  7. package/dist-viewer/assets/index-Ds6DZpGP.css +1 -0
  8. package/dist-viewer/assets/main-uV4JzQPd.js +14 -0
  9. package/dist-viewer/assets/mount-B-qFOcWp.js +2 -0
  10. package/dist-viewer/assets/panel-CoQ6iQpX.js +1 -0
  11. package/dist-viewer/assets/panel-Coi9H1nv.css +1 -0
  12. package/dist-viewer/assets/registry-DI74Wtj8.js +2 -0
  13. package/dist-viewer/assets/worker-Dw3Ps3hk.js +181 -0
  14. package/dist-viewer/favicon.svg +1 -0
  15. package/dist-viewer/index.html +19 -0
  16. package/dist-viewer/specbook.js +1 -0
  17. package/index.html +12 -0
  18. package/package.json +60 -3
  19. package/src/app.tsx +363 -0
  20. package/src/assets/mark.svg +1 -0
  21. package/src/badge.ts +14 -0
  22. package/src/branding.ts +92 -0
  23. package/src/components/Catalog.tsx +324 -0
  24. package/src/components/EnvBadge.tsx +46 -0
  25. package/src/components/EnvironmentMenu.tsx +262 -0
  26. package/src/components/Fields.tsx +220 -0
  27. package/src/components/GitHubStar.tsx +22 -0
  28. package/src/components/ListResizer.tsx +70 -0
  29. package/src/components/ModeHint.tsx +17 -0
  30. package/src/components/Palette.tsx +85 -0
  31. package/src/components/PoweredBy.tsx +15 -0
  32. package/src/components/ShareMenu.tsx +94 -0
  33. package/src/components/ThemeToggle.tsx +52 -0
  34. package/src/embed/panel.tsx +114 -0
  35. package/src/embed/styles.ts +27 -0
  36. package/src/embed.ts +65 -0
  37. package/src/engine.ts +432 -0
  38. package/src/extension.ts +101 -0
  39. package/src/fielddoc.ts +83 -0
  40. package/src/main.tsx +16 -0
  41. package/src/markdown.ts +133 -0
  42. package/src/nav.ts +118 -0
  43. package/src/panel/Body.tsx +182 -0
  44. package/src/panel/BodyEditor.tsx +92 -0
  45. package/src/panel/CodeSample.tsx +237 -0
  46. package/src/panel/Combo.tsx +124 -0
  47. package/src/panel/ControlsGrid.tsx +189 -0
  48. package/src/panel/FieldDoc.tsx +42 -0
  49. package/src/panel/FieldTable.tsx +113 -0
  50. package/src/panel/Host.tsx +100 -0
  51. package/src/panel/OperationDocs.tsx +181 -0
  52. package/src/panel/Panel.tsx +193 -0
  53. package/src/panel/ParamGrid.tsx +396 -0
  54. package/src/panel/RequestPanel.tsx +625 -0
  55. package/src/panel/RequestView.tsx +108 -0
  56. package/src/panel/ResponseView.tsx +182 -0
  57. package/src/panel/SecretsBar.tsx +156 -0
  58. package/src/panel/SequenceDocs.tsx +132 -0
  59. package/src/panel/SequencePanel.tsx +247 -0
  60. package/src/panel/StepExchange.tsx +118 -0
  61. package/src/panel/completions.ts +114 -0
  62. package/src/panel/draft.ts +106 -0
  63. package/src/panel/fieldnotes-split.ts +55 -0
  64. package/src/panel/fieldnotes.ts +40 -0
  65. package/src/panel/graphql.ts +31 -0
  66. package/src/panel/icons.tsx +85 -0
  67. package/src/panel/jsonCursor.ts +115 -0
  68. package/src/panel/stepnotes-split.ts +38 -0
  69. package/src/panel/stepnotes.ts +58 -0
  70. package/src/routing.ts +120 -0
  71. package/src/runtime.ts +32 -0
  72. package/src/schema.ts +359 -0
  73. package/src/secrets.ts +61 -0
  74. package/src/status.ts +27 -0
  75. package/src/store.ts +10 -0
  76. package/src/styles/app.css +821 -0
  77. package/src/styles/panel.css +1897 -0
  78. package/src/styles/tokens.css +122 -0
  79. package/src/theme.ts +37 -0
  80. package/src/viewer/Opening.tsx +43 -0
  81. package/src/viewer/bundles.ts +74 -0
  82. package/src/viewer/client.ts +54 -0
  83. package/src/viewer/embed.ts +78 -0
  84. package/src/viewer/faker-en.ts +3 -0
  85. package/src/viewer/main.tsx +285 -0
  86. package/src/viewer/mount.ts +52 -0
  87. package/src/viewer/protocol.ts +51 -0
  88. package/src/viewer/recent.ts +22 -0
  89. package/src/viewer/registry-data.ts +4 -0
  90. package/src/viewer/registry.ts +154 -0
  91. package/src/viewer/share.ts +134 -0
  92. package/src/viewer/site.ts +3 -0
  93. package/src/viewer/uploads.ts +53 -0
  94. package/src/viewer/viewer.css +184 -0
  95. package/src/viewer/worker-factory.ts +14 -0
  96. package/src/viewer/worker.ts +171 -0
  97. package/src/virtual.d.ts +104 -0
@@ -0,0 +1,821 @@
1
+ @layer reset {
2
+ *,
3
+ *::before,
4
+ *::after {
5
+ box-sizing: border-box;
6
+ }
7
+ body {
8
+ margin: 0;
9
+ }
10
+ button,
11
+ input,
12
+ select,
13
+ textarea {
14
+ font: inherit;
15
+ color: inherit;
16
+ }
17
+ }
18
+
19
+ @layer base {
20
+ html,
21
+ body,
22
+ #app {
23
+ height: 100%;
24
+ }
25
+ body {
26
+ background: var(--sb-bg);
27
+ color: var(--sb-text);
28
+ font: 14px / 1.5 var(--sb-font-sans);
29
+ }
30
+ code,
31
+ pre,
32
+ kbd {
33
+ font-family: var(--sb-font-mono);
34
+ font-size: 12.5px;
35
+ }
36
+ kbd {
37
+ padding: 1px 6px;
38
+ border: 1px solid var(--sb-border);
39
+ border-radius: 4px;
40
+ background: var(--sb-surface-raised);
41
+ color: var(--sb-text-muted);
42
+ }
43
+ h1,
44
+ h2,
45
+ h3,
46
+ p {
47
+ margin: 0;
48
+ }
49
+ h1 {
50
+ font-size: 22px;
51
+ font-weight: 650;
52
+ letter-spacing: -0.01em;
53
+ }
54
+ h2 {
55
+ font-size: 13px;
56
+ font-weight: 600;
57
+ letter-spacing: 0.04em;
58
+ text-transform: uppercase;
59
+ color: var(--sb-text-muted);
60
+ }
61
+ html,
62
+ body {
63
+ overscroll-behavior: none;
64
+ }
65
+ :focus-visible {
66
+ outline: 2px solid var(--sb-focus-ring);
67
+ outline-offset: 1px;
68
+ }
69
+ }
70
+
71
+ @layer components {
72
+ .app {
73
+ display: flex;
74
+ flex-direction: column;
75
+ height: 100%;
76
+ }
77
+ /* a phone's address bar comes and goes; dvh follows it, so nothing sits behind the toolbar */
78
+ @supports (height: 100dvh) {
79
+ .app {
80
+ height: 100dvh;
81
+ }
82
+ }
83
+ .header {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: var(--sb-space-4);
87
+ height: var(--sb-header-h, 53px);
88
+ flex: 0 0 auto;
89
+ padding: 8px var(--sb-space-4);
90
+ background: var(--sb-surface);
91
+ border-bottom: 1px solid var(--sb-border);
92
+ flex-wrap: wrap;
93
+ }
94
+ .brand {
95
+ display: inline-flex;
96
+ align-items: center;
97
+ gap: 8px;
98
+ font-weight: 700;
99
+ letter-spacing: -0.01em;
100
+ background: none;
101
+ border: 0;
102
+ cursor: pointer;
103
+ font-size: 15px;
104
+ }
105
+ .spacer {
106
+ flex: 1;
107
+ }
108
+ .row {
109
+ display: flex;
110
+ gap: var(--sb-space-3);
111
+ align-items: center;
112
+ flex-wrap: wrap;
113
+ }
114
+ .muted {
115
+ color: var(--sb-text-muted);
116
+ }
117
+ .small {
118
+ font-size: 12px;
119
+ }
120
+ .count {
121
+ font-weight: 400;
122
+ opacity: 0.7;
123
+ margin-left: 4px;
124
+ }
125
+
126
+ /* ---- shell: a list that never changes width, and a stage the panel docks into */
127
+ .app {
128
+ --sb-list-w: clamp(320px, 28vw, 440px);
129
+ }
130
+ .layout {
131
+ flex: 1;
132
+ min-height: 0;
133
+ display: grid;
134
+ grid-template-columns: var(--sb-list-w) minmax(0, 1fr);
135
+ }
136
+ /* classic scrollbars (a mouse, or "always show") are thin and quiet rather than a wide grey bar */
137
+ .list-col,
138
+ .stage,
139
+ .panel-body,
140
+ .panel-main.scroll,
141
+ .tab-pane,
142
+ .seq-left {
143
+ scrollbar-width: thin;
144
+ scrollbar-color: var(--sb-border) transparent;
145
+ }
146
+ .list-col {
147
+ min-height: 0;
148
+ overflow: auto;
149
+ /* scrolling to the end of the list must not drag the whole page along */
150
+ overscroll-behavior: contain;
151
+ padding: var(--sb-space-3);
152
+ background: var(--sb-surface);
153
+ border-right: 1px solid var(--sb-border);
154
+ }
155
+ .stage {
156
+ min-height: 0;
157
+ overflow: auto;
158
+ padding: var(--sb-space-6) 32px 96px;
159
+ }
160
+ @media (max-width: 900px) {
161
+ .layout {
162
+ grid-template-columns: minmax(0, 1fr);
163
+ }
164
+ .stage {
165
+ display: none;
166
+ }
167
+ /* a phone opens on the overview as a splash; the list comes after "Browse" */
168
+ .layout[data-view="splash"] .list-col {
169
+ display: none;
170
+ }
171
+ .layout[data-view="splash"] .stage {
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ padding: var(--sb-space-6) var(--sb-space-4);
176
+ }
177
+ .layout[data-view="splash"] .overview {
178
+ min-height: 0;
179
+ /* a touch above the exact middle reads as centred */
180
+ padding-bottom: 6vh;
181
+ }
182
+ }
183
+ .overview .overview-browse {
184
+ display: none;
185
+ }
186
+ @media (max-width: 820px) {
187
+ .overview .overview-browse {
188
+ display: inline-flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ min-height: 48px;
192
+ padding-block: 0;
193
+ padding-inline: var(--sb-space-6);
194
+ font-size: 16px;
195
+ line-height: 1.2;
196
+ text-align: center;
197
+ }
198
+ .overview-hint,
199
+ .overview-keys {
200
+ display: none;
201
+ }
202
+ }
203
+ /* the environment chip repeats the header's selector on a phone, where that sits behind a menu */
204
+ .header-env {
205
+ display: none;
206
+ }
207
+ /* the header's settings: inline on a desktop, a menu on a phone */
208
+ .header-controls {
209
+ display: contents;
210
+ }
211
+ /* `.header` raises it above the shared `.icon` rule, which would otherwise show it on a desktop */
212
+ .header .menu-btn {
213
+ display: none;
214
+ width: 36px;
215
+ height: 36px;
216
+ font-size: 20px;
217
+ line-height: 1;
218
+ }
219
+ .theme-toggle {
220
+ width: 32px;
221
+ height: 32px;
222
+ border: 1px solid var(--sb-border);
223
+ border-radius: var(--sb-radius-control);
224
+ color: var(--sb-text);
225
+ }
226
+ .brand-logo {
227
+ height: 24px;
228
+ width: auto;
229
+ max-width: 120px;
230
+ object-fit: contain;
231
+ }
232
+ .seed-input {
233
+ width: 80px;
234
+ }
235
+ .palette-btn {
236
+ display: flex;
237
+ justify-content: space-between;
238
+ align-items: center;
239
+ gap: var(--sb-space-6);
240
+ min-width: min(360px, 40vw);
241
+ padding: 6px 10px;
242
+ border: 1px solid var(--sb-border);
243
+ border-radius: var(--sb-radius-control);
244
+ background: var(--sb-bg);
245
+ color: var(--sb-text-muted);
246
+ cursor: pointer;
247
+ }
248
+ .palette-btn:hover {
249
+ border-color: var(--sb-text-muted);
250
+ }
251
+
252
+ /* ---- palette */
253
+ .palette-backdrop {
254
+ position: fixed;
255
+ inset: 0;
256
+ z-index: 20;
257
+ background: rgb(15 23 42 / 40%);
258
+ display: flex;
259
+ justify-content: center;
260
+ padding-top: 14vh;
261
+ }
262
+ .palette-dismiss {
263
+ position: absolute;
264
+ inset: 0;
265
+ border: 0;
266
+ padding: 0;
267
+ background: none;
268
+ cursor: default;
269
+ }
270
+ .palette {
271
+ position: relative;
272
+ width: min(620px, 92vw);
273
+ max-height: 60vh;
274
+ display: flex;
275
+ flex-direction: column;
276
+ background: var(--sb-surface);
277
+ border: 1px solid var(--sb-border);
278
+ border-radius: var(--sb-radius-card);
279
+ box-shadow: 0 20px 50px rgb(0 0 0 / 25%);
280
+ overflow: hidden;
281
+ }
282
+ .palette input {
283
+ border: 0;
284
+ border-bottom: 1px solid var(--sb-border);
285
+ border-radius: 0;
286
+ padding: 14px 16px;
287
+ font-size: 15px;
288
+ outline: none;
289
+ }
290
+ .palette ul {
291
+ list-style: none;
292
+ margin: 0;
293
+ padding: 6px;
294
+ overflow: auto;
295
+ }
296
+ .palette li button {
297
+ display: flex;
298
+ justify-content: space-between;
299
+ gap: var(--sb-space-3);
300
+ width: 100%;
301
+ padding: 8px 10px;
302
+ border: 0;
303
+ border-radius: var(--sb-radius-control);
304
+ background: none;
305
+ text-align: left;
306
+ cursor: pointer;
307
+ }
308
+ .palette li button.sel {
309
+ background: var(--sb-surface-raised);
310
+ }
311
+ .pad {
312
+ padding: 12px;
313
+ }
314
+
315
+ /* ---- shared pieces */
316
+ .states {
317
+ display: flex;
318
+ gap: var(--sb-space-2);
319
+ align-items: center;
320
+ flex-wrap: wrap;
321
+ margin-bottom: var(--sb-space-4);
322
+ }
323
+ .state {
324
+ display: inline-flex;
325
+ gap: 6px;
326
+ align-items: center;
327
+ padding: 3px 10px;
328
+ border: 1px solid var(--sb-border);
329
+ border-radius: 999px;
330
+ background: var(--sb-surface);
331
+ font-size: 12px;
332
+ cursor: pointer;
333
+ }
334
+ .state span {
335
+ color: var(--sb-text-muted);
336
+ }
337
+ table.headers {
338
+ border-collapse: collapse;
339
+ font-size: 12px;
340
+ width: 100%;
341
+ }
342
+ table.headers th {
343
+ text-align: left;
344
+ padding: 3px 12px 3px 0;
345
+ font-family: var(--sb-font-mono);
346
+ color: var(--sb-text-muted);
347
+ vertical-align: top;
348
+ }
349
+ table.headers td {
350
+ word-break: break-all;
351
+ }
352
+ .state.s2 {
353
+ border-color: var(--sb-success);
354
+ }
355
+ .state.s4,
356
+ .state.s5 {
357
+ border-color: var(--sb-warning);
358
+ }
359
+ .state.forced {
360
+ background: var(--sb-accent);
361
+ border-color: var(--sb-accent);
362
+ color: var(--sb-text-on-accent);
363
+ }
364
+ .state.forced span {
365
+ color: inherit;
366
+ }
367
+ .state.last::before {
368
+ content: "●";
369
+ font-size: 8px;
370
+ color: var(--sb-success);
371
+ }
372
+ .tabs {
373
+ display: inline-flex;
374
+ border: 1px solid var(--sb-border);
375
+ border-radius: var(--sb-radius-control);
376
+ overflow: hidden;
377
+ }
378
+ .tabs .tab {
379
+ padding: 3px 12px;
380
+ border: 0;
381
+ background: none;
382
+ cursor: pointer;
383
+ }
384
+ .tabs .tab.on {
385
+ background: var(--sb-surface-raised);
386
+ font-weight: 600;
387
+ }
388
+ .lock {
389
+ margin-bottom: var(--sb-space-3);
390
+ }
391
+ /* ---- response */
392
+ .state-badge {
393
+ padding: 2px 10px;
394
+ border-radius: 999px;
395
+ font-weight: 600;
396
+ font-size: 12px;
397
+ }
398
+ .state-badge.s2 {
399
+ background: var(--sb-success-subtle);
400
+ color: var(--sb-success);
401
+ }
402
+ .state-badge.s3 {
403
+ background: var(--sb-surface-raised);
404
+ }
405
+ .state-badge.s4,
406
+ .state-badge.s5 {
407
+ background: var(--sb-warning-subtle);
408
+ color: var(--sb-warning);
409
+ }
410
+ .conf {
411
+ font-size: 12px;
412
+ font-weight: 600;
413
+ }
414
+ .conf.ok {
415
+ color: var(--sb-success);
416
+ }
417
+ .conf.warn {
418
+ color: var(--sb-warning);
419
+ }
420
+ .conf.none {
421
+ color: var(--sb-text-muted);
422
+ font-weight: 400;
423
+ }
424
+ .checks {
425
+ list-style: none;
426
+ margin: var(--sb-space-3) 0 0;
427
+ padding: var(--sb-space-3) 0 0;
428
+ border-top: 1px solid var(--sb-border);
429
+ }
430
+ .checks .ok {
431
+ color: var(--sb-success);
432
+ }
433
+ .checks .bad {
434
+ color: var(--sb-danger);
435
+ }
436
+ .error-text {
437
+ color: var(--sb-danger);
438
+ }
439
+
440
+ /* ---- sequence journey */
441
+ .journey {
442
+ list-style: none;
443
+ margin: 0;
444
+ padding: 0;
445
+ }
446
+ .jstep {
447
+ display: grid;
448
+ grid-template-columns: 28px minmax(0, 1fr);
449
+ gap: var(--sb-space-3);
450
+ position: relative;
451
+ padding-bottom: var(--sb-space-4);
452
+ }
453
+ .jstep::before {
454
+ content: "";
455
+ position: absolute;
456
+ left: 13px;
457
+ top: 28px;
458
+ bottom: 0;
459
+ width: 2px;
460
+ background: var(--sb-border);
461
+ }
462
+ .jstep:last-child::before {
463
+ display: none;
464
+ }
465
+ .jmark {
466
+ width: 28px;
467
+ height: 28px;
468
+ border-radius: 50%;
469
+ display: grid;
470
+ place-items: center;
471
+ background: var(--sb-surface-raised);
472
+ border: 2px solid var(--sb-border);
473
+ font-weight: 700;
474
+ font-size: 12px;
475
+ }
476
+ .jstep.pass .jmark {
477
+ background: var(--sb-success-subtle);
478
+ border-color: var(--sb-success);
479
+ color: var(--sb-success);
480
+ }
481
+ .jstep.fail .jmark,
482
+ .jstep.error .jmark {
483
+ background: var(--sb-danger-subtle);
484
+ border-color: var(--sb-danger);
485
+ color: var(--sb-danger);
486
+ }
487
+ .jstep.paused .jmark,
488
+ .jstep.running .jmark {
489
+ border-color: var(--sb-accent);
490
+ color: var(--sb-accent);
491
+ }
492
+ .jstep.skipped {
493
+ opacity: 0.6;
494
+ }
495
+ .jhead {
496
+ display: flex;
497
+ gap: var(--sb-space-3);
498
+ align-items: baseline;
499
+ flex-wrap: wrap;
500
+ border: 0;
501
+ background: none;
502
+ padding: 2px 0;
503
+ text-align: left;
504
+ cursor: pointer;
505
+ }
506
+ .jhead:disabled {
507
+ cursor: default;
508
+ }
509
+ .jname {
510
+ font-weight: 600;
511
+ }
512
+ .cap {
513
+ font-size: 12px;
514
+ padding: 1px 8px;
515
+ border-radius: 999px;
516
+ background: var(--sb-surface-raised);
517
+ color: var(--sb-text-muted);
518
+ }
519
+ .cap b {
520
+ color: var(--sb-text);
521
+ font-weight: 600;
522
+ }
523
+ .cap em {
524
+ font-style: normal;
525
+ opacity: 0.8;
526
+ }
527
+ .cap.use {
528
+ background: var(--sb-warning-subtle);
529
+ color: var(--sb-warning);
530
+ }
531
+ .cap.make {
532
+ background: var(--sb-success-subtle);
533
+ color: var(--sb-success);
534
+ }
535
+ .pause {
536
+ margin-top: var(--sb-space-3);
537
+ padding: var(--sb-space-3);
538
+ border-left: 3px solid var(--sb-accent);
539
+ background: var(--sb-bg);
540
+ border-radius: var(--sb-radius-control);
541
+ }
542
+
543
+ /* ---- shared controls */
544
+ .btn {
545
+ padding: 6px 14px;
546
+ border: 1px solid var(--sb-border);
547
+ border-radius: var(--sb-radius-control);
548
+ background: var(--sb-surface);
549
+ cursor: pointer;
550
+ }
551
+ .btn:hover {
552
+ background: var(--sb-surface-raised);
553
+ }
554
+ .btn.primary {
555
+ background: var(--sb-accent);
556
+ border-color: var(--sb-accent);
557
+ color: var(--sb-text-on-accent);
558
+ font-weight: 600;
559
+ }
560
+ .btn.primary:hover {
561
+ background: var(--sb-accent-hover);
562
+ }
563
+ .btn:disabled {
564
+ opacity: 0.55;
565
+ cursor: default;
566
+ }
567
+ input[type="text"],
568
+ input[type="number"],
569
+ input[type="date"],
570
+ select,
571
+ textarea.val {
572
+ padding: 5px 8px;
573
+ border: 1px solid var(--sb-border);
574
+ border-radius: var(--sb-radius-control);
575
+ background: var(--sb-surface);
576
+ }
577
+ .field {
578
+ display: grid;
579
+ gap: 4px;
580
+ margin-bottom: var(--sb-space-3);
581
+ }
582
+ .field > .label {
583
+ font-weight: 600;
584
+ font-size: 12px;
585
+ }
586
+ .nested {
587
+ border-left: 2px solid var(--sb-border);
588
+ padding-left: var(--sb-space-3);
589
+ }
590
+ .method {
591
+ font: 700 11px var(--sb-font-mono);
592
+ padding: 1px 6px;
593
+ border-radius: 4px;
594
+ background: var(--sb-surface-raised);
595
+ }
596
+ .method.GET {
597
+ color: var(--sb-method-get);
598
+ }
599
+ .method.POST {
600
+ color: var(--sb-method-post);
601
+ }
602
+ .method.PUT {
603
+ color: var(--sb-method-put);
604
+ }
605
+ .method.PATCH {
606
+ color: var(--sb-method-patch);
607
+ }
608
+ .method.DELETE {
609
+ color: var(--sb-method-delete);
610
+ }
611
+ .badge {
612
+ padding: 1px 8px;
613
+ border-radius: 999px;
614
+ font-size: 12px;
615
+ font-weight: 600;
616
+ }
617
+ .badge.pass {
618
+ background: var(--sb-success-subtle);
619
+ color: var(--sb-success);
620
+ }
621
+ .badge.fail,
622
+ .badge.error {
623
+ background: var(--sb-danger-subtle);
624
+ color: var(--sb-danger);
625
+ }
626
+ }
627
+
628
+ @layer components {
629
+ /* the header's settings move into the menu where they no longer fit on one line */
630
+ @media (max-width: 900px) {
631
+ .header {
632
+ position: relative;
633
+ gap: var(--sb-space-3);
634
+ flex-wrap: nowrap;
635
+ }
636
+ /* the name gives way before the header can outgrow the screen: the palette cannot shrink below its
637
+ padding, so a long name or a wider system font would otherwise push the menu button off the edge */
638
+ .brand {
639
+ flex: 0 1 auto;
640
+ min-width: 0;
641
+ }
642
+ .brand-logo {
643
+ flex: none;
644
+ }
645
+ .brand-name {
646
+ overflow: hidden;
647
+ text-overflow: ellipsis;
648
+ white-space: nowrap;
649
+ }
650
+ .palette-btn {
651
+ flex: 1;
652
+ min-width: 0;
653
+ }
654
+ .palette-btn span {
655
+ overflow: hidden;
656
+ text-overflow: ellipsis;
657
+ white-space: nowrap;
658
+ }
659
+ .palette-btn kbd,
660
+ .header .spacer {
661
+ display: none;
662
+ }
663
+ .header .menu-btn {
664
+ display: inline-flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ flex: 0 0 auto;
668
+ }
669
+ .header-env {
670
+ display: inline-flex;
671
+ flex: 0 0 auto;
672
+ }
673
+ .header-controls {
674
+ display: none;
675
+ position: absolute;
676
+ top: 100%;
677
+ right: var(--sb-space-2);
678
+ left: var(--sb-space-2);
679
+ z-index: 30;
680
+ flex-direction: column;
681
+ align-items: stretch;
682
+ gap: var(--sb-space-3);
683
+ padding: var(--sb-space-4);
684
+ border: 1px solid var(--sb-border);
685
+ border-radius: var(--sb-radius-control);
686
+ background: var(--sb-surface);
687
+ box-shadow: 0 12px 32px color-mix(in srgb, var(--sb-text) 18%, transparent);
688
+ }
689
+ .header-controls.open {
690
+ display: flex;
691
+ }
692
+ .header-controls .row {
693
+ justify-content: space-between;
694
+ flex-wrap: nowrap;
695
+ }
696
+ .header-controls select {
697
+ min-width: 140px;
698
+ }
699
+ /* in the menu there is room for the words, and each link is a full row */
700
+ .header-controls .gh-label,
701
+ .header-controls .share-label {
702
+ display: inline;
703
+ }
704
+ .header-controls .gh-star,
705
+ .header-controls .share > .btn {
706
+ width: 100%;
707
+ justify-content: flex-start;
708
+ }
709
+ .list-col {
710
+ padding: var(--sb-space-2);
711
+ }
712
+ }
713
+ }
714
+
715
+ @layer components {
716
+ /* iOS zooms the page whenever a field under 16px is focused, so on a phone every field is 16px.
717
+ !important because individual cells and editors set their own smaller sizes. */
718
+ @media (max-width: 820px) {
719
+ input,
720
+ select,
721
+ textarea {
722
+ /* biome-ignore lint/complexity/noImportantStyles: cell editors set their own smaller sizes */
723
+ font-size: 16px !important;
724
+ }
725
+ }
726
+ }
727
+
728
+ @layer components {
729
+ .share {
730
+ position: relative;
731
+ }
732
+ .share-pop {
733
+ position: absolute;
734
+ top: calc(100% + 6px);
735
+ right: 0;
736
+ z-index: 40;
737
+ width: min(360px, 90vw);
738
+ display: grid;
739
+ gap: var(--sb-space-3);
740
+ padding: var(--sb-space-3);
741
+ background: var(--sb-surface);
742
+ border: 1px solid var(--sb-border);
743
+ border-radius: var(--sb-radius-control);
744
+ box-shadow: 0 12px 32px color-mix(in srgb, var(--sb-text) 18%, transparent);
745
+ }
746
+ .share-pop p {
747
+ margin: 0;
748
+ }
749
+ .share-link {
750
+ width: 100%;
751
+ min-width: 0;
752
+ font-size: 12px;
753
+ }
754
+ .share-actions {
755
+ display: flex;
756
+ gap: var(--sb-space-2);
757
+ flex-wrap: wrap;
758
+ }
759
+ .share-targets {
760
+ display: flex;
761
+ gap: var(--sb-space-3);
762
+ flex-wrap: wrap;
763
+ margin: 0;
764
+ padding: 0;
765
+ list-style: none;
766
+ }
767
+ .share-targets a {
768
+ color: var(--sb-accent);
769
+ }
770
+ @media (max-width: 820px) {
771
+ .share-pop {
772
+ position: static;
773
+ width: auto;
774
+ }
775
+ }
776
+ }
777
+
778
+ @layer components {
779
+ .powered-by {
780
+ margin: var(--sb-space-6) 0 0;
781
+ font-size: 12px;
782
+ }
783
+ .powered-by a {
784
+ color: var(--sb-text-muted);
785
+ text-decoration: none;
786
+ }
787
+ .powered-by a:hover {
788
+ color: var(--sb-accent);
789
+ text-decoration: underline;
790
+ }
791
+ }
792
+
793
+ @layer components {
794
+ /* one line, always: the palette gives way before anything wraps out of sight */
795
+ .header {
796
+ flex-wrap: nowrap;
797
+ }
798
+ .palette-btn {
799
+ flex: 1 1 180px;
800
+ min-width: 120px;
801
+ max-width: 360px;
802
+ }
803
+ /* GitHub and Share keep their icons and lose their words where the header is tight */
804
+ @media (max-width: 1360px) {
805
+ .gh-label,
806
+ .share-label {
807
+ display: none;
808
+ }
809
+ }
810
+ .gh-star {
811
+ color: var(--sb-text);
812
+ }
813
+ .share > .btn,
814
+ .gh-star {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ gap: 6px;
818
+ text-decoration: none;
819
+ white-space: nowrap;
820
+ }
821
+ }