@gemboss/ui 0.3.0 → 0.4.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/README.md CHANGED
@@ -238,6 +238,25 @@ import { AppShell, BrandLockup, AccountMenu, AccountMenuItem, Icon, RailItem } f
238
238
  (`href`) or a button; `disabled` on a link drops the href and sets `aria-disabled`; `className` is added
239
239
  to the row's class.
240
240
 
241
+ ## Report pages (0.4.0)
242
+
243
+ Plain CSS in `kit-plain.css` for a numbers page written by hand (first user: gemnews `/admin`, 03/10/2026). The
244
+ surface computes every number and ratio; the kit only draws. Markup for each is in the catalog's "Báo cáo" section.
245
+
246
+ - `ui-metrics--grid` + `ui-metric--lg` + `ui-metric__foot`: KPI tiles in an even grid (never 3 + 1 orphan), label on
247
+ top, big value, a foot line for the comparison.
248
+ - `ui-delta` (`--good` / `--bad`): change vs a previous period. Good or bad is the surface's call (reads up is good,
249
+ bounce up is bad); the arrow is direction, written in the markup. No tone = neutral or "no previous period".
250
+ - `ui-barlist`: horizontal bars. `--v` (0..1) is the length, `--c` the colour; label and value on one line, the bar
251
+ full width below it, so no fixed label column gets squeezed at 320px. `__share`, `__note`.
252
+ - `ui-chart`: stacked columns over time plus an optional hollow-dot series. `--v` per segment and dot, `data-max`
253
+ prints the axis top, gridlines at quarters. The drawing is `aria-hidden`; the numbers for a screen reader are the
254
+ surface's job (a table in `ui-sr-only`).
255
+ - `ui-legend` (`__item`, `__item--dot`), series colours `--ui-series-1..5` (1 is the brand iris; no red beside green).
256
+ - `ui-funnel`: steps with the conversion from the previous step (`__rate`); a row when wide, a column at ≤ 520px.
257
+ - `ui-grid--2` / `--3`: card grid that drops to one column; `ui-section-head`: an `h2` + note between page head and
258
+ card titles.
259
+
241
260
  ## The catalog: every token, class and component, with its markup
242
261
 
243
262
  `npm run catalog` builds `catalog/index.html` (also shipped in the npm tarball, and built by `prepack`):
@@ -283,7 +302,7 @@ something the base cannot express, add a token or a prop here, and every surface
283
302
  |---|---|
284
303
  | `gemboss-ui.css` | built at pack time: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `packages/ui/src/auth.css` + `packages/ui/src/assistant.css` |
