@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/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)}
|