@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.
@@ -79,7 +79,7 @@
79
79
  and reading as "the font keeps shrinking". New rule: pick a step here, never a
80
80
  raw px. Floor is 12px (--fs-caption); 11px (--fs-micro) is reserved for bold
81
81
  micro-badges/count-pills ONLY — nothing below that. Body default is 14px, one
82
- 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. */
82
+ 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"). */
83
83
  --gemboss-fs-micro: 11px; /* bold count-pills / status chips only */
84
84
  --gemboss-fs-caption: 12px; /* metadata, timestamps, secondary labels */
85
85
  --gemboss-fs-body: 14px; /* default body / reading text */
package/src/kit-plain.css CHANGED
@@ -22,8 +22,7 @@
22
22
 
23
23
  Key-value list: `<dl class="ui-kv"><dt>Owner</dt><dd>Lily</dd>…</dl>` — label column sized to
24
24
  its longest label, value column takes the rest and wraps long keys and URLs. */
25
- .ui-btn--icon { flex: none; width: var(--ui-control-h); padding: 0; }
26
- .ui-btn--icon.ui-btn--sm { width: 28px; }
25
+ .ui-btn--icon { flex: none; width: var(--ui-btn-h); padding: 0; }
27
26
  .ui-btn--icon svg { width: 18px; height: 18px; }
28
27
  /* Menu: the SURFACE is the shell's `gemboss-popover-surface` and the rows are the account menu's row (shell.css
29
28
  declares `.gemboss-account-menu-item, .ui-menu__item` together), so a kit menu and the account menu on one page
@@ -114,8 +113,6 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
114
113
  </div><div class="ui-modal__actions"><button class="ui-btn">Copy</button></div><button class="ui-modal__close">…` */
115
114
  .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; }
116
115
  .ui-modal__actions { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); }
117
- /* 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). */
118
- :is(.ui-modal__head, .ui-modal__foot) .ui-btn--icon { width: 28px; padding: 0; }
119
116
  /* A popup taller than the screen: the body scrolls, the head and foot stay, so the actions are always reachable
120
117
  (320×568 with a long form put Huỷ/Xoá below the fold, unreachable under the locked page; review #3633). */
121
118
  .ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
@@ -151,8 +148,8 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
151
148
  dưới cùng (delta + so với gì). */
152
149
  .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
153
150
  .ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
154
- .ui-metric--lg .ui-metric__val { font-size: var(--gemboss-fs-h1, 24px); line-height: 1.1; }
155
- .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--gemboss-fs-body, 14px); }
151
+ .ui-metric--lg .ui-metric__val { font-size: 24px; line-height: 32px; } /* Polaris headingXl, as the admin's analytics cards */
152
+ .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--ui-fs-body); } /* bodyMd 13 (0.12: was 14) */
156
153
  .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); }
157
154
  @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
158
155
 
@@ -200,7 +197,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
200
197
  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. */
201
198
  .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; }
202
199
  .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; }
203
- .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; }
200
+ .ui-funnel__val { font-size: 20px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 24px; } /* Polaris headingLg */
204
201
  .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
205
202
  .ui-funnel__rate { order: -1; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-series-1); font-variant-numeric: tabular-nums; }
206
203
  @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
@@ -215,7 +212,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
215
212
  @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
216
213
  @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
217
214
  .ui-section-head { margin: var(--ui-space-4) 0 0; }
218
- .ui-section-head > h2 { margin: 0; font-size: var(--gemboss-fs-h3, 16px); font-weight: 650; color: var(--ui-text); }
215
+ .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) */
219
216
  .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
220
217
 
221
218
  /* 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, "中文"
@@ -464,3 +461,23 @@ button.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-off
464
461
  @media (pointer: coarse), (max-width: 47.9975em) { .ui-pager > a, .ui-pager > [aria-disabled="true"] { min-width: 44px; min-height: 44px; } }
465
462
  /* Phone: 11 targets of 44px wrapped into 3-4 rows at 320 (measured). Keep Previous · current · Next; the range wraps under. */
