@gemboss/ui 0.12.0 → 0.21.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/CHANGELOG.md +395 -1
- package/README.md +321 -6
- package/catalog/editor.html +611 -0
- package/catalog/index.html +3114 -13
- package/gemboss-ui.css +3 -4682
- package/package.json +16 -4
- package/src/app-shell.css +1 -256
- package/src/assistant.css +1 -558
- package/src/auth.css +1 -606
- package/src/board-move.ts +194 -0
- package/src/chat-composer.css +1 -110
- package/src/chat.css +1 -432
- package/src/collections.css +1 -0
- package/src/collections.tsx +543 -0
- package/src/editor/controls.tsx +206 -0
- package/src/editor/css-url.ts +11 -0
- package/src/editor/frame.tsx +242 -0
- package/src/editor/index.ts +43 -0
- package/src/editor/panel.tsx +188 -0
- package/src/editor/panes.tsx +44 -0
- package/src/editor/swatch.ts +45 -0
- package/src/editor-plain.js +1 -0
- package/src/editor.css +1 -1099
- package/src/gemboss-field.css +1 -112
- package/src/gemboss-tokens.css +1 -164
- package/src/icons.tsx +2 -0
- package/src/index-table.tsx +179 -0
- package/src/index.ts +19 -0
- package/src/kit-plain.css +1 -483
- package/src/kit-plain.js +1 -440
- package/src/menu-layer.ts +87 -0
- package/src/menu.tsx +28 -2
- package/src/portal.css +1 -139
- package/src/reactions-data.ts +59 -0
- package/src/reactions.css +1 -0
- package/src/reactions.tsx +139 -0
- package/src/resource-list-select.tsx +72 -0
- package/src/segmented-click.ts +20 -0
- package/src/segmented.tsx +98 -0
- package/src/selection.tsx +78 -0
- package/src/shell.css +1 -1079
- package/src/theme-dark.css +1 -66
- package/src/ui.css +1 -610
package/src/ui.css
CHANGED
|
@@ -1,610 +1 @@
|
|
|
1
|
-
/* ══
|
|
2
|
-
Tool/App UI kit — the shared primitive layer for the internal operator surfaces
|
|
3
|
-
(admin console, seller app chrome, theme-editor). ONE semantic token spine
|
|
4
|
-
(`--ui-*`) that every primitive reads, plus the primitive CSS itself.
|
|
5
|
-
|
|
6
|
-
Why this exists: buttons/cards/badges were forked into 4–8 hand-rolled class
|
|
7
|
-
families per surface, each re-deriving color/spacing/centering — so the same
|
|
8
|
-
primitive drifted (e.g. a link-button whose text wasn't vertically centered).
|
|
9
|
-
See docs/frontend-shared-system-plan.md.
|
|
10
|
-
|
|
11
|
-
The `--ui-*` layer is SEMANTIC and resolved per surface: components only read
|
|
12
|
-
`--ui-*`; each surface maps those to its own tokens (admin → `--adm-*` below),
|
|
13
|
-
so one <Button> looks native everywhere without hard-coding a palette.
|
|
14
|
-
══ */
|
|
15
|
-
|
|
16
|
-
:root {
|
|
17
|
-
--ui-surface: #ffffff;
|
|
18
|
-
/* --ui-* is a SEMANTIC layer resolved PER SURFACE — it references each surface's own base tokens
|
|
19
|
-
rather than guessing from the OS. The default (`:root`) maps onto the seller app's warm-ivory
|
|
20
|
-
`--gemboss-*` set; `.gemboss-admin-shell` below remaps onto the dark `--adm-*` set. Literal fallbacks
|
|
21
|
-
keep the kit usable if a surface's base stylesheet isn't loaded. This is the "unify under --ui-*"
|
|
22
|
-
backbone — one token spine, no duplicated palette. */
|
|
23
|
-
--ui-surface: var(--gemboss-surface, #ffffff);
|
|
24
|
-
--ui-surface-2: var(--gemboss-hover, #eceef2);
|
|
25
|
-
--ui-text: var(--gemboss-text, #1b1c1e);
|
|
26
|
-
--ui-text-muted: var(--gemboss-text-muted, #686b71);
|
|
27
|
-
--ui-border: var(--gemboss-divider, #e5e7ea);
|
|
28
|
-
--ui-accent: var(--gemboss-primary, #1b1c1e);
|
|
29
|
-
--ui-accent-hover: var(--gemboss-primary-dark, #000000);
|
|
30
|
-
--ui-accent-fg: #ffffff;
|
|
31
|
-
--ui-accent-soft: var(--gemboss-active-bg, #f0f1f4); /* tinted bg for a selected/active state */
|
|
32
|
-
--ui-accent-strong: var(--gemboss-active-fg, #1b1c1e); /* readable accent text ON --ui-accent-soft */
|
|
33
|
-
--ui-text-strong: var(--gemboss-text-strong, #1b1c1e);
|
|
34
|
-
/* Chữ theo tông (ui-metric, …). Nền tối của admin ánh xạ lại bên dưới để còn đọc được. */
|
|
35
|
-
--ui-success-fg: var(--gemboss-success);
|
|
36
|
-
--ui-warning-fg: var(--gemboss-primary-dark);
|
|
37
|
-
--ui-danger-fg: var(--gemboss-danger-strong);
|
|
38
|
-
/* ONE focus ring for every control (02/10/2026): the admin's, iris — gemboss-shell.css points Polaris'
|
|
39
|
-
--p-color-border-focus at --gemboss-accent. Before, kit buttons, tabs and checks drew black, fields
|
|
40
|
-
and selects #005bd3 (Polaris' default, which the export shipped), and the admin drew iris. */
|
|
41
|
-
--ui-focus: var(--gemboss-accent, #5661f5);
|
|
42
|
-
--ui-radius-sm: 6px;
|
|
43
|
-
--ui-radius-md: 8px;
|
|
44
|
-
--ui-radius-lg: 12px;
|
|
45
|
-
--ui-space-1: 4px;
|
|
46
|
-
--ui-space-2: 8px;
|
|
47
|
-
--ui-space-3: 12px;
|
|
48
|
-
--ui-space-4: 16px;
|
|
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
|
-
--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);
|
|
58
|
-
--ui-fs-body: 13px;
|
|
59
|
-
--ui-fs-caption: 12px;
|
|
60
|
-
/* Content column width for the seller app shell — ONE source of truth for every wide surface
|
|
61
|
-
right of the 240px sidebar (bare Page, .gemboss-w-dashboard, .gemboss-w-gallery, and the
|
|
62
|
-
navigation skeleton), so they cannot drift apart again. The /admin console is a SEPARATE
|
|
63
|
-
surface and still sets its own 1200px in admin/admin-shell.css; it does not read this token.
|
|
64
|
-
900px. Polaris' own cap is 998px — 41.375rem primary column + 20rem secondary + 1rem gutter,
|
|
65
|
-
read out of the installed @shopify/polaris stylesheet — and this sits deliberately BELOW it.
|
|
66
|
-
|
|
67
|
-
The reason is a measurement, not a preference about Shopify. The previous value was 1080/1040,
|
|
68
|
-
and moving to Polaris' 998 was the obvious "correct" answer, so that shipped first (#2250). Then
|
|
69
|
-
it turned out to be invisible: 1040 → 998 is 42px total, which is 21px of extra gutter per side.
|
|
70
|
-
Screenshotted side by side at 1440 on the same page seconds apart, the two rows were
|
|
71
|
-
indistinguishable, and Chris — who asked for this — could not see any change at all.
|
|
72
|
-
|
|
73
|
-
A cap only does its job if the breathing room it buys is legible. Measured gutters on
|
|
74
|
-
.Polaris-Page at 1440 beside the 240px sidebar:
|
|
75
|
-
|
|
76
|
-
1040px → 75 / 86 (before)
|
|
77
|
-
998px → 96 / 107 Polaris' number; +21px a side, not perceivable
|
|
78
|
-
960px → 115 / 126
|
|
79
|
-
900px → 145 / 156 +70px a side, roughly double the old gutter
|
|
80
|
-
|
|
81
|
-
900 is the first value where the column visibly stops leaning on the rail. The one cost, and it
|
|
82
|
-
was measured rather than guessed: card descriptions gain a line (Cart drawer 2 → 3). The 3-up
|
|
83
|
-
grid holds, and no width from 320 to 1920 overflows.
|
|
84
|
-
|
|
85
|
-
Still fixed px, no percentage, for the reason Polaris uses px: a % cap grows the reading measure
|
|
86
|
-
with the monitor (80% of 2560 is 2048px of line length). Responsiveness belongs to the padding
|
|
87
|
-
and to the secondary column dropping, not to the cap.
|
|
88
|
-
|
|
89
|
-
.gemboss-w-form stays 662px — that IS `.Polaris-Page--narrowWidth`, the primary column alone,
|
|
90
|
-
and a form is the one surface where Polaris' measure is about the input, not the page. */
|
|
91
|
-
--ui-content-max: 900px;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/* Admin console bridge: the black chrome defines `--adm-*`; remap `--ui-*` onto them so every
|
|
95
|
-
<Button>/<Select>/<Tag>/<Modal> inside .gemboss-admin-shell adopts the dark palette automatically. */
|
|
96
|
-
.gemboss-admin-shell {
|
|
97
|
-
--ui-surface: var(--adm-surface);
|
|
98
|
-
--ui-surface-2: var(--adm-surface-2);
|
|
99
|
-
--ui-text: var(--adm-text);
|
|
100
|
-
--ui-text-muted: var(--adm-muted);
|
|
101
|
-
--ui-border: var(--adm-divider);
|
|
102
|
-
--ui-accent: var(--adm-accent);
|
|
103
|
-
--ui-accent-hover: var(--gemboss-primary-dark, #000000);
|
|
104
|
-
--ui-accent-fg: #ffffff;
|
|
105
|
-
--ui-accent-soft: var(--adm-accent-soft);
|
|
106
|
-
--ui-accent-strong: var(--adm-accent-fg);
|
|
107
|
-
--ui-text-strong: var(--adm-text-strong);
|
|
108
|
-
/* Tông trên nền tối #16161a: pha 70% với trắng ⇒ tương phản 6,2 / 9,5 / 4,8 (đo 01/10/2026, review #3609). */
|
|
109
|
-
--ui-success-fg: color-mix(in srgb, var(--gemboss-success) 70%, #ffffff);
|
|
110
|
-
--ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
|
|
111
|
-
--ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
|
|
112
|
-
--ui-focus: var(--adm-accent);
|
|
113
|
-
--ui-card-shadow: none;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* ── Button ──
|
|
117
|
-
inline-flex + center so a <button> and an <a> render identically (anchors ignore
|
|
118
|
-
`height` as inline elements — the root cause of the misaligned link-buttons). */
|
|
119
|
-
.ui-btn {
|
|
120
|
-
display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-space-2);
|
|
121
|
-
height: var(--ui-btn-h); padding: 0 var(--ui-space-3); box-sizing: border-box;
|
|
122
|
-
line-height: 1; white-space: nowrap; vertical-align: middle;
|
|
123
|
-
border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
|
|
124
|
-
cursor: pointer; text-decoration: none; user-select: none;
|
|
125
|
-
border: 1px solid var(--ui-border); background: transparent; color: var(--ui-text);
|
|
126
|
-
transition: background .12s ease, border-color .12s ease;
|
|
127
|
-
}
|
|
128
|
-
.ui-btn:hover { background: var(--ui-surface-2); text-decoration: none; }
|
|
129
|
-
.ui-btn:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 2px; }
|
|
130
|
-
.ui-btn[disabled], .ui-btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
|
|
131
|
-
.ui-btn--primary { background: var(--ui-accent); border-color: var(--ui-accent); color: var(--ui-accent-fg); }
|
|
132
|
-
.ui-btn--primary:hover { background: var(--ui-accent-hover); border-color: var(--ui-accent-hover); }
|
|
133
|
-
.ui-btn--ghost { border-color: transparent; background: transparent; color: var(--ui-text-muted); }
|
|
134
|
-
.ui-btn--ghost:hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
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. */
|
|
136
|
-
|
|
137
|
-
/* ── Card ── */
|
|
138
|
-
.ui-card {
|
|
139
|
-
background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
|
|
140
|
-
box-shadow: var(--ui-card-shadow); padding: var(--ui-space-4); color: var(--ui-text);
|
|
141
|
-
}
|
|
142
|
-
.ui-card__title { font-size: 14px; line-height: 20px; font-weight: 650; margin: 0 0 var(--ui-space-2); }
|
|
143
|
-
/* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
|
|
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). */
|
|
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); }
|
|
146
|
-
.ui-card__head > .ui-card__title { margin: 0; }
|
|
147
|
-
.ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
|
|
148
|
-
|
|
149
|
-
/* ── Select ──
|
|
150
|
-
Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
|
|
151
|
-
uses the muted token via %-encoding fallback).
|
|
152
|
-
Its BOX is the text field's (29/09/2026, Chris): same --gemboss-field-* tokens for ground, line and
|
|
153
|
-
ink, same hover, same focus ring as `.gemboss-field`. Before, a grey --ui-surface-2 select stood next
|
|
154
|
-
to a bordered white field in the same form (gempress editor, admin forms), and `:focus` removed the
|
|
155
|
-
outline and only recoloured the border, so a keyboard user saw almost nothing. Height is
|
|
156
|
-
--ui-control-h, the field's (Polaris Select, 32): a field, not a button (28, --ui-btn-h). */
|
|
157
|
-
.ui-select {
|
|
158
|
-
height: var(--ui-control-h); padding: 0 32px 0 12px; max-width: 100%;
|
|
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;
|
|
160
|
-
border: 1px solid var(--p-color-input-border, var(--gemboss-field-line)); border-radius: var(--ui-radius-md);
|
|
161
|
-
color: var(--p-color-text, var(--gemboss-field-ink)); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 450;
|
|
162
|
-
appearance: none; -webkit-appearance: none; cursor: pointer;
|
|
163
|
-
}
|
|
164
|
-
.ui-select:hover:not(:disabled) {
|
|
165
|
-
border-color: var(--p-color-input-border-hover, var(--gemboss-field-line-hover));
|
|
166
|
-
background-color: var(--p-color-input-bg-surface-hover, var(--gemboss-field-bg-hover));
|
|
167
|
-
}
|
|
168
|
-
.ui-select:focus { outline: none; }
|
|
169
|
-
.ui-select:focus-visible:not(:disabled) {
|
|
170
|
-
outline: 2px solid var(--p-color-border-focus, var(--ui-focus)); outline-offset: 1px;
|
|
171
|
-
border-color: var(--p-color-input-border-active, var(--gemboss-field-line-active));
|
|
172
|
-
}
|
|
173
|
-
.ui-select:disabled { opacity: .5; cursor: not-allowed; }
|
|
174
|
-
.ui-select--slim { height: 30px; font-size: var(--ui-fs-caption); }
|
|
175
|
-
|
|
176
|
-
/* ── Tag / Chip ──
|
|
177
|
-
Small inline labels. `solid` = filled muted pill (was gemboss-vault-tag); `chip` =
|
|
178
|
-
bordered mono token (was gemboss-vault-chip). `accent` tone for the highlighted state. */
|
|
179
|
-
.ui-tag {
|
|
180
|
-
display: inline-flex; align-items: center; gap: 6px;
|
|
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);
|
|
183
|
-
}
|
|
184
|
-
.ui-tag--accent { background: var(--adm-accent-soft, rgba(20,22,26,.05)); color: var(--adm-accent-fg, var(--ui-accent)); }
|
|
185
|
-
.ui-tag--chip {
|
|
186
|
-
text-transform: none; letter-spacing: 0; font-size: 11px; font-weight: 500;
|
|
187
|
-
padding: 2px 8px; background: transparent; border: 1px solid var(--ui-border);
|
|
188
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
189
|
-
}
|
|
190
|
-
.ui-tag--chip.ui-tag--accent { color: var(--adm-accent-fg, var(--ui-accent)); border-color: var(--ui-accent); background: transparent; }
|
|
191
|
-
|
|
192
|
-
/* Danh sách link dạng chip: `<div class="ui-chips"><a class="ui-tag ui-tag--chip" href>…</a>…</div>` (01/10/2026,
|
|
193
|
-
partner-radar /competitors: "đã tích hợp với GemPages"). `.ui-tag--chip` vốn là nhãn TĨNH cao ~19px, dưới mức 24px
|
|
194
|
-
của link trong danh sách (WCAG 2.5.8, §9.1) — visual-gate đo đúng chỗ đó đỏ ở 320 / 390 / 768. Chip là link thì cao
|
|
195
|
-
≥ 28px, chữ thường (không mono), có hover + focus-visible; dưới ngón tay là điều khiển, lên 44px (khối coarse dưới).
|
|
196
|
-
Khe 8px: hai target nhỏ hơn 44px phải cách nhau ≥ 8px. */
|
|
197
|
-
.ui-chips { display: flex; flex-wrap: wrap; gap: var(--ui-space-2, 8px); margin: 0; padding: 0; list-style: none; }
|
|
198
|
-
.ui-chips > a.ui-tag--chip {
|
|
199
|
-
min-height: 28px; padding: 4px 12px; border-radius: var(--ui-radius-md, 8px);
|
|
200
|
-
font-family: inherit; font-size: var(--gemboss-fs-caption, 12px); color: var(--ui-text); text-decoration: none;
|
|
201
|
-
}
|
|
202
|
-
.ui-chips > a.ui-tag--chip:hover { background: var(--ui-surface-2); border-color: var(--ui-text-muted); }
|
|
203
|
-
.ui-chips > a.ui-tag--chip:focus-visible { outline: 2px solid var(--p-color-border-focus, var(--ui-focus)); outline-offset: 1px; }
|
|
204
|
-
/* Danh sách LỌC dài (chuyên môn, hạng…) dùng ui-chips thay ui-seg: ui-seg không xuống dòng nên tràn ngang ở 320px.
|
|
205
|
-
Ô đang chọn = `aria-current="page"` (lọc là một URL), cùng tông với ui-seg đang chọn. */
|
|
206
|
-
.ui-chips > a.ui-tag--chip[aria-current="page"] { background: var(--ui-accent-soft); border-color: var(--ui-accent); color: var(--ui-accent-strong); font-weight: 600; }
|
|
207
|
-
|
|
208
|
-
/* ── Tông màu cho tag: `ui-tag--success | --warning | --danger` (token ngữ nghĩa có sẵn). Trạng thái, nguồn,
|
|
209
|
-
kết quả kiểm — thay cho màu viết cứng ở từng surface. */
|
|
210
|
-
.ui-tag--success { background: var(--gemboss-success-soft); color: var(--gemboss-success-strong, var(--gemboss-success)); }
|
|
211
|
-
.ui-tag--warning { background: var(--gemboss-warning-soft); color: var(--gemboss-warning); }
|
|
212
|
-
.ui-tag--danger { background: var(--gemboss-danger-soft); color: var(--gemboss-danger-strong, var(--gemboss-danger)); }
|
|
213
|
-
|
|
214
|
-
/* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
|
|
215
|
-
Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
|
|
216
|
-
.ui-muted { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
|
|
217
|
-
|
|
218
|
-
/* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ 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). */
|
|
221
|
-
.ui-page-head { margin-bottom: 20px; }
|
|
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) */
|
|
223
|
-
.ui-page-head p { color: var(--ui-text-muted); font-size: var(--ui-fs-body); margin-top: 4px; }
|
|
224
|
-
|
|
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.
|
|
226
|
-
Đưa lên từ admin `.gemboss-admin-toolbar`. Chỉ BỐ CỤC: ô tìm là `gemboss-field gemboss-field--group`. */
|
|
227
|
-
.ui-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
228
|
-
|
|
229
|
-
/* ── Ô số liệu: `<div class="ui-metrics"><div class="ui-metric"><span class="ui-metric__val">12</span>
|
|
230
|
-
<span class="ui-metric__lbl">Nhãn</span></div></div>`; tông `ui-metric--success | --warning | --danger`.
|
|
231
|
-
Đưa lên từ admin `.gemboss-ec-metric` (GemChat). Khác `ui-stat` (viên đếm nhỏ, có thể bấm làm bộ lọc). */
|
|
232
|
-
.ui-metrics { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0; }
|
|
233
|
-
.ui-metric { display: flex; flex-direction: column; gap: 1px; min-width: 78px; padding: 8px 12px; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: 12px; }
|
|
234
|
-
/* Cả ô là link (`<a class="ui-metric" href>`): vùng chạm là cả ô (~52px), không phải con số 20px bên trong
|
|
235
|
-
(visual-gate đỏ ở partner-radar /ops 01/10/2026 khi link nằm trong __val). */
|
|
236
|
-
a.ui-metric { text-decoration: none; color: inherit; }
|
|
237
|
-
a.ui-metric:hover { border-color: var(--ui-text-muted); }
|
|
238
|
-
a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
239
|
-
.ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
|
|
240
|
-
.ui-metric__lbl { font-size: var(--ui-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
|
|
241
|
-
.ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
|
|
242
|
-
.ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
|
|
243
|
-
.ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
|
|
244
|
-
.ui-metric--warning .ui-metric__val { color: var(--ui-warning-fg); }
|
|
245
|
-
.ui-metric--danger { border-color: color-mix(in srgb, var(--gemboss-danger-strong) 40%, transparent); }
|
|
246
|
-
.ui-metric--danger .ui-metric__val { color: var(--ui-danger-fg); }
|
|
247
|
-
|
|
248
|
-
/* ── Cặp nhãn – giá trị: `<dl class="ui-dl"><div><dt>Quốc gia</dt><dd>IL</dd></div>…</dl>` — dữ kiện của một hồ sơ.
|
|
249
|
-
Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
|
|
250
|
-
.ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
|
|
251
|
-
.ui-dl > div { min-width: 0; }
|
|
252
|
-
.ui-dl dt { font-size: var(--ui-fs-caption); color: var(--ui-text-muted); }
|
|
253
|
-
.ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
|
|
254
|
-
|
|
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
|
|
256
|
-
làm. Mỗi tông mang màu chữ riêng (nền tông luôn sáng, kể cả trong admin tối). Không có tông = ghi chú trung tính.
|
|
257
|
-
role="status"/"alert" CHỈ khi thông báo xuất hiện sau khi trang đã tải (vùng live); có sẵn lúc tải thì không role. */
|
|
258
|
-
.ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--ui-fs-body); overflow-wrap: anywhere; } /* a URL or key in a notice wraps instead of being cut by its card (gemwatcher Pulse, 0.6.2) */
|
|
259
|
-
.ui-notice--warning { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); color: var(--gemboss-warning); }
|
|
260
|
-
.ui-notice--danger { background: var(--gemboss-danger-soft); border-color: var(--gemboss-danger-border); color: var(--gemboss-danger-strong); }
|
|
261
|
-
.ui-notice--success { background: var(--gemboss-success-soft); border-color: var(--gemboss-success-border); color: var(--gemboss-success-strong); }
|
|
262
|
-
|
|
263
|
-
/* ── Phân trang: `<nav class="ui-pager" aria-label="Trang"><a href rel="prev"><span aria-hidden="true">‹</span> Trước</a><span aria-current="page">2 / 9</span>
|
|
264
|
-
<a href rel="next">Sau ›</a></nav>`. Link ≥ 32px (≥ 24px WCAG 2.5.8), 44px dưới ngón tay (khối coarse). */
|
|
265
|
-
.ui-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0; }
|
|
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); }
|
|
267
|
-
.ui-pager > a:hover { background: var(--ui-surface-2); }
|
|
268
|
-
.ui-pager > a:focus-visible { outline: 2px solid var(--p-color-border-focus, var(--ui-focus)); outline-offset: 1px; }
|
|
269
|
-
.ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
|
|
270
|
-
|
|
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
|
|
273
|
-
fill. The admin's /admin console draws it too (components/ui/Badge.tsx). Tones (`--success|--warning|--critical|
|
|
274
|
-
--info`, `--dot`) are in kit-plain.css, for the non-React surfaces; the seller app uses Polaris <Badge tone>. */
|
|
275
|
-
.ui-badge {
|
|
276
|
-
display: inline-flex; align-items: center; gap: 4px;
|
|
277
|
-
font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; white-space: nowrap;
|
|
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);
|
|
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) */
|
|
280
|
-
|
|
281
|
-
/* ── Modal ──
|
|
282
|
-
Rendered INLINE (not portaled): a portal escapes the .gemboss-admin-shell token
|
|
283
|
-
scope and would render light. Kept in-tree so --ui-* resolves to the surface.
|
|
284
|
-
0.7.3: Polaris Modal's density (#1099, README "Popup density"). Buttons are 28 everywhere since 0.12.0 (--ui-btn-h). */
|
|
285
|
-
.ui-overlay {
|
|
286
|
-
position: fixed; inset: 0; z-index: 500;
|
|
287
|
-
background: rgba(0, 0, 0, 0.5);
|
|
288
|
-
display: grid; place-items: center; padding: 20px;
|
|
289
|
-
}
|
|
290
|
-
.ui-modal {
|
|
291
|
-
width: min(460px, 100%);
|
|
292
|
-
background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: 16px;
|
|
293
|
-
box-shadow: var(--gemboss-shadow-overlay, 0 20px 60px rgba(0, 0, 0, 0.4)); overflow: hidden;
|
|
294
|
-
}
|
|
295
|
-
/* Head = title + the one close button every modal used to draw for itself. */
|
|
296
|
-
.ui-modal__head {
|
|
297
|
-
display: flex; align-items: center; gap: 12px;
|
|
298
|
-
padding: 12px 16px; border-bottom: 1px solid var(--ui-border);
|
|
299
|
-
font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text);
|
|
300
|
-
}
|
|
301
|
-
.ui-modal__title { min-width: 0; flex: 1 1 auto; }
|
|
302
|
-
.ui-modal__close {
|
|
303
|
-
flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
|
|
304
|
-
width: 28px; height: 28px; padding: 0;
|
|
305
|
-
border: 0; border-radius: var(--ui-radius-md, 8px); background: transparent;
|
|
306
|
-
color: var(--ui-text-muted); cursor: pointer;
|
|
307
|
-
}
|
|
308
|
-
.ui-modal__close svg { width: 20px; height: 20px; }
|
|
309
|
-
.ui-modal__close:hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
310
|
-
.ui-modal__close:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
311
|
-
/* Here, not in kit-plain.css: the admin loads only this file (0.7.3). */
|
|
312
|
-
.ui-modal__body { padding: 16px; }
|
|
313
|
-
.ui-modal__foot {
|
|
314
|
-
display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
|
|
315
|
-
padding: 12px 16px; border-top: 1px solid var(--ui-border);
|
|
316
|
-
}
|
|
317
|
-
/* 44px under a thumb or at phone width; -8px keeps the head 53. */
|
|
318
|
-
@media (pointer: coarse), (max-width: 47.9975em) {
|
|
319
|
-
.ui-modal__close { width: 44px; height: 44px; margin: -8px -8px -8px 0; }
|
|
320
|
-
:is(.ui-modal__head, .ui-modal__foot) .ui-btn { min-height: 44px; }
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/* `size="lg"`: only the HOOK here (this sheet is paid on every screen); its geometry is in
|
|
324
|
-
SettingsDialog.module.css, the one consumer's chunk. */
|
|
325
|
-
.ui-modal--lg { position: relative; }
|
|
326
|
-
|
|
327
|
-
/* The kit's multi-line field is the ONE field family (01/10/2026, Chris: "form control đồng bộ"): same
|
|
328
|
-
border token, hover and focus ring as `gemboss-field` / `.ui-select` (it drew a third look on GemFactory). */
|
|
329
|
-
.ui-textarea {
|
|
330
|
-
width: 100%; min-height: 96px; resize: vertical; box-sizing: border-box;
|
|
331
|
-
background: var(--p-color-input-bg-surface, var(--gemboss-field-bg));
|
|
332
|
-
border: 1px solid var(--p-color-input-border, var(--gemboss-field-line)); border-radius: var(--ui-radius-md);
|
|
333
|
-
padding: 12px; color: var(--p-color-text, var(--gemboss-field-ink)); font-size: var(--ui-fs-body); line-height: 1.5; font-family: inherit;
|
|
334
|
-
}
|
|
335
|
-
.ui-textarea::placeholder { color: var(--p-color-text-secondary, var(--gemboss-field-placeholder)); }
|
|
336
|
-
.ui-textarea:hover:not(:disabled) {
|
|
337
|
-
border-color: var(--p-color-input-border-hover, var(--gemboss-field-line-hover));
|
|
338
|
-
background-color: var(--p-color-input-bg-surface-hover, var(--gemboss-field-bg-hover));
|
|
339
|
-
}
|
|
340
|
-
.ui-textarea:focus { outline: none; }
|
|
341
|
-
.ui-textarea:focus-visible:not(:disabled) {
|
|
342
|
-
outline: 2px solid var(--p-color-border-focus, var(--ui-focus)); outline-offset: 1px;
|
|
343
|
-
border-color: var(--p-color-input-border-active, var(--gemboss-field-line-active));
|
|
344
|
-
}
|
|
345
|
-
.ui-textarea:disabled { opacity: .5; cursor: not-allowed; }
|
|
346
|
-
/* On a PHONE-WIDTH screen every field of the family reads 16px (iOS zooms the page on tap below that)
|
|
347
|
-
and a select is a 44px target — by WIDTH, as `gemboss-field` does, because a desktop browser at 320px
|
|
348
|
-
still reports pointer: fine. Measured on GemFactory at 390px before this: select 13px, textarea 14px. */
|
|
349
|
-
@media (max-width: 47.9975em) {
|
|
350
|
-
.ui-select.ui-select, .ui-textarea { font-size: 16px; }
|
|
351
|
-
/* Height only for the full select: `--slim` is a density decision (table rows), as the coarse block keeps it. */
|
|
352
|
-
.ui-select:not(.ui-select--slim) { min-height: 44px; }
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/* ── Segmented control ──
|
|
356
|
-
Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
|
|
357
|
-
selected segment uses the accent-soft state. */
|
|
358
|
-
.ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
|
|
359
|
-
.ui-seg :is(button, a) {
|
|
360
|
-
display: inline-grid; place-items: center; width: calc(var(--ui-btn-h) - 2px); height: calc(var(--ui-btn-h) - 2px);
|
|
361
|
-
border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
|
|
362
|
-
}
|
|
363
|
-
.ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-caption); font-weight: 550; }
|
|
364
|
-
.ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
|
|
365
|
-
.ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
366
|
-
.ui-seg :is(button, a):focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
367
|
-
/* Lọc / chế độ xem bằng LINK (trang server-render, chọn = đổi URL): `<a aria-current="page">` là ô đang chọn. */
|
|
368
|
-
.ui-seg a { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; color: var(--ui-text-muted); }
|
|
369
|
-
.ui-seg a[aria-current="page"],
|
|
370
|
-
.ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
371
|
-
.ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
|
|
372
|
-
|
|
373
|
-
/* ── Stat chip ──
|
|
374
|
-
Count-led pill; clickable ones double as filters (active = accent-soft). */
|
|
375
|
-
.ui-stats { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: 24px; }
|
|
376
|
-
.ui-stat {
|
|
377
|
-
display: inline-flex; align-items: center; gap: var(--ui-space-2);
|
|
378
|
-
padding: 6px 12px; border-radius: 999px; font-family: inherit;
|
|
379
|
-
background: var(--ui-surface); border: 1px solid var(--ui-border);
|
|
380
|
-
font-size: var(--ui-fs-caption); color: var(--ui-text-muted);
|
|
381
|
-
}
|
|
382
|
-
.ui-stat b { color: var(--ui-text-strong); font-weight: 650; }
|
|
383
|
-
.ui-stat.is-clickable { cursor: pointer; transition: border-color 0.14s ease, background-color 0.14s ease; }
|
|
384
|
-
.ui-stat.is-clickable:hover { border-color: var(--ui-text-muted); background: var(--ui-surface-2); }
|
|
385
|
-
.ui-stat.is-clickable.active { border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
386
|
-
.ui-stat.is-clickable.active b { color: var(--ui-accent-strong); }
|
|
387
|
-
|
|
388
|
-
/* ── Toast / notification bar ──
|
|
389
|
-
App-wide "done / failed" pill for async actions (delete/duplicate/publish…).
|
|
390
|
-
Polaris-style: dark pill, bottom-center, error variant in critical red. Lives
|
|
391
|
-
in <ToastProvider> (components/ui/Toast.tsx), portaled to <body>. */
|
|
392
|
-
.gemboss-toast-viewport {
|
|
393
|
-
position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
|
|
394
|
-
z-index: 2147483000; /* above every overlay/modal/popup */
|
|
395
|
-
display: flex; flex-direction: column; align-items: center; gap: 12px;
|
|
396
|
-
width: max-content; max-width: min(560px, calc(100vw - 32px)); pointer-events: none;
|
|
397
|
-
}
|
|
398
|
-
.gemboss-toast {
|
|
399
|
-
pointer-events: auto;
|
|
400
|
-
display: flex; align-items: center; gap: 12px;
|
|
401
|
-
min-width: 220px; max-width: 100%; padding: 12px 12px 12px 16px;
|
|
402
|
-
background: var(--ui-text-strong, #1a1917); color: var(--gemboss-on-primary, #fff); border-radius: 12px;
|
|
403
|
-
font-size: var(--ui-fs-body, 13px); line-height: 1.4; font-weight: 500;
|
|
404
|
-
box-shadow: var(--gemboss-shadow-overlay, 0 8px 28px rgba(0, 0, 0, 0.24));
|
|
405
|
-
animation: gemboss-toast-in 240ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
406
|
-
}
|
|
407
|
-
.gemboss-toast.is-error { background: var(--gemboss-danger, #d72c0d); }
|
|
408
|
-
.gemboss-toast-text { flex: 1 1 auto; min-width: 0; }
|
|
409
|
-
.gemboss-toast-action {
|
|
410
|
-
flex: 0 0 auto; border: 0; background: transparent; color: var(--gemboss-on-primary, #fff); cursor: pointer;
|
|
411
|
-
padding: 4px 8px; border-radius: 8px; font: inherit; font-weight: 650;
|
|
412
|
-
text-decoration: underline; text-underline-offset: 2px;
|
|
413
|
-
transition: background 0.14s ease;
|
|
414
|
-
}
|
|
415
|
-
.gemboss-toast-action:hover { background: rgba(255, 255, 255, 0.16); }
|
|
416
|
-
.gemboss-toast-dismiss {
|
|
417
|
-
flex: 0 0 auto; display: inline-grid; place-items: center;
|
|
418
|
-
width: 24px; height: 24px; border: 0; border-radius: 8px; cursor: pointer;
|
|
419
|
-
background: rgba(255, 255, 255, 0.12); color: var(--gemboss-on-primary, #fff);
|
|
420
|
-
transition: background 0.14s ease;
|
|
421
|
-
}
|
|
422
|
-
.gemboss-toast-dismiss:hover { background: rgba(255, 255, 255, 0.26); }
|
|
423
|
-
.gemboss-toast-action:focus-visible,
|
|
424
|
-
.gemboss-toast-dismiss:focus-visible { outline: 2px solid var(--gemboss-on-primary, #fff); outline-offset: 2px; }
|
|
425
|
-
@keyframes gemboss-toast-in {
|
|
426
|
-
from { opacity: 0; transform: translateY(12px); }
|
|
427
|
-
to { opacity: 1; transform: translateY(0); }
|
|
428
|
-
}
|
|
429
|
-
@media (prefers-reduced-motion: reduce) { .gemboss-toast { animation: none; } }
|
|
430
|
-
|
|
431
|
-
/* ── The kit's controls under a thumb ──
|
|
432
|
-
* Measured by hit test on /admin/{theme-kernel,media,style-packs} at 390 and 1366 with a coarse
|
|
433
|
-
* pointer: segmented buttons 36x36, selects 30-36 tall, clickable stat tiles 29, the asset row's
|
|
434
|
-
* note button 30x30. §9.1 puts every one of them in the CONTROL tier (44px).
|
|
435
|
-
*
|
|
436
|
-
* HERE rather than in each surface's stylesheet, because this is the shared primitive layer — the
|
|
437
|
-
* same reason the kit exists at all. The admin console and the seller app both read it.
|
|
438
|
-
*
|
|
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.
|
|
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; } }
|
|
444
|
-
@media (pointer: coarse) {
|
|
445
|
-
/* NOT `.ui-textarea`: it already declares `min-height: 96px` earlier in this file, so adding it to
|
|
446
|
-
a 44px group LOWERS it — same specificity, later line. The note dialog's box would have gone from
|
|
447
|
-
96 to ~58 on every touch device, inside a block whose whole point is bigger targets.
|
|
448
|
-
NOT `.ui-btn--sm` either: `min-height` always beats `height`, so a 44px floor voids the compact
|
|
449
|
-
variant — including four of them on the seller's own /app/settings/store. A density variant is a
|
|
450
|
-
decision, not an oversight; it gets its own measurement pass or none. */
|
|
451
|
-
.ui-btn:not(.ui-btn--sm),
|
|
452
|
-
.ui-stat.is-clickable,
|
|
453
|
-
.ui-chips > a.ui-tag--chip,
|
|
454
|
-
.ui-pager > a,
|
|
455
|
-
.ui-seg :is(button, a) { min-height: 44px; }
|
|
456
|
-
/* A clickable stat whose label is one short token ("7d" on /admin/gem-intelligence) measured 40x44
|
|
457
|
-
in the 13/09/2026 sweep: the height floor held, the width did not. */
|
|
458
|
-
.ui-stat.is-clickable { min-width: 44px; justify-content: center; }
|
|
459
|
-
/* Same for a one-word link chip: partner-radar /posts "All" measured 41x44 at 320 / 390 / 768 (visual-gate 03/10/2026,
|
|
460
|
-
after its labels went English and got shorter). */
|
|
461
|
-
.ui-chips > a.ui-tag--chip { min-width: 44px; justify-content: center; }
|
|
462
|
-
/* `--slim` is a second single class and lands later in this file, so it would win on order over a
|
|
463
|
-
bare `.ui-select`. Doubled here to decide it on specificity instead. */
|
|
464
|
-
.ui-select.ui-select,
|
|
465
|
-
.ui-select--slim.ui-select--slim { min-height: 44px; }
|
|
466
|
-
/* The icon-only segment is 36 WIDE as well — a target is an area. */
|
|
467
|
-
.ui-seg :is(button, a) { min-width: 44px; }
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/* ── Skeleton ──
|
|
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
|
|
472
|
-
trí — nên nó tắt hẳn với `prefers-reduced-motion`, giữ nguyên vạch tĩnh. */
|
|
473
|
-
.ui-skeleton { display: flex; flex-direction: column; gap: var(--ui-space-2); }
|
|
474
|
-
.ui-skeleton__line {
|
|
475
|
-
display: block; height: 12px; border-radius: 6px;
|
|
476
|
-
background: linear-gradient(90deg, var(--ui-surface-2) 25%, var(--ui-border) 37%, var(--ui-surface-2) 63%);
|
|
477
|
-
background-size: 400% 100%;
|
|
478
|
-
animation: ui-skeleton-sheen 1.4s ease-in-out infinite;
|
|
479
|
-
}
|
|
480
|
-
@keyframes ui-skeleton-sheen { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
|
|
481
|
-
@media (prefers-reduced-motion: reduce) {
|
|
482
|
-
.ui-skeleton__line { animation: none; background: var(--ui-surface-2); }
|
|
483
|
-
}
|
|
484
|
-
/* Nhãn cho trình đọc màn hình — thứ `<p>Loading…</p>` cũ vốn có và một khối hình thuần sẽ mất. */
|
|
485
|
-
.ui-sr-only {
|
|
486
|
-
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
487
|
-
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/* ── Resizer: the seam you drag between two panels ──
|
|
492
|
-
One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
|
|
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
|
|
494
|
-
placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
|
|
495
|
-
(and may put `dragging` on the seam itself to keep its grip lit).
|
|
496
|
-
Markup: <div class="ui-resizer" role="separator" aria-orientation="vertical" tabindex="0"
|
|
497
|
-
aria-label="…" aria-valuenow aria-valuemin aria-valuemax>. Pointer events, not mouse events (touch
|
|
498
|
-
is a pointer); arrow keys move it — a focusable separator that ignores the keyboard is INERT. */
|
|
499
|
-
/* `touch-action: none` is what makes a FINGER drag reach this element at all. `preventDefault()` on
|
|
500
|
-
`pointerdown` does not stop panning: the browser hands the gesture to the nearest scroller and sends
|
|
501
|
-
`pointercancel`, so the seam jumps once and stops. */
|
|
502
|
-
.ui-resizer { cursor: col-resize; position: relative; touch-action: none; outline: none; }
|
|
503
|
-
/* The grip pill is invisible at rest (a clean seam) and fades in over the zone, on keyboard focus,
|
|
504
|
-
and while a drag runs. Focus shows the pill rather than a ring: on a 5px seam a ring is a smear. */
|
|
505
|
-
.ui-resizer::before {
|
|
506
|
-
content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
507
|
-
width: 4px; height: 44px; border-radius: var(--gemboss-radius-full);
|
|
508
|
-
background: var(--gemboss-primary);
|
|
509
|
-
opacity: 0;
|
|
510
|
-
transition: opacity 0.14s ease, height 0.14s ease;
|
|
511
|
-
}
|
|
512
|
-
.ui-resizer:hover::before,
|
|
513
|
-
.ui-resizer:focus-visible::before,
|
|
514
|
-
.ui-resizer.dragging::before,
|
|
515
|
-
body.ui-resizing .ui-resizer::before { opacity: 1; height: 52px; }
|
|
516
|
-
/* A FINGER NEEDS MORE THAN 5px, and the line must still look like 5px. WCAG 2.5.8 puts the floor for
|
|
517
|
-
this kind of target at 24px, so the HIT area grows and the visible seam does not — widening the
|
|
518
|
-
element itself would also move the columns. Only where the pointer is coarse. */
|
|
519
|
-
@media (pointer: coarse) {
|
|
520
|
-
.ui-resizer::after {
|
|
521
|
-
content: ""; position: absolute; top: 0; bottom: 0; left: -14px; right: -14px;
|
|
522
|
-
touch-action: none; /* the band must claim the gesture too, or the scroller still wins */
|
|
523
|
-
z-index: 2; /* above the panels either side, transparent */
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-select: none !important; }
|
|
527
|
-
/* While dragging, a full-viewport transparent shield sits above everything — iframes included — so the
|
|
528
|
-
pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
|
|
529
|
-
body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
|
|
530
|
-
|
|
531
|
-
/* ── Tabs · Table · Empty state ──
|
|
532
|
-
Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
|
|
533
|
-
file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
|
|
534
|
-
for these; nothing in apps/admin uses the classes below, and they restyle no existing component.
|
|
535
|
-
Only `--ui-*` / `--gemboss-*` tokens, so the dark `.gemboss-admin-shell` bridge applies too.
|
|
536
|
-
|
|
537
|
-
Tabs: `<div class="ui-tabs" role="tablist"><button role="tab" aria-selected="true">…` — the
|
|
538
|
-
selected state IS `aria-selected`, so what a screen reader announces and what is painted cannot
|
|
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). */
|
|
541
|
-
.ui-tabs {
|
|
542
|
-
display: flex; gap: var(--ui-space-2); border-bottom: 1px solid var(--ui-border);
|
|
543
|
-
overflow-x: auto; scrollbar-width: none;
|
|
544
|
-
}
|
|
545
|
-
.ui-tabs > button,
|
|
546
|
-
.ui-tabs > a {
|
|
547
|
-
position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
|
|
548
|
-
min-height: var(--ui-btn-h); padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
|
|
549
|
-
border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
|
|
550
|
-
font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550; white-space: nowrap;
|
|
551
|
-
color: var(--ui-text-muted);
|
|
552
|
-
transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
|
|
553
|
-
}
|
|
554
|
-
.ui-tabs > button:hover,
|
|
555
|
-
.ui-tabs > a:hover { color: var(--ui-text); background: var(--ui-surface-2); }
|
|
556
|
-
.ui-tabs > button:focus-visible,
|
|
557
|
-
.ui-tabs > a:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
558
|
-
.ui-tabs > a { text-decoration: none; }
|
|
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. */
|
|
560
|
-
.ui-tabs > a[aria-current="page"],
|
|
561
|
-
.ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); }
|
|
562
|
-
.ui-tabs > a[aria-current="page"]::after,
|
|
563
|
-
.ui-tabs > button[aria-selected="true"]::after {
|
|
564
|
-
content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
|
|
565
|
-
height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
|
|
566
|
-
}
|
|
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; } }
|
|
569
|
-
|
|
570
|
-
/* Table: wrap it — `<div class="ui-table-wrap"><table class="ui-table">` — so a wide table scrolls
|
|
571
|
-
inside its card instead of pushing the page sideways at 320px. Numeric cells take `.is-num`:
|
|
572
|
-
right-aligned tabular figures, so a column of numbers lines up digit for digit. */
|
|
573
|
-
.ui-table-wrap {
|
|
574
|
-
overflow-x: auto; max-width: 100%;
|
|
575
|
-
background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
|
|
576
|
-
box-shadow: var(--ui-card-shadow);
|
|
577
|
-
}
|
|
578
|
-
.ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-fs-body); color: var(--ui-text); }
|
|
579
|
-
.ui-table th,
|
|
580
|
-
.ui-table td {
|
|
581
|
-
padding: var(--ui-space-2) 6px; text-align: left; vertical-align: middle;
|
|
582
|
-
border-bottom: 1px solid var(--ui-border);
|
|
583
|
-
}
|
|
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 */
|
|
591
|
-
.ui-table tbody tr:last-child > td { border-bottom: 0; }
|
|
592
|
-
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
|
|
593
|
-
.ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
594
|
-
/* Dòng phụ dưới nội dung chính của một ô (lớp, nguồn, ngày…): `<td><a>Loox</a><span class="ui-sub">Cộng sinh</span></td>`.
|
|
595
|
-
Trước đây mỗi surface tự viết `.muted` riêng trong ô bảng (partner-radar, gemwatcher). */
|
|
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).
|
|
597
|
-
Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
|
|
598
|
-
.ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
|
|
599
|
-
.ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
|
|
600
|
-
|
|
601
|
-
/* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
|
|
602
|
-
<button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
|
|
603
|
-
.ui-empty {
|
|
604
|
-
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--ui-space-2);
|
|
605
|
-
padding: var(--ui-space-4); padding-block: 40px; text-align: center;
|
|
606
|
-
color: var(--ui-text-muted); font-size: var(--ui-fs-body);
|
|
607
|
-
}
|
|
608
|
-
.ui-empty__title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text-strong); }
|
|
609
|
-
.ui-empty__body { margin: 0; max-width: 48ch; line-height: 1.5; }
|
|
610
|
-
.ui-empty > .ui-btn { margin-top: var(--ui-space-2); }
|
|
1
|
+
:root{--ui-surface: #ffffff;--ui-surface: var(--gemboss-surface, #ffffff);--ui-surface-2: var(--gemboss-hover, #eceef2);--ui-text: var(--gemboss-text, #1b1c1e);--ui-text-muted: var(--gemboss-text-muted, #686b71);--ui-border: var(--gemboss-divider, #e5e7ea);--ui-accent: var(--gemboss-primary, #1b1c1e);--ui-accent-hover: var(--gemboss-primary-dark, #000000);--ui-accent-fg: #ffffff;--ui-accent-soft: var(--gemboss-active-bg, #f0f1f4);--ui-accent-strong: var(--gemboss-active-fg, #1b1c1e);--ui-text-strong: var(--gemboss-text-strong, #1b1c1e);--ui-success-fg: var(--gemboss-success);--ui-warning-fg: var(--gemboss-primary-dark);--ui-danger-fg: var(--gemboss-danger-strong);--ui-focus: var(--gemboss-accent, #5661f5);--ui-radius-sm: 6px;--ui-radius-md: 8px;--ui-radius-lg: 12px;--ui-space-1: 4px;--ui-space-2: 8px;--ui-space-3: 12px;--ui-space-4: 16px;--ui-control-h: 32px;--ui-btn-h: 28px;--ui-card-shadow: 0 1px 0 rgba(26,26,26,.07);--ui-fs-body: 13px;--ui-fs-caption: 12px;--ui-content-max: 900px}.gemboss-admin-shell{--ui-surface: var(--adm-surface);--ui-surface-2: var(--adm-surface-2);--ui-text: var(--adm-text);--ui-text-muted: var(--adm-muted);--ui-border: var(--adm-divider);--ui-accent: var(--adm-accent);--ui-accent-hover: var(--gemboss-primary-dark, #000000);--ui-accent-fg: #ffffff;--ui-accent-soft: var(--adm-accent-soft);--ui-accent-strong: var(--adm-accent-fg);--ui-text-strong: var(--adm-text-strong);--ui-success-fg: color-mix(in srgb, var(--gemboss-success) 70%, #ffffff);--ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);--ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);--ui-focus: var(--adm-accent);--ui-card-shadow: none}.ui-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--ui-space-2);height:var(--ui-btn-h);padding:0 var(--ui-space-3);box-sizing:border-box;line-height:1;white-space:nowrap;vertical-align:middle;border-radius:var(--ui-radius-md);font-family:inherit;font-size:var(--ui-fs-caption);font-weight:550;cursor:pointer;text-decoration:none;user-select:none;border:1px solid var(--ui-border);background:transparent;color:var(--ui-text);transition:background .12s ease,border-color .12s ease}.ui-btn:hover{background:var(--ui-surface-2);text-decoration:none}.ui-btn:focus-visible{outline:2px solid var(--ui-focus);outline-offset:2px}.ui-btn[disabled],.ui-btn[aria-disabled=true]{opacity:.5;cursor:not-allowed}.ui-btn--primary{background:var(--ui-accent);border-color:var(--ui-accent);color:var(--ui-accent-fg)}.ui-btn--primary:hover{background:var(--ui-accent-hover);border-color:var(--ui-accent-hover)}.ui-btn--ghost{border-color:transparent;background:transparent;color:var(--ui-text-muted)}.ui-btn--ghost:hover{background:var(--ui-surface-2);color:var(--ui-text)}.ui-card{background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius-lg);box-shadow:var(--ui-card-shadow);padding:var(--ui-space-4);color:var(--ui-text)}.ui-card__title{font-size:14px;line-height:20px;font-weight:650;margin:0 0 var(--ui-space-2)}.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)}.ui-card__head>.ui-card__title{margin:0}.ui-card__head>:last-child:not(.ui-card__title){display:flex;flex-wrap:wrap;gap:var(--ui-space-2)}.ui-select{height:var(--ui-control-h);padding:0 32px 0 12px;max-width:100%;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;border:1px solid var(--p-color-input-border, var(--gemboss-field-line));border-radius:var(--ui-radius-md);color:var(--p-color-text, var(--gemboss-field-ink));font-family:inherit;font-size:var(--ui-fs-body);font-weight:450;appearance:none;-webkit-appearance:none;cursor:pointer}.ui-select:hover:not(:disabled){border-color:var(--p-color-input-border-hover, var(--gemboss-field-line-hover));background-color:var(--p-color-input-bg-surface-hover, var(--gemboss-field-bg-hover))}.ui-select:focus{outline:none}.ui-select:focus-visible:not(:disabled){outline:2px solid var(--p-color-border-focus, var(--ui-focus));outline-offset:1px;border-color:var(--p-color-input-border-active, var(--gemboss-field-line-active))}.ui-select:disabled{opacity:.5;cursor:not-allowed}.ui-select--slim{height:30px;font-size:var(--ui-fs-caption)}.ui-tag{display:inline-flex;align-items:center;gap:6px;font-size:var(--ui-fs-caption);line-height:20px;font-weight:450;padding:0 6px;border-radius:var(--ui-radius-md);background:var(--ui-surface-2);color:var(--ui-text-muted)}.ui-tag--accent{background:var(--adm-accent-soft, rgba(20,22,26,.05));color:var(--adm-accent-fg, var(--ui-accent))}.ui-tag--chip{text-transform:none;letter-spacing:0;font-size:11px;font-weight:500;padding:2px 8px;background:transparent;border:1px solid var(--ui-border);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.ui-tag--chip.ui-tag--accent{color:var(--adm-accent-fg, var(--ui-accent));border-color:var(--ui-accent);background:transparent}.ui-chips{display:flex;flex-wrap:wrap;gap:var(--ui-space-2, 8px);margin:0;padding:0;list-style:none}.ui-chips>a.ui-tag--chip{min-height:28px;padding:4px 12px;border-radius:var(--ui-radius-md, 8px);font-family:inherit;font-size:var(--gemboss-fs-caption, 12px);color:var(--ui-text);text-decoration:none}.ui-chips>a.ui-tag--chip:hover{background:var(--ui-surface-2);border-color:var(--ui-text-muted)}.ui-chips>a.ui-tag--chip:focus-visible{outline:2px solid var(--p-color-border-focus, var(--ui-focus));outline-offset:1px}.ui-chips>a.ui-tag--chip[aria-current=page]{background:var(--ui-accent-soft);border-color:var(--ui-accent);color:var(--ui-accent-strong);font-weight:600}.ui-tag--success{background:var(--gemboss-success-soft);color:var(--gemboss-success-strong, var(--gemboss-success))}.ui-tag--warning{background:var(--gemboss-warning-soft);color:var(--gemboss-warning)}.ui-tag--danger{background:var(--gemboss-danger-soft);color:var(--gemboss-danger-strong, var(--gemboss-danger))}.ui-muted{color:var(--ui-text-muted);font-size:var(--ui-fs-caption)}.ui-page-head{margin-bottom:20px}.ui-page-head h1{color:var(--ui-text-strong);font-size:20px;line-height:24px;font-weight:700;letter-spacing:-.01em}.ui-page-head p{color:var(--ui-text-muted);font-size:var(--ui-fs-body);margin-top:4px}.ui-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:20px}.ui-metrics{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0}.ui-metric{display:flex;flex-direction:column;gap:1px;min-width:78px;padding:8px 12px;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:12px}a.ui-metric{text-decoration:none;color:inherit}a.ui-metric:hover{border-color:var(--ui-text-muted)}a.ui-metric:focus-visible{outline:2px solid var(--ui-focus);outline-offset:1px}.ui-metric__val{font-size:17px;font-weight:700;color:var(--ui-text);font-variant-numeric:tabular-nums;line-height:1.15}.ui-metric__lbl{font-size:var(--ui-fs-caption);font-weight:500;color:var(--ui-text-muted)}.ui-metric--success{border-color:color-mix(in srgb,var(--gemboss-success) 40%,transparent)}.ui-metric--success .ui-metric__val{color:var(--ui-success-fg)}.ui-metric--warning{border-color:color-mix(in srgb,#c99a2e 40%,transparent)}.ui-metric--warning .ui-metric__val{color:var(--ui-warning-fg)}.ui-metric--danger{border-color:color-mix(in srgb,var(--gemboss-danger-strong) 40%,transparent)}.ui-metric--danger .ui-metric__val{color:var(--ui-danger-fg)}.ui-dl{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px 16px;margin:0}.ui-dl>div{min-width:0}.ui-dl dt{font-size:var(--ui-fs-caption);color:var(--ui-text-muted)}.ui-dl dd{margin:4px 0 0;font-weight:600;color:var(--ui-text);overflow-wrap:anywhere}.ui-notice{padding:12px 16px;border-radius:var(--ui-radius-md);border:1px solid var(--ui-border);background:var(--ui-surface-2);color:var(--ui-text);font-size:var(--ui-fs-body);overflow-wrap:anywhere}.ui-notice--warning{background:var(--gemboss-warning-soft);border-color:var(--gemboss-warning-border);color:var(--gemboss-warning)}.ui-notice--danger{background:var(--gemboss-danger-soft);border-color:var(--gemboss-danger-border);color:var(--gemboss-danger-strong)}.ui-notice--success{background:var(--gemboss-success-soft);border-color:var(--gemboss-success-border);color:var(--gemboss-success-strong)}.ui-pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:16px 0}.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)}.ui-pager>a:hover{background:var(--ui-surface-2)}.ui-pager>a:focus-visible{outline:2px solid var(--p-color-border-focus, var(--ui-focus));outline-offset:1px}.ui-pager>[aria-current=page]{color:var(--ui-text-muted);font-size:var(--ui-fs-caption)}.ui-badge{display:inline-flex;align-items:center;gap:4px;font-size:var(--ui-fs-caption);line-height:16px;font-weight:450;white-space:nowrap;padding:2px 8px;border-radius:var(--ui-radius-md);background:color-mix(in srgb,var(--ui-text) 8%,transparent);color:var(--ui-text-muted)}.ui-overlay{position:fixed;inset:0;z-index:500;background:#00000080;display:grid;place-items:center;padding:20px}.ui-modal{width:min(460px,100%);background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:16px;box-shadow:var(--gemboss-shadow-overlay, 0 20px 60px rgba(0, 0, 0, .4));overflow:hidden}.ui-modal__head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--ui-border);font-size:14px;line-height:20px;font-weight:650;color:var(--ui-text)}.ui-modal__title{min-width:0;flex:1 1 auto}.ui-modal__close{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:var(--ui-radius-md, 8px);background:transparent;color:var(--ui-text-muted);cursor:pointer}.ui-modal__close svg{width:20px;height:20px}.ui-modal__close:hover{background:var(--ui-surface-2);color:var(--ui-text)}.ui-modal__close:focus-visible{outline:2px solid var(--ui-focus);outline-offset:-2px}.ui-modal__body{padding:16px}.ui-modal__foot{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;padding:12px 16px;border-top:1px solid var(--ui-border)}@media (pointer: coarse),(max-width: 47.9975em){.ui-modal__close{width:44px;height:44px;margin:-8px -8px -8px 0}:is(.ui-modal__head,.ui-modal__foot) .ui-btn{min-height:44px}}.ui-modal--lg{position:relative}.ui-textarea{width:100%;min-height:96px;resize:vertical;box-sizing:border-box;background:var(--p-color-input-bg-surface, var(--gemboss-field-bg));border:1px solid var(--p-color-input-border, var(--gemboss-field-line));border-radius:var(--ui-radius-md);padding:12px;color:var(--p-color-text, var(--gemboss-field-ink));font-size:var(--ui-fs-body);line-height:1.5;font-family:inherit}.ui-textarea::placeholder{color:var(--p-color-text-secondary, var(--gemboss-field-placeholder))}.ui-textarea:hover:not(:disabled){border-color:var(--p-color-input-border-hover, var(--gemboss-field-line-hover));background-color:var(--p-color-input-bg-surface-hover, var(--gemboss-field-bg-hover))}.ui-textarea:focus{outline:none}.ui-textarea:focus-visible:not(:disabled){outline:2px solid var(--p-color-border-focus, var(--ui-focus));outline-offset:1px;border-color:var(--p-color-input-border-active, var(--gemboss-field-line-active))}.ui-textarea:disabled{opacity:.5;cursor:not-allowed}@media (max-width: 47.9975em){.ui-select.ui-select,.ui-textarea{font-size:16px}.ui-select:not(.ui-select--slim){min-height:44px}}.ui-seg{display:inline-flex;border:1px solid var(--ui-border);border-radius:var(--ui-radius-md);overflow:hidden}.ui-seg :is(button,a){display:inline-grid;place-items:center;width:calc(var(--ui-btn-h) - 2px);height:calc(var(--ui-btn-h) - 2px);border:0;background:transparent;color:var(--ui-text-muted);cursor:pointer;font-family:inherit}.ui-seg :is(button,a).is-text{width:auto;padding:0 12px;font-size:var(--ui-fs-caption);font-weight:550;grid-auto-flow:column;column-gap:2px}.ui-seg :is(button,a)+:is(button,a){border-left:1px solid var(--ui-border)}.ui-seg :is(button,a):hover{background:var(--ui-surface-2);color:var(--ui-text)}.ui-seg :is(button,a):focus-visible{outline:2px solid var(--ui-focus);outline-offset:-2px}.ui-seg a{display:inline-flex;align-items:center;justify-content:center;text-decoration:none;color:var(--ui-text-muted)}.ui-seg a[aria-current=page],.ui-seg button.active{background:var(--ui-accent-soft);color:var(--ui-accent-strong)}.ui-seg svg{width:16px;height:16px;fill:currentColor}.ui-seg svg[fill=none]{fill:none}.ui-seg :is(button,a):disabled{opacity:.45;cursor:not-allowed}.ui-seg :is(button,a):disabled:not(.active):hover{background:transparent;color:var(--ui-text-muted)}.ui-seg--fill{display:flex;width:100%}.ui-seg.ui-seg--fill>:is(button,a).is-text{flex:1 1 0;min-width:0}.ui-seg-n{margin-left:6px;font-variant-numeric:tabular-nums;color:var(--ui-text-muted);font-weight:450}.ui-stats{display:flex;flex-wrap:wrap;gap:var(--ui-space-2);margin-bottom:24px}.ui-stat{display:inline-flex;align-items:center;gap:var(--ui-space-2);padding:6px 12px;border-radius:999px;font-family:inherit;background:var(--ui-surface);border:1px solid var(--ui-border);font-size:var(--ui-fs-caption);color:var(--ui-text-muted)}.ui-stat b{color:var(--ui-text-strong);font-weight:650}.ui-stat.is-clickable{cursor:pointer;transition:border-color .14s ease,background-color .14s ease}.ui-stat.is-clickable:hover{border-color:var(--ui-text-muted);background:var(--ui-surface-2)}.ui-stat.is-clickable.active{border-color:var(--ui-accent);background:var(--ui-accent-soft);color:var(--ui-accent-strong)}.ui-stat.is-clickable.active b{color:var(--ui-accent-strong)}.gemboss-toast-viewport{position:fixed;left:50%;bottom:24px;transform:translate(-50%);z-index:2147483000;display:flex;flex-direction:column;align-items:center;gap:12px;width:max-content;max-width:min(560px,calc(100vw - 32px));pointer-events:none}.gemboss-toast{pointer-events:auto;display:flex;align-items:center;gap:12px;min-width:220px;max-width:100%;padding:12px 12px 12px 16px;background:var(--ui-text-strong, #1a1917);color:var(--gemboss-on-primary, #fff);border-radius:12px;font-size:var(--ui-fs-body, 13px);line-height:1.4;font-weight:500;box-shadow:var(--gemboss-shadow-overlay, 0 8px 28px rgba(0, 0, 0, .24));animation:gemboss-toast-in .24s cubic-bezier(.16,1,.3,1)}.gemboss-toast.is-error{background:var(--gemboss-danger, #d72c0d)}.gemboss-toast-text{flex:1 1 auto;min-width:0}.gemboss-toast-action{flex:0 0 auto;border:0;background:transparent;color:var(--gemboss-on-primary, #fff);cursor:pointer;padding:4px 8px;border-radius:8px;font:inherit;font-weight:650;text-decoration:underline;text-underline-offset:2px;transition:background .14s ease}.gemboss-toast-action:hover{background:#ffffff29}.gemboss-toast-dismiss{flex:0 0 auto;display:inline-grid;place-items:center;width:24px;height:24px;border:0;border-radius:8px;cursor:pointer;background:#ffffff1f;color:var(--gemboss-on-primary, #fff);transition:background .14s ease}.gemboss-toast-dismiss:hover{background:#ffffff42}.gemboss-toast-action:focus-visible,.gemboss-toast-dismiss:focus-visible{outline:2px solid var(--gemboss-on-primary, #fff);outline-offset:2px}@keyframes gemboss-toast-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@media (prefers-reduced-motion: reduce){.gemboss-toast{animation:none}}@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}}@media (pointer: coarse){.ui-btn:not(.ui-btn--sm),.ui-stat.is-clickable,.ui-chips>a.ui-tag--chip,.ui-pager>a,.ui-seg :is(button,a){min-height:44px}.ui-stat.is-clickable{min-width:44px;justify-content:center}.ui-chips>a.ui-tag--chip{min-width:44px;justify-content:center}.ui-select.ui-select,.ui-select--slim.ui-select--slim{min-height:44px}.ui-seg :is(button,a){min-width:44px}}.ui-skeleton{display:flex;flex-direction:column;gap:var(--ui-space-2)}.ui-skeleton__line{display:block;height:12px;border-radius:6px;background:linear-gradient(90deg,var(--ui-surface-2) 25%,var(--ui-border) 37%,var(--ui-surface-2) 63%);background-size:400% 100%;animation:ui-skeleton-sheen 1.4s ease-in-out infinite}@keyframes ui-skeleton-sheen{0%{background-position:100% 50%}to{background-position:0 50%}}@media (prefers-reduced-motion: reduce){.ui-skeleton__line{animation:none;background:var(--ui-surface-2)}}.ui-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.ui-resizer{cursor:col-resize;position:relative;touch-action:none;outline:none}.ui-resizer:before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:4px;height:44px;border-radius:var(--gemboss-radius-full);background:var(--gemboss-primary);opacity:0;transition:opacity .14s ease,height .14s ease}.ui-resizer:hover:before,.ui-resizer:focus-visible:before,.ui-resizer.dragging:before,body.ui-resizing .ui-resizer:before{opacity:1;height:52px}@media (pointer: coarse){.ui-resizer:after{content:"";position:absolute;inset:0 -14px;touch-action:none;z-index:2}}body.ui-resizing,body.ui-resizing *{cursor:col-resize!important;user-select:none!important}body.ui-resizing:after{content:"";position:fixed;inset:0;z-index:9999;cursor:col-resize}.ui-tabs{display:flex;gap:var(--ui-space-2);border-bottom:1px solid var(--ui-border);overflow-x:auto;scrollbar-width:none}.ui-tabs>button,.ui-tabs>a{position:relative;flex:0 0 auto;display:inline-flex;align-items:center;gap:var(--ui-space-2);min-height:var(--ui-btn-h);padding:0 var(--ui-space-3);border:0;background:transparent;cursor:pointer;border-radius:var(--ui-radius-sm) var(--ui-radius-sm) 0 0;font-family:inherit;font-size:var(--ui-fs-caption);font-weight:550;white-space:nowrap;color:var(--ui-text-muted);transition:color var(--gemboss-transition-fast) ease,background-color var(--gemboss-transition-fast) ease}.ui-tabs>button:hover,.ui-tabs>a:hover{color:var(--ui-text);background:var(--ui-surface-2)}.ui-tabs>button:focus-visible,.ui-tabs>a:focus-visible{outline:2px solid var(--ui-focus);outline-offset:-2px}.ui-tabs>a{text-decoration:none}.ui-tabs>a[aria-current=page],.ui-tabs>button[aria-selected=true]{color:var(--ui-text-strong)}.ui-tabs>a[aria-current=page]:after,.ui-tabs>button[aria-selected=true]:after{content:"";position:absolute;left:var(--ui-space-3);right:var(--ui-space-3);bottom:0;height:2px;border-radius:var(--gemboss-radius-full);background:var(--ui-accent)}.ui-tabs>button:disabled{opacity:.5;cursor:not-allowed}@media (pointer: coarse),(max-width: 47.9975em){.ui-tabs>:is(button,a){min-height:44px}}.ui-table-wrap{overflow-x:auto;max-width:100%;background:var(--ui-surface);border:1px solid var(--ui-border);border-radius:var(--ui-radius-lg);box-shadow:var(--ui-card-shadow)}.ui-table{width:100%;border-collapse:collapse;font-size:var(--ui-fs-body);color:var(--ui-text)}.ui-table th,.ui-table td{padding:var(--ui-space-2) 6px;text-align:left;vertical-align:middle;border-bottom:1px solid var(--ui-border)}.ui-table :is(th,td):where(:first-child){padding-left:var(--ui-space-3)}.ui-table :is(th,td):where(:last-child){padding-right:var(--ui-space-3)}.ui-table th{font-weight:450;color:var(--ui-text-muted);white-space:nowrap}.ui-table thead th{padding-block:var(--ui-space-3)}.ui-table tbody tr:last-child>td{border-bottom:0}.ui-table tbody tr:hover{background:var(--ui-surface-2)}.ui-table .is-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}.ui-table td>a{display:inline-flex;align-items:center;min-height:24px}.ui-table .ui-sub{display:block;margin-top:2px;font-size:var(--ui-fs-caption);color:var(--ui-text-muted);font-weight:400}.ui-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ui-space-2);padding:var(--ui-space-4);padding-block:40px;text-align:center;color:var(--ui-text-muted);font-size:var(--ui-fs-body)}.ui-empty__title{margin:0;font-size:14px;line-height:20px;font-weight:650;color:var(--ui-text-strong)}.ui-empty__body{margin:0;max-width:48ch;line-height:1.5}.ui-empty>.ui-btn{margin-top:var(--ui-space-2)}
|