@gemboss/ui 0.10.0 → 0.12.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/gemboss-ui.css CHANGED
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  * GemBoss admin design system — GENERATED — do not edit; regenerate from gemboss:
3
3
  * node apps/admin/scripts/export-ui-css.mjs --out <path> (npm run export:ui-css)
4
- * gemboss commit: 527a9099c443fa986e3844442da5497d4623e8a3
5
- * sources sha256: cf447622a2b99d887e6fa00ecfbc2c11423bbafafb1273411820018baed1e77e
4
+ * gemboss commit: e4c3bf3203ec143af9abdade7f6c686be85f7de0
5
+ * sources sha256: 9b0060f874885bf4ba4ab9dc6448bc06ae386328bb9bfebcf23bfc6c2671561d
6
6
  * contents: 1 allow-listed rule from apps/admin/src/app/globals.css + packages/ui/src/gemboss-tokens.css
7
7
  * (verbatim) + packages/ui/src/ui.css (verbatim) + packages/ui/src/gemboss-field.css (verbatim)
8
8
  * + 181 rules from packages/ui/src/shell.css (the shared shell)
@@ -96,7 +96,7 @@
96
96
  and reading as "the font keeps shrinking". New rule: pick a step here, never a
97
97
  raw px. Floor is 12px (--fs-caption); 11px (--fs-micro) is reserved for bold
98
98
  micro-badges/count-pills ONLY — nothing below that. Body default is 14px, one
99
- step up from Polaris bodyMd (13px; bodySm is 12) which the app had been leaning on for prose. The kit (ui.css) reads --ui-fs-body, 13px, because it sits beside Polaris components. */
99
+ step up from Polaris bodyMd (13px; bodySm is 12) which the app had been leaning on for prose. The kit (ui.css, kit-plain.css) reads --ui-fs-body, 13px, because it sits beside Polaris components; since 0.12.0 it reads none of body/h1-h3 here (README "Admin density"). */
100
100
  --gemboss-fs-micro: 11px; /* bold count-pills / status chips only */
101
101
  --gemboss-fs-caption: 12px; /* metadata, timestamps, secondary labels */
102
102
  --gemboss-fs-body: 14px; /* default body / reading text */
@@ -181,7 +181,7 @@
181
181
  }
182
182
 
183
183
  /* ═══ ui.css ═══ */
184
- /* ════════════════════════════════════════════════════════════════════════════
184
+ /* ══
185
185
  Tool/App UI kit — the shared primitive layer for the internal operator surfaces
186
186
  (admin console, seller app chrome, theme-editor). ONE semantic token spine
187
187
  (`--ui-*`) that every primitive reads, plus the primitive CSS itself.
@@ -194,7 +194,7 @@
194
194
  The `--ui-*` layer is SEMANTIC and resolved per surface: components only read
195
195
  `--ui-*`; each surface maps those to its own tokens (admin → `--adm-*` below),
196
196
  so one <Button> looks native everywhere without hard-coding a palette.
197
- ════════════════════════════════════════════════════════════════════════════ */
197
+ ══ */
198
198
 
199
199
  :root {
200
200
  --ui-surface: #ffffff;
@@ -229,10 +229,15 @@
229
229
  --ui-space-2: 8px;
230
230
  --ui-space-3: 12px;
231
231
  --ui-space-4: 16px;
232
- /* ONE control height under a mouse: Polaris' TextField/Select (32px). Field, select, button and
233
- segment share it, so a toolbar row is one height (was 32 / 36 / 34 / 36). Under a thumb every
234
- control is 44px (the coarse block below; kit-plain.css adds phone WIDTH for non-admin surfaces). */
232
+ /* Polaris' two heights under a mouse (0.12.0): a FIELD (gemboss-field, ui-select) is TextField/Select, 32px; a BUTTON
233
+ (ui-btn, ui-seg, ui-tabs) is Button, 28px. A toolbar row is 32 + 28, as in the admin. Under a thumb every control is
234
+ 44px (the coarse block below; kit-plain.css adds phone WIDTH for non-admin surfaces). */
235
235
  --ui-control-h: 32px;
236
+ --ui-btn-h: 28px;
237
+ /* Polaris Card (13.9.5): no grey frame of its own, --p-shadow-100 under it. The edge is ONE 1px line in the surface's
238
+ --ui-border (Polaris paints a bevel inset on a ::before; a border cannot be covered by a full-bleed child, and it
239
+ follows a surface's dark --ui-border, which a light bevel would not). */
240
+ --ui-card-shadow: 0 1px 0 rgba(26,26,26,.07);
236
241
  --ui-fs-body: 13px;
237
242
  --ui-fs-caption: 12px;
238
243
  /* Content column width for the seller app shell — ONE source of truth for every wide surface
@@ -288,16 +293,17 @@
288
293
  --ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
289
294
  --ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
290
295
  --ui-focus: var(--adm-accent);
296
+ --ui-card-shadow: none;
291
297
  }
292
298
 
293
- /* ── Button ──────────────────────────────────────────────────────────────────
299
+ /* ── Button ──
294
300
  inline-flex + center so a <button> and an <a> render identically (anchors ignore
295
301
  `height` as inline elements — the root cause of the misaligned link-buttons). */
296
302
  .ui-btn {
297
303
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-space-2);
298
- height: var(--ui-control-h); padding: 0 var(--ui-space-4); box-sizing: border-box;
304
+ height: var(--ui-btn-h); padding: 0 var(--ui-space-3); box-sizing: border-box;
299
305
  line-height: 1; white-space: nowrap; vertical-align: middle;
300
- border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 550;
306
+ border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
301
307
  cursor: pointer; text-decoration: none; user-select: none;
302
308
  border: 1px solid var(--ui-border); background: transparent; color: var(--ui-text);
303
309
  transition: background .12s ease, border-color .12s ease;
@@ -309,33 +315,33 @@
309
315
  .ui-btn--primary:hover { background: var(--ui-accent-hover); border-color: var(--ui-accent-hover); }
310
316
  .ui-btn--ghost { border-color: transparent; background: transparent; color: var(--ui-text-muted); }
311
317
  .ui-btn--ghost:hover { background: var(--ui-surface-2); color: var(--ui-text); }
312
- .ui-btn--sm { height: 28px; padding: 0 var(--ui-space-3); font-size: var(--ui-fs-caption); }
318
+ /* `ui-btn--sm`: the default size under a mouse since 0.12.0; it only marks a button the admin keeps compact under a finger. */
313
319
 
314
- /* ── Card ──────────────────────────────────────────────────────────────────── */
320
+ /* ── Card ── */
315
321
  .ui-card {
316
322
  background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
317
- padding: var(--ui-space-4); color: var(--ui-text);
323
+ box-shadow: var(--ui-card-shadow); padding: var(--ui-space-4); color: var(--ui-text);
318
324
  }
319
- .ui-card__title { font-size: 14px; font-weight: 600; margin: 0 0 var(--ui-space-2); }
325
+ .ui-card__title { font-size: 14px; line-height: 20px; font-weight: 650; margin: 0 0 var(--ui-space-2); }
320
326
  /* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
321
327
  Tiêu đề trái, hành động phải, xuống dòng khi hẹp (01/10/2026: partner-radar rel-head / agr). */
322
328
  .ui-card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ui-space-2); margin-bottom: var(--ui-space-2); }
