@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/src/kit-plain.css CHANGED
@@ -1,483 +1 @@
1
- /* kit-plain.css — kit primitives that only the NON-React surfaces use (GemFactory, gemcare, gempress write the
2
- markup by hand). They ship inside gemboss-ui.css (scripts/build-css.mjs `own`), but the admin does NOT import
3
- this file: `ui.css` is loaded by the admin's root layout, so every rule there is paid on every admin screen,
4
- and adding these to it pushed the admin shell 0.2 KB over its first-load budget (CI of #3591, 01/10/2026).
5
- The admin has Polaris for menus, checkboxes and description lists. */
6
-
7
- /* ── Icon button · Menu · Key-value list ──────────────────────────────────────
8
- Added 01/10/2026 for GemFactory's item dialog, which had seven actions in one header row; the
9
- secondary five now sit behind one "⋯" button. Plain CSS, same contract as the tabs and tables in ui.css.
10
-
11
- Icon button: `<button class="ui-btn ui-btn--icon" aria-label="…"><svg…></button>` — square, the
12
- label is REQUIRED (there is no text for a screen reader to read otherwise).
13
-
14
- Menu: a `<details>` so it opens with no script. `<details class="ui-menu ui-menu--end">
15
- <summary class="ui-btn ui-btn--icon" aria-label="More actions">…</summary><div class="ui-menu__list">
16
- <button class="ui-menu__item">Copy #12<span class="ui-menu__hint">for Claude Code</span></button>
17
- <hr class="ui-menu__sep"><button class="ui-menu__item ui-menu__item--danger">Delete…</button></div>
18
- </details>`. `--end` aligns the list to the right edge of its button. Closing on an outside click,
19
- on an item, and on Escape is the page's job (a few lines; GemFactory's ui-kit.mjs has them).
20
- Items are 44px under EVERY pointer, not only coarse: they touch each other (0px apart), and §9.1
21
- only allows a gap under 8px between targets that are already 44px.
22
-
23
- Key-value list: `<dl class="ui-kv"><dt>Owner</dt><dd>Lily</dd>…</dl>` — label column sized to
24
- its longest label, value column takes the rest and wraps long keys and URLs. */
25
- .ui-btn--icon { flex: none; width: var(--ui-btn-h); padding: 0; }
26
- .ui-btn--icon svg { width: 18px; height: 18px; }
27
- /* Menu: the SURFACE is the shell's `gemboss-popover-surface` and the rows are the account menu's row (shell.css
28
- declares `.gemboss-account-menu-item, .ui-menu__item` together), so a kit menu and the account menu on one page
29
- look the same and a fix to one reaches the other (§9.3). Markup: `<div class="ui-menu__list gemboss-popover-surface">`.
30
- Here: only placement, the hint, the separator and the disabled state. */
31
- .ui-menu { position: relative; display: inline-block; }
32
- .ui-menu > summary { list-style: none; cursor: pointer; }
33
- .ui-menu > summary::-webkit-details-marker { display: none; }
34
- .ui-menu__list { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; display: grid; min-width: 220px; padding: var(--ui-space-1); box-sizing: border-box; }
35
- .ui-menu--end .ui-menu__list { left: auto; right: 0; }
36
- .ui-menu__item { display: flex; align-items: center; gap: var(--ui-space-2); white-space: nowrap; border-radius: var(--ui-radius-sm); }
37
- .ui-menu__item:is([disabled], [aria-disabled="true"]) { opacity: .5; cursor: not-allowed; pointer-events: none; }
38
- .ui-menu__hint { margin-left: auto; padding-left: var(--ui-space-4); color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
39
- .ui-menu__sep { height: 1px; margin: var(--ui-space-1) 0; border: 0; background: var(--ui-border); }
40
- /* Checkbox: the LABEL is the target; disabled turns both the label and the box to not-allowed. Focus ring in the
41
- kit's own token (no --p-* here: this file is appended after the export's Polaris resolver runs). */
42
- .ui-check { display: inline-flex; align-items: center; gap: var(--ui-space-2); min-height: 44px; max-width: 100%; overflow-wrap: break-word; cursor: pointer; font-size: var(--ui-fs-body); color: var(--ui-text); }
43
- .ui-check > input { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--ui-accent); cursor: inherit; }
44
- .ui-check > input:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 2px; }
45
- .ui-check:has(> input:disabled) { opacity: .5; cursor: not-allowed; }
46
- /* 0.9.0: a long run with no spaces (a URL, "clinical/premium/…/utility") breaks inside the label instead of widening
47
- the page: gemwatcher Operations overflowed 69px at 320 on one switch description (#375). Three parts: the label is
48
- capped at its parent (max-width above), its TEXT child may shrink (min-width 0, the last child only so a badge in
49
- front keeps its width), and break-word breaks the run once it cannot fit a line.
50
- break-word, NOT anywhere: anywhere also drops the label's min-content, and a flex row then squeezes a short label
51
- ("Lock" beside a long band name in GemFactory) into "Lo/ck". Two cases stay the surface's job: the text must be an
52
- element (`input + span`; bare text is an anonymous flex item min-width cannot reach), and in a flex ROW the label
53
- keeps its min-content, so that row must wrap. */
54
- .ui-check > :last-child:not(input) { min-width: 0; }
55
- .ui-label { display: block; margin: 0 0 var(--ui-space-1); font-size: var(--ui-fs-caption); font-weight: 600; color: var(--ui-text-muted); }
56
- .ui-kv { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--ui-space-2) var(--ui-space-4); margin: 0; font-size: var(--ui-fs-body); }
57
- .ui-kv > dt { color: var(--ui-text-muted); }
58
- .ui-kv > dd { margin: 0; min-width: 0; overflow-wrap: anywhere; color: var(--ui-text); }
59
-
60
- /* Under a thumb the icon-only button is 44 WIDE as well (the kit's coarse block already raises every .ui-btn to 44 tall). */
61
- @media (pointer: coarse) { .ui-btn--icon:not(.ui-btn--sm) { min-width: 44px; } }
62
- /* …and at phone WIDTH, as gemboss-field does: a desktop browser at 320px still reports pointer: fine. */
63
- @media (max-width: 47.9975em) { .ui-btn--icon:not(.ui-btn--sm) { min-width: 44px; min-height: 44px; } }
64
-
65
- /* ── Link · Media card (02/10/2026) ──────────────────────────────────────────
66
- GemFactory, gemcare and gempress left text links in the browser's default blue-and-purple, and each drew its own
67
- thumbnail card. The admin uses Polaris' Link and has no media card, so both live here, not in ui.css.
68
-
69
- Link: `<a class="ui-link" href>` for a link inside text. Iris (--gemboss-accent-strong, 6.2:1 on white), underlined
70
- so it is not told by colour alone (WCAG 1.4.1); visited looks the same (these are tools, not articles). Markdown or
71
- other generated HTML you cannot add classes to: wrap it in `.ui-prose` and its bare `<a>` get the same look.
72
- `.ui-prose` links sit at :where() specificity (0.7.3), so any component's own link rule wins inside it.
73
-
74
- Media card: `<article class="ui-card ui-card--media"><a|button class="ui-card__media"><img alt=""></…>
75
- <div class="ui-card__body">…</div><div class="ui-card__foot">…</div></article>`. The picture is 16:10 and
76
- cropped to fill; the card has no padding of its own, the body and foot do. */
77
- .ui-link,
78
- .ui-prose :where(a:not([class])) {
79
- color: var(--gemboss-accent-strong, #3a44de);
80
- text-decoration: underline;
81
- text-decoration-thickness: from-font;
82
- text-underline-offset: 2px;
83
- }
84
- .ui-link:visited,
85
- .ui-prose :where(a:not([class])):visited { color: var(--gemboss-accent-strong, #3a44de); }
86
- .ui-link:hover,
87
- .ui-prose :where(a:not([class])):hover { color: var(--gemboss-accent, #5661f5); text-decoration-thickness: 2px; }
88
- .ui-link:focus-visible,
89
- .ui-prose :where(a:not([class])):focus-visible { outline: 2px solid var(--ui-focus, #5661f5); outline-offset: 2px; border-radius: 2px; }
90
-
91
- .ui-card--media { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
92
- .ui-card__media {
93
- display: block; width: 100%; aspect-ratio: 16 / 10; margin: 0; padding: 0; border: 0;
94
- border-bottom: 1px solid var(--ui-border); background: var(--ui-surface-2); overflow: hidden; color: inherit;
95
- }
96
- .ui-card__media > img { display: block; width: 100%; height: 100%; object-fit: cover; }
97
- button.ui-card__media, a.ui-card__media { cursor: pointer; }
98
- .ui-card__media:focus-visible { outline: 2px solid var(--ui-focus, #5661f5); outline-offset: -2px; }
99
- /* justify-items: start: a tag or a button in the body keeps its own width instead of stretching across the card. */
100
- .ui-card__body { display: grid; justify-items: start; gap: var(--ui-space-2, 8px); padding: var(--ui-space-3, 12px) var(--ui-space-4, 16px); min-width: 0; }
101
- .ui-card__foot {
102
- display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ui-space-2, 8px);
103
- margin-top: auto; padding: 0 var(--ui-space-4, 16px) var(--ui-space-3, 12px);
104
- color: var(--ui-text-muted); font-size: var(--ui-fs-caption, 13px);
105
- }
106
-
107
- /* Popup body and foot: `<div class="ui-modal__body">…</div><div class="ui-modal__foot"><button class="ui-btn">…</button></div>`.
108
- Their padding moved to ui.css in 0.7.3 (the admin loads that file, not this one). kit-plain.js's openModal writes
109
- exactly this shape. What stays here is what only the plain-HTML popups use:
110
-
111
- A head that carries more than a title (a large popup's subtitle and its own actions, GemFactory's detail popup):
112
- `<div class="ui-modal__head"><div class="ui-modal__title">Mega menu<span class="ui-modal__subtitle">chrome/header</span>
113
- </div><div class="ui-modal__actions"><button class="ui-btn">Copy</button></div><button class="ui-modal__close">…` */
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; }
115
- .ui-modal__actions { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); }
116
- /* A popup taller than the screen: the body scrolls, the head and foot stay, so the actions are always reachable
117
- (320×568 with a long form put Huỷ/Xoá below the fold, unreachable under the locked page; review #3633). */
118
- .ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
119
- .ui-modal > .ui-modal__head, .ui-modal > .ui-modal__foot { flex: none; }
120
- .ui-modal > .ui-modal__body { flex: 1 1 auto; min-height: 0; overflow: auto; }
121
-
122
- /* ── Báo cáo: delta · KPI lớn · thanh ngang · biểu đồ cột · chú thích · phễu · lưới · tiêu đề mục ─────────────────
123
- Thêm 03/10/2026 cho trang số của gemnews (gemnews.ops.gemboss.ai/admin): nó tự vẽ thanh ngang, biểu đồ theo ngày và
124
- lưới hai cột trong admin.css riêng, và bề mặt thứ hai cần báo cáo sẽ chép lại y như vậy. README "promote what a second
125
- surface needs". Chỉ CSS: số liệu và tỉ lệ do bề mặt tính, kit chỉ vẽ.
126
-
127
- Màu chuỗi: `--ui-series-1..5`, theo thứ tự dùng. 1 là iris thương hiệu; các màu sau đủ khác nhau cả với người mù màu
128
- đỏ-lục (không đặt đỏ cạnh lục). Đặt màu cho một chuỗi bằng `style="--c:var(--ui-series-2)"` trên đúng phần tử. */
129
- :root {
130
- --ui-series-1: var(--gemboss-accent, #5661f5);
131
- --ui-series-2: #0f9f8f;
132
- --ui-series-3: #d9822b;
133
- --ui-series-4: #c2417a;
134
- --ui-series-5: #6b7280;
135
- }
136
-
137
- /* Delta: `<span class="ui-delta ui-delta--good"><span aria-hidden="true">↑</span> 12%</span>`. Tốt / xấu là của BỀ MẶT
138
- quyết (lượt đọc tăng là tốt, tỉ lệ thoát tăng là xấu), mũi tên là hướng. Không tông = trung tính (không đổi, hoặc
139
- "chưa có kỳ trước"). Luôn kèm chữ cho trình đọc màn hình nếu mũi tên là thứ duy nhất nói hướng. */
140
- /* Wraps (0.6.2): a delta that carries its comparison ("▼ 50% vs previous 30 days (20m)") ran 73px out of a 145px metric at
141
- 390 on gemcare. Short ones ("↑ 18%") still sit on one line; long ones break between words, never inside a number. */
142
- .ui-delta { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; }
143
- .ui-delta--good { color: var(--ui-success-fg); }
144
- .ui-delta--bad { color: var(--ui-danger-fg); }
145
-
146
- /* KPI lớn trong lưới đều: `<div class="ui-metrics ui-metrics--grid"><div class="ui-metric ui-metric--lg">…`. Lưới
147
- auto-fit chia đều hàng, nên 4 ô không bao giờ thành 3 + 1 mồ côi ở khổ rộng; ≤ 520px luôn hai cột. `__foot` là dòng
148
- dưới cùng (delta + so với gì). */
149
- .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
150
- .ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
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) */
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); }
154
- @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
155
-
156
- /* Thanh ngang: `<ol class="ui-barlist"><li><span class="ui-barlist__label">Google</span><span class="ui-barlist__val">60
157
- <span class="ui-barlist__share">93,8%</span></span><span class="ui-barlist__bar" style="--v:.94"></span></li></ol>`.
158
- `--v` = độ dài 0..1 (thường n / n lớn nhất), `--c` đổi màu. Nhãn và số ở một dòng, thanh ở dòng dưới chạy hết bề
159
- ngang: không có cột nhãn cố định nào để bị bóp ở 320px. `__note` = dòng phụ (ví dụ "Đọc hết 30%"). */
160
- .ui-barlist { display: grid; gap: var(--ui-space-3); margin: 0; padding: 0; list-style: none; font-size: var(--ui-fs-body, 13px); }
161
- .ui-barlist > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 4px var(--ui-space-3); }
162
- .ui-barlist__label { min-width: 0; overflow-wrap: anywhere; color: var(--ui-text); }
163
- .ui-barlist__label a { color: inherit; }
164
- .ui-barlist__val { font-weight: 600; color: var(--ui-text); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
165
- .ui-barlist__share { margin-left: 6px; font-weight: 400; color: var(--ui-text-muted); }
166
- .ui-barlist__note { grid-column: 1 / -1; margin-top: -2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
167
- .ui-barlist__bar { grid-column: 1 / -1; position: relative; height: 6px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; }
168
- .ui-barlist__bar::after { content: ""; position: absolute; inset: 0 auto 0 0; width: calc(var(--v, 0) * 100%); min-width: 3px; border-radius: inherit; background: var(--c, var(--ui-series-1)); }
169
-
170
- /* Biểu đồ cột chồng theo thời gian, thêm (tuỳ chọn) một chuỗi CHẤM:
171
- `<figure class="ui-chart"><div class="ui-chart__plot" data-max="80"><div class="ui-chart__col" title="02/10 · …">
172
- <span class="ui-chart__seg" style="--v:.4;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" …></span>
173
- <span class="ui-chart__dot" style="--v:.5"></span></div>…</div><div class="ui-chart__axis"><span>01/10</span>…</div>
174
- <figcaption class="ui-legend">…</figcaption></figure>`. `--v` = giá trị / trục (0..1), chồng từ dưới lên theo thứ tự
175
- markup. Cột rộng tối đa 48px và dàn đều hết bề ngang: 4 ngày số liệu không thành 4 khối to. `data-max` in ở góc
176
- trên trái làm mốc trục. Đường kẻ ngang ở 25/50/75%. Số thật cho trình đọc màn hình là
177
- việc của bề mặt (một bảng `ui-sr-only` cạnh biểu đồ): phần vẽ nên `aria-hidden`. */
178
- .ui-chart { margin: 0; }
179
- .ui-chart__plot { position: relative; display: flex; align-items: flex-end; justify-content: space-between; gap: 3px; height: 180px; padding-top: 18px; border-bottom: 1px solid var(--ui-border);
180
- background: linear-gradient(to top, var(--ui-border) 1px, transparent 1px) 0 0 / 100% 25% repeat-y; background-origin: content-box; background-clip: content-box; }
181
- .ui-chart__plot[data-max]::before { content: attr(data-max); position: absolute; top: 0; left: 0; font-size: var(--gemboss-fs-micro, 11px); line-height: 1; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; }
182
- .ui-chart__col { position: relative; flex: 1 1 0; min-width: 0; max-width: 48px; height: 100%; display: flex; flex-direction: column-reverse; }
183
- .ui-chart__col:hover { background: color-mix(in srgb, var(--ui-text) 6%, transparent); }
184
- .ui-chart__seg { display: block; flex: none; height: calc(var(--v, 0) * 100%); background: var(--c, var(--ui-series-1)); }
185
- .ui-chart__dot { position: absolute; left: 50%; bottom: calc(var(--v, 0) * 100%); width: 8px; height: 8px; margin: 0 0 -4px -4px; box-sizing: border-box; border-radius: 50%; background: var(--ui-surface); border: 2px solid var(--c, var(--ui-text-strong, var(--ui-text))); }
186
- .ui-chart__axis { display: flex; justify-content: space-between; gap: var(--ui-space-2); margin-top: 6px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); font-variant-numeric: tabular-nums; }
187
-
188
- /* Chú thích: `<p class="ui-legend"><span class="ui-legend__item"><i style="--c:var(--ui-series-1)"></i>Tiện ích</span>
189
- <span class="ui-legend__item ui-legend__item--dot"><i></i>Lượt mở</span></p>`. `--dot` = chuỗi vẽ bằng chấm rỗng. */
190
- .ui-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: var(--ui-space-2) 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
191
- .ui-legend__item { display: inline-flex; align-items: center; gap: 6px; }
192
- .ui-legend__item > i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--c, var(--ui-series-1)); }
193
- .ui-legend__item--dot > i { border-radius: 50%; background: var(--ui-surface); border: 2px solid var(--c, var(--ui-text-strong, var(--ui-text))); box-sizing: border-box; }
194
-
195
- /* Phễu: `<ol class="ui-funnel"><li><span class="ui-funnel__val">105</span><span class="ui-funnel__lbl">Lời mời hiện
196
- </span></li><li><span class="ui-funnel__rate">4,8%</span><span class="ui-funnel__val">5</span>…</li></ol>`. Mỗi bước
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. */
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; }
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; }
200
- .ui-funnel__val { font-size: 20px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 24px; } /* Polaris headingLg */
201
- .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
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; }
203
- @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
204
-
205
- /* Lưới bảng điều khiển: `<div class="ui-grid ui-grid--2">` card cạnh card, xuống một cột ở ≤ 960px (--3: ≤ 1100px hai
206
- cột, ≤ 720px một). Card trong lưới không kéo cao theo card bên cạnh. Tiêu đề MỤC trong trang (nhỏ hơn ui-page-head,
207
- lớn hơn tiêu đề card): `<div class="ui-section-head"><h2>Nội dung</h2><p>…</p></div>`. */
208
- .ui-grid { display: grid; gap: var(--ui-space-4); align-items: start; }
209
- .ui-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
210
- .ui-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
211
- @media (max-width: 1100px) { .ui-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
212
- @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
213
- @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
214
- .ui-section-head { margin: var(--ui-space-4) 0 0; }
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) */
216
- .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
217
-
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, "中文"
219
- thành 中 / 文, vì ô cao cố định 36px mà nút được co). Nút giữ đủ bề ngang chữ; cả nhóm rộng hơn chỗ trống thì cuộn
220
- ngang trong chính nó, trang không tràn. Ở đây chứ không ở ui.css: ngân sách CSS first-load của admin còn ~0,1 KB. */
221
- .ui-seg { max-width: 100%; overflow-x: auto; } /* thanh cuộn KHÔNG ẩn: lựa chọn bị khuất phải có dấu hiệu (review #3630) */
222
- .ui-seg :is(button, a).is-text { flex: none; white-space: nowrap; }
223
- /* Ở 320px, bốn nút chữ (Tất cả · Tiếng Việt · English · 中文) thiếu ~10px: bớt padding thay vì bắt người dùng cuộn mới
224
- thấy lựa chọn cuối. Nút vẫn cao 36px, chữ vẫn một dòng. */
225
- @media (max-width: 360px) { .ui-seg :is(button, a).is-text { padding: 0 9px; } }
226
- /* Tab / nhóm nút là ĐIỀU KHIỂN: ≥ 44px dưới ngón tay (§9.1). Khối coarse của ui.css chỉ bắt `pointer: coarse`; ở khổ điện
227
- thoại trình duyệt máy tính vẫn báo `fine`, nên nâng theo khổ như ui-btn--icon ở trên. */
228
- @media (pointer: coarse), (max-width: 47.9975em) { .ui-seg :is(button, a) { height: 44px; min-height: 44px; } }
229
-
230
- /* ── Polaris parity: the primitives every surface was drawing for itself (02/10/2026) ──────────────────────
231
- An audit of the six surfaces on the package (gemwatcher, GemFactory, gempress, gemcare, partner-radar,
232
- content-radar) found the same gaps re-filled six ways: no destructive button (four surfaces fell back to the
233
- browser's `confirm()`), one-tone badges, banners as bare colour blocks, hand-made radio / switch / tooltip /
234
- spinner, and help and error text under a field in five sizes. The admin has all of them from Polaris; this is
235
- the same shape in plain CSS, with Polaris' measurements and GemBoss' tokens (the danger / success / warning
236
- set the kit's tags and notices already use). The admin does not load this file. */
237
-
238
- /* Destructive button: `ui-btn ui-btn--critical` (red text on the default button) and
239
- `ui-btn ui-btn--primary ui-btn--critical` (red fill), Polaris' `tone="critical"` in both variants. Red text is
240
- --gemboss-danger-strong: 7.3:1 on white, 5.7:1 on the hover ground (shell.css says why not --gemboss-danger). */
241
- .ui-btn--critical { color: var(--gemboss-danger-strong); }
242
- .ui-btn--critical:hover { background: var(--gemboss-danger-soft); color: var(--gemboss-danger-strong); }
243
- .ui-btn--primary.ui-btn--critical { background: var(--gemboss-danger); border-color: var(--gemboss-danger); color: var(--gemboss-on-primary); }
244
- .ui-btn--primary.ui-btn--critical:hover { background: var(--gemboss-danger-strong); border-color: var(--gemboss-danger-strong); color: var(--gemboss-on-primary); }
245
- .ui-btn--critical:focus-visible { outline-color: var(--gemboss-danger-strong); }
246
-
247
- /* Under a thumb, and at phone WIDTH (a desktop browser at 320px reports pointer: fine), EVERY kit button is a 44px
248
- control, the compact one included: §9.1 has no density exception, and GemFactory, gempress and gemcare each
249
- restated this floor for `--sm` themselves. ui.css's coarse block leaves `--sm` alone for the admin's own
250
- settings page; this file is not loaded there. */
251
- @media (pointer: coarse), (max-width: 47.9975em) {
252
- .ui-btn, .ui-btn.ui-btn--sm { min-height: 44px; }
253
- .ui-select.ui-select { min-height: 44px; }
254
- /* A target is an AREA: the compact icon button (28 wide) and an icon segment (30) grow sideways as well. */
255
- .ui-btn--icon.ui-btn--sm, .ui-seg :is(button, a):not(.is-text) { min-width: 44px; }
256
- }
257
-
258
- /* Badge tones: `<span class="ui-badge ui-badge--success">Active</span>`. Polaris' tones on GemBoss' soft grounds (Polaris' `attention` is `--warning` here: the fleet has one amber).
259
- `ui-badge--dot` puts a status dot before the label (filled = done, the tone's colour). */
260
- .ui-badge--success { background: var(--gemboss-success-soft); color: var(--gemboss-success-strong); }
261
- .ui-badge--warning { background: var(--gemboss-warning-soft); color: var(--gemboss-warning); }
262
- .ui-badge--critical { background: var(--gemboss-danger-soft); color: var(--gemboss-danger-strong); }
263
- .ui-badge--info { background: var(--gemboss-accent-soft); color: var(--gemboss-accent-strong); }
264
- .ui-badge--dot::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
265
-
266
- /* Banner: Polaris' in-page Banner. A tone strip with an icon and a title, a body, optional actions, optional
267
- dismiss. `ui-notice` stays for a one-line note; a banner is for something the person must read or act on.
268
- <div class="ui-banner ui-banner--warning" role="status">
269
- <div class="ui-banner__head"><svg class="ui-banner__icon" …/><h3 class="ui-banner__title">Slack token expired</h3>
270
- <button type="button" class="ui-banner__close" aria-label="Dismiss">…</button></div>
271
- <div class="ui-banner__body"><p>Monday's digest will not send.</p>
272
- <div class="ui-banner__actions"><a class="ui-btn" href>Reconnect</a></div></div></div>
273
- No tone = neutral. role="status" / "alert" only when it appears after the page loaded. */
274
- .ui-banner {
275
- --ui-banner-bg: var(--ui-surface-2); --ui-banner-fg: var(--ui-text);
276
- border-radius: var(--ui-radius-lg); overflow: hidden; background: var(--ui-surface); color: var(--ui-text);
277
- box-shadow: 0 0 0 1px var(--ui-border), 0 3px 1px -1px rgba(26, 26, 26, 0.07);
278
- font-size: var(--ui-fs-body); line-height: 20px;
279
- }
280
- .ui-banner--success { --ui-banner-bg: var(--gemboss-success-soft); --ui-banner-fg: var(--gemboss-success-strong); }
281
- .ui-banner--warning { --ui-banner-bg: var(--gemboss-warning-soft); --ui-banner-fg: var(--gemboss-warning); }
282
- .ui-banner--critical { --ui-banner-bg: var(--gemboss-danger-soft); --ui-banner-fg: var(--gemboss-danger-strong); }
283
- .ui-banner--info { --ui-banner-bg: var(--gemboss-accent-soft); --ui-banner-fg: var(--gemboss-accent-strong); }
284
- .ui-banner__head { display: flex; align-items: center; gap: var(--ui-space-2); min-height: 32px; padding: 6px var(--ui-space-3); background: var(--ui-banner-bg); color: var(--ui-banner-fg); }
285
- .ui-banner__icon { flex: none; width: 20px; height: 20px; fill: currentColor; }
286
- .ui-banner__title { flex: 1 1 auto; min-width: 0; margin: 0; font-size: var(--ui-fs-body); font-weight: 650; line-height: 20px; }
287
- .ui-banner__close {
288
- flex: none; display: inline-grid; place-items: center; width: 32px; height: 32px; margin: -6px -8px -6px 0;
289
- border: 0; border-radius: var(--ui-radius-md); background: transparent; color: inherit; cursor: pointer;
290
- }
291
- .ui-banner__close:hover { background: color-mix(in srgb, currentColor 10%, transparent); }
292
- .ui-banner__close:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
293
- .ui-banner__close svg { width: 16px; height: 16px; }
294
- .ui-banner__body { padding: var(--ui-space-3) var(--ui-space-4); }
295
- .ui-banner__body > :first-child { margin-top: 0; }
296
- .ui-banner__body > :last-child { margin-bottom: 0; }
297
- .ui-banner__actions { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-top: var(--ui-space-3); }
298
- @media (pointer: coarse), (max-width: 47.9975em) { .ui-banner__close { width: 44px; height: 44px; margin: -12px -12px -12px 0; } }
299
-
300
- /* Help and error text under a field, Polaris' Labelled help text and InlineError:
301
- <label class="ui-label" for="x">Domain</label><input id="x" class="gemboss-field gemboss-field--tall"
302
- aria-describedby="x-help x-err" aria-invalid="true"><p class="ui-help" id="x-help">Without https://</p>
303
- <p class="ui-error" id="x-err">This store is already tracked.</p>
304
- aria-invalid paints the field in the critical tone; the message says what to do, not only what is wrong. */
305
- .ui-help { margin: var(--ui-space-1) 0 0; font-size: var(--ui-fs-body); line-height: 20px; color: var(--ui-text-muted); }
306
- .ui-error:empty { display: none; } /* a placeholder kept for aria-describedby draws no icon */
307
- .ui-error { display: flex; gap: 6px; margin: var(--ui-space-1) 0 0; font-size: var(--ui-fs-body); line-height: 20px; color: var(--gemboss-danger-strong); }
308
- .ui-error::before {
309
- content: ""; flex: none; width: 16px; height: 16px; margin-top: 2px; background: currentColor;
310
- -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 4h1.5v5.5h-1.5V6Zm.75 9a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'/></svg>") center / contain no-repeat;
311
- mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 4h1.5v5.5h-1.5V6Zm.75 9a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'/></svg>") center / contain no-repeat;
312
- }
313
- .gemboss-field[aria-invalid="true"], .gemboss-field--group:has([aria-invalid="true"]),
314
- .ui-select[aria-invalid="true"], .ui-textarea[aria-invalid="true"] {
315
- border-color: var(--gemboss-danger-strong); background-color: var(--gemboss-danger-soft);
316
- }
317
-
318
- /* Radio: the checkbox's row with `type="radio"` — `<fieldset class="ui-choices"><legend class="ui-label">Plan</legend>
319
- <label class="ui-check"><input type="radio" name="plan" checked><span>Basic</span></label>…</fieldset>`.
320
- One minmax(0, 1fr) column: an auto column grows to the longest unbroken word in any label (0.9.0). */
321
- .ui-choices { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; margin: 0; padding: 0; border: 0; min-width: 0; }
322
- .ui-choices > legend { padding: 0; }
323
-
324
- /* Switch: a checkbox that reads as on / off, for a setting that applies at once (no Save button).
325
- `<label class="ui-check ui-switch"><input type="checkbox" role="switch" checked><span>Send Monday digest</span></label>`.
326
- Track 32×20, thumb 16; the row stays the checkbox's 44px target. */
327
- .ui-switch > input[type="checkbox"] {
328
- appearance: none; -webkit-appearance: none; position: relative; width: 32px; height: 20px; margin: 0;
329
- border-radius: var(--gemboss-radius-full); background: var(--gemboss-field-line); /* 3.5:1 on white, WCAG 1.4.11 */ transition: background-color var(--gemboss-transition-fast) ease;
330
- }
331
- .ui-switch > input[type="checkbox"]::before {
332
- content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
333
- background: var(--gemboss-surface); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); transition: transform var(--gemboss-transition-fast) ease;
334
- }
335
- .ui-switch > input[type="checkbox"]:checked { background: var(--ui-accent); }
336
- .ui-switch > input[type="checkbox"]:checked::before { transform: translateX(12px); }
337
- @media (prefers-reduced-motion: reduce) { .ui-switch > input[type="checkbox"], .ui-switch > input[type="checkbox"]::before { transition: none; } }
338
-
339
- /* Tooltip: a SHORT label for an icon-only control, shown on hover and keyboard focus. Not for anything the person
340
- needs to act on (a touch screen never hovers).
341
- <span class="ui-tooltip"><button class="ui-btn ui-btn--icon" aria-describedby="t1" aria-label="Archive">…</button>
342
- <span class="ui-tooltip__bubble" role="tooltip" id="t1">Archive</span></span> `--end` aligns it right. */
343
- .ui-tooltip { position: relative; display: inline-flex; }
344
- .ui-tooltip__bubble {
345
- position: absolute; z-index: 30; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
346
- width: max-content; max-width: 240px; padding: 4px 8px; border-radius: var(--ui-radius-md);
347
- background: var(--ui-surface); color: var(--ui-text); font-size: var(--ui-fs-body); line-height: 20px; font-weight: 450;
348
- box-shadow: 0 8px 16px -4px rgba(26, 26, 26, 0.22), 0 0 0 1px var(--ui-border);
349
- opacity: 0; visibility: hidden; pointer-events: none; transition: opacity var(--gemboss-transition-fast) ease;
350
- }
351
- .ui-tooltip--end .ui-tooltip__bubble { left: auto; right: 0; transform: none; }
352
- .ui-tooltip:hover > .ui-tooltip__bubble, .ui-tooltip:focus-within > .ui-tooltip__bubble { opacity: 1; visibility: visible; }
353
-
354
- /* Spinner: `<span class="ui-spinner" role="status"><span class="ui-sr-only">Loading…</span></span>`, 20px
355
- (Polaris small); `--lg` is 44px. Reduced motion turns it slower, never off: it is the only sign of waiting. */
356
- .ui-spinner {
357
- display: inline-block; flex: none; width: 20px; height: 20px; box-sizing: border-box; border-radius: 50%;
358
- border: 2px solid color-mix(in srgb, var(--ui-text) 18%, transparent); border-top-color: var(--ui-text);
359
- animation: ui-spin 0.7s linear infinite; vertical-align: middle;
360
- }
361
- .ui-spinner--lg { width: 44px; height: 44px; border-width: 4px; }
362
- @keyframes ui-spin { to { transform: rotate(360deg); } }
363
- @media (prefers-reduced-motion: reduce) { .ui-spinner { animation-duration: 2s; } }
364
-
365
- /* Progress: `<progress class="ui-progress" value="37" max="100" aria-label="Import">37%</progress>`, 8px, the
366
- primary fill (Polaris' small ProgressBar). Tones: `--success`, `--critical`. */
367
- .ui-progress { appearance: none; -webkit-appearance: none; display: block; width: 100%; height: 8px; border: 0; border-radius: var(--gemboss-radius-full); overflow: hidden; background: var(--gemboss-divider); color: var(--ui-accent); }
368
- .ui-progress::-webkit-progress-bar { background: var(--gemboss-divider); border-radius: inherit; }
369
- .ui-progress::-webkit-progress-value { background: currentColor; border-radius: inherit; }
370
- .ui-progress::-moz-progress-bar { background: currentColor; border-radius: inherit; }
371
- .ui-progress--success { color: var(--gemboss-success); }
372
- .ui-progress--critical { color: var(--gemboss-danger); }
373
-
374
- /* Wider popup: `openModal({ className: "ui-modal--wide" })`, Polaris' default Modal (620px). The kit's own 460 is
375
- Polaris' small step plus one; a form with two columns or a table needs this one. */
376
- .ui-modal--wide { width: min(620px, 100%); }
377
-
378
- /* Native popup: `<dialog class="ui-modal">` opened with `showModal()`, for React (and other) surfaces that do not load
379
- kit-plain.js. The browser does what openModal does by hand: Escape closes it, the page behind is inert, it sits in
380
- the top layer above the shell's topbar. Same head / body / foot markup; `::backdrop` is the kit's overlay. gemcare
381
- and content-radar already used `<dialog>`, each with its own width, padding and backdrop (02/10/2026: four shapes,
382
- 420 to 760 wide). Close on a backdrop click yourself: a click whose target IS the dialog landed outside the panel.
383
- `<dialog class="ui-modal" aria-labelledby="t"><div class="ui-modal__head"><div class="ui-modal__title" id="t">…</div>
384
- <button type="button" class="ui-modal__close" aria-label="Close">…</button></div><div class="ui-modal__body">…</div>
385
- <div class="ui-modal__foot">…</div></dialog>` */
386
- dialog.ui-modal { padding: 0; max-width: calc(100% - 40px); max-height: calc(100dvh - 40px); color: var(--ui-text); }
387
- dialog.ui-modal[open] { display: flex; flex-direction: column; }
388
- dialog.ui-modal::backdrop { background: rgba(0, 0, 0, 0.5); } /* the overlay's own scrim (0.7.3: Polaris' .5, no blur) */
389
-
390
- /* ── 0.5.1: what the first surfaces on 0.5.0 still drew themselves (GemFactory, content-radar, 03/10/2026) ─────────── */
391
-
392
- /* At phone width a short compact button ("Bỏ") was 44 tall but 41 wide: a target is an area. */
393
- @media (pointer: coarse), (max-width: 47.9975em) { .ui-btn.ui-btn--sm { min-width: 44px; } }
394
-
395
- /* A long label in a badge ("Đã dùng 1 lần + 4 bài cùng nguồn · 2 Hot News") wraps instead of running out of its card. */
396
- .ui-badge--wrap { white-space: normal; overflow-wrap: anywhere; line-height: 16px; }
397
-
398
- /* Page head with actions on the right, wrapping under the title when narrow:
399
- `<div class="ui-page-head ui-page-head--actions"><div><h1>…</h1><p>…</p></div><div class="ui-page-head__actions">
400
- <button class="ui-btn ui-btn--primary">…</button></div></div>`. */
401
- .ui-page-head--actions { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px 16px; }
402
- .ui-page-head--actions > :first-child { min-width: 0; }
403
- .ui-page-head__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); }
404
-
405
- /* Chips that TOGGLE (a filter applied in place, no URL): `<div class="ui-chips"><button type="button" class="ui-tag
406
- ui-tag--chip" aria-pressed="true">CRO</button>…`. Same box as the link chips (ui.css); pressed = the link's current. */
407
- .ui-chips > button.ui-tag--chip {
408
- min-height: 28px; padding: 4px 12px; border-radius: var(--ui-radius-md, 8px); cursor: pointer;
409
- font-family: inherit; font-size: var(--gemboss-fs-caption, 12px); color: var(--ui-text);
410
- }
411
- .ui-chips > button.ui-tag--chip:hover { background: var(--ui-surface-2); border-color: var(--ui-text-muted); }
412
- .ui-chips > button.ui-tag--chip:focus-visible { outline: 2px solid var(--ui-focus, #5661f5); outline-offset: 1px; }
413
- .ui-chips > button.ui-tag--chip[aria-pressed="true"] { background: var(--ui-accent-soft); border-color: var(--ui-accent); color: var(--ui-accent-strong); font-weight: 600; }
414
- @media (pointer: coarse), (max-width: 47.9975em) { .ui-chips > button.ui-tag--chip { min-height: 44px; min-width: 44px; justify-content: center; } }
415
-
416
- /* Chat at phone WIDTH (gemwatcher's assistant, measured 03/10/2026): the composer read 14px (iOS zooms the page on tap
417
- below 16) and "new chat" was 35px wide under a fine pointer. shell.css raises the latter under `pointer: coarse`; a
418
- desktop window at 390px reports fine. Here and not in chat.css / shell.css: the admin's first-load budget. */
419
- @media (max-width: 47.9975em) {
420
- /* Class doubled: chat.css is appended AFTER this file in gemboss-ui.css, so a same-weight rule there would win. */
421
- .gemboss-ec-input.gemboss-ec-input textarea { font-size: 16px; }
422
- .gemboss-assistant-collapse, .gemboss-assistant-newchat { min-height: 44px; min-width: 44px; }
423
- }
424
-
425
- /* Segmented buttons that say their state the accessible way: `aria-pressed="true"` looks like `.active` (ui-tabs reads
426
- aria-selected already; gemcare had to set both on every group). */
427
- .ui-seg button[aria-pressed="true"] { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
428
-
429
- /* Read-only field: visibly not editable (the admin's field has no such state; gempress kept its own `.field[readonly]`).
430
- The attribute doubled = (0,3,0), so it also holds over the field's own hover rule, which comes earlier. */
431
- :is(.gemboss-field, .ui-textarea)[readonly][readonly] { background: var(--ui-surface-2); border-color: var(--ui-border); cursor: default; }
432
-
433
- /* A popup whose body and foot sit inside one <form>: the form takes no box, so `.ui-modal > .ui-modal__body` (the
434
- scrolling body) and the pinned foot still lay out as the popup's own children (gemcare's escalate form, 0.5.1). */
435
- .ui-modal > form { display: contents; }
436
- .ui-modal > form > .ui-modal__head, .ui-modal > form > .ui-modal__foot { flex: none; }
437
- .ui-modal > form > .ui-modal__body { flex: 1 1 auto; min-height: 0; overflow: auto; }
438
-
439
- /* A metric that FILTERS the view below it, as a toggle: `<button type="button" class="ui-metric" aria-pressed="true">`
440
- (gemwatcher's Radar cards: as links to the same hash, Cmd-click opened an unfiltered tab, Space did nothing and the
441
- chosen card was told by colour alone). A metric link that is the current view: `a.ui-metric[aria-current="page"]`.
442
- Selected = iris edge, the shell's "this one" colour. */
443
- button.ui-metric { font: inherit; text-align: left; color: inherit; cursor: pointer; }
444
- button.ui-metric:hover { border-color: var(--ui-text-muted); }
445
- button.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
446
- /* Class doubled: the chosen card is usually the one under the pointer, and `button.ui-metric:hover` (0,2,1) would
447
- repaint its edge grey the moment it is clicked (measured on gemwatcher Radar). */
448
- .ui-metric.ui-metric:is([aria-pressed="true"], [aria-current="page"]) { border-color: var(--gemboss-accent); box-shadow: inset 0 0 0 1px var(--gemboss-accent); }
449
-
450
- /* 0.7.3: numbered pager + ui-more, for the plain surfaces (the admin uses Polaris Pagination). Markup and rationale:
451
- README "Long lists". Here, not in ui.css: ui.css is exported verbatim into the admin, under a 90 KB budget. */
452
- .ui-pager > a, .ui-pager > [aria-disabled="true"] { min-width: 32px; justify-content: center; box-sizing: border-box; gap: 4px; }
453
- .ui-pager > a[aria-current="page"] { background: var(--ui-text); border-color: var(--ui-text); color: var(--ui-surface); font-size: var(--ui-fs-body); font-weight: 600; }
454
- .ui-pager > a[aria-current="page"]:hover { background: var(--ui-text); }
455
- .ui-pager > [aria-disabled="true"] { 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-muted); font-size: var(--ui-fs-body); opacity: .55; cursor: default; }
456
- .ui-pager > [aria-disabled="true"]:hover { background: none; }
457
- .ui-pager__gap { min-width: 16px; text-align: center; color: var(--ui-text-muted); }
458
- .ui-pager__range { margin-left: auto; color: var(--ui-text-muted); font-size: var(--ui-fs-body); font-variant-numeric: tabular-nums; }
459
- .ui-more { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; padding: 16px 0; color: var(--ui-text-muted); font-size: var(--ui-fs-body); text-align: center; }
460
- .ui-more[hidden] { display: none; }
461
- @media (pointer: coarse), (max-width: 47.9975em) { .ui-pager > a, .ui-pager > [aria-disabled="true"] { min-width: 44px; min-height: 44px; } }
462
- /* Phone: 11 targets of 44px wrapped into 3-4 rows at 320 (measured). Keep Previous · current · Next; the range wraps under. */
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); }
1
+ .ui-btn--icon{flex:none;width:var(--ui-btn-h);padding:0}.ui-btn--icon svg{width:18px;height:18px}.ui-menu{position:relative;display:inline-block}.ui-menu>summary{list-style:none;cursor:pointer}.ui-menu>summary::-webkit-details-marker{display:none}.ui-menu__list{position:absolute;z-index:20;top:calc(100% + 4px);left:0;display:grid;min-width:220px;padding:var(--ui-space-1);box-sizing:border-box}.ui-menu--end .ui-menu__list{left:auto;right:0}.ui-menu__list[popover]{position:fixed;inset:auto;margin:0;overflow:auto}.ui-menu__list[popover]:not(:popover-open){display:none}:where(.ui-menu__list[popover]){border:0;background:none;color:inherit}:root[data-gb-menus=float] .ui-menu[open]>.ui-menu__list:not([popover]){visibility:hidden}.ui-menu__item{display:flex;align-items:center;gap:var(--ui-space-2);white-space:nowrap;border-radius:var(--ui-radius-sm)}.ui-menu__item:is([disabled],[aria-disabled=true]){opacity:.5;cursor:not-allowed;pointer-events:none}.ui-menu__hint{margin-left:auto;padding-left:var(--ui-space-4);color:var(--ui-text-muted);font-size:var(--ui-fs-caption)}.ui-menu__sep{height:1px;margin:var(--ui-space-1) 0;border:0;background:var(--ui-border)}.ui-check{display:inline-flex;align-items:center;gap:var(--ui-space-2);min-height:44px;max-width:100%;overflow-wrap:break-word;cursor:pointer;font-size:var(--ui-fs-body);color:var(--ui-text)}.ui-check>input{flex:none;width:18px;height:18px;margin:0;accent-color:var(--ui-accent);cursor:inherit}.ui-check>input:focus-visible{outline:2px solid var(--ui-focus);outline-offset:2px}.ui-check:has(>input:disabled){opacity:.5;cursor:not-allowed}.ui-check>:last-child:not(input){min-width:0}.ui-label{display:block;margin:0 0 var(--ui-space-1);font-size:var(--ui-fs-caption);font-weight:600;color:var(--ui-text-muted)}.ui-kv{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:var(--ui-space-2) var(--ui-space-4);margin:0;font-size:var(--ui-fs-body)}.ui-kv>dt{color:var(--ui-text-muted)}.ui-kv>dd{margin:0;min-width:0;overflow-wrap:anywhere;color:var(--ui-text)}@media (pointer: coarse){.ui-btn--icon:not(.ui-btn--sm){min-width:44px}}@media (max-width: 47.9975em){.ui-btn--icon:not(.ui-btn--sm){min-width:44px;min-height:44px}}.ui-link,.ui-prose :where(a:not([class])){color:var(--gemboss-accent-strong, #3a44de);text-decoration:underline;text-decoration-thickness:from-font;text-underline-offset:2px}.ui-link:visited,.ui-prose :where(a:not([class])):visited{color:var(--gemboss-accent-strong, #3a44de)}.ui-link:hover,.ui-prose :where(a:not([class])):hover{color:var(--gemboss-accent, #5661f5);text-decoration-thickness:2px}.ui-link:focus-visible,.ui-prose :where(a:not([class])):focus-visible{outline:2px solid var(--ui-focus, #5661f5);outline-offset:2px;border-radius:2px}.ui-card--media{position:relative;padding:0;overflow:hidden;display:flex;flex-direction:column}.ui-card__link{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;color:var(--ui-text);font-size:var(--ui-fs-body);line-height:20px;font-weight:650;text-decoration:none;overflow-wrap:anywhere}.ui-card__link:after{content:"";position:absolute;inset:0;z-index:0}.ui-card__link:focus-visible{outline:none}.ui-card--media:has(.ui-card__link){cursor:pointer}.ui-card--media:has(.ui-card__link:hover){border-color:var(--ui-text-muted)}.ui-card--media:has(.ui-card__link:focus-visible){outline:2px solid var(--ui-focus);outline-offset:1px}.ui-card--media :is(.ui-card__foot button,.ui-card__foot a,.ui-card__body a:not(.ui-card__link),.ui-card__body button,select,input,summary){position:relative;z-index:1}.ui-card__menu{position:absolute;top:8px;right:8px;z-index:2}.ui-card--media:has(>.ui-card__menu){overflow:visible}.ui-card--media:has(>.ui-card__menu)>.ui-card__media{border-radius:inherit;border-bottom-left-radius:0;border-bottom-right-radius:0}.ui-card__meta{color:var(--ui-text-muted);font-size:var(--ui-fs-caption);line-height:16px}.ui-card--media[data-ratio="4/3"]>.ui-card__media{aspect-ratio:4 / 3}.ui-card--media[data-ratio="1/1"]>.ui-card__media{aspect-ratio:1 / 1}.ui-card__media{display:block;width:100%;aspect-ratio:16 / 10;margin:0;padding:0;border:0;border-bottom:1px solid var(--ui-border);background:var(--ui-surface-2);overflow:hidden;color:inherit}.ui-card__media>img{display:block;width:100%;height:100%;object-fit:cover}button.ui-card__media,a.ui-card__media{cursor:pointer}.ui-card__media:focus-visible{outline:2px solid var(--ui-focus, #5661f5);outline-offset:-2px}.ui-card__body{display:grid;justify-items:start;gap:var(--ui-space-2, 8px);padding:var(--ui-space-3, 12px) var(--ui-space-4, 16px);min-width:0}.ui-card__foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--ui-space-2, 8px);margin-top:auto;padding:0 var(--ui-space-4, 16px) var(--ui-space-3, 12px);color:var(--ui-text-muted);font-size:var(--ui-fs-caption, 13px)}.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}.ui-modal__actions{flex:none;display:flex;flex-wrap:wrap;align-items:center;gap:var(--ui-space-2)}.ui-overlay>.ui-modal{display:flex;flex-direction:column;max-height:calc(100dvh - 40px)}.ui-modal>.ui-modal__head,.ui-modal>.ui-modal__foot{flex:none}.ui-modal>.ui-modal__body{flex:1 1 auto;min-height:0;overflow:auto}:root{--ui-series-1: var(--gemboss-accent, #5661f5);--ui-series-2: #0f9f8f;--ui-series-3: #d9822b;--ui-series-4: #c2417a;--ui-series-5: #6b7280}.ui-delta{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:2px;font-size:var(--gemboss-fs-caption);font-weight:600;color:var(--ui-text-muted);font-variant-numeric:tabular-nums}.ui-delta--good{color:var(--ui-success-fg)}.ui-delta--bad{color:var(--ui-danger-fg)}.ui-metrics--grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(180px,100%),1fr));gap:var(--ui-space-3);margin:0}.ui-metric--lg{gap:4px;padding:var(--ui-space-3) var(--ui-space-4)}.ui-metric--lg .ui-metric__val{font-size:24px;line-height:32px}.ui-metric--lg .ui-metric__lbl{order:-1;font-size:var(--ui-fs-body)}.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)}@media (max-width: 520px){.ui-metrics--grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.ui-barlist{display:grid;gap:var(--ui-space-3);margin:0;padding:0;list-style:none;font-size:var(--ui-fs-body, 13px)}.ui-barlist>li{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:baseline;gap:4px var(--ui-space-3)}.ui-barlist__label{min-width:0;overflow-wrap:anywhere;color:var(--ui-text)}.ui-barlist__label a{color:inherit}.ui-barlist__val{font-weight:600;color:var(--ui-text);font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right}.ui-barlist__share{margin-left:6px;font-weight:400;color:var(--ui-text-muted)}.ui-barlist__note{grid-column:1 / -1;margin-top:-2px;font-size:var(--gemboss-fs-caption);color:var(--ui-text-muted)}.ui-barlist__bar{grid-column:1 / -1;position:relative;height:6px;border-radius:999px;background:var(--ui-surface-2);overflow:hidden}.ui-barlist__bar:after{content:"";position:absolute;inset:0 auto 0 0;width:calc(var(--v, 0) * 100%);min-width:3px;border-radius:inherit;background:var(--c, var(--ui-series-1))}.ui-chart{margin:0}.ui-chart__plot{position:relative;display:flex;align-items:flex-end;justify-content:space-between;gap:3px;height:180px;padding-top:18px;border-bottom:1px solid var(--ui-border);background:linear-gradient(to top,var(--ui-border) 1px,transparent 1px) 0 0 / 100% 25% repeat-y;background-origin:content-box;background-clip:content-box}.ui-chart__plot[data-max]:before{content:attr(data-max);position:absolute;top:0;left:0;font-size:var(--gemboss-fs-micro, 11px);line-height:1;color:var(--ui-text-muted);font-variant-numeric:tabular-nums}.ui-chart__col{position:relative;flex:1 1 0;min-width:0;max-width:48px;height:100%;display:flex;flex-direction:column-reverse}.ui-chart__col:hover{background:color-mix(in srgb,var(--ui-text) 6%,transparent)}.ui-chart__seg{display:block;flex:none;height:calc(var(--v, 0) * 100%);background:var(--c, var(--ui-series-1))}.ui-chart__dot{position:absolute;left:50%;bottom:calc(var(--v, 0) * 100%);width:8px;height:8px;margin:0 0 -4px -4px;box-sizing:border-box;border-radius:50%;background:var(--ui-surface);border:2px solid var(--c, var(--ui-text-strong, var(--ui-text)))}.ui-chart__axis{display:flex;justify-content:space-between;gap:var(--ui-space-2);margin-top:6px;font-size:var(--gemboss-fs-caption);color:var(--ui-text-muted);font-variant-numeric:tabular-nums}.ui-legend{display:flex;flex-wrap:wrap;gap:6px 16px;margin:var(--ui-space-2) 0 0;font-size:var(--gemboss-fs-caption);color:var(--ui-text-muted)}.ui-legend__item{display:inline-flex;align-items:center;gap:6px}.ui-legend__item>i{display:inline-block;width:10px;height:10px;border-radius:2px;background:var(--c, var(--ui-series-1))}.ui-legend__item--dot>i{border-radius:50%;background:var(--ui-surface);border:2px solid var(--c, var(--ui-text-strong, var(--ui-text)));box-sizing:border-box}.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}.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}.ui-funnel__val{font-size:20px;font-weight:700;color:var(--ui-text);font-variant-numeric:tabular-nums;line-height:24px}.ui-funnel__lbl{font-size:var(--gemboss-fs-caption);color:var(--ui-text-muted);overflow-wrap:anywhere}.ui-funnel__rate{order:-1;font-size:var(--gemboss-fs-caption);font-weight:600;color:var(--ui-series-1);font-variant-numeric:tabular-nums}@media (max-width: 520px){.ui-funnel{grid-auto-flow:row}}.ui-grid{display:grid;gap:var(--ui-space-4);align-items:start}.ui-grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}.ui-grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}@media (max-width: 1100px){.ui-grid--3{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width: 960px){.ui-grid--2{grid-template-columns:minmax(0,1fr)}}@media (max-width: 720px){.ui-grid--3{grid-template-columns:minmax(0,1fr)}}.ui-section-head{margin:var(--ui-space-4) 0 0}.ui-section-head>h2{margin:0;font-size:14px;line-height:20px;font-weight:650;color:var(--ui-text)}.ui-section-head>p{margin:2px 0 0;font-size:var(--gemboss-fs-caption);color:var(--ui-text-muted)}.ui-seg{max-width:100%;overflow-x:auto}.ui-seg :is(button,a).is-text{flex:none;white-space:nowrap}@media (max-width: 360px){.ui-seg :is(button,a).is-text{padding:0 9px}}@media (pointer: coarse),(max-width: 47.9975em){.ui-seg :is(button,a){height:44px;min-height:44px}}.ui-btn--critical{color:var(--gemboss-danger-strong)}.ui-btn--critical:hover{background:var(--gemboss-danger-soft);color:var(--gemboss-danger-strong)}.ui-btn--primary.ui-btn--critical{background:var(--gemboss-danger);border-color:var(--gemboss-danger);color:var(--gemboss-on-primary)}.ui-btn--primary.ui-btn--critical:hover{background:var(--gemboss-danger-strong);border-color:var(--gemboss-danger-strong);color:var(--gemboss-on-primary)}.ui-btn--critical:focus-visible{outline-color:var(--gemboss-danger-strong)}@media (pointer: coarse),(max-width: 47.9975em){.ui-btn,.ui-btn.ui-btn--sm,.ui-select.ui-select{min-height:44px}.ui-btn--icon.ui-btn--sm,.ui-seg :is(button,a):not(.is-text){min-width:44px}}.ui-badge--success{background:var(--gemboss-success-soft);color:var(--gemboss-success-strong)}.ui-badge--warning{background:var(--gemboss-warning-soft);color:var(--gemboss-warning)}.ui-badge--critical{background:var(--gemboss-danger-soft);color:var(--gemboss-danger-strong)}.ui-badge--info{background:var(--gemboss-accent-soft);color:var(--gemboss-accent-strong)}.ui-badge--dot:before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:currentColor}.ui-banner{--ui-banner-bg: var(--ui-surface-2);--ui-banner-fg: var(--ui-text);border-radius:var(--ui-radius-lg);overflow:hidden;background:var(--ui-surface);color:var(--ui-text);box-shadow:0 0 0 1px var(--ui-border),0 3px 1px -1px #1a1a1a12;font-size:var(--ui-fs-body);line-height:20px}.ui-banner--success{--ui-banner-bg: var(--gemboss-success-soft);--ui-banner-fg: var(--gemboss-success-strong)}.ui-banner--warning{--ui-banner-bg: var(--gemboss-warning-soft);--ui-banner-fg: var(--gemboss-warning)}.ui-banner--critical{--ui-banner-bg: var(--gemboss-danger-soft);--ui-banner-fg: var(--gemboss-danger-strong)}.ui-banner--info{--ui-banner-bg: var(--gemboss-accent-soft);--ui-banner-fg: var(--gemboss-accent-strong)}.ui-banner__head{display:flex;align-items:center;gap:var(--ui-space-2);min-height:32px;padding:6px var(--ui-space-3);background:var(--ui-banner-bg);color:var(--ui-banner-fg)}.ui-banner__icon{flex:none;width:20px;height:20px;fill:currentColor}.ui-banner__title{flex:1 1 auto;min-width:0;margin:0;font-size:var(--ui-fs-body);font-weight:650;line-height:20px}.ui-banner__close{flex:none;display:inline-grid;place-items:center;width:32px;height:32px;margin:-6px -8px -6px 0;border:0;border-radius:var(--ui-radius-md);background:transparent;color:inherit;cursor:pointer}.ui-banner__close:hover{background:color-mix(in srgb,currentColor 10%,transparent)}.ui-banner__close:focus-visible{outline:2px solid var(--ui-focus);outline-offset:-2px}.ui-banner__close svg{width:16px;height:16px}.ui-banner__body{padding:var(--ui-space-3) var(--ui-space-4)}.ui-banner__body>:first-child{margin-top:0}.ui-banner__body>:last-child{margin-bottom:0}.ui-banner__actions{display:flex;flex-wrap:wrap;gap:var(--ui-space-2);margin-top:var(--ui-space-3)}@media (pointer: coarse),(max-width: 47.9975em){.ui-banner__close{width:44px;height:44px;margin:-12px -12px -12px 0}}.ui-help{margin:var(--ui-space-1) 0 0;font-size:var(--ui-fs-body);line-height:20px;color:var(--ui-text-muted)}.ui-error:empty{display:none}.ui-error{display:flex;gap:6px;margin:var(--ui-space-1) 0 0;font-size:var(--ui-fs-body);line-height:20px;color:var(--gemboss-danger-strong)}.ui-error:before{content:"";flex:none;width:16px;height:16px;margin-top:2px;background:currentColor;-webkit-mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 4h1.5v5.5h-1.5V6Zm.75 9a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'/></svg>") center / contain no-repeat;mask:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path d='M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Zm-.75 4h1.5v5.5h-1.5V6Zm.75 9a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'/></svg>") center / contain no-repeat}.gemboss-field[aria-invalid=true],.gemboss-field--group:has([aria-invalid=true]),.ui-select[aria-invalid=true],.ui-textarea[aria-invalid=true]{border-color:var(--gemboss-danger-strong);background-color:var(--gemboss-danger-soft)}.ui-choices{display:grid;grid-template-columns:minmax(0,1fr);gap:0;margin:0;padding:0;border:0;min-width:0}.ui-choices>legend{padding:0}.ui-switch>input[type=checkbox]{appearance:none;-webkit-appearance:none;position:relative;width:32px;height:20px;margin:0;border-radius:var(--gemboss-radius-full);background:var(--gemboss-field-line);transition:background-color var(--gemboss-transition-fast) ease}.ui-switch>input[type=checkbox]:before{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--gemboss-surface);box-shadow:0 1px 2px #0003;transition:transform var(--gemboss-transition-fast) ease}.ui-switch>input[type=checkbox]:checked{background:var(--ui-accent)}.ui-switch>input[type=checkbox]:checked:before{transform:translate(12px)}@media (prefers-reduced-motion: reduce){.ui-switch>input[type=checkbox],.ui-switch>input[type=checkbox]:before{transition:none}}.ui-tooltip{position:relative;display:inline-flex}.ui-tooltip__bubble{position:absolute;z-index:30;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);width:max-content;max-width:240px;padding:4px 8px;border-radius:var(--ui-radius-md);background:var(--ui-surface);color:var(--ui-text);font-size:var(--ui-fs-body);line-height:20px;font-weight:450;box-shadow:0 8px 16px -4px #1a1a1a38,0 0 0 1px var(--ui-border);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--gemboss-transition-fast) ease}.ui-tooltip--end .ui-tooltip__bubble{left:auto;right:0;transform:none}.ui-tooltip:hover>.ui-tooltip__bubble,.ui-tooltip:focus-within>.ui-tooltip__bubble{opacity:1;visibility:visible}.ui-spinner{display:inline-block;flex:none;width:20px;height:20px;box-sizing:border-box;border-radius:50%;border:2px solid color-mix(in srgb,var(--ui-text) 18%,transparent);border-top-color:var(--ui-text);animation:ui-spin .7s linear infinite;vertical-align:middle}.ui-spinner--lg{width:44px;height:44px;border-width:4px}@keyframes ui-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.ui-spinner{animation-duration:2s}}.ui-progress{appearance:none;-webkit-appearance:none;display:block;width:100%;height:8px;border:0;border-radius:var(--gemboss-radius-full);overflow:hidden;background:var(--gemboss-divider);color:var(--ui-accent)}.ui-progress::-webkit-progress-bar{background:var(--gemboss-divider);border-radius:inherit}.ui-progress::-webkit-progress-value{background:currentColor;border-radius:inherit}.ui-progress::-moz-progress-bar{background:currentColor;border-radius:inherit}.ui-progress--success{color:var(--gemboss-success)}.ui-progress--critical{color:var(--gemboss-danger)}.ui-modal--wide{width:min(620px,100%)}dialog.ui-modal{padding:0;max-width:calc(100% - 40px);max-height:calc(100dvh - 40px);color:var(--ui-text)}dialog.ui-modal[open]{display:flex;flex-direction:column}dialog.ui-modal::backdrop{background:#00000080}@media (pointer: coarse),(max-width: 47.9975em){.ui-btn.ui-btn--sm{min-width:44px}}.ui-badge--wrap{white-space:normal;overflow-wrap:anywhere;line-height:16px}.ui-page-head--actions{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:12px 16px}.ui-page-head--actions>:first-child{min-width:0}.ui-page-head__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ui-space-2)}.ui-chips>button.ui-tag--chip{min-height:28px;padding:4px 12px;border-radius:var(--ui-radius-md, 8px);cursor:pointer;font-family:inherit;font-size:var(--gemboss-fs-caption, 12px);color:var(--ui-text)}.ui-chips>button.ui-tag--chip:hover{background:var(--ui-surface-2);border-color:var(--ui-text-muted)}.ui-chips>button.ui-tag--chip:focus-visible{outline:2px solid var(--ui-focus, #5661f5);outline-offset:1px}.ui-chips>button.ui-tag--chip[aria-pressed=true]{background:var(--ui-accent-soft);border-color:var(--ui-accent);color:var(--ui-accent-strong);font-weight:600}@media (pointer: coarse),(max-width: 47.9975em){.ui-chips>button.ui-tag--chip{min-height:44px;min-width:44px;justify-content:center}}@media (max-width: 47.9975em){.gemboss-ec-input.gemboss-ec-input textarea{font-size:16px}.gemboss-assistant-collapse,.gemboss-assistant-newchat{min-height:44px;min-width:44px}}.ui-seg button[aria-pressed=true]{background:var(--ui-accent-soft);color:var(--ui-accent-strong)}:is(.gemboss-field,.ui-textarea)[readonly][readonly]{background:var(--ui-surface-2);border-color:var(--ui-border);cursor:default}.ui-modal>form{display:contents}.ui-modal>form>.ui-modal__head,.ui-modal>form>.ui-modal__foot{flex:none}.ui-modal>form>.ui-modal__body{flex:1 1 auto;min-height:0;overflow:auto}button.ui-metric{font:inherit;text-align:left;color:inherit;cursor:pointer}button.ui-metric:hover{border-color:var(--ui-text-muted)}button.ui-metric:focus-visible{outline:2px solid var(--ui-focus);outline-offset:1px}.ui-metric.ui-metric:is([aria-pressed=true],[aria-current=page]){border-color:var(--gemboss-accent);box-shadow:inset 0 0 0 1px var(--gemboss-accent)}.ui-pager>a,.ui-pager>[aria-disabled=true]{min-width:32px;justify-content:center;box-sizing:border-box;gap:4px}.ui-pager>a[aria-current=page]{background:var(--ui-text);border-color:var(--ui-text);color:var(--ui-surface);font-size:var(--ui-fs-body);font-weight:600}.ui-pager>a[aria-current=page]:hover{background:var(--ui-text)}.ui-pager>[aria-disabled=true]{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-muted);font-size:var(--ui-fs-body);opacity:.55;cursor:default}.ui-pager>[aria-disabled=true]:hover{background:none}.ui-pager__gap{min-width:16px;text-align:center;color:var(--ui-text-muted)}.ui-pager__range{margin-left:auto;color:var(--ui-text-muted);font-size:var(--ui-fs-body);font-variant-numeric:tabular-nums}.ui-more{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px;padding:16px 0;color:var(--ui-text-muted);font-size:var(--ui-fs-body);text-align:center}.ui-more[hidden]{display:none}@media (pointer: coarse),(max-width: 47.9975em){.ui-pager>a,.ui-pager>[aria-disabled=true]{min-width:44px;min-height:44px}}@media (max-width: 35.9975em){.ui-pager>a:not([rel]):not([aria-current]):not([aria-disabled]),.ui-pager__gap{display:none}}.ui-chip{display:inline-flex;align-items:center;justify-content:center;gap:4px;box-sizing:border-box;height:var(--ui-btn-h, 28px);padding:0 12px;border:1px dashed var(--ui-border);border-radius:var(--ui-radius-md);background:var(--ui-surface);color:var(--ui-text);font:inherit;font-size:var(--ui-fs-caption);font-weight:550;line-height:16px;white-space:nowrap;text-decoration:none;cursor:pointer}.ui-chip:hover{border-style:solid;background:var(--ui-surface-2)}.ui-chip:focus-visible{outline:2px solid var(--ui-focus);outline-offset:1px}.ui-chip:is([aria-pressed=true],[aria-current=page],[aria-current=true]){border-style:solid;border-color:var(--ui-accent);background:var(--ui-accent-soft);color:var(--ui-accent-strong)}@media (pointer: coarse),(max-width: 47.9975em){.ui-chip{height:auto;min-height:44px;min-width:44px}}.ui-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--ui-space-4)}.ui-stack--tight{gap:var(--ui-space-2)}