@gemboss/ui 0.10.0 → 0.13.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: 4fa51b50e62110c020ab1052fada16e025665f4a
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
@@ -3516,7 +3541,23 @@ html[data-gb-navigating] .gb-route-cover {
3516
3541
  .ui-link:focus-visible,
3517
3542
  .ui-prose :where(a:not([class])):focus-visible { outline: 2px solid var(--ui-focus, #5661f5); outline-offset: 2px; border-radius: 2px; }
3518
3543
 
3519
- .ui-card--media { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
3544
+ .ui-card--media { position: relative; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
3545
+ /* 0.13.0 (README "Collections", card grid): the title is the card's link and its overlay covers the card
3546
+ (`a.ui-card__link`; the picture link is aria-hidden, out of the tab order); `.ui-card__menu` (a ⋯ ui-menu) sits in the
3547
+ picture's corner above it, and the card stops clipping so the open menu is not cut off; `data-ratio="4/3"` for a theme
3548
+ or a page. */
3549
+ .ui-card__link { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; color: var(--ui-text); font-size: var(--ui-fs-body); line-height: 20px; font-weight: 650; text-decoration: none; overflow-wrap: anywhere; }
3550
+ .ui-card__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
3551
+ .ui-card__link:focus-visible { outline: none; }
3552
+ .ui-card--media:has(.ui-card__link) { cursor: pointer; }
3553
+ .ui-card--media:has(.ui-card__link:hover) { border-color: var(--ui-text-muted); }
3554
+ .ui-card--media:has(.ui-card__link:focus-visible) { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
3555
+ .ui-card--media :is(.ui-card__foot button, .ui-card__foot a, .ui-card__body a:not(.ui-card__link), .ui-card__body button, select, input, summary) { position: relative; z-index: 1; }
3556
+ .ui-card__menu { position: absolute; top: 8px; right: 8px; z-index: 2; }
3557
+ .ui-card--media:has(> .ui-card__menu) { overflow: visible; }
3558
+ .ui-card--media:has(> .ui-card__menu) > .ui-card__media { border-radius: inherit; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
3559
+ .ui-card__meta { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); line-height: 16px; }
3560
+ .ui-card--media[data-ratio="4/3"] > .ui-card__media { aspect-ratio: 4 / 3; }
3520
3561
  .ui-card__media {
3521
3562
  display: block; width: 100%; aspect-ratio: 16 / 10; margin: 0; padding: 0; border: 0;
3522
3563
  border-bottom: 1px solid var(--ui-border); background: var(--ui-surface-2); overflow: hidden; color: inherit;
@@ -3541,8 +3582,6 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3541
3582
  </div><div class="ui-modal__actions"><button class="ui-btn">Copy</button></div><button class="ui-modal__close">…` */
3542
3583
  .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
3584
  .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
3585
  /* A popup taller than the screen: the body scrolls, the head and foot stay, so the actions are always reachable
3547
3586
  (320×568 with a long form put Huỷ/Xoá below the fold, unreachable under the locked page; review #3633). */
3548
3587
  .ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
@@ -3578,8 +3617,8 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3578
3617
  dưới cùng (delta + so với gì). */
3579
3618
  .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
3580
3619
  .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); }
3620
+ .ui-metric--lg .ui-metric__val { font-size: 24px; line-height: 32px; } /* Polaris headingXl, as the admin's analytics cards */
3621
+ .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--ui-fs-body); } /* bodyMd 13 (0.12: was 14) */
3583
3622
  .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
3623
  @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
3585
3624
 
@@ -3627,7 +3666,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3627
3666
  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
3667
  .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
3668
  .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; }
3669
+ .ui-funnel__val { font-size: 20px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 24px; } /* Polaris headingLg */
3631
3670
  .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
3632
3671
  .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
3672
  @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
@@ -3642,7 +3681,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3642
3681
  @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
3643
3682
  @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
3644
3683
  .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); }
3684
+ .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
3685
  .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
3647
3686
 
3648
3687
  /* 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 +3931,26 @@ button.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-off
3892
3931
  /* Phone: 11 targets of 44px wrapped into 3-4 rows at 320 (measured). Keep Previous · current · Next; the range wraps under. */
3893
3932
  @media (max-width: 35.9975em) { .ui-pager > a:not([rel]):not([aria-current]):not([aria-disabled]), .ui-pager__gap { display: none; } }
3894
3933
 
3934
+ /* 0.12.0: filter chip and card stack, both drawn by hand on every surface the 05/10/2026 density audit read.
3935
+ Filter chip: `<div class="ui-chips"><button type="button" class="ui-chip" aria-pressed="true">Shopify Plus</button>
3936
+ <a class="ui-chip" href="?country=us" aria-current="page">US</a></div>`. Polaris FilterPill's shape (radius 8, a 1px line,
3937
+ dashed until applied) at the Button's density: 28px, 12px/550, so a filter row and a button row are one height.
3938
+ Applied = `aria-pressed="true"` (in place) or `aria-current="page"` (a URL), solid line and the selected tint. */
3939
+ .ui-chip {
3940
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px; box-sizing: border-box;
3941
+ height: var(--ui-btn-h, 28px); padding: 0 12px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-md);
3942
+ background: var(--ui-surface); color: var(--ui-text); font: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
3943
+ line-height: 16px; white-space: nowrap; text-decoration: none; cursor: pointer;
3944
+ }
3945
+ .ui-chip:hover { border-style: solid; background: var(--ui-surface-2); }
3946
+ .ui-chip:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
3947
+ .ui-chip:is([aria-pressed="true"], [aria-current="page"], [aria-current="true"]) { border-style: solid; border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
3948
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-chip { height: auto; min-height: 44px; min-width: 44px; } }
3949
+ /* Card stack: `<div class="ui-stack"><div class="ui-card">…</div><div class="ui-card">…</div></div>`. 16px between blocks,
3950
+ Polaris BlockStack gap 400, the admin's spacing between cards on a page. `--tight` is 8 (inside a card). */
3951
+ .ui-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ui-space-4); }
3952
+ .ui-stack--tight { gap: var(--ui-space-2); }
3953
+
3895
3954
  /* ═══ @gemboss/ui src/chat.css ═══ */
3896
3955
  /*
3897
3956
  * The chat BODY — what an answer looks like, wherever a GemBoss surface puts a chat (01/10/2026).
@@ -4638,6 +4697,7 @@ dialog.ui-modal.gb-search { width: min(640px, 100%); margin-top: 8vh; max-height
4638
4697
  --gemboss-danger-border: #5c2a24;
4639
4698
  --ui-accent-fg: #0d0d12;
4640
4699
  --ui-warning-fg: #e8c063;
4700
+ --ui-card-shadow: none; /* a dark drop under a dark card is noise */
4641
4701
  --gb-brand-ink: #f2f3f5;
4642
4702
  }
4643
4703
  /* 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.13.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",
@@ -21,6 +21,7 @@
21
21
  "./chat.css": "./src/chat.css",
22
22
  "./chat-composer.css": "./src/chat-composer.css",
23
23
  "./editor.css": "./src/editor.css",
24
+ "./collections.css": "./src/collections.css",
24
25
  "./auth.css": "./src/auth.css",
25
26
  "./kit-plain.css": "./src/kit-plain.css",
26
27
  "./kit-plain.js": "./src/kit-plain.js",
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; }