323
329
  .ui-card__head > .ui-card__title { margin: 0; }
324
330
  .ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
325
331
 
326
- /* ── Select ──────────────────────────────────────────────────────────────────
332
+ /* ── Select ──
327
333
  Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
328
334
  uses the muted token via %-encoding fallback).
329
335
  Its BOX is the text field's (29/09/2026, Chris): same --gemboss-field-* tokens for ground, line and
330
336
  ink, same hover, same focus ring as `.gemboss-field`. Before, a grey --ui-surface-2 select stood next
331
337
  to a bordered white field in the same form (gempress editor, admin forms), and `:focus` removed the
332
338
  outline and only recoloured the border, so a keyboard user saw almost nothing. Height is
333
- --ui-control-h, the button's and the field's, because a select sits in toolbars beside both. */
339
+ --ui-control-h, the field's (Polaris Select, 32): a field, not a button (28, --ui-btn-h). */
334
340
  .ui-select {
335
341
  height: var(--ui-control-h); padding: 0 32px 0 12px; max-width: 100%;
336
342
  background: var(--gemboss-field-bg) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 20 20'><path fill='%239a9aa4' d='M5 7l5 5 5-5z'/></svg>") no-repeat right 10px center;
337
343
  border: 1px solid var(--gemboss-field-line); border-radius: var(--ui-radius-md);
338
- color: var(--gemboss-field-ink); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 500;
344
+ color: var(--gemboss-field-ink); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 450;
339
345
  appearance: none; -webkit-appearance: none; cursor: pointer;
340
346
  }