466
463
  @media (max-width: 35.9975em) { .ui-pager > a:not([rel]):not([aria-current]):not([aria-disabled]), .ui-pager__gap { display: none; } }
464
+
465
+ /* 0.12.0: filter chip and card stack, both drawn by hand on every surface the 05/10/2026 density audit read.
466
+ Filter chip: `<div class="ui-chips"><button type="button" class="ui-chip" aria-pressed="true">Shopify Plus</button>
467
+ <a class="ui-chip" href="?country=us" aria-current="page">US</a></div>`. Polaris FilterPill's shape (radius 8, a 1px line,
468
+ dashed until applied) at the Button's density: 28px, 12px/550, so a filter row and a button row are one height.
469
+ Applied = `aria-pressed="true"` (in place) or `aria-current="page"` (a URL), solid line and the selected tint. */
470
+ .ui-chip {
471
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px; box-sizing: border-box;
472
+ height: var(--ui-btn-h, 28px); padding: 0 12px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-md);
473
+ background: var(--ui-surface); color: var(--ui-text); font: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
474
+ line-height: 16px; white-space: nowrap; text-decoration: none; cursor: pointer;
475
+ }
476
+ .ui-chip:hover { border-style: solid; background: var(--ui-surface-2); }
477
+ .ui-chip:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
478
+ .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); }
479
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-chip { height: auto; min-height: 44px; min-width: 44px; } }
480
+ /* Card stack: `<div class="ui-stack"><div class="ui-card">…</div><div class="ui-card">…</div></div>`. 16px between blocks,
481
+ Polaris BlockStack gap 400, the admin's spacing between cards on a page. `--tight` is 8 (inside a card). */
482
+ .ui-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ui-space-4); }
483
+ .ui-stack--tight { gap: var(--ui-space-2); }
@@ -56,6 +56,7 @@
56
56
  --gemboss-danger-border: #5c2a24;
57
57
  --ui-accent-fg: #0d0d12;
58
58
  --ui-warning-fg: #e8c063;
59
+ --ui-card-shadow: none; /* a dark drop under a dark card is noise */
59
60
  --gb-brand-ink: #f2f3f5;
60
61
  }
61
62
  /* Avatar initials in dark: the tone's light end, so the text clears 4.5:1 on its tinted disc. */
package/src/ui.css CHANGED
@@ -1,4 +1,4 @@
1
- /* ════════════════════════════════════════════════════════════════════════════
1
+ /* ══
2
2
  Tool/App UI kit — the shared primitive layer for the internal operator surfaces
3
3
  (admin console, seller app chrome, theme-editor). ONE semantic token spine
4
4
  (`--ui-*`) that every primitive reads, plus the primitive CSS itself.
@@ -11,7 +11,7 @@
11
11
  The `--ui-*` layer is SEMANTIC and resolved per surface: components only read
12
12
  `--ui-*`; each surface maps those to its own tokens (admin → `--adm-*` below),
13
13
  so one <Button> looks native everywhere without hard-coding a palette.
14
- ════════════════════════════════════════════════════════════════════════════ */
14
+ ══ */
15
15
 