285
304
  | `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). The admin's `gemboss-shell.css` @imports it on line 2; the export refuses a shared-shell rule in the admin's sheet or an admin-only rule here (one owner). |
286
- | `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): menu, key/value list, icon button; 0.3.0 adds `ui-link` / `ui-prose`, the media card (`ui-card--media`, `__media`, `__body`, `__foot`) and the popup's `ui-modal__body` / `__foot`. Shipped inside `gemboss-ui.css`; the admin does not import it (its first-load CSS budget has ~0.1 KB headroom). |
305
+ | `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): menu, key/value list, icon button; 0.3.0 adds `ui-link` / `ui-prose`, the media card (`ui-card--media`, `__media`, `__body`, `__foot`) and the popup's `ui-modal__body` / `__foot`. Shipped inside `gemboss-ui.css`; the admin does not import it (its first-load CSS budget has ~0.1 KB headroom).; (0.4.0) report pages: delta, large KPI grid, bar list, column chart, legend, funnel, card grid, section head. |
287
306
  | `packages/ui/src/kit-plain.js` | 0.3.0: the behaviour those surfaces each wrote by hand: `gembossUI.openModal` (Escape, backdrop, Tab trap, stacking, ask-before-close, focus back), `toast` (text, error tone), `initMenus` (outside click, item, blur, Escape closes the menu before the popup). A classic script: inline it or `<script src>`; see "Plain-HTML behaviour" below. |
288
307
  | `packages/ui/src/**/*.tsx` | the components, as TypeScript source. Next: add `@gemboss/ui` to `transpilePackages`. Vite compiles it as is. |
289
308
 
@@ -26,8 +26,8 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
26
26
 
27
27
  @media (max-width:760px){.cat{grid-template-columns:1fr;padding:16px}.cat-nav{position:static;flex-direction:row;flex-wrap:wrap}}
28
28
  </style></head><body>
29
- <div class="cat"><nav class="cat-nav" aria-label="Mục"><a href="#tokens">Token</a><a href="#buttons">Nút</a><a href="#fields">Ô nhập</a><a href="#choice">Tab, nhóm nút, thẻ</a><a href="#surfaces">Thẻ, số liệu, bảng, trạng thái trống</a><a href="#resizer">Thanh kéo giữa hai cột</a><a href="#modal">Hộp thoại, toast, menu (JS thuần)</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</a><a href="#chat">Thân chat</a><a href="#assistant">Trợ lý: cột phiên</a><a href="#icons">Icon</a></nav>
30
- <main><header class="cat-head"><h1>@gemboss/ui 0.3.0</h1><p>Ngôn ngữ thiết kế chung của các công cụ GemBoss. Markup dưới mỗi ví dụ là đúng thứ package render ra; bề mặt không dùng React (HTML tĩnh, Python) chép markup này và nạp <code>gemboss-ui.css</code>. Dựng từ gemboss <code>7d7968ae7238</code>.</p></header>
29
+ <div class="cat"><nav class="cat-nav" aria-label="Mục"><a href="#tokens">Token</a><a href="#buttons">Nút</a><a href="#fields">Ô nhập</a><a href="#choice">Tab, nhóm nút, thẻ</a><a href="#surfaces">Thẻ, số liệu, bảng, trạng thái trống</a><a href="#report">Báo cáo</a><a href="#resizer">Thanh kéo giữa hai cột</a><a href="#modal">Hộp thoại, toast, menu (JS thuần)</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</a><a href="#chat">Thân chat</a><a href="#assistant">Trợ lý: cột phiên</a><a href="#icons">Icon</a></nav>
30
+ <main><header class="cat-head"><h1>@gemboss/ui 0.4.0</h1><p>Ngôn ngữ thiết kế chung của các công cụ GemBoss. Markup dưới mỗi ví dụ là đúng thứ package render ra; bề mặt không dùng React (HTML tĩnh, Python) chép markup này và nạp <code>gemboss-ui.css</code>. Dựng từ gemboss <code>e86d83befc81</code>.</p></header>
31
31
  <section id="tokens"><h2>Token</h2><p>Đổi giao diện một bề mặt bằng cách đặt lại token, không viết đè class. Tên và giá trị đọc thẳng từ gemboss-ui.css.</p><div class="cat-swatches"><div class="cat-swatch"><i style="background:var(--gemboss-primary)"></i><code>--gemboss-primary</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-dark)"></i><code>--gemboss-primary-dark</code><span>#000000</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-soft)"></i><code>--gemboss-primary-soft</code><span>#f2f3f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent)"></i><code>--gemboss-accent</code><span>#5661f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-strong)"></i><code>--gemboss-accent-strong</code><span>#3a44de</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-soft)"></i><code>--gemboss-accent-soft</code><span>#eef0fe</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-strong)"></i><code>--gemboss-text-strong</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text)"></i><code>--gemboss-text</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-muted)"></i><code>--gemboss-text-muted</code><span>#686b71</span></div><div class="cat-swatch"><i style="background:var(--gemboss-surface)"></i><code>--gemboss-surface</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-canvas)"></i><code>--gemboss-canvas</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-preview-canvas)"></i><code>--gemboss-preview-canvas</code><span>#eceef1</span></div><div class="cat-swatch"><i style="background:var(--gemboss-rail-bg)"></i><code>--gemboss-rail-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-panel-bg)"></i><code>--gemboss-panel-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider)"></i><code>--gemboss-divider</code><span>#e5e7ea</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider-soft)"></i><code>--gemboss-divider-soft</code><span>#edeff2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-hover)"></i><code>--gemboss-hover</code><span>#eceef2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-bg)"></i><code>--gemboss-active-bg</code><span>#f0f1f4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-fg)"></i><code>--gemboss-active-fg</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger)"></i><code>--gemboss-danger</code><span>#d72c0d</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-strong)"></i><code>--gemboss-danger-strong</code><span>#b42318</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-soft)"></i><code>--gemboss-danger-soft</code><span>#fff0e9</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-border)"></i><code>--gemboss-danger-border</code><span>#f5c9bc</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success)"></i><code>--gemboss-success</code><span>#007f5f</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-strong)"></i><code>--gemboss-success-strong</code><span>#0a6a4c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-soft)"></i><code>--gemboss-success-soft</code><span>#e3f1df</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-border)"></i><code>--gemboss-success-border</code><span>#b7dfcd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning)"></i><code>--gemboss-warning</code><span>#9a6700</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-soft)"></i><code>--gemboss-warning-soft</code><span>#fff8e0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-border)"></i><code>--gemboss-warning-border</code><span>#f5d569</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-bg)"></i><code>--gemboss-dark-bg</code><span>#0a0a0c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-rail)"></i><code>--gemboss-dark-rail</code><span>#0e0e11</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface)"></i><code>--gemboss-dark-surface</code><span>#16161a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface-2)"></i><code>--gemboss-dark-surface-2</code><span>#1e1e24</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-divider)"></i><code>--gemboss-dark-divider</code><span>#2a2a31</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-text)"></i><code>--gemboss-dark-text</code><span>#ededf0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-muted)"></i><code>--gemboss-dark-muted</code><span>#9a9aa4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg)"></i><code>--gemboss-field-bg</code><span>#fdfdfd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg-hover)"></i><code>--gemboss-field-bg-hover</code><span>#fafafa</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-ink)"></i><code>--gemboss-field-ink</code><span>#303030</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-placeholder)"></i><code>--gemboss-field-placeholder</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line)"></i><code>--gemboss-field-line</code><span>#8a8a8a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-hover)"></i><code>--gemboss-field-line-hover</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-active)"></i><code>--gemboss-field-line-active</code><span>#1a1a1a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-ink)"></i><code>--gemboss-chip-ink</code><span>#303633</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line)"></i><code>--gemboss-chip-line</code><span>#cfd6ce</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line-hover)"></i><code>--gemboss-chip-line-hover</code><span>#aab4ad</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-bg-hover)"></i><code>--gemboss-chip-bg-hover</code><span>#f6f7f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-on-primary)"></i><code>--gemboss-on-primary</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-app-bg)"></i><code>--gemboss-app-bg</code><span>#ffffff</span></div></div><table class="ui-table cat-scale"><thead><tr><th>Token</th><th>Giá trị</th><th>Mẫu</th></tr></thead><tbody><tr><td><code>--gemboss-fs-micro</code></td><td>11px</td><td style="font-size:var(--gemboss-fs-micro)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-caption</code></td><td>12px</td><td style="font-size:var(--gemboss-fs-caption)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body</code></td><td>14px</td><td style="font-size:var(--gemboss-fs-body)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body-lg</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-body-lg)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h3</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-h3)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h2</code></td><td>20px</td><td style="font-size:var(--gemboss-fs-h2)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h1</code></td><td>24px</td><td style="font-size:var(--gemboss-fs-h1)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-display</code></td><td>30px</td><td style="font-size:var(--gemboss-fs-display)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-radius-xs</code></td><td>4px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xs)"></i></td></tr><tr><td><code>--gemboss-radius-sm</code></td><td>6px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-sm)"></i></td></tr><tr><td><code>--gemboss-radius-md</code></td><td>8px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-md)"></i></td></tr><tr><td><code>--gemboss-radius-lg</code></td><td>12px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-lg)"></i></td></tr><tr><td><code>--gemboss-radius-xl</code></td><td>16px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xl)"></i></td></tr><tr><td><code>--gemboss-radius-2xl</code></td><td>20px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-2xl)"></i></td></tr><tr><td><code>--gemboss-radius-full</code></td><td>999px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-full)"></i></td></tr><tr><td><code>--gemboss-shadow-resting</code></td><td class="cat-small">0 1px 2px rgba(31, 30, 29, 0.04), 0 2px 6px rgba(31, 30, 29, 0.06)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-resting);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-raised</code></td><td class="cat-small">0 4px 16px rgba(31, 30, 29, 0.10)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-raised);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-overlay</code></td><td class="cat-small">0 16px 48px rgba(31, 30, 29, 0.18)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-overlay);border:0"></i></td></tr></tbody></table></section>
32
32
  <section id="buttons"><h2>Nút</h2><p>Một lớp <code>ui-btn</code>, ba biến thể, một cỡ nhỏ. Dùng được trên <code>&lt;button&gt;</code> lẫn <code>&lt;a&gt;</code> (căn giữa sẵn).</p><figure class="cat-demo"><figcaption>Mặc định · chính · ghost · nhỏ · tắt</figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn">Lưu nháp</button><button type="button" class="ui-btn ui-btn--primary">Xuất bản</button><button type="button" class="ui-btn ui-btn--ghost">Huỷ</button><button type="button" class="ui-btn ui-btn--sm">Nhỏ</button><button type="button" class="ui-btn ui-btn--primary" disabled>Đang lưu…</button><a class="ui-btn" href="#buttons">Link dạng nút</a></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
33
33
  &lt;button type=&quot;button&quot; class=&quot;ui-btn&quot;&gt;Lưu nháp&lt;/button&gt;
@@ -244,6 +244,253 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
244
244
  &lt;/table&gt;
245
245
  &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Trống</figcaption><div class="cat-live"><div class="ui-empty">Chưa có gì ở đây.<button type="button" class="ui-btn ui-btn--primary">Tạo mục đầu tiên</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-empty&quot;&gt;Chưa có gì ở đây.&lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary&quot;&gt;Tạo mục đầu tiên&lt;/button&gt;
246
246
  &lt;/div&gt;</code></pre></details></figure></section>
247
+ <section id="report"><h2>Báo cáo</h2><p>Trang số liệu viết tay (gemnews /admin, 03/10/2026): KPI có so kỳ trước, thanh ngang, biểu đồ cột chồng, chú thích, phễu, lưới card và tiêu đề mục. Chỉ CSS (<code>kit-plain.css</code>): bề mặt tính số và tỉ lệ rồi đặt vào <code>--v</code> (0..1); màu chuỗi lấy từ <code>--ui-series-1..5</code> qua <code>--c</code>. Tốt / xấu của delta là bề mặt quyết, không phải hướng mũi tên.</p><figure class="cat-demo"><figcaption>KPI lớn trong lưới đều (<code>ui-metrics--grid</code> + <code>ui-metric--lg</code>, <code>ui-delta--good|--bad</code>)</figcaption><div class="cat-live"><div class="ui-metrics ui-metrics--grid"><div class="ui-metric ui-metric--lg"><span class="ui-metric__lbl">Lượt đọc bài</span><span class="ui-metric__val">1.284</span><span class="ui-metric__foot"><span class="ui-delta ui-delta--good"><span aria-hidden="true">↑</span> 18%</span>so với 28 ngày trước</span></div><div class="ui-metric ui-metric--lg"><span class="ui-metric__lbl">Tỉ lệ đọc hết</span><span class="ui-metric__val">29,1%</span><span class="ui-metric__foot"><span class="ui-delta ui-delta--bad"><span aria-hidden="true">↓</span> 3,2 điểm</span>so với 28 ngày trước</span></div><div class="ui-metric ui-metric--lg"><span class="ui-metric__lbl">Người đọc</span><span class="ui-metric__val">412</span><span class="ui-metric__foot"><span class="ui-delta">Chưa có kỳ trước</span></span></div><div class="ui-metric ui-metric--lg"><span class="ui-metric__lbl">Lượt mở website</span><span class="ui-metric__val">640</span><span class="ui-metric__foot"><span class="ui-delta"><span aria-hidden="true">→</span> 0%</span>so với 28 ngày trước</span></div></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-metrics ui-metrics--grid&quot;&gt;
248
+ &lt;div class=&quot;ui-metric ui-metric--lg&quot;&gt;
249
+ &lt;span class=&quot;ui-metric__lbl&quot;&gt;Lượt đọc bài&lt;/span&gt;
250
+ &lt;span class=&quot;ui-metric__val&quot;&gt;1.284&lt;/span&gt;
251
+ &lt;span class=&quot;ui-metric__foot&quot;&gt;
252
+ &lt;span class=&quot;ui-delta ui-delta--good&quot;&gt;
253
+ &lt;span aria-hidden=&quot;true&quot;&gt;↑&lt;/span&gt; 18%&lt;/span&gt;so với 28 ngày trước&lt;/span&gt;
254
+ &lt;/div&gt;
255
+ &lt;div class=&quot;ui-metric ui-metric--lg&quot;&gt;
256
+ &lt;span class=&quot;ui-metric__lbl&quot;&gt;Tỉ lệ đọc hết&lt;/span&gt;
257
+ &lt;span class=&quot;ui-metric__val&quot;&gt;29,1%&lt;/span&gt;
258
+ &lt;span class=&quot;ui-metric__foot&quot;&gt;
259
+ &lt;span class=&quot;ui-delta ui-delta--bad&quot;&gt;
260
+ &lt;span aria-hidden=&quot;true&quot;&gt;↓&lt;/span&gt; 3,2 điểm&lt;/span&gt;so với 28 ngày trước&lt;/span&gt;
261
+ &lt;/div&gt;
262
+ &lt;div class=&quot;ui-metric ui-metric--lg&quot;&gt;
263
+ &lt;span class=&quot;ui-metric__lbl&quot;&gt;Người đọc&lt;/span&gt;
264
+ &lt;span class=&quot;ui-metric__val&quot;&gt;412&lt;/span&gt;
265
+ &lt;span class=&quot;ui-metric__foot&quot;&gt;
266
+ &lt;span class=&quot;ui-delta&quot;&gt;Chưa có kỳ trước&lt;/span&gt;
267
+ &lt;/span&gt;
268
+ &lt;/div&gt;
269
+ &lt;div class=&quot;ui-metric ui-metric--lg&quot;&gt;
270
+ &lt;span class=&quot;ui-metric__lbl&quot;&gt;Lượt mở website&lt;/span&gt;
271
+ &lt;span class=&quot;ui-metric__val&quot;&gt;640&lt;/span&gt;
272
+ &lt;span class=&quot;ui-metric__foot&quot;&gt;
273
+ &lt;span class=&quot;ui-delta&quot;&gt;
274
+ &lt;span aria-hidden=&quot;true&quot;&gt;→&lt;/span&gt; 0%&lt;/span&gt;so với 28 ngày trước&lt;/span&gt;
275
+ &lt;/div&gt;
276
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Biểu đồ cột chồng + chuỗi chấm (<code>ui-chart</code>, <code>data-max</code> là mốc trục) · chú thích (<code>ui-legend</code>)</figcaption><div class="cat-live"><figure class="ui-chart"><div class="ui-chart__plot" data-max="60" aria-hidden="true"><div class="ui-chart__col" title="20/09 · Tiện ích 0 · Website 0 · Lượt mở 0"><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.000"></span></div><div class="ui-chart__col" title="21/09 · Tiện ích 0 · Website 0 · Lượt mở 0"><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.000"></span></div><div class="ui-chart__col" title="22/09 · Tiện ích 0 · Website 0 · Lượt mở 0"><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.000;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.000"></span></div><div class="ui-chart__col" title="23/09 · Tiện ích 2 · Website 1 · Lượt mở 0"><span class="ui-chart__seg" style="--v:0.033;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.017;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.000"></span></div><div class="ui-chart__col" title="24/09 · Tiện ích 46 · Website 11 · Lượt mở 24"><span class="ui-chart__seg" style="--v:0.767;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.183;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.400"></span></div><div class="ui-chart__col" title="25/09 · Tiện ích 15 · Website 4 · Lượt mở 40"><span class="ui-chart__seg" style="--v:0.250;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.067;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.667"></span></div><div class="ui-chart__col" title="26/09 · Tiện ích 10 · Website 2 · Lượt mở 18"><span class="ui-chart__seg" style="--v:0.167;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.033;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.300"></span></div><div class="ui-chart__col" title="27/09 · Tiện ích 19 · Website 5 · Lượt mở 15"><span class="ui-chart__seg" style="--v:0.317;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.083;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.250"></span></div><div class="ui-chart__col" title="28/09 · Tiện ích 25 · Website 6 · Lượt mở 22"><span class="ui-chart__seg" style="--v:0.417;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.100;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.367"></span></div><div class="ui-chart__col" title="29/09 · Tiện ích 14 · Website 4 · Lượt mở 30"><span class="ui-chart__seg" style="--v:0.233;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.067;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.500"></span></div><div class="ui-chart__col" title="30/09 · Tiện ích 7 · Website 2 · Lượt mở 12"><span class="ui-chart__seg" style="--v:0.117;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.033;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.200"></span></div><div class="ui-chart__col" title="31/09 · Tiện ích 22 · Website 5 · Lượt mở 25"><span class="ui-chart__seg" style="--v:0.367;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.083;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.417"></span></div><div class="ui-chart__col" title="32/09 · Tiện ích 18 · Website 4 · Lượt mở 20"><span class="ui-chart__seg" style="--v:0.300;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.067;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.333"></span></div><div class="ui-chart__col" title="33/09 · Tiện ích 24 · Website 6 · Lượt mở 28"><span class="ui-chart__seg" style="--v:0.400;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" style="--v:0.100;--c:var(--ui-series-2)"></span><span class="ui-chart__dot" style="--v:0.467"></span></div></div><div class="ui-chart__axis" aria-hidden="true"><span>20/09</span><span>03/10</span></div><figcaption class="ui-legend"><span class="ui-legend__item"><i style="--c:var(--ui-series-1)"></i>Tiện ích</span><span class="ui-legend__item"><i style="--c:var(--ui-series-2)"></i>Website</span><span class="ui-legend__item ui-legend__item--dot"><i></i>Lượt mở website</span></figcaption></figure></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;figure class=&quot;ui-chart&quot;&gt;
277
+ &lt;div class=&quot;ui-chart__plot&quot; data-max=&quot;60&quot; aria-hidden=&quot;true&quot;&gt;
278
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;20/09 · Tiện ích 0 · Website 0 · Lượt mở 0&quot;&gt;
279
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-1)&quot;&gt;
280
+ &lt;/span&gt;
281
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-2)&quot;&gt;
282
+ &lt;/span&gt;
283
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.000&quot;&gt;
284
+ &lt;/span&gt;
285
+ &lt;/div&gt;
286
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;21/09 · Tiện ích 0 · Website 0 · Lượt mở 0&quot;&gt;
287
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-1)&quot;&gt;
288
+ &lt;/span&gt;
289
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-2)&quot;&gt;
290
+ &lt;/span&gt;
291
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.000&quot;&gt;
292
+ &lt;/span&gt;
293
+ &lt;/div&gt;
294
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;22/09 · Tiện ích 0 · Website 0 · Lượt mở 0&quot;&gt;
295
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-1)&quot;&gt;
296
+ &lt;/span&gt;
297
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.000;--c:var(--ui-series-2)&quot;&gt;
298
+ &lt;/span&gt;
299
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.000&quot;&gt;
300
+ &lt;/span&gt;
301
+ &lt;/div&gt;
302
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;23/09 · Tiện ích 2 · Website 1 · Lượt mở 0&quot;&gt;
303
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.033;--c:var(--ui-series-1)&quot;&gt;
304
+ &lt;/span&gt;
305
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.017;--c:var(--ui-series-2)&quot;&gt;
306
+ &lt;/span&gt;
307
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.000&quot;&gt;
308
+ &lt;/span&gt;
309
+ &lt;/div&gt;
310
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;24/09 · Tiện ích 46 · Website 11 · Lượt mở 24&quot;&gt;
311
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.767;--c:var(--ui-series-1)&quot;&gt;
312
+ &lt;/span&gt;
313
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.183;--c:var(--ui-series-2)&quot;&gt;
314
+ &lt;/span&gt;
315
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.400&quot;&gt;
316
+ &lt;/span&gt;
317
+ &lt;/div&gt;
318
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;25/09 · Tiện ích 15 · Website 4 · Lượt mở 40&quot;&gt;
319
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.250;--c:var(--ui-series-1)&quot;&gt;
320
+ &lt;/span&gt;
321
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.067;--c:var(--ui-series-2)&quot;&gt;
322
+ &lt;/span&gt;
323
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.667&quot;&gt;
324
+ &lt;/span&gt;
325
+ &lt;/div&gt;
326
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;26/09 · Tiện ích 10 · Website 2 · Lượt mở 18&quot;&gt;
327
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.167;--c:var(--ui-series-1)&quot;&gt;
328
+ &lt;/span&gt;
329
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.033;--c:var(--ui-series-2)&quot;&gt;
330
+ &lt;/span&gt;
331
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.300&quot;&gt;
332
+ &lt;/span&gt;
333
+ &lt;/div&gt;
334
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;27/09 · Tiện ích 19 · Website 5 · Lượt mở 15&quot;&gt;
335
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.317;--c:var(--ui-series-1)&quot;&gt;
336
+ &lt;/span&gt;
337
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.083;--c:var(--ui-series-2)&quot;&gt;
338
+ &lt;/span&gt;
339
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.250&quot;&gt;
340
+ &lt;/span&gt;
341
+ &lt;/div&gt;
342
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;28/09 · Tiện ích 25 · Website 6 · Lượt mở 22&quot;&gt;
343
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.417;--c:var(--ui-series-1)&quot;&gt;
344
+ &lt;/span&gt;
345
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.100;--c:var(--ui-series-2)&quot;&gt;
346
+ &lt;/span&gt;
347
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.367&quot;&gt;
348
+ &lt;/span&gt;
349
+ &lt;/div&gt;
350
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;29/09 · Tiện ích 14 · Website 4 · Lượt mở 30&quot;&gt;
351
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.233;--c:var(--ui-series-1)&quot;&gt;
352
+ &lt;/span&gt;
353
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.067;--c:var(--ui-series-2)&quot;&gt;
354
+ &lt;/span&gt;
355
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.500&quot;&gt;
356
+ &lt;/span&gt;
357
+ &lt;/div&gt;
358
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;30/09 · Tiện ích 7 · Website 2 · Lượt mở 12&quot;&gt;
359
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.117;--c:var(--ui-series-1)&quot;&gt;
360
+ &lt;/span&gt;
361
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.033;--c:var(--ui-series-2)&quot;&gt;
362
+ &lt;/span&gt;
363
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.200&quot;&gt;
364
+ &lt;/span&gt;
365
+ &lt;/div&gt;
366
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;31/09 · Tiện ích 22 · Website 5 · Lượt mở 25&quot;&gt;
367
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.367;--c:var(--ui-series-1)&quot;&gt;
368
+ &lt;/span&gt;
369
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.083;--c:var(--ui-series-2)&quot;&gt;
370
+ &lt;/span&gt;
371
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.417&quot;&gt;
372
+ &lt;/span&gt;
373
+ &lt;/div&gt;
374
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;32/09 · Tiện ích 18 · Website 4 · Lượt mở 20&quot;&gt;
375
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.300;--c:var(--ui-series-1)&quot;&gt;
376
+ &lt;/span&gt;
377
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.067;--c:var(--ui-series-2)&quot;&gt;
378
+ &lt;/span&gt;
379
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.333&quot;&gt;
380
+ &lt;/span&gt;
381
+ &lt;/div&gt;
382
+ &lt;div class=&quot;ui-chart__col&quot; title=&quot;33/09 · Tiện ích 24 · Website 6 · Lượt mở 28&quot;&gt;
383
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.400;--c:var(--ui-series-1)&quot;&gt;
384
+ &lt;/span&gt;
385
+ &lt;span class=&quot;ui-chart__seg&quot; style=&quot;--v:0.100;--c:var(--ui-series-2)&quot;&gt;
386
+ &lt;/span&gt;
387
+ &lt;span class=&quot;ui-chart__dot&quot; style=&quot;--v:0.467&quot;&gt;
388
+ &lt;/span&gt;
389
+ &lt;/div&gt;
390
+ &lt;/div&gt;
391
+ &lt;div class=&quot;ui-chart__axis&quot; aria-hidden=&quot;true&quot;&gt;
392
+ &lt;span&gt;20/09&lt;/span&gt;
393
+ &lt;span&gt;03/10&lt;/span&gt;
394
+ &lt;/div&gt;
395
+ &lt;figcaption class=&quot;ui-legend&quot;&gt;
396
+ &lt;span class=&quot;ui-legend__item&quot;&gt;
397
+ &lt;i style=&quot;--c:var(--ui-series-1)&quot;&gt;
398
+ &lt;/i&gt;Tiện ích&lt;/span&gt;
399
+ &lt;span class=&quot;ui-legend__item&quot;&gt;
400
+ &lt;i style=&quot;--c:var(--ui-series-2)&quot;&gt;
401
+ &lt;/i&gt;Website&lt;/span&gt;
402
+ &lt;span class=&quot;ui-legend__item ui-legend__item--dot&quot;&gt;
403
+ &lt;i&gt;
404
+ &lt;/i&gt;Lượt mở website&lt;/span&gt;
405
+ &lt;/figcaption&gt;
406
+ &lt;/figure&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Thanh ngang (<code>ui-barlist</code>, <code>--v</code> = n / n lớn nhất, <code>__note</code> dòng phụ)</figcaption><div class="cat-live"><ol class="ui-barlist"><li><span class="ui-barlist__label">Tiếng Việt</span><span class="ui-barlist__val">512<span class="ui-barlist__share">49,8%</span></span><span class="ui-barlist__bar" style="--v:1"></span><span class="ui-barlist__note">Đọc hết 31%</span></li><li><span class="ui-barlist__label">English</span><span class="ui-barlist__val">390<span class="ui-barlist__share">37,9%</span></span><span class="ui-barlist__bar" style="--v:.762;--c:var(--ui-series-2)"></span><span class="ui-barlist__note">Đọc hết 27%</span></li><li><span class="ui-barlist__label">中文</span><span class="ui-barlist__val">126<span class="ui-barlist__share">12,3%</span></span><span class="ui-barlist__bar" style="--v:.246;--c:var(--ui-series-3)"></span><span class="ui-barlist__note">Đọc hết 35%</span></li></ol></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;ol class=&quot;ui-barlist&quot;&gt;
407
+ &lt;li&gt;
408
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;Tiếng Việt&lt;/span&gt;
409
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;512&lt;span class=&quot;ui-barlist__share&quot;&gt;49,8%&lt;/span&gt;
410
+ &lt;/span&gt;
411
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:1&quot;&gt;
412
+ &lt;/span&gt;
413
+ &lt;span class=&quot;ui-barlist__note&quot;&gt;Đọc hết 31%&lt;/span&gt;
414
+ &lt;/li&gt;
415
+ &lt;li&gt;
416
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;English&lt;/span&gt;
417
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;390&lt;span class=&quot;ui-barlist__share&quot;&gt;37,9%&lt;/span&gt;
418
+ &lt;/span&gt;
419
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:.762;--c:var(--ui-series-2)&quot;&gt;
420
+ &lt;/span&gt;
421
+ &lt;span class=&quot;ui-barlist__note&quot;&gt;Đọc hết 27%&lt;/span&gt;
422
+ &lt;/li&gt;
423
+ &lt;li&gt;
424
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;中文&lt;/span&gt;
425
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;126&lt;span class=&quot;ui-barlist__share&quot;&gt;12,3%&lt;/span&gt;
426
+ &lt;/span&gt;
427
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:.246;--c:var(--ui-series-3)&quot;&gt;
428
+ &lt;/span&gt;
429
+ &lt;span class=&quot;ui-barlist__note&quot;&gt;Đọc hết 35%&lt;/span&gt;
430
+ &lt;/li&gt;
431
+ &lt;/ol&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Phễu (<code>ui-funnel</code>, <code>__rate</code> = đi tiếp từ bước ngay trước)</figcaption><div class="cat-live"><ol class="ui-funnel"><li><span class="ui-funnel__val">1.050</span><span class="ui-funnel__lbl">Lời mời cài hiện ra</span></li><li><span class="ui-funnel__rate">4,8%</span><span class="ui-funnel__val">50</span><span class="ui-funnel__lbl">Bấm Thêm vào Chrome</span></li><li><span class="ui-funnel__rate">38%</span><span class="ui-funnel__val">19</span><span class="ui-funnel__lbl">Trình duyệt có tiện ích</span></li></ol></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;ol class=&quot;ui-funnel&quot;&gt;
432
+ &lt;li&gt;
433
+ &lt;span class=&quot;ui-funnel__val&quot;&gt;1.050&lt;/span&gt;
434
+ &lt;span class=&quot;ui-funnel__lbl&quot;&gt;Lời mời cài hiện ra&lt;/span&gt;
435
+ &lt;/li&gt;
436
+ &lt;li&gt;
437
+ &lt;span class=&quot;ui-funnel__rate&quot;&gt;4,8%&lt;/span&gt;
438
+ &lt;span class=&quot;ui-funnel__val&quot;&gt;50&lt;/span&gt;
439
+ &lt;span class=&quot;ui-funnel__lbl&quot;&gt;Bấm Thêm vào Chrome&lt;/span&gt;
440
+ &lt;/li&gt;
441
+ &lt;li&gt;
442
+ &lt;span class=&quot;ui-funnel__rate&quot;&gt;38%&lt;/span&gt;
443
+ &lt;span class=&quot;ui-funnel__val&quot;&gt;19&lt;/span&gt;
444
+ &lt;span class=&quot;ui-funnel__lbl&quot;&gt;Trình duyệt có tiện ích&lt;/span&gt;
445
+ &lt;/li&gt;
446
+ &lt;/ol&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Lưới card (<code>ui-grid--2</code>) · tiêu đề mục (<code>ui-section-head</code>)</figcaption><div class="cat-live"><div class="ui-section-head"><h2>Người đọc</h2><p>Đọc ở đâu, bằng ngôn ngữ nào.</p></div><div class="ui-grid ui-grid--2" style="margin-top:12px"><div class="ui-card"><div class="ui-card__head"><h3 class="ui-card__title">Theo nơi đọc</h3></div><ol class="ui-barlist"><li><span class="ui-barlist__label">Tiện ích</span><span class="ui-barlist__val">70<span class="ui-barlist__share">88,6%</span></span><span class="ui-barlist__bar" style="--v:1"></span></li><li><span class="ui-barlist__label">Website</span><span class="ui-barlist__val">9<span class="ui-barlist__share">11,4%</span></span><span class="ui-barlist__bar" style="--v:.129;--c:var(--ui-series-2)"></span></li></ol></div><div class="ui-card"><div class="ui-card__head"><h3 class="ui-card__title">Theo thiết bị</h3></div><ol class="ui-barlist"><li><span class="ui-barlist__label">Máy tính</span><span class="ui-barlist__val">50<span class="ui-barlist__share">78,1%</span></span><span class="ui-barlist__bar" style="--v:1"></span></li><li><span class="ui-barlist__label">Điện thoại</span><span class="ui-barlist__val">14<span class="ui-barlist__share">21,9%</span></span><span class="ui-barlist__bar" style="--v:.28"></span></li></ol></div></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-section-head&quot;&gt;
447
+ &lt;h2&gt;Người đọc&lt;/h2&gt;
448
+ &lt;p&gt;Đọc ở đâu, bằng ngôn ngữ nào.&lt;/p&gt;
449
+ &lt;/div&gt;
450
+ &lt;div class=&quot;ui-grid ui-grid--2&quot; style=&quot;margin-top:12px&quot;&gt;
451
+ &lt;div class=&quot;ui-card&quot;&gt;
452
+ &lt;div class=&quot;ui-card__head&quot;&gt;
453
+ &lt;h3 class=&quot;ui-card__title&quot;&gt;Theo nơi đọc&lt;/h3&gt;
454
+ &lt;/div&gt;
455
+ &lt;ol class=&quot;ui-barlist&quot;&gt;
456
+ &lt;li&gt;
457
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;Tiện ích&lt;/span&gt;
458
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;70&lt;span class=&quot;ui-barlist__share&quot;&gt;88,6%&lt;/span&gt;
459
+ &lt;/span&gt;
460
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:1&quot;&gt;
461
+ &lt;/span&gt;
462
+ &lt;/li&gt;
463
+ &lt;li&gt;
464
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;Website&lt;/span&gt;
465
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;9&lt;span class=&quot;ui-barlist__share&quot;&gt;11,4%&lt;/span&gt;
466
+ &lt;/span&gt;
467
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:.129;--c:var(--ui-series-2)&quot;&gt;
468
+ &lt;/span&gt;
469
+ &lt;/li&gt;
470
+ &lt;/ol&gt;
471
+ &lt;/div&gt;
472
+ &lt;div class=&quot;ui-card&quot;&gt;
473
+ &lt;div class=&quot;ui-card__head&quot;&gt;
474
+ &lt;h3 class=&quot;ui-card__title&quot;&gt;Theo thiết bị&lt;/h3&gt;
475
+ &lt;/div&gt;
476
+ &lt;ol class=&quot;ui-barlist&quot;&gt;
477
+ &lt;li&gt;
478
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;Máy tính&lt;/span&gt;
479
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;50&lt;span class=&quot;ui-barlist__share&quot;&gt;78,1%&lt;/span&gt;
480
+ &lt;/span&gt;
481
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:1&quot;&gt;
482
+ &lt;/span&gt;
483
+ &lt;/li&gt;
484
+ &lt;li&gt;
485
+ &lt;span class=&quot;ui-barlist__label&quot;&gt;Điện thoại&lt;/span&gt;
486
+ &lt;span class=&quot;ui-barlist__val&quot;&gt;14&lt;span class=&quot;ui-barlist__share&quot;&gt;21,9%&lt;/span&gt;
487
+ &lt;/span&gt;
488
+ &lt;span class=&quot;ui-barlist__bar&quot; style=&quot;--v:.28&quot;&gt;
489
+ &lt;/span&gt;
490
+ &lt;/li&gt;
491
+ &lt;/ol&gt;
492
+ &lt;/div&gt;
493
+ &lt;/div&gt;</code></pre></details></figure></section>
247
494
  <section id="resizer"><h2>Thanh kéo giữa hai cột</h2><p>Thanh <code>ui-resizer</code> đứng giữa hai cột, người dùng kéo để đổi độ rộng. Phần NHÌN (vạch nắm hiện khi rê chuột hoặc focus bàn phím, vùng chạm 24px cho ngón tay, lớp che khi đang kéo) là của gemboss-ui.css; vị trí và hành vi là của bề mặt: dùng <b>pointer event</b> (chạm là một pointer), gắn <code>ui-resizing</code> lên <code>&lt;body&gt;</code> trong lúc kéo, phím mũi tên / Home / End di chuyển nó, kẹp theo bề rộng THẬT của khung (không theo một con số), và nhớ độ rộng theo trình duyệt. Admin: <code>useResizableChatPanel</code>; gemcare: viết tay theo đúng markup này.</p><figure class="cat-demo"><figcaption>Rê chuột hoặc Tab vào khe giữa để thấy vạch nắm</figcaption><div class="cat-live"><div style="display:grid;grid-template-columns:200px 5px minmax(0,1fr);height:140px;border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-md);overflow:hidden"><div style="padding:12px;background:var(--gemboss-surface)">Danh sách</div><div class="ui-resizer" role="separator" aria-orientation="vertical" aria-label="Độ rộng danh sách" tabindex="0" aria-valuenow="200" aria-valuemin="160" aria-valuemax="420"></div><div style="padding:12px;background:var(--gemboss-surface)">Nội dung</div></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div style=&quot;display:grid;grid-template-columns:200px 5px minmax(0,1fr);height:140px;border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-md);overflow:hidden&quot;&gt;
248
495
  &lt;div style=&quot;padding:12px;background:var(--gemboss-surface)&quot;&gt;Danh sách&lt;/div&gt;
249
496
  &lt;div class=&quot;ui-resizer&quot; role=&quot;separator&quot; aria-orientation=&quot;vertical&quot; aria-label=&quot;Độ rộng danh sách&quot; tabindex=&quot;0&quot; aria-valuenow=&quot;200&quot; aria-valuemin=&quot;160&quot; aria-valuemax=&quot;420&quot;&gt;
package/gemboss-ui.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * GemBoss admin design system — GENERATED — do not edit; regenerate from gemboss:
3
3
  * node apps/admin/scripts/export-ui-css.mjs --out <path> (npm run export:ui-css)
4
- * gemboss commit: 7d7968ae7238c5f4e7643d54dbac372f32ef5775
4
+ * gemboss commit: e86d83befc81e8a35c127cbe327f3ad895713c22
5
5
  * sources sha256: 2807a6d2a8b27730eaa31cbe6195b633cbba9467b0ab62b9244a7f814bb8d205
6
6
  * contents: 1 allow-listed rule from apps/admin/src/app/globals.css + packages/ui/src/gemboss-tokens.css
7
7
  * (verbatim) + packages/ui/src/ui.css (verbatim) + packages/ui/src/gemboss-field.css (verbatim)
@@ -3273,6 +3273,112 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
3273
3273
  .ui-modal > .ui-modal__head, .ui-modal > .ui-modal__foot { flex: none; }
3274
3274
  .ui-modal > .ui-modal__body { flex: 1 1 auto; min-height: 0; overflow: auto; }
3275
3275
 
3276
+ /* ── 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 ─────────────────
3277
+ 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à
3278
+ 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
3279
+ surface needs". Chỉ CSS: số liệu và tỉ lệ do bề mặt tính, kit chỉ vẽ.
3280
+
3281
+ 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
3282
+ đỏ-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ử. */
3283
+ :root {
3284
+ --ui-series-1: var(--gemboss-accent, #5661f5);
3285
+ --ui-series-2: #0f9f8f;
3286
+ --ui-series-3: #d9822b;
3287
+ --ui-series-4: #c2417a;
3288
+ --ui-series-5: #6b7280;
3289
+ }
3290
+
3291
+ /* 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
3292
+ 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
3293
+ "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. */
3294
+ .ui-delta { display: inline-flex; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
3295
+ .ui-delta--good { color: var(--ui-success-fg); }
3296
+ .ui-delta--bad { color: var(--ui-danger-fg); }
3297
+
3298
+ /* KPI lớn trong lưới đều: `<div class="ui-metrics ui-metrics--grid"><div class="ui-metric ui-metric--lg">…`. Lưới
3299
+ 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
3300
+ dưới cùng (delta + so với gì). */
3301
+ .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
3302
+ .ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
3303
+ .ui-metric--lg .ui-metric__val { font-size: var(--gemboss-fs-h1, 24px); line-height: 1.1; }
3304
+ .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--gemboss-fs-body, 14px); }
3305
+ .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); }
3306
+ @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
3307
+
3308
+ /* Thanh ngang: `<ol class="ui-barlist"><li><span class="ui-barlist__label">Google</span><span class="ui-barlist__val">60
3309
+ <span class="ui-barlist__share">93,8%</span></span><span class="ui-barlist__bar" style="--v:.94"></span></li></ol>`.
3310
+ `--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ề
3311
+ 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%"). */
3312
+ .ui-barlist { display: grid; gap: var(--ui-space-3); margin: 0; padding: 0; list-style: none; font-size: var(--ui-fs-body, 13px); }
3313
+ .ui-barlist > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 4px var(--ui-space-3); }
3314
+ .ui-barlist__label { min-width: 0; overflow-wrap: anywhere; color: var(--ui-text); }
3315
+ .ui-barlist__label a { color: inherit; }
3316
+ .ui-barlist__val { font-weight: 600; color: var(--ui-text); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
3317
+ .ui-barlist__share { margin-left: 6px; font-weight: 400; color: var(--ui-text-muted); }
3318
+ .ui-barlist__note { grid-column: 1 / -1; margin-top: -2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
3319
+ .ui-barlist__bar { grid-column: 1 / -1; position: relative; height: 6px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; }
3320
+ .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)); }
3321
+
3322
+ /* Biểu đồ cột chồng theo thời gian, thêm (tuỳ chọn) một chuỗi CHẤM:
3323
+ `<figure class="ui-chart"><div class="ui-chart__plot" data-max="80"><div class="ui-chart__col" title="02/10 · …">
3324
+ <span class="ui-chart__seg" style="--v:.4;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" …></span>
3325
+ <span class="ui-chart__dot" style="--v:.5"></span></div>…</div><div class="ui-chart__axis"><span>01/10</span>…</div>
3326
+ <figcaption class="ui-legend">…</figcaption></figure>`. `--v` = giá trị / trục (0..1), chồng từ dưới lên theo thứ tự
3327
+ 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
3328
+ 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à
3329
+ 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`. */
3330
+ .ui-chart { margin: 0; }
3331
+ .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);
3332
+ 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; }
3333
+ .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; }
3334
+ .ui-chart__col { position: relative; flex: 1 1 0; min-width: 0; max-width: 48px; height: 100%; display: flex; flex-direction: column-reverse; }
3335
+ .ui-chart__col:hover { background: color-mix(in srgb, var(--ui-text) 6%, transparent); }
3336
+ .ui-chart__seg { display: block; flex: none; height: calc(var(--v, 0) * 100%); background: var(--c, var(--ui-series-1)); }
3337
+ .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))); }
3338
+ .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; }
3339
+
3340
+ /* Chú thích: `<p class="ui-legend"><span class="ui-legend__item"><i style="--c:var(--ui-series-1)"></i>Tiện ích</span>
3341
+ <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. */
3342
+ .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); }
3343
+ .ui-legend__item { display: inline-flex; align-items: center; gap: 6px; }
3344
+ .ui-legend__item > i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--c, var(--ui-series-1)); }
3345
+ .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; }
3346
+
3347
+ /* 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
3348
+ </span></li><li><span class="ui-funnel__rate">4,8%</span><span class="ui-funnel__val">5</span>…</li></ol>`. Mỗi bước
3349
+ 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. */
3350
+ .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; }
3351
+ .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; }
3352
+ .ui-funnel__val { font-size: var(--gemboss-fs-h2, 20px); font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
3353
+ .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
3354
+ .ui-funnel__rate { order: -1; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-series-1); font-variant-numeric: tabular-nums; }
3355
+ @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
3356
+
3357
+ /* 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
3358
+ 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,
3359
+ lớn hơn tiêu đề card): `<div class="ui-section-head"><h2>Nội dung</h2><p>…</p></div>`. */
3360
+ .ui-grid { display: grid; gap: var(--ui-space-4); align-items: start; }
3361
+ .ui-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3362
+ .ui-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
3363
+ @media (max-width: 1100px) { .ui-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
3364
+ @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
3365
+ @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
3366
+ .ui-section-head { margin: var(--ui-space-4) 0 0; }
3367
+ .ui-section-head > h2 { margin: 0; font-size: var(--gemboss-fs-h3, 16px); font-weight: 650; color: var(--ui-text); }
3368
+ .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
3369
+
3370
+ /* 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, "中文"
3371
+ 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
3372
+ 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. */
3373
+ .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) */
3374
+ .ui-seg :is(button, a).is-text { flex: none; white-space: nowrap; }
3375
+ /* Ở 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
3376
+ thấy lựa chọn cuối. Nút vẫn cao 36px, chữ vẫn một dòng. */
3377
+ @media (max-width: 360px) { .ui-seg :is(button, a).is-text { padding: 0 9px; } }
3378
+ /* 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
3379
+ 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. */
3380
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-seg :is(button, a) { height: 44px; min-height: 44px; } }
3381
+
3276
3382
  /* ═══ @gemboss/ui src/chat.css ═══ */