341
347
  .ui-select:hover:not(:disabled) {
@@ -350,13 +356,13 @@
350
356
  .ui-select:disabled { opacity: .5; cursor: not-allowed; }
351
357
  .ui-select--slim { height: 30px; font-size: var(--ui-fs-caption); }
352
358
 
353
- /* ── Tag / Chip ──────────────────────────────────────────────────────────────
359
+ /* ── Tag / Chip ──
354
360
  Small inline labels. `solid` = filled muted pill (was gemboss-vault-tag); `chip` =
355
361
  bordered mono token (was gemboss-vault-chip). `accent` tone for the highlighted state. */
356
362
  .ui-tag {
357
363
  display: inline-flex; align-items: center; gap: 6px;
358
- font-size: var(--gemboss-fs-micro, 11px); font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase;
359
- padding: 1px 6px; border-radius: 6px; background: var(--ui-surface-2); color: var(--ui-text-muted);
364
+ font-size: var(--ui-fs-caption); line-height: 20px; font-weight: 450;
365
+ padding: 0 6px; border-radius: var(--ui-radius-md); background: var(--ui-surface-2); color: var(--ui-text-muted);
360
366
  }
361
367
  .ui-tag--accent { background: var(--adm-accent-soft, rgba(20,22,26,.05)); color: var(--adm-accent-fg, var(--ui-accent)); }
362
368
  .ui-tag--chip {
@@ -390,13 +396,13 @@
390
396
 
391
397
  /* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
392
398
  Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
393
- .ui-muted { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
399
+ .ui-muted { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
394
400
 
395
401
  /* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ admin
396
- `.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này). Cố ý không đặt margin cho h1
397
- và không dùng flex: admin không reset margin tiêu đề, nên đổi ở đây là đổi khoảng cách của 14 trang admin. */
402
+ `.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này). Không đặt margin cho h1
403
+ và không dùng flex: margin 0 đến từ reset (Polaris ở admin, app-shell.css trong shell của bề mặt từ 0.12.0). */
398
404
  .ui-page-head { margin-bottom: 20px; }
399
- .ui-page-head h1 { color: var(--ui-text-strong); font-size: 22px; line-height: 28px; font-weight: 650; letter-spacing: -0.01em; } /* own line-height: the 20px body line overlapped a wrapped title (0.5.1) */
405
+ .ui-page-head h1 { color: var(--ui-text-strong); font-size: 20px; line-height: 24px; font-weight: 700; letter-spacing: -0.01em; } /* own line-height: the 20px body line overlapped a wrapped title (0.5.1) */
400
406
  .ui-page-head p { color: var(--ui-text-muted); font-size: var(--ui-fs-body); margin-top: 4px; }
401
407
 
402
408
  /* ── Thanh công cụ: `<div class="ui-toolbar">` — ô tìm giãn, điều khiển bám phải, một hàng gọn, xuống dòng khi hẹp.
@@ -414,7 +420,7 @@ a.ui-metric { text-decoration: none; color: inherit; }
414
420
  a.ui-metric:hover { border-color: var(--ui-text-muted); }
415
421
  a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
416
422
  .ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
417
- .ui-metric__lbl { font-size: var(--gemboss-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
423
+ .ui-metric__lbl { font-size: var(--ui-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
418
424
  .ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
419
425
  .ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
420
426
  .ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
@@ -426,7 +432,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
426
432
  Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
427
433
  .ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
428
434
  .ui-dl > div { min-width: 0; }
429
- .ui-dl dt { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
435
+ .ui-dl dt { font-size: var(--ui-fs-caption); color: var(--ui-text-muted); }
430
436
  .ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
431
437
 
432
438
  /* ── Thông báo: `<div class="ui-notice ui-notice--warning">…</div>`. Lỗi tải, cảnh báo cấu hình, việc cần người
@@ -443,22 +449,22 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
443
449
  .ui-pager > a { display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); color: var(--ui-text); text-decoration: none; font-size: var(--ui-fs-body); }
444
450
  .ui-pager > a:hover { background: var(--ui-surface-2); }
445
451
  .ui-pager > a:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
446
- .ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
452
+ .ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
447
453
 
448
- /* ── Badge ────────────────────────────────────────────────────────────────────
449
- Polaris' Badge (02/10/2026; was an uppercase mono pill): 12px/550, padding 2/8, radius 8, the secondary
454
+ /* ── Badge ──
455
+ Polaris' Badge (02/10/2026; was an uppercase mono pill): 12px/450 (its text span; 550 was the box), padding 2/8, radius 8, the secondary
450
456
  fill. The admin's /admin console draws it too (components/ui/Badge.tsx). Tones (`--success|--warning|--critical|
451
457
  --info`, `--dot`) are in kit-plain.css, for the non-React surfaces; the seller app uses Polaris <Badge tone>. */
452
458
  .ui-badge {
453
459
  display: inline-flex; align-items: center; gap: 4px;
454
- font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 550; white-space: nowrap;
460
+ font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; white-space: nowrap;
455
461
  padding: 2px 8px; border-radius: var(--ui-radius-md); background: color-mix(in srgb, var(--ui-text) 8%, transparent); color: var(--ui-text-muted);
456
462
  } /* a tint of the ink, not --ui-surface-2: that is the table row hover, and a badge vanished on hover (0.5.1) */
457
463
 
458
- /* ── Modal ────────────────────────────────────────────────────────────────────
464
+ /* ── Modal ──
459
465
  Rendered INLINE (not portaled): a portal escapes the .gemboss-admin-shell token
460
466
  scope and would render light. Kept in-tree so --ui-* resolves to the surface.
461
- 0.7.3: Polaris Modal's density (#1099, README "Popup density"). Popup only: --ui-control-h stays 32. */
467
+ 0.7.3: Polaris Modal's density (#1099, README "Popup density"). Buttons are 28 everywhere since 0.12.0 (--ui-btn-h). */
462
468
  .ui-overlay {
463
469
  position: fixed; inset: 0; z-index: 500;
464
470
  background: rgba(0, 0, 0, 0.5);
@@ -491,7 +497,6 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
491
497
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
492
498
  padding: 12px 16px; border-top: 1px solid var(--ui-border);
493
499
  }
494
- :is(.ui-modal__head, .ui-modal__foot) .ui-btn { height: 28px; padding: 0 12px; font-size: var(--ui-fs-caption); }
495
500
  /* 44px under a thumb or at phone width; -8px keeps the head 53. */
496
501
  @media (pointer: coarse), (max-width: 47.9975em) {
497
502
  .ui-modal__close { width: 44px; height: 44px; margin: -8px -8px -8px 0; }
@@ -530,15 +535,15 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
530
535
  .ui-select:not(.ui-select--slim) { min-height: 44px; }
531
536
  }
532
537
 
533
- /* ── Segmented control ────────────────────────────────────────────────────────
538
+ /* ── Segmented control ──
534
539
  Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
535
540
  selected segment uses the accent-soft state. */
536
541
  .ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
537
542
  .ui-seg :is(button, a) {
538
- display: inline-grid; place-items: center; width: calc(var(--ui-control-h) - 2px); height: calc(var(--ui-control-h) - 2px);
543
+ display: inline-grid; place-items: center; width: calc(var(--ui-btn-h) - 2px); height: calc(var(--ui-btn-h) - 2px);
539
544
  border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
540
545
  }
541
- .ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
546
+ .ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-caption); font-weight: 550; }
542
547
  .ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
543
548
  .ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
544
549
  .ui-seg :is(button, a):focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
@@ -548,7 +553,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
548
553
  .ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
549
554
  .ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
550
555
 
551
- /* ── Stat chip ────────────────────────────────────────────────────────────────
556
+ /* ── Stat chip ──
552
557
  Count-led pill; clickable ones double as filters (active = accent-soft). */
553
558
  .ui-stats { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: 24px; }
554
559
  .ui-stat {
@@ -563,7 +568,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
563
568
  .ui-stat.is-clickable.active { border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
564
569
  .ui-stat.is-clickable.active b { color: var(--ui-accent-strong); }
565
570
 
566
- /* ── Toast / notification bar ─────────────────────────────────────────────────
571
+ /* ── Toast / notification bar ──
567
572
  App-wide "done / failed" pill for async actions (delete/duplicate/publish…).
568
573
  Polaris-style: dark pill, bottom-center, error variant in critical red. Lives
569
574
  in <ToastProvider> (components/ui/Toast.tsx), portaled to <body>. */
@@ -606,7 +611,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
606
611
  }
607
612
  @media (prefers-reduced-motion: reduce) { .gemboss-toast { animation: none; } }
608
613
 
609
- /* ── The kit's controls under a thumb ─────────────────────────────────────────────────────────
614
+ /* ── The kit's controls under a thumb ──
610
615
  * Measured by hit test on /admin/{theme-kernel,media,style-packs} at 390 and 1366 with a coarse
611
616
  * pointer: segmented buttons 36x36, selects 30-36 tall, clickable stat tiles 29, the asset row's
612
617
  * note button 30x30. §9.1 puts every one of them in the CONTROL tier (44px).
@@ -614,9 +619,11 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
614
619
  * HERE rather than in each surface's stylesheet, because this is the shared primitive layer — the
615
620
  * same reason the kit exists at all. The admin console and the seller app both read it.
616
621
  *
617
- * `pointer: coarse` only: a mouse keeps the density these surfaces were designed at. Growing a box
618
- * for every pointer is what #2147 did to a working width.
622
+ * The block below is `pointer: coarse` only: a mouse at desktop width keeps the density these surfaces were
623
+ * designed at. Growing a box for every pointer is what #2147 did to a working width.
619
624
  */
625
+ /* Phone WIDTH (< 768) with a mouse too (a narrow window): buttons and segments are 44 (0.12.0; at desktop width 28). */
626
+ @media (max-width: 47.9975em) { .ui-btn:not(.ui-btn--sm), .ui-seg :is(button, a) { min-height: 44px; } .ui-seg :is(button, a):not(.is-text) { min-width: 44px; } }
620
627
  @media (pointer: coarse) {
621
628
  /* NOT `.ui-textarea`: it already declares `min-height: 96px` earlier in this file, so adding it to
622
629
  a 44px group LOWERS it — same specificity, later line. The note dialog's box would have gone from
@@ -643,7 +650,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
643
650
  .ui-seg :is(button, a) { min-width: 44px; }
644
651
  }
645
652
 
646
- /* ── Skeleton ─────────────────────────────────────────────────────────────────
653
+ /* ── Skeleton ──
647
654
  Vạch xám giữ chỗ cho nội dung chưa về. Ánh sáng chạy qua là gợi ý "đang chờ", không phải trang
648
655
  trí — nên nó tắt hẳn với `prefers-reduced-motion`, giữ nguyên vạch tĩnh. */
649
656
  .ui-skeleton { display: flex; flex-direction: column; gap: var(--ui-space-2); }
@@ -664,7 +671,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
664
671
 
665
672
  }
666
673
 
667
- /* ── Resizer: the seam you drag between two panels ────────────────────────────
674
+ /* ── Resizer: the seam you drag between two panels ──
668
675
  One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
669
676
  inbox columns too (they adopt it at their next ui:sync). The element is the drag target; its GEOMETRY (a flex item, a grid edge, an absolutely
670
677
  placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
@@ -704,7 +711,7 @@ body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-selec
704
711
  pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
705
712
  body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
706
713
 
707
- /* ── Tabs · Table · Empty state ───────────────────────────────────────────────
714
+ /* ── Tabs · Table · Empty state ──
708
715
  Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
709
716
  file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
710
717
  for these; nothing in apps/admin uses the classes below, and they restyle no existing component.
@@ -712,18 +719,18 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
712
719
 
713
720
  Tabs: `<div class="ui-tabs" role="tablist"><button role="tab" aria-selected="true">…` — the
714
721
  selected state IS `aria-selected`, so what a screen reader announces and what is painted cannot
715
- disagree. 44px under EVERY pointer (§9.1 control tier), not only coarse: tabs sit 4px apart, and
716
- that gap is only acceptable between targets that are already 44px. */
722
+ disagree. Polaris Tabs under a mouse (0.12.0): 28px, 12px/550; 8px apart, the §9.1 gap for targets under 44.
723
+ 44px under a finger or at phone width (the rule after these). */
717
724
  .ui-tabs {
718
- display: flex; gap: var(--ui-space-1); border-bottom: 1px solid var(--ui-border);
725
+ display: flex; gap: var(--ui-space-2); border-bottom: 1px solid var(--ui-border);
719
726
  overflow-x: auto; scrollbar-width: none;
720
727
  }
721
728
  .ui-tabs > button,
722
729
  .ui-tabs > a {
723
730
  position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
724
- min-height: 44px; padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
731
+ min-height: var(--ui-btn-h); padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
725
732
  border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
726
- font-family: inherit; font-size: var(--ui-fs-body); font-weight: 500; white-space: nowrap;
733
+ font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550; white-space: nowrap;
727
734
  color: var(--ui-text-muted);
728
735
  transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
729
736
  }
@@ -734,13 +741,14 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
734
741
  .ui-tabs > a { text-decoration: none; }
735
742
  /* Tab bằng link (mỗi tab là một URL): `<nav class="ui-tabs"><a aria-current="page">` — cùng hình với tab nút. */
736
743
  .ui-tabs > a[aria-current="page"],
737
- .ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); font-weight: 600; }
744
+ .ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); }
738
745
  .ui-tabs > a[aria-current="page"]::after,
739
746
  .ui-tabs > button[aria-selected="true"]::after {
740
747
  content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
741
748
  height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
742
749
  }
743
750
  .ui-tabs > button:disabled { opacity: .5; cursor: not-allowed; }
751
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-tabs > :is(button, a) { min-height: 44px; } }
744
752
 
745
753
  /* Table: wrap it — `<div class="ui-table-wrap"><table class="ui-table">` — so a wide table scrolls
746
754
  inside its card instead of pushing the page sideways at 320px. Numeric cells take `.is-num`:
@@ -748,16 +756,21 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
748
756
  .ui-table-wrap {
749
757
  overflow-x: auto; max-width: 100%;
750
758
  background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
759
+ box-shadow: var(--ui-card-shadow);
751
760
  }
752
761
  .ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-fs-body); color: var(--ui-text); }
753
762
  .ui-table th,
754
763
  .ui-table td {
755
- padding: var(--ui-space-2) var(--ui-space-3); text-align: left; vertical-align: middle;
764
+ padding: var(--ui-space-2) 6px; text-align: left; vertical-align: middle;
756
765
  border-bottom: 1px solid var(--ui-border);
757
766
  }
758
- .ui-table th {
759
- font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); white-space: nowrap;
760
- }
767
+ /* Polaris DataTable (13.9.5, measured on /app/analytics 05/10/2026): cells 8px 6px (a row 36), header 12px 6px (45),
768
+ the first column 12px in from the edge. */
769
+ /* :where() keeps the edge columns at the base rule's weight, so a surface's own `.x-table td { padding }` still wins. */
770
+ .ui-table :is(th, td):where(:first-child) { padding-left: var(--ui-space-3); }
771
+ .ui-table :is(th, td):where(:last-child) { padding-right: var(--ui-space-3); }
772
+ .ui-table th { font-weight: 450; color: var(--ui-text-muted); white-space: nowrap; }
773
+ .ui-table thead th { padding-block: var(--ui-space-3); } /* the column header; a row header (tbody th) is a cell */
761
774
  .ui-table tbody tr:last-child > td { border-bottom: 0; }
762
775
  .ui-table tbody tr:hover { background: var(--ui-surface-2); }
763
776
  .ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
@@ -766,7 +779,7 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
766
779
  /* Link nằm thẳng trong ô (tên bản ghi dẫn sang trang chi tiết) là link trong DANH SÁCH: ≥ 24px (WCAG 2.5.8, §9.1).
767
780
  Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
768
781
  .ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
769
- .ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
782
+ .ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
770
783
 
771
784
  /* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
772
785
  <button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
@@ -775,7 +788,7 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
775
788
  padding: var(--ui-space-4); padding-block: 40px; text-align: center;
776
789
  color: var(--ui-text-muted); font-size: var(--ui-fs-body);
777
790
  }
778
- .ui-empty__title { margin: 0; font-size: var(--gemboss-fs-h3); font-weight: 600; color: var(--ui-text-strong); }
791
+ .ui-empty__title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text-strong); }
779
792
  .ui-empty__body { margin: 0; max-width: 48ch; line-height: 1.5; }
780
793
  .ui-empty > .ui-btn { margin-top: var(--ui-space-2); }
781
794
 
@@ -2040,6 +2053,19 @@ body.gemboss-assistant-unread .gnav-item.gnav-assistant::after {
2040
2053
  /* Headings get their own line (Polaris heading line-heights are ~1.25-1.4): inheriting the body's 20px made a wrapped 22px
2041
2054
  * title overlap itself (gempress, 0.5.1). */
2042
2055
  :where(h1, h2, h3) { letter-spacing: -0.01em; line-height: 1.3; }
2056
+ /* Polaris' heading reset (0.12.0, 05/10/2026): in the admin an <h2> or a <p> with no class is body text with no margin,
2057
+ * because Polaris' global sheet sets `h1…h6, p { margin: 0; font-size: 1em; font-weight: regular }` and every heading
2058
+ * that looks like one is a Text variant. Surfaces had the browser's instead: a bare <h2> at 19.5px bold with 16px margins
2059
+ * sat in GemFactory and taskdesk pages beside kit cards titled 14px. Here, only INSIDE the shell (`.gemboss-platform-
2060
+ * shell`, the AppShell root every surface on the kit renders): a login page or a public page keeps its own type. Never
2061
+ * inside an article the surface shows: `.ui-prose` or `[data-content]` (the contract), and the wrappers the fleet already
2062
+ * uses for rendered HTML, read on 05/10/2026, by exact name (gemcommunity `md-body` and `recap-md`,
2063
+ * content-radar `article-body`, the assistants' `asst-md`; any class containing `prose` or `markdown`, any case, so ProseMirror too). A NEW wrapper: mark it `ui-prose` or
2064
+ * `data-content`. scripts/density.mjs skips exactly this list (ARTICLE); density.test.ts fails if the two differ. A post body keeps the browser's scale. Only an element WITHOUT a class: a surface's `h2.gf-title` that sets only its size keeps the
2065
+ * browser's bold and margins (a zero-specificity author rule still beats the browser's own sheet, review #3787). */
2066
+ :where(.gemboss-platform-shell) :where(h1, h2, h3, h4, h5, h6, p):where(:not([class])):not(:where(.ui-prose, [data-content], [class*="prose" i], [class*="markdown" i], .md-body, .recap-md, .asst-md, .article-body) *) {
2067
+ margin: 0; font-size: 1em; font-weight: inherit; line-height: inherit;
2068
+ }
2043
2069
 
2044
2070
  .gemboss-topbar-start { display: flex; align-items: center; gap: 8px; min-width: 0; }
2045
2071
  .gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
@@ -3449,8 +3475,7 @@ html[data-gb-navigating] .gb-route-cover {
3449
3475
 
3450
3476
  Key-value list: `<dl class="ui-kv"><dt>Owner</dt><dd>Lily</dd>…</dl>` — label column sized to
3451
3477
  its longest label, value column takes the rest and wraps long keys and URLs. */
3452
- .ui-btn--icon { flex: none; width: var(--ui-control-h); padding: 0; }
3453
- .ui-btn--icon.ui-btn--sm { width: 28px; }
3478
+ .ui-btn--icon { flex: none; width: var(--ui-btn-h); padding: 0; }
3454
3479
  .ui-btn--icon svg { width: 18px; height: 18px; }
3455
3480
  /* Menu: the SURFACE is the shell's `gemboss-popover-surface` and the rows are the account menu's row (shell.css
3456
3481
  declares `.gemboss-account-menu-item, .ui-menu__item` together), so a kit menu and the account menu on one page
@@ -3541,8 +3566,6 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3541
3566
  </div><div class="ui-modal__actions"><button class="ui-btn">Copy</button></div><button class="ui-modal__close">…` */
3542
3567
  .ui-modal__subtitle { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; color: var(--ui-text-muted); overflow-wrap: anywhere; }
3543
3568
  .ui-modal__actions { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); }
3544
- /* An icon button in the popup's head or foot is square at the popup's 28 (ui.css gives those buttons 0/12 padding). */
3545
- :is(.ui-modal__head, .ui-modal__foot) .ui-btn--icon { width: 28px; padding: 0; }
3546
3569
  /* A popup taller than the screen: the body scrolls, the head and foot stay, so the actions are always reachable
3547
3570
  (320×568 with a long form put Huỷ/Xoá below the fold, unreachable under the locked page; review #3633). */
3548
3571
  .ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
@@ -3578,8 +3601,8 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3578
3601
  dưới cùng (delta + so với gì). */
3579
3602
  .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
3580
3603
  .ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
3581
- .ui-metric--lg .ui-metric__val { font-size: var(--gemboss-fs-h1, 24px); line-height: 1.1; }
3582
- .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--gemboss-fs-body, 14px); }
3604
+ .ui-metric--lg .ui-metric__val { font-size: 24px; line-height: 32px; } /* Polaris headingXl, as the admin's analytics cards */
3605
+ .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--ui-fs-body); } /* bodyMd 13 (0.12: was 14) */
3583
3606
  .ui-metric__foot { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
3584
3607
  @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
3585
3608
 
@@ -3627,7 +3650,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3627
3650
  sau bước đầu mang `__rate` = tỉ lệ đi tiếp từ bước NGAY TRƯỚC (bề mặt tính). Ngang ở khổ rộng, dọc ở ≤ 520px. */
3628
3651
  .ui-funnel { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--ui-space-2); margin: 0; padding: 0; list-style: none; }
3629
3652
  .ui-funnel > li { position: relative; display: flex; flex-direction: column; gap: 2px; padding: var(--ui-space-3); border-radius: var(--ui-radius-md); background: var(--ui-surface-2); min-width: 0; }
3630
- .ui-funnel__val { font-size: var(--gemboss-fs-h2, 20px); font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
3653
+ .ui-funnel__val { font-size: 20px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 24px; } /* Polaris headingLg */
3631
3654
  .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
3632
3655
  .ui-funnel__rate { order: -1; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-series-1); font-variant-numeric: tabular-nums; }
3633
3656
  @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
@@ -3642,7 +3665,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3642
3665
  @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
3643
3666
  @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
3644
3667
  .ui-section-head { margin: var(--ui-space-4) 0 0; }
3645
- .ui-section-head > h2 { margin: 0; font-size: var(--gemboss-fs-h3, 16px); font-weight: 650; color: var(--ui-text); }
3668
+ .ui-section-head > h2 { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text); } /* Polaris headingMd (0.12: was 16) */
3646
3669
  .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
3647
3670
 
3648
3671
  /* Nhóm nút CHỮ không bao giờ bẻ chữ (03/10/2026, thanh lọc của gemnews ở 390px: "Mọi ngôn ngữ" thành hai dòng, "中文"
@@ -3892,6 +3915,26 @@ button.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-off
3892
3915
  /* Phone: 11 targets of 44px wrapped into 3-4 rows at 320 (measured). Keep Previous · current · Next; the range wraps under. */
3893
3916
  @media (max-width: 35.9975em) { .ui-pager > a:not([rel]):not([aria-current]):not([aria-disabled]), .ui-pager__gap { display: none; } }
3894
3917
 
3918
+ /* 0.12.0: filter chip and card stack, both drawn by hand on every surface the 05/10/2026 density audit read.
3919
+ Filter chip: `<div class="ui-chips"><button type="button" class="ui-chip" aria-pressed="true">Shopify Plus</button>
3920
+ <a class="ui-chip" href="?country=us" aria-current="page">US</a></div>`. Polaris FilterPill's shape (radius 8, a 1px line,
3921
+ dashed until applied) at the Button's density: 28px, 12px/550, so a filter row and a button row are one height.
3922
+ Applied = `aria-pressed="true"` (in place) or `aria-current="page"` (a URL), solid line and the selected tint. */
3923
+ .ui-chip {
3924
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px; box-sizing: border-box;
3925
+ height: var(--ui-btn-h, 28px); padding: 0 12px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-md);
3926
+ background: var(--ui-surface); color: var(--ui-text); font: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
3927
+ line-height: 16px; white-space: nowrap; text-decoration: none; cursor: pointer;
3928
+ }
3929
+ .ui-chip:hover { border-style: solid; background: var(--ui-surface-2); }
3930
+ .ui-chip:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
3931
+ .ui-chip:is([aria-pressed="true"], [aria-current="page"]) { border-style: solid; border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
3932
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-chip { height: auto; min-height: 44px; min-width: 44px; } }
3933
+ /* Card stack: `<div class="ui-stack"><div class="ui-card">…</div><div class="ui-card">…</div></div>`. 16px between blocks,
3934
+ Polaris BlockStack gap 400, the admin's spacing between cards on a page. `--tight` is 8 (inside a card). */
3935
+ .ui-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ui-space-4); }
3936
+ .ui-stack--tight { gap: var(--ui-space-2); }
3937
+
3895
3938
  /* ═══ @gemboss/ui src/chat.css ═══ */
3896
3939
  /*
3897
3940
  * The chat BODY — what an answer looks like, wherever a GemBoss surface puts a chat (01/10/2026).
@@ -4638,6 +4681,7 @@ dialog.ui-modal.gb-search { width: min(640px, 100%); margin-top: 8vh; max-height
4638
4681
  --gemboss-danger-border: #5c2a24;
4639
4682
  --ui-accent-fg: #0d0d12;
4640
4683
  --ui-warning-fg: #e8c063;
4684
+ --ui-card-shadow: none; /* a dark drop under a dark card is noise */
4641
4685
  --gb-brand-ink: #f2f3f5;
4642
4686
  }
4643
4687
  /* Avatar initials in dark: the tone's light end, so the text clears 4.5:1 on its tinted disc. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gemboss/ui",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "GemBoss design system for every surface in the fleet: the admin shell (topbar + rail + drawer) as React components, and the admin stylesheet exported as one CSS file.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/src/app-shell.css CHANGED
@@ -26,6 +26,19 @@
26
26
  /* Headings get their own line (Polaris heading line-heights are ~1.25-1.4): inheriting the body's 20px made a wrapped 22px
27
27
  * title overlap itself (gempress, 0.5.1). */
28
28
  :where(h1, h2, h3) { letter-spacing: -0.01em; line-height: 1.3; }