16
16
  :root {
17
17
  --ui-surface: #ffffff;
@@ -46,10 +46,15 @@
46
46
  --ui-space-2: 8px;
47
47
  --ui-space-3: 12px;
48
48
  --ui-space-4: 16px;
49
- /* ONE control height under a mouse: Polaris' TextField/Select (32px). Field, select, button and
50
- segment share it, so a toolbar row is one height (was 32 / 36 / 34 / 36). Under a thumb every
51
- control is 44px (the coarse block below; kit-plain.css adds phone WIDTH for non-admin surfaces). */
49
+ /* Polaris' two heights under a mouse (0.12.0): a FIELD (gemboss-field, ui-select) is TextField/Select, 32px; a BUTTON
50
+ (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
51
+ 44px (the coarse block below; kit-plain.css adds phone WIDTH for non-admin surfaces). */
52
52
  --ui-control-h: 32px;
53
+ --ui-btn-h: 28px;
54
+ /* 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
55
+ --ui-border (Polaris paints a bevel inset on a ::before; a border cannot be covered by a full-bleed child, and it
56
+ follows a surface's dark --ui-border, which a light bevel would not). */
57
+ --ui-card-shadow: 0 1px 0 rgba(26,26,26,.07);
53
58
  --ui-fs-body: 13px;
54
59
  --ui-fs-caption: 12px;
55
60
  /* Content column width for the seller app shell — ONE source of truth for every wide surface
@@ -105,16 +110,17 @@
105
110
  --ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
106
111
  --ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
107
112
  --ui-focus: var(--adm-accent);
113
+ --ui-card-shadow: none;
108
114
  }
109
115
 
110
- /* ── Button ──────────────────────────────────────────────────────────────────
116
+ /* ── Button ──
111
117
  inline-flex + center so a <button> and an <a> render identically (anchors ignore
112
118
  `height` as inline elements — the root cause of the misaligned link-buttons). */
113
119
  .ui-btn {
114
120
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-space-2);
115
- height: var(--ui-control-h); padding: 0 var(--ui-space-4); box-sizing: border-box;
121
+ height: var(--ui-btn-h); padding: 0 var(--ui-space-3); box-sizing: border-box;
116
122
  line-height: 1; white-space: nowrap; vertical-align: middle;
117
- border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 550;
123
+ border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
118
124
  cursor: pointer; text-decoration: none; user-select: none;
119
125
  border: 1px solid var(--ui-border); background: transparent; color: var(--ui-text);
120
126
  transition: background .12s ease, border-color .12s ease;
@@ -126,33 +132,33 @@
126
132
  .ui-btn--primary:hover { background: var(--ui-accent-hover); border-color: var(--ui-accent-hover); }
127
133
  .ui-btn--ghost { border-color: transparent; background: transparent; color: var(--ui-text-muted); }
128
134
  .ui-btn--ghost:hover { background: var(--ui-surface-2); color: var(--ui-text); }
129
- .ui-btn--sm { height: 28px; padding: 0 var(--ui-space-3); font-size: var(--ui-fs-caption); }
135
+ /* `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. */
130
136
 
131
- /* ── Card ──────────────────────────────────────────────────────────────────── */
137
+ /* ── Card ── */
132
138
  .ui-card {
133
139
  background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
134
- padding: var(--ui-space-4); color: var(--ui-text);
140
+ box-shadow: var(--ui-card-shadow); padding: var(--ui-space-4); color: var(--ui-text);
135
141
  }
136
- .ui-card__title { font-size: 14px; font-weight: 600; margin: 0 0 var(--ui-space-2); }
142
+ .ui-card__title { font-size: 14px; line-height: 20px; font-weight: 650; margin: 0 0 var(--ui-space-2); }
137
143
  /* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
138
144
  Tiêu đề trái, hành động phải, xuống dòng khi hẹp (01/10/2026: partner-radar rel-head / agr). */
139
145
  .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); }
140
146
  .ui-card__head > .ui-card__title { margin: 0; }
141
147
  .ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
142
148
 