3277
3383
  /*
3278
3384
  * The chat BODY — what an answer looks like, wherever a GemBoss surface puts a chat (01/10/2026).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gemboss/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "GemBoss design system for every surface in the fleet: the admin shell (topbar + rail + drawer) as React components, and the admin stylesheet exported as one CSS file.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/src/kit-plain.css CHANGED
@@ -108,3 +108,109 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
108
108
  .ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
109
109
  .ui-modal > .ui-modal__head, .ui-modal > .ui-modal__foot { flex: none; }
110
110
  .ui-modal > .ui-modal__body { flex: 1 1 auto; min-height: 0; overflow: auto; }
111
+
112
+ /* ── 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 ─────────────────
113
+ 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à
114
+ 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
115
+ surface needs". Chỉ CSS: số liệu và tỉ lệ do bề mặt tính, kit chỉ vẽ.
116
+
117
+ 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
118
+ đỏ-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ử. */
119
+ :root {
120
+ --ui-series-1: var(--gemboss-accent, #5661f5);
121
+ --ui-series-2: #0f9f8f;
122
+ --ui-series-3: #d9822b;
123
+ --ui-series-4: #c2417a;
124
+ --ui-series-5: #6b7280;
125
+ }
126
+
127
+ /* 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
128
+ 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
129
+ "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. */
130
+ .ui-delta { display: inline-flex; align-items: baseline; gap: 2px; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
131
+ .ui-delta--good { color: var(--ui-success-fg); }
132
+ .ui-delta--bad { color: var(--ui-danger-fg); }
133
+
134
+ /* KPI lớn trong lưới đều: `<div class="ui-metrics ui-metrics--grid"><div class="ui-metric ui-metric--lg">…`. Lưới
135
+ 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
136
+ dưới cùng (delta + so với gì). */
137
+ .ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
138
+ .ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
139
+ .ui-metric--lg .ui-metric__val { font-size: var(--gemboss-fs-h1, 24px); line-height: 1.1; }
140
+ .ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--gemboss-fs-body, 14px); }
141
+ .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); }
142
+ @media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
143
+
144
+ /* Thanh ngang: `<ol class="ui-barlist"><li><span class="ui-barlist__label">Google</span><span class="ui-barlist__val">60
145
+ <span class="ui-barlist__share">93,8%</span></span><span class="ui-barlist__bar" style="--v:.94"></span></li></ol>`.
146
+ `--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ề
147
+ 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%"). */
148
+ .ui-barlist { display: grid; gap: var(--ui-space-3); margin: 0; padding: 0; list-style: none; font-size: var(--ui-fs-body, 13px); }
149
+ .ui-barlist > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 4px var(--ui-space-3); }
150
+ .ui-barlist__label { min-width: 0; overflow-wrap: anywhere; color: var(--ui-text); }
151
+ .ui-barlist__label a { color: inherit; }
152
+ .ui-barlist__val { font-weight: 600; color: var(--ui-text); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
153
+ .ui-barlist__share { margin-left: 6px; font-weight: 400; color: var(--ui-text-muted); }
154
+ .ui-barlist__note { grid-column: 1 / -1; margin-top: -2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
155
+ .ui-barlist__bar { grid-column: 1 / -1; position: relative; height: 6px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; }
156
+ .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)); }
157
+
158
+ /* Biểu đồ cột chồng theo thời gian, thêm (tuỳ chọn) một chuỗi CHẤM:
159
+ `<figure class="ui-chart"><div class="ui-chart__plot" data-max="80"><div class="ui-chart__col" title="02/10 · …">
160
+ <span class="ui-chart__seg" style="--v:.4;--c:var(--ui-series-1)"></span><span class="ui-chart__seg" …></span>
161
+ <span class="ui-chart__dot" style="--v:.5"></span></div>…</div><div class="ui-chart__axis"><span>01/10</span>…</div>
162
+ <figcaption class="ui-legend">…</figcaption></figure>`. `--v` = giá trị / trục (0..1), chồng từ dưới lên theo thứ tự
163
+ 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
164
+ 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à
165
+ 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`. */
166
+ .ui-chart { margin: 0; }
167
+ .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);
168
+ 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; }
169
+ .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; }
170
+ .ui-chart__col { position: relative; flex: 1 1 0; min-width: 0; max-width: 48px; height: 100%; display: flex; flex-direction: column-reverse; }
171
+ .ui-chart__col:hover { background: color-mix(in srgb, var(--ui-text) 6%, transparent); }
172
+ .ui-chart__seg { display: block; flex: none; height: calc(var(--v, 0) * 100%); background: var(--c, var(--ui-series-1)); }
173
+ .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))); }
174
+ .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; }
175
+
176
+ /* Chú thích: `<p class="ui-legend"><span class="ui-legend__item"><i style="--c:var(--ui-series-1)"></i>Tiện ích</span>
177
+ <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. */
178
+ .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); }
179
+ .ui-legend__item { display: inline-flex; align-items: center; gap: 6px; }
180
+ .ui-legend__item > i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--c, var(--ui-series-1)); }
181
+ .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; }
182
+
183
+ /* 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
184
+ </span></li><li><span class="ui-funnel__rate">4,8%</span><span class="ui-funnel__val">5</span>…</li></ol>`. Mỗi bước
185
+ 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. */
186
+ .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; }
187
+ .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; }
188
+ .ui-funnel__val { font-size: var(--gemboss-fs-h2, 20px); font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
189
+ .ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
190
+ .ui-funnel__rate { order: -1; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-series-1); font-variant-numeric: tabular-nums; }
191
+ @media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
192
+
193
+ /* 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
194
+ 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,
195
+ lớn hơn tiêu đề card): `<div class="ui-section-head"><h2>Nội dung</h2><p>…</p></div>`. */
196
+ .ui-grid { display: grid; gap: var(--ui-space-4); align-items: start; }
197
+ .ui-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
198
+ .ui-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
199
+ @media (max-width: 1100px) { .ui-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
200
+ @media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
201
+ @media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
202
+ .ui-section-head { margin: var(--ui-space-4) 0 0; }
203
+ .ui-section-head > h2 { margin: 0; font-size: var(--gemboss-fs-h3, 16px); font-weight: 650; color: var(--ui-text); }
204
+ .ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
205
+
206
+ /* 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, "中文"
207
+ 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
208
+ 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. */
209
+ .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) */
210
+ .ui-seg :is(button, a).is-text { flex: none; white-space: nowrap; }
211
+ /* Ở 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
212
+ thấy lựa chọn cuối. Nút vẫn cao 36px, chữ vẫn một dòng. */
213
+ @media (max-width: 360px) { .ui-seg :is(button, a).is-text { padding: 0 9px; } }
214
+ /* 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
215
+ 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. */
216
+ @media (pointer: coarse), (max-width: 47.9975em) { .ui-seg :is(button, a) { height: 44px; min-height: 44px; } }