29
+ /* Polaris' heading reset (0.12.0, 05/10/2026): in the admin an <h2> or a <p> with no class is body text with no margin,
30
+ * because Polaris' global sheet sets `h1…h6, p { margin: 0; font-size: 1em; font-weight: regular }` and every heading
31
+ * that looks like one is a Text variant. Surfaces had the browser's instead: a bare <h2> at 19.5px bold with 16px margins
32
+ * sat in GemFactory and taskdesk pages beside kit cards titled 14px. Here, only INSIDE the shell (`.gemboss-platform-
33
+ * shell`, the AppShell root every surface on the kit renders): a login page or a public page keeps its own type. Never
34
+ * inside an article the surface shows: `.ui-prose` or `[data-content]` (the contract), and the wrappers the fleet already
35
+ * uses for rendered HTML, read on 05/10/2026, by exact name (gemcommunity `md-body` and `recap-md`,
36
+ * content-radar `article-body`, the assistants' `asst-md`; any class containing `prose` or `markdown`, any case, so ProseMirror too). A NEW wrapper: mark it `ui-prose` or
37
+ * `data-content`. scripts/density.mjs skips exactly this list (ARTICLE); density.test.ts fails if the two differ. A post body keeps the browser's scale. Only an element WITHOUT a class: a surface's `h2.gf-title` that sets only its size keeps the
38
+ * browser's bold and margins (a zero-specificity author rule still beats the browser's own sheet, review #3787). */
39
+ :where(.gemboss-platform-shell) :where(h1, h2, h3, h4, h5, h6, p):where(:not([class])):not(:where(.ui-prose, [data-content], [class*="prose" i], [class*="markdown" i], .md-body, .recap-md, .asst-md, .article-body) *) {
40
+ margin: 0; font-size: 1em; font-weight: inherit; line-height: inherit;
41
+ }
29
42
 
30
43
  .gemboss-topbar-start { display: flex; align-items: center; gap: 8px; min-width: 0; }
31
44
  .gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
package/src/editor.css CHANGED
@@ -28,6 +28,9 @@
28
28
  @keyframes gemboss-editor-enter { from { opacity: 0; } to { opacity: 1; } }
29
29
  @keyframes gemboss-editor-toast-in { from { opacity: 0; transform: translate(-50%, -8px); } to { opacity: 1; transform: translate(-50%, 0); } }
30
30
  @keyframes gemboss-pagepicker-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
31
+ /* The top bar's menus (⋯ More, Publish ▾). Named by `.gemboss-editor-menu` since before 0.9.0 but never defined, so they
32
+ opened with no motion while the page picker beside them slid in: the same entry as the picker's. */
33
+ @keyframes gemboss-flyout-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
31
34
  @keyframes gemboss-statusbar-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
32
35
 
33
36
  /* Editor is position:fixed and self-contained, so main needs no height/padding —
@@ -206,8 +209,11 @@
206
209
  z-index: 500; /* above the editor (z-index 400) */
207
210
  min-width: 210px;
208
211
  padding: 6px;
209
- animation: gemboss-flyout-in var(--gemboss-transition-fast) ease both;
212
+ /* No fill mode: once it has landed the menu holds no animated value (a `both` fill left an identity transform on it). */
213
+ animation: gemboss-flyout-in var(--gemboss-transition-fast) ease;
210
214
  }