143
- /* ── Select ──────────────────────────────────────────────────────────────────
149
+ /* ── Select ──
144
150
  Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
145
151
  uses the muted token via %-encoding fallback).
146
152
  Its BOX is the text field's (29/09/2026, Chris): same --gemboss-field-* tokens for ground, line and
147
153
  ink, same hover, same focus ring as `.gemboss-field`. Before, a grey --ui-surface-2 select stood next
148
154
  to a bordered white field in the same form (gempress editor, admin forms), and `:focus` removed the
149
155
  outline and only recoloured the border, so a keyboard user saw almost nothing. Height is
150
- --ui-control-h, the button's and the field's, because a select sits in toolbars beside both. */
156
+ --ui-control-h, the field's (Polaris Select, 32): a field, not a button (28, --ui-btn-h). */
151
157
  .ui-select {
152
158
  height: var(--ui-control-h); padding: 0 32px 0 12px; max-width: 100%;
153
159
  background: var(--p-color-input-bg-surface, 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;
154
160
  border: 1px solid var(--p-color-input-border, var(--gemboss-field-line)); border-radius: var(--ui-radius-md);
155
- color: var(--p-color-text, var(--gemboss-field-ink)); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 500;
161
+ color: var(--p-color-text, var(--gemboss-field-ink)); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 450;
156
162
  appearance: none; -webkit-appearance: none; cursor: pointer;
157
163
  }
158
164
  .ui-select:hover:not(:disabled) {
@@ -167,13 +173,13 @@
167
173
  .ui-select:disabled { opacity: .5; cursor: not-allowed; }
168
174
  .ui-select--slim { height: 30px; font-size: var(--ui-fs-caption); }
169
175
 
170
- /* ── Tag / Chip ──────────────────────────────────────────────────────────────
176
+ /* ── Tag / Chip ──
171
177
  Small inline labels. `solid` = filled muted pill (was gemboss-vault-tag); `chip` =
172
178
  bordered mono token (was gemboss-vault-chip). `accent` tone for the highlighted state. */
173
179
  .ui-tag {
174
180
  display: inline-flex; align-items: center; gap: 6px;
175
- font-size: var(--gemboss-fs-micro, 11px); font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase;
176
- padding: 1px 6px; border-radius: 6px; background: var(--ui-surface-2); color: var(--ui-text-muted);
181
+ font-size: var(--ui-fs-caption); line-height: 20px; font-weight: 450;
182
+ padding: 0 6px; border-radius: var(--ui-radius-md); background: var(--ui-surface-2); color: var(--ui-text-muted);
177
183
  }
178
184
  .ui-tag--accent { background: var(--adm-accent-soft, rgba(20,22,26,.05)); color: var(--adm-accent-fg, var(--ui-accent)); }
179
185
  .ui-tag--chip {
@@ -207,13 +213,13 @@
207
213
 
208
214
  /* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
209
215
  Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
210
- .ui-muted { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
216
+ .ui-muted { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
211
217
 
212
218
  /* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ admin
213
- `.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
214
- 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. */
219
+ `.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
220
+ 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). */
215
221
  .ui-page-head { margin-bottom: 20px; }
216
- .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) */
222
+ .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) */
217
223
  .ui-page-head p { color: var(--ui-text-muted); font-size: var(--ui-fs-body); margin-top: 4px; }
218
224
 
219
225
  /* ── 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.
@@ -231,7 +237,7 @@ a.ui-metric { text-decoration: none; color: inherit; }
231
237
  a.ui-metric:hover { border-color: var(--ui-text-muted); }
232
238
  a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
233
239
  .ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
234
- .ui-metric__lbl { font-size: var(--gemboss-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
240
+ .ui-metric__lbl { font-size: var(--ui-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
235
241
  .ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
236
242
  .ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
237
243
  .ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
@@ -243,7 +249,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
243
249
  Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
244
250
  .ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
245
251
  .ui-dl > div { min-width: 0; }
246
- .ui-dl dt { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
252
+ .ui-dl dt { font-size: var(--ui-fs-caption); color: var(--ui-text-muted); }
247
253
  .ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
248
254
 
249
255
  /* ── 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
@@ -260,22 +266,22 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
260
266
  .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); }
261
267
  .ui-pager > a:hover { background: var(--ui-surface-2); }
262
268
  .ui-pager > a:focus-visible { outline: 2px solid var(--p-color-border-focus, var(--ui-focus)); outline-offset: 1px; }
263
- .ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
269
+ .ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
264
270
 
265
- /* ── Badge ────────────────────────────────────────────────────────────────────
266
- Polaris' Badge (02/10/2026; was an uppercase mono pill): 12px/550, padding 2/8, radius 8, the secondary
271
+ /* ── Badge ──
272
+ 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
267
273
  fill. The admin's /admin console draws it too (components/ui/Badge.tsx). Tones (`--success|--warning|--critical|
268
274
  --info`, `--dot`) are in kit-plain.css, for the non-React surfaces; the seller app uses Polaris <Badge tone>. */
269
275
  .ui-badge {
270
276
  display: inline-flex; align-items: center; gap: 4px;
271
- font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 550; white-space: nowrap;
277
+ font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; white-space: nowrap;
272
278
  padding: 2px 8px; border-radius: var(--ui-radius-md); background: color-mix(in srgb, var(--ui-text) 8%, transparent); color: var(--ui-text-muted);
273
279
  } /* a tint of the ink, not --ui-surface-2: that is the table row hover, and a badge vanished on hover (0.5.1) */
274
280
 
275
- /* ── Modal ────────────────────────────────────────────────────────────────────
281
+ /* ── Modal ──
276
282
  Rendered INLINE (not portaled): a portal escapes the .gemboss-admin-shell token
277
283
  scope and would render light. Kept in-tree so --ui-* resolves to the surface.
278
- 0.7.3: Polaris Modal's density (#1099, README "Popup density"). Popup only: --ui-control-h stays 32. */
284
+ 0.7.3: Polaris Modal's density (#1099, README "Popup density"). Buttons are 28 everywhere since 0.12.0 (--ui-btn-h). */
279
285
  .ui-overlay {
280
286
  position: fixed; inset: 0; z-index: 500;
281
287
  background: rgba(0, 0, 0, 0.5);
@@ -308,7 +314,6 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
308
314
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
309
315
  padding: 12px 16px; border-top: 1px solid var(--ui-border);
310
316
  }
311
- :is(.ui-modal__head, .ui-modal__foot) .ui-btn { height: 28px; padding: 0 12px; font-size: var(--ui-fs-caption); }
312
317
  /* 44px under a thumb or at phone width; -8px keeps the head 53. */
313
318
  @media (pointer: coarse), (max-width: 47.9975em) {
314
319
  .ui-modal__close { width: 44px; height: 44px; margin: -8px -8px -8px 0; }
@@ -347,15 +352,15 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
347
352
  .ui-select:not(.ui-select--slim) { min-height: 44px; }
348
353
  }
349
354
 
350
- /* ── Segmented control ────────────────────────────────────────────────────────
355
+ /* ── Segmented control ──
351
356
  Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
352
357
  selected segment uses the accent-soft state. */
353
358
  .ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
354
359
  .ui-seg :is(button, a) {
355
- display: inline-grid; place-items: center; width: calc(var(--ui-control-h) - 2px); height: calc(var(--ui-control-h) - 2px);
360
+ display: inline-grid; place-items: center; width: calc(var(--ui-btn-h) - 2px); height: calc(var(--ui-btn-h) - 2px);
356
361
  border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
357
362
  }
358
- .ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
363
+ .ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-caption); font-weight: 550; }
359
364
  .ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
360
365
  .ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
361
366
  .ui-seg :is(button, a):focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
@@ -365,7 +370,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
365
370
  .ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
366
371
  .ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
367
372
 
368
- /* ── Stat chip ────────────────────────────────────────────────────────────────
373
+ /* ── Stat chip ──
369
374
  Count-led pill; clickable ones double as filters (active = accent-soft). */
370
375
  .ui-stats { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: 24px; }
371
376
  .ui-stat {
@@ -380,7 +385,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
380
385
  .ui-stat.is-clickable.active { border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
381
386
  .ui-stat.is-clickable.active b { color: var(--ui-accent-strong); }
382
387
 
383
- /* ── Toast / notification bar ─────────────────────────────────────────────────
388
+ /* ── Toast / notification bar ──
384
389
  App-wide "done / failed" pill for async actions (delete/duplicate/publish…).
385
390
  Polaris-style: dark pill, bottom-center, error variant in critical red. Lives
386
391
  in <ToastProvider> (components/ui/Toast.tsx), portaled to <body>. */
@@ -423,7 +428,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
423
428
  }
424
429
  @media (prefers-reduced-motion: reduce) { .gemboss-toast { animation: none; } }
425
430
 
426
- /* ── The kit's controls under a thumb ─────────────────────────────────────────────────────────
431
+ /* ── The kit's controls under a thumb ──
427
432
  * Measured by hit test on /admin/{theme-kernel,media,style-packs} at 390 and 1366 with a coarse
428
433
  * pointer: segmented buttons 36x36, selects 30-36 tall, clickable stat tiles 29, the asset row's
429
434
  * note button 30x30. §9.1 puts every one of them in the CONTROL tier (44px).
@@ -431,9 +436,11 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
431
436
  * HERE rather than in each surface's stylesheet, because this is the shared primitive layer — the
432
437
  * same reason the kit exists at all. The admin console and the seller app both read it.
433
438
  *
434
- * `pointer: coarse` only: a mouse keeps the density these surfaces were designed at. Growing a box
435
- * for every pointer is what #2147 did to a working width.
439
+ * The block below is `pointer: coarse` only: a mouse at desktop width keeps the density these surfaces were
440
+ * designed at. Growing a box for every pointer is what #2147 did to a working width.
436
441
  */
442
+ /* Phone WIDTH (< 768) with a mouse too (a narrow window): buttons and segments are 44 (0.12.0; at desktop width 28). */
443
+ @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; } }
437
444
  @media (pointer: coarse) {
438
445
  /* NOT `.ui-textarea`: it already declares `min-height: 96px` earlier in this file, so adding it to
439
446
  a 44px group LOWERS it — same specificity, later line. The note dialog's box would have gone from
@@ -460,7 +467,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
460
467
  .ui-seg :is(button, a) { min-width: 44px; }
461
468
  }
462
469
 
463
- /* ── Skeleton ─────────────────────────────────────────────────────────────────
470
+ /* ── Skeleton ──
464
471
  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
465
472
  trí — nên nó tắt hẳn với `prefers-reduced-motion`, giữ nguyên vạch tĩnh. */
466
473
  .ui-skeleton { display: flex; flex-direction: column; gap: var(--ui-space-2); }
@@ -481,7 +488,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
481
488
 
482
489
  }
483
490
 
484
- /* ── Resizer: the seam you drag between two panels ────────────────────────────
491
+ /* ── Resizer: the seam you drag between two panels ──
485
492
  One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
486
493
  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
487
494
  placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
@@ -521,7 +528,7 @@ body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-selec
521
528
  pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
522
529
  body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
523
530
 
524
- /* ── Tabs · Table · Empty state ───────────────────────────────────────────────
531
+ /* ── Tabs · Table · Empty state ──
525
532
  Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
526
533
  file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
527
534
  for these; nothing in apps/admin uses the classes below, and they restyle no existing component.
@@ -529,18 +536,18 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
529
536
 
530
537
  Tabs: `<div class="ui-tabs" role="tablist"><button role="tab" aria-selected="true">…` — the
531
538
  selected state IS `aria-selected`, so what a screen reader announces and what is painted cannot
532
- disagree. 44px under EVERY pointer (§9.1 control tier), not only coarse: tabs sit 4px apart, and
533
- that gap is only acceptable between targets that are already 44px. */
539
+ disagree. Polaris Tabs under a mouse (0.12.0): 28px, 12px/550; 8px apart, the §9.1 gap for targets under 44.
540
+ 44px under a finger or at phone width (the rule after these). */
534
541
  .ui-tabs {
535
- display: flex; gap: var(--ui-space-1); border-bottom: 1px solid var(--ui-border);
542
+ display: flex; gap: var(--ui-space-2); border-bottom: 1px solid var(--ui-border);
536
543
  overflow-x: auto; scrollbar-width: none;
537
544
  }
538
545
  .ui-tabs > button,
539
546
  .ui-tabs > a {
540
547
  position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
541
- min-height: 44px; padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
548
+ min-height: var(--ui-btn-h); padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
542
549
  border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
543
- font-family: inherit; font-size: var(--ui-fs-body); font-weight: 500; white-space: nowrap;
550
+ font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550; white-space: nowrap;
544
551
  color: var(--ui-text-muted);
545
552
  transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
546
553
  }
@@ -551,13 +558,14 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
551
558
  .ui-tabs > a { text-decoration: none; }
552
559
  /* 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. */
553
560
  .ui-tabs > a[aria-current="page"],
554
- .ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); font-weight: 600; }
561
+ .ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); }
555
562
  .ui-tabs > a[aria-current="page"]::after,
556
563
  .ui-tabs > button[aria-selected="true"]::after {
557
564
  content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
558
565
  height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
559
566
  }
560
567
  .ui-tabs > button:disabled { opacity: .5; cursor: not-allowed; }
568
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-tabs > :is(button, a) { min-height: 44px; } }
561
569
 
562
570
  /* Table: wrap it — `<div class="ui-table-wrap"><table class="ui-table">` — so a wide table scrolls
563
571
  inside its card instead of pushing the page sideways at 320px. Numeric cells take `.is-num`:
@@ -565,16 +573,21 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
565
573
  .ui-table-wrap {
566
574
  overflow-x: auto; max-width: 100%;
567
575
  background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
576
+ box-shadow: var(--ui-card-shadow);
568
577
  }
569
578
  .ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-fs-body); color: var(--ui-text); }
570
579
  .ui-table th,
571
580
  .ui-table td {
572
- padding: var(--ui-space-2) var(--ui-space-3); text-align: left; vertical-align: middle;
581
+ padding: var(--ui-space-2) 6px; text-align: left; vertical-align: middle;
573
582
  border-bottom: 1px solid var(--ui-border);
574
583
  }
575
- .ui-table th {
576
- font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); white-space: nowrap;
577
- }
584
+ /* Polaris DataTable (13.9.5, measured on /app/analytics 05/10/2026): cells 8px 6px (a row 36), header 12px 6px (45),
585
+ the first column 12px in from the edge. */
586
+ /* :where() keeps the edge columns at the base rule's weight, so a surface's own `.x-table td { padding }` still wins. */
587
+ .ui-table :is(th, td):where(:first-child) { padding-left: var(--ui-space-3); }
588
+ .ui-table :is(th, td):where(:last-child) { padding-right: var(--ui-space-3); }
589
+ .ui-table th { font-weight: 450; color: var(--ui-text-muted); white-space: nowrap; }
590
+ .ui-table thead th { padding-block: var(--ui-space-3); } /* the column header; a row header (tbody th) is a cell */
578
591
  .ui-table tbody tr:last-child > td { border-bottom: 0; }
579
592
  .ui-table tbody tr:hover { background: var(--ui-surface-2); }
580
593
  .ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
@@ -583,7 +596,7 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
583
596
  /* 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).
584
597
  Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
585
598
  .ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
586
- .ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
599
+ .ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
587
600
 
588
601
  /* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
589
602
  <button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
@@ -592,6 +605,6 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
592
605
  padding: var(--ui-space-4); padding-block: 40px; text-align: center;
593
606
  color: var(--ui-text-muted); font-size: var(--ui-fs-body);
594
607
  }
595
- .ui-empty__title { margin: 0; font-size: var(--gemboss-fs-h3); font-weight: 600; color: var(--ui-text-strong); }
608
+ .ui-empty__title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text-strong); }
596
609
  .ui-empty__body { margin: 0; max-width: 48ch; line-height: 1.5; }
597
610
  .ui-empty > .ui-btn { margin-top: var(--ui-space-2); }