215
+ @media (prefers-reduced-motion: reduce) {
216
+ .gemboss-editor-menu { animation: none; } }
211
217
  .gemboss-editor-menu-item {
212
218
  display: flex;
213
219
  align-items: center;
@@ -244,9 +250,9 @@
244
250
  and frame tightly and the preview's top gap matches its gap to the chat sidebar. */
245
251
  .gemboss-editor-body { flex: 1; min-height: 0; display: flex; gap: 0; padding: var(--gemboss-editor-inset); box-sizing: border-box; }
246
252
  /* Both panels become rounded, bordered cards (design-system surface). The preview's direct
247
- child is the .gemboss-editor-preview-pane wrapper (it positions the section-outline overlay),
253
+ child is the .gemboss-editor-preview-pane wrapper (it positions the overlays: Global styles, the score panel),
248
254
  so the card styling lives on the WRAPPER — `overflow: hidden` also clips the iframe + the
249
- outline panel to the rounded corners. (Targeting .gemboss-pv here would miss: it's now a
255
+ overlays to the rounded corners. (Targeting .gemboss-pv here would miss: it's now a
250
256
  grandchild inside the wrapper.) */
251
257
  .gemboss-editor-body > .gemboss-ec,
252
258
  .gemboss-editor-body > .gemboss-editor-preview-pane {
@@ -260,19 +266,15 @@
260
266
  never reached the screen and the theme editor's copy of the row carried no class at all. The look below is that
261
267
  inline style, written as CSS with the same tokens: the same computed values, one owner.
262
268
  The LOOK hangs on the row (`.gemboss-ec-tabs > [role="tab"]`); `.gemboss-ec-tab` is what the pointer blocks size
263
- (44px under touch here, the 44px overlay under a mouse in chat.css). The theme editor's tabs wear the look but not
264
- `.gemboss-ec-tab` yet, so they stay 36px under touch: adding it is a visible change, left for its own decision. */
265
- /* OFF THE SPACE SCALE, KNOWINGLY: the row's 10px sides and the tab's 7px padding are the inline style's numbers, carried
266
- unchanged so the admin does not move a pixel (the typography gate never read them while they were inline strings).
267
- Snapping to the scale (12px, 8px) makes the row 4px wider inside and each tab 2px taller: a visible change, left to
268
- its own decision. Named here so it is one edit, not a hunt. */
269
+ (44px under touch here, the 44px overlay under a mouse in chat.css). Every editor's tabs wear it: the theme editor's
270
+ did not until 05/10/2026 and stayed 36px under touch while the page and section editors' were 44.
271
+ Spacing is the scale (`--ui-space-*`, ui.css): the inline style's 10px row sides and 7px tab padding became 12 and 8
272
+ on 05/10/2026, so under a mouse each tab is 2px taller and the row 4px narrower inside. */
269
273
  .gemboss-ec-tabs {
270
- --gemboss-ec-tabs-pad-x: 10px;
271
- --gemboss-ec-tab-pad: 7px;
272
- display: flex; gap: 4px; padding: 8px var(--gemboss-ec-tabs-pad-x); border-bottom: 1px solid var(--gemboss-divider); flex: none;
274
+ display: flex; gap: var(--ui-space-1); padding: var(--ui-space-2) var(--ui-space-3); border-bottom: 1px solid var(--gemboss-divider); flex: none;
273
275
  }
274
276
  .gemboss-ec-tabs > [role="tab"] {
275
- flex: 1; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; padding: var(--gemboss-ec-tab-pad); border-radius: 8px;
277
+ flex: 1; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; padding: var(--ui-space-2); border-radius: 8px;
276
278
  display: flex; align-items: center; justify-content: center; gap: 6px;
277
279
  border: 1px solid transparent; background: transparent; color: var(--gemboss-text-muted); box-shadow: none;
278
280
  }
@@ -754,10 +756,11 @@ body.ui-resizing .gemboss-editor-body .gemboss-pv { will-change: flex-basis; }
754
756
  .gemboss-pageedit-scope{display:none}}
755
757
  .gemboss-pageedit-pv{flex:1;min-width:0;background:var(--gemboss-preview-canvas);display:flex;justify-content:center;overflow:auto;position:relative}
756
758
  .gemboss-pageedit-pv .gemboss-pv-frame iframe{width:100%;height:100%;border:0;background:#fff;display:block}
757
- .gemboss-pageedit-chip{display:flex;align-items:center;gap:8px;margin:0 12px 8px;padding:6px 12px;border-radius:8px;background:rgba(86,97,245,.1);border:1px solid rgba(86,97,245,.3);font-size:12px;color:#3a44de}
759
+ /* Neutral like every other selected thing in the admin (`--gemboss-active-*`), not iris: iris is for focus and links. */
760
+ .gemboss-pageedit-chip{display:flex;align-items:center;gap:8px;margin:0 12px 8px;padding:6px 12px;border-radius:8px;background:var(--gemboss-active-bg);border:1px solid var(--gemboss-divider);font-size:12px;color:var(--gemboss-active-fg)}
758
761
  .gemboss-pageedit-chip strong{text-transform:capitalize}
759
- .gemboss-pageedit-chip button{margin-left:auto;border:0;background:transparent;color:#3a44de;cursor:pointer;font-size:12px}
760
- /* Copy a winning test shipped onto this page. Amber, not the editor's indigo: this is not a mode the
762
+ .gemboss-pageedit-chip button{margin-left:auto;border:0;background:transparent;color:var(--gemboss-active-fg);cursor:pointer;font-size:12px}
763
+ /* Copy a winning test shipped onto this page. Amber, not the selection chip's neutral: this is not a mode the
761
764
  seller turned on, it is a difference between what they are reading and what shoppers are served. */
762
765
  .gemboss-pageedit-standing{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 16px;background:rgba(176,125,24,.09);border-bottom:1px solid rgba(176,125,24,.28);font-size:13px;line-height:1.5;color:#7a5710}
763
766
  .gemboss-pageedit-standing code{font-size:12px;background:rgba(176,125,24,.14);border-radius:4px;padding:2px 4px}
@@ -771,7 +774,7 @@ body.ui-resizing .gemboss-editor-body .gemboss-pv { will-change: flex-basis; }
771
774
  .gemboss-layers-list{flex:1;min-height:0;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:4px}
772
775
  .gemboss-layer{display:flex;align-items:center;gap:4px;border:1px solid transparent;border-radius:8px;padding:0 4px 0 6px}
773
776
  .gemboss-layer:hover{background:var(--gemboss-hover,rgba(0,0,0,.04))}
774
- .gemboss-layer.is-sel{border-color:var(--gemboss-primary, #1b1c1e);background:rgba(86,97,245,.06)}
777
+ .gemboss-layer.is-sel{border-color:var(--gemboss-primary, #1b1c1e);background:var(--gemboss-active-bg)}
775
778
  .gemboss-layer-main{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:1px;background:transparent;border:0;cursor:pointer;text-align:left;padding:8px 4px}
776
779
  .gemboss-layer-type{font-size:13px;font-weight:600;color:var(--gemboss-text,#1b1c1e);text-transform:capitalize}
777
780
  .gemboss-layer-head{font-size:var(--gemboss-fs-caption);color:var(--gemboss-text-muted,#686b71);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:195px}
@@ -1043,11 +1046,11 @@ select.gemboss-ei-inp{appearance:none;background-image:linear-gradient(45deg,tra
1043
1046
  .gemboss-guest-signin { font-size: var(--gemboss-fs-body); font-weight: 600; color: var(--gemboss-text-muted); text-decoration: none; }
1044
1047
  .gemboss-guest-signin:hover { color: var(--gemboss-text); }
1045
1048
  .gemboss-guest-keep {
1046
- border: 0; border-radius: 999px; background: #5661f5; color: #fff; cursor: pointer;
1049
+ border: 0; border-radius: 999px; background: var(--gemboss-primary); color: var(--gemboss-on-primary); cursor: pointer;
1047
1050
  font: inherit; font-size: var(--gemboss-fs-caption); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
1048
1051
  padding: 8px 20px; white-space: nowrap;
1049
1052
  }
1050
- .gemboss-guest-keep:hover { background: #3a44de; }
1053
+ .gemboss-guest-keep:hover { background: var(--gemboss-primary-dark); }
1051
1054
  @media (max-width: 720px) {
1052
1055
  .gemboss-guest-note { display: none; }
1053
1056
  }