/* web-admin prototype design system — REQ-005 design generation v2.
   Token-only: no raw colour literals. Consumes Foundation v0.11.0 via
   ../../tokens/tokens.css and the shared kit via ../../ui-kit/signal.css.
   Tonal order (surface.page < register < raised < focus ink) follows the
   confirmed sample's INK / MINERAL / PAPER / ACCENT depth, at product
   intensity: brand stays in the rail, the summary and one focus mark. */

/* ---------- composed type stacks ----------
   --font-family-mono is Latin-only (ui-monospace, SFMono-Regular, Consolas,
   monospace). Every page here is Chinese, so mono-set Chinese fell through to
   whatever fontconfig picked — outside the Foundation entirely, and worst on
   .ux-rail-label, the sidebar's own nav text. Appending the Foundation's UI
   stack keeps Latin and digits on mono while CJK lands on the declared face
   (PingFang SC / Microsoft YaHei). Composed from existing tokens; tokens.css is
   Foundation v0.11.0 and stays untouched. Whether the Foundation's mono token
   itself should carry a CJK fallback is an S3 decision, not an S2 edit. */
:root {
  --ux-mono-cjk: var(--font-family-mono), var(--font-family-ui);
}

/* The UA stylesheet sets `font-family: monospace` on code/kbd/samp/pre, and
   `monospace` is a generic with no CJK family anywhere in its resolution — so
   every unscoped <code> on all 20 pages let its Chinese fall to whatever the
   machine's font config last-resort happens to be. Measured: 26 <code> computed
   to a bare `monospace` (5 of them holding Chinese), and a 64px Han probe
   painted by that stack is byte-different from the same probe in the UI stack —
   two different Chinese faces on one page. Specificity (0,0,1) is below every
   scoped rule (.ux-notes code and friends at 0,1,1), so this only fills the
   hole the author left. */
code, kbd, samp, pre { font-family: var(--ux-mono-cjk); }

/* DBT-02 floor. A bare <small> resolves to the UA `smaller` step (0.83em),
   which lands at 10px under a 12px parent — below the floor with no class for
   a token-level rule to catch. Explicit classes still win on specificity. */
small { font-size: var(--font-size-meta); }

/* ---------- icon ---------- */
.ico {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.ico--18 { width: 18px; height: 18px; }
.ico--20 { width: 20px; height: 20px; }
.ico--14 { width: 14px; height: 14px; }
.ux-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ---------- frame ---------- */
.ux-frame { display: grid; grid-template-columns: 244px minmax(0, 1fr); min-height: 100vh; }
.ux-stage { display: flex; flex-direction: column; min-width: 0; }

/* ---------- rail: the one dark structural mass ---------- */
.ux-rail {
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
  background: linear-gradient(178deg, var(--color-surface-focus), var(--color-surface-brand));
  color: var(--color-content-inverse);
  border-right: 1px solid color-mix(in srgb, var(--color-brand-mark) 22%, transparent);
}
.ux-rail-brand {
  display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 20px;
  border-bottom: 1px solid color-mix(in srgb, var(--color-content-inverse) 12%, transparent);
}
.ux-rail-mark { display: grid; gap: 3px; width: 22px; flex: none; }
.ux-rail-mark span { height: 2px; background: var(--color-brand-mark); display: block; }
.ux-rail-mark span:nth-child(2) { width: 15px; }
.ux-rail-mark span:nth-child(3) { width: 19px; }
.ux-rail-mark span:nth-child(4) { width: 11px; }
.ux-rail-word { font-size: 15px; font-weight: 600; letter-spacing: .5px; display: flex; align-items: baseline; gap: 7px; }
.ux-rail-word em {
  font: var(--font-size-meta) var(--ux-mono-cjk); font-style: normal; letter-spacing: 1.6px;
  color: var(--color-brand-mark); text-transform: uppercase;
}
.ux-rail-nav { flex: 1; overflow: auto; padding: 16px 0 20px; }
.ux-rail-group { margin-bottom: 10px; }
/* A group header wears the same icon grammar as its leaves. Omitting the
   icon on groups while every leaf carries one is what made the rail read as
   two unrelated systems. */
.ux-rail-sec {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 20px 5px; font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.5px;
  text-transform: uppercase; color: var(--color-content-inverse-meta);
}
/* Same 16px as every leaf icon. At 15px the rail column mixed two sizes,
   which is the "尺寸不齐" defect the icon v2 rework claimed to have closed. */
.ux-rail-sec > .ico { width: 16px; height: 16px; flex: none; }
.ux-rail-sec > .ux-rail-label { flex: 1 1 auto; min-width: 0; }
.ux-rail-sec--in { padding: 7px 20px 4px 44px; }
/* Needs the doubled class to outrank `.ux-rail-sec > .ico` (0,2,0) — at
   (0,1,0) this rule was dead and every caret rendered at the same 16px as the
   group icon it is meant to subordinate. */
.ux-rail-sec > .ico.ux-rail-caret { width: 12px; height: 12px; }
.ux-rail-caret { width: 12px; height: 12px; flex: none; opacity: .7; }
.ux-rail-group.is-open > .ux-rail-sec .ux-rail-caret,
.ux-rail-nest.is-open > .ux-rail-sec .ux-rail-caret { transform: rotate(180deg); }
.ux-rail-leaf {
  position: relative; display: flex; align-items: center; gap: 10px; margin-bottom: 2px;
  padding: 9px 20px; font-size: 14px; color: var(--color-content-inverse-meta); text-decoration: none;
}
.ux-rail-leaf::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent;
}
.ux-rail-leaf:hover { color: var(--color-content-inverse); background: color-mix(in srgb, var(--color-content-inverse) 6%, transparent); text-decoration: none; }
.ux-rail-leaf.is-active { color: var(--color-content-inverse); background: color-mix(in srgb, var(--color-content-inverse) 9%, transparent); }
.ux-rail-leaf.is-active::before { background: var(--color-action-promise); }
.ux-rail-leaf.is-active .ico { color: var(--color-brand-mark); }
.ux-rail-sub { list-style: none; margin: 2px 0 0; padding: 0; }
/* sys_menu nests three deep in exactly one place (系统管理 > 日志管理 > 登录/操作
   日志), so depth is carried by indent, not by a second visual language. */
.ux-rail-sub--d2 { margin-top: 1px; }
.ux-rail-sub--d2 > li > .ux-rail-link { padding-left: 46px; font-size: 13px; }
.ux-rail-link {
  position: relative; display: flex; align-items: center; gap: 10px;
  padding: 9px 20px 9px 22px; font-size: 14px; color: var(--color-content-inverse-meta);
  text-decoration: none;
}
.ux-rail-link::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: transparent; transition: background-color .15s ease;
}
.ux-rail-link:hover { color: var(--color-content-inverse); background: color-mix(in srgb, var(--color-content-inverse) 6%, transparent); text-decoration: none; }
.ux-rail-link.is-active { color: var(--color-content-inverse); background: color-mix(in srgb, var(--color-content-inverse) 9%, transparent); }
.ux-rail-link.is-active::before { background: var(--color-action-promise); }
.ux-rail-link.is-active .ico { color: var(--color-brand-mark); }
.ux-rail-link > span { white-space: nowrap; }
/* Two honest reasons a rail row is not an ordinary sys_menu row, drawn the
   same way and never dropped: sys_menu.visible='1' still resolves by direct
   URL, and the two non-menu entries (固定路由 / 未注册路由) exist only in the
   router, not in the menu table. */
.ux-rail-tag {
  flex: 0 0 auto; margin-left: auto; font: var(--font-size-meta)/1 var(--ux-mono-cjk); font-style: normal;
  letter-spacing: .4px; color: var(--color-content-inverse-meta);
  border: 1px solid color-mix(in srgb, var(--color-content-inverse) 20%, transparent);
  border-radius: var(--rounded-sm); padding: 2px 4px;
}
.ux-rail-tag--ext { border-style: dashed; }
/* 74% mixed toward transparent measured 3.71–3.78:1 over the rail gradient —
   the deliberately-quieter row was the only rail text under AA, while the
   normal row sits at 5.65:1. 86% keeps it visibly quieter without dropping the
   row below 4.5:1. Same shape as the .ux-alert--on-dark fix. */
.ux-rail-link.is-quiet, .ux-rail-leaf:has(.ux-rail-tag) { color: color-mix(in srgb, var(--color-content-inverse-meta) 86%, transparent); }
.ux-rail-link.is-quiet .ico { opacity: .7; }
.ux-rail-foot {
  padding: 14px 20px 18px; border-top: 1px solid color-mix(in srgb, var(--color-content-inverse) 12%, transparent);
  display: grid; gap: 4px;
}
.ux-rail-kicker { font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.5px; color: var(--color-brand-mark); }
.ux-rail-muted { font-size: var(--font-size-meta); color: var(--color-content-inverse-meta); }

/* ---------- topbar ---------- */
.ux-topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  height: 56px; padding: 0 24px; background: var(--color-surface-raised);
  border-bottom: 1px solid var(--color-content-border);
  position: sticky; top: 0; z-index: 20;
}
.ux-crumb { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
.ux-crumb-link { color: var(--color-content-meta); }
.ux-crumb-link:hover { color: var(--color-content-ink); }
.ux-crumb-sep { color: var(--color-content-border-strong); }
.ux-crumb-cur { font-weight: 500; }
.ux-top-tools { display: flex; align-items: center; gap: 6px; }
.ux-search {
  display: flex; align-items: center; gap: 8px; width: 230px; height: 32px; padding: 0 10px;
  border: 1px solid var(--color-content-border); border-radius: var(--rounded-sm);
  background: var(--color-surface-page); color: var(--color-content-meta);
}
.ux-search input {
  border: 0; background: transparent; outline: none; width: 100%; font-size: 13px; color: var(--color-content-ink);
}
.ux-search input::placeholder { color: var(--color-content-meta); }
.ux-search:focus-within { border-color: var(--color-action-promise); }
.ux-icon-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border: 0; border-radius: var(--rounded-sm);
  background: transparent; color: var(--color-content-meta);
}
.ux-icon-btn:hover { background: var(--color-surface-mineral); color: var(--color-content-ink); }
.ux-icon-btn--badge::after {
  content: ""; position: absolute; top: 6px; right: 6px; width: 6px; height: 6px;
  background: var(--color-status-blocking);
}
.ux-user {
  display: flex; align-items: center; gap: 9px; height: 36px; padding: 0 8px 0 4px;
  border: 0; background: transparent; border-radius: var(--rounded-sm);
  color: inherit; text-decoration: none; font: inherit;
}
.ux-user:hover { background: var(--color-surface-mineral); text-decoration: none; }
.ux-avatar {
  width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%;
  background: var(--color-action-promise); color: var(--color-action-on-promise);
  font: 600 12px var(--font-family-display);
}
/* identity card / avatar specimen: same token fill, one step larger */
.ux-avatar--lg { width: 56px; height: 56px; font-size: 22px; }
.ux-user-name { display: grid; text-align: left; font-size: 13px; line-height: 1.25; }
.ux-user-name em { font: var(--font-size-meta) var(--ux-mono-cjk); font-style: normal; color: var(--color-content-meta); letter-spacing: .5px; }

/* ---------- tag view ---------- */
.ux-tagbar { background: var(--color-surface-raised); border-bottom: 1px solid var(--color-content-border); }
.ux-tags { display: flex; align-items: center; gap: 4px; padding: 0 16px; height: 38px; overflow: auto; }
.ux-tags-kicker { font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.4px; color: var(--color-content-meta); margin-right: 8px; flex: none; }
.ux-tag {
  position: relative; display: inline-flex; align-items: center; gap: 7px; flex: none;
  height: 24px; padding: 0 10px; border: 1px solid var(--color-content-border);
  border-radius: var(--rounded-sm); font-size: 12px; color: var(--color-content-meta);
  text-decoration: none; background: var(--color-surface-page);
}
.ux-tag:hover { color: var(--color-content-ink); text-decoration: none; }
.ux-tag.is-active {
  color: var(--color-content-ink); background: var(--color-surface-raised);
  border-color: var(--color-content-border-strong);
}
.ux-tag.is-active::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--color-brand-mark);
}
.ux-tags-refresh { margin-left: auto; flex: none; }

/* ---------- prototype scaffolding: meta bar + notes ---------- */
.ux-meta {
  display: flex; align-items: center; gap: 20px; padding: 7px 24px;
  background: var(--color-surface-brand); color: var(--color-content-inverse-meta);
  font-size: 12px; flex-wrap: wrap; line-height: 1.5;
}
.ux-meta > span, .ux-meta > a { white-space: nowrap; min-width: 0; }
.ux-meta code { overflow-wrap: anywhere; white-space: normal; }
.ux-meta b { font-weight: 400; color: var(--color-content-inverse-meta); margin-right: 7px; }
.ux-meta code { font: 12px var(--ux-mono-cjk); color: var(--color-brand-mark); }
.ux-meta a { color: var(--color-content-inverse); text-decoration: none; }
.ux-meta a:hover { text-decoration: underline; }

.ux-work {
  flex: 1; display: grid; grid-template-columns: 330px minmax(0, 1fr);
  align-items: start; background: var(--color-surface-page);
}
/* 330px, not the 250px this started at: the notes column carries two 3-column
   tables (FR coverage, API endpoints) whose third column is a status badge.
   At 250px those tables wrapped into unreadable stubs, and a rubric the reviewer
   cannot read is not a rubric. The canvas absorbs the difference via 1fr. */
.ux-notes {
  position: sticky; top: 56px; max-height: calc(100vh - 56px); overflow: auto;
  padding: 22px 20px 40px; background: var(--color-surface-mineral);
  border-right: 1px solid var(--color-content-border);
}
/* h2, not h3: these seven sections are the top level of the derivation column.
   At h3 they sat below a level that did not exist — eight h3s, then the page
   h1 further down the document, so a heading-outline reader entered the page
   at depth 3 before ever reaching depth 1. */
.ux-notes h2 {
  margin: 0 0 8px; font-size: 12px; font-weight: 600; letter-spacing: .3px;
  position: relative; padding-bottom: 9px;
}
.ux-notes h2::after {
  content: ""; position: absolute; left: 0; bottom: 0; width: 34px; height: 2px;
  background: linear-gradient(90deg, var(--color-action-promise), transparent);
}
.ux-notes section { margin-bottom: 22px; }
.ux-notes p, .ux-notes li { font-size: 12px; line-height: 1.7; color: var(--color-content-meta); margin: 0 0 6px; }
.ux-notes ul { margin: 0; padding-left: 16px; }
.ux-notes code { font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-locator); }
/* Stable codes. Contracts and BUG reports cite D1/R1/Q1, so the anchor has to
   survive a prose rewrite — it is carried in a <b>, not in the sentence. */
.ux-notes li > b:first-child {
  font: 600 var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-locator);
  letter-spacing: .5px; margin-right: 5px;
}
/* The two rubric tables. Dense, rule-separated, no card chrome — same register
   as the ledgers on the canvas so the rubric reads as part of the same system. */
.ux-notes table { width: 100%; border-collapse: collapse; font-size: var(--font-size-meta); }
.ux-notes th {
  text-align: left; font: 600 var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: .4px;
  color: var(--color-content-meta); padding: 0 6px 5px 0; border-bottom: 1px solid var(--color-content-border);
}
.ux-notes td { padding: 5px 6px 5px 0; border-bottom: 1px solid var(--color-content-border); vertical-align: top; line-height: 1.5; }
.ux-notes tr:last-child td { border-bottom: 0; }
.ux-notes td:first-child { white-space: nowrap; }
.ux-notes td b { font-weight: 500; color: var(--color-content-ink); }
.badge-status {
  display: inline-block; white-space: nowrap;
  font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: .3px; padding: 1px 5px;
  border-radius: var(--rounded-sm); border: 1px solid currentColor;
}
.badge-status[data-status="covered"] { color: var(--color-action-promise); }
.badge-status[data-status="partial"] { color: var(--color-status-warning); }
.badge-status[data-status="open"] { color: var(--color-status-blocking); }
.ux-canvas { min-width: 0; padding: 0 0 56px; background: var(--color-surface-page); }
.ux-canvas > .ux-page { padding: 24px 24px 0; }

/* ---------- page head ---------- */
.ux-page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 20px; }
.ux-kicker { font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.6px; color: var(--color-content-meta); }
.ux-page h1 { font-size: 28px; line-height: 1.35; font-weight: 500; margin: 8px 0 6px; }
.ux-page-sub { margin: 0; font-size: 13px; color: var(--color-content-meta); max-width: 68ch; }
.ux-page-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }

/* ---------- register: the working surface ---------- */
.ux-register {
  background: linear-gradient(112deg, var(--color-surface-register-start), var(--color-surface-register-end));
  border-radius: var(--rounded-md); overflow: hidden;
}
.ux-register-head {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 18px 22px 15px;
}
.ux-register-head h2 { font-size: 16px; font-weight: 500; margin: 5px 0 0; }
.ux-register-head .ux-count { font: 26px var(--ux-mono-cjk); color: var(--color-content-locator); line-height: 1; }
.ux-register-total { display: flex; align-items: baseline; gap: 10px; }
.ux-toolbar {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
  padding: 0 22px 16px; flex-wrap: wrap;
}
.ux-toolbar--lined { padding: 16px 22px; border-top: 1px solid var(--color-content-border); }
.ux-filters { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.ux-register-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 22px; border-top: 1px solid var(--color-content-border);
  font-size: 12px; color: var(--color-content-meta); flex-wrap: wrap;
}
.ux-pager { display: flex; align-items: center; gap: 4px; }
.ux-pager b { font: 12px var(--ux-mono-cjk); color: var(--color-content-ink); font-weight: 400; }
.ux-pager .ux-icon-btn { width: 26px; height: 26px; border: 1px solid var(--color-content-border); }
.ux-pager .is-on { background: var(--color-action-promise); color: var(--color-action-on-promise); border-color: var(--color-action-promise); }

/* ---------- controls (no native chrome) ---------- */
.ux-field { display: grid; gap: 6px; font-size: 13px; min-width: 0; }
.ux-field > span { font-size: 12px; color: var(--color-content-meta); }
.ux-input, .ux-select, .ux-textarea {
  display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px;
  min-width: 152px; background: var(--color-surface-raised);
  border: 1px solid var(--color-content-border); border-radius: var(--rounded-sm);
  font-size: 13px; color: var(--color-content-ink);
}
.ux-input::placeholder { color: var(--color-content-meta); }
/* Reset the native control inside the token box. Without this the UA paints its
   own white field with a 2px mid-grey inset border inside our
   --color-surface-raised box — measured at a 204x34 wrapper holding a 158x26
   inner control, i.e. a hard-edged box in a box, and the reason filter fields
   read as the wrong colour next to the .ux-select beside them (which has no
   native control). .ux-search already had this reset and .ux-input did not.
   `font: inherit` matters too — inputs do not inherit font in the UA sheet, so
   the typed value was rendering in the browser's default face, not ours. */
.ux-input input, .ux-input textarea, .ux-select input, .ux-textarea textarea {
  flex: 1 1 auto; min-width: 0; border: 0; background: transparent;
  outline: none; color: inherit; font: inherit;
}
.ux-input:focus-within, .ux-select:focus-within, .ux-textarea:focus-within {
  border-color: var(--color-action-promise); outline: none;
}
/* Hover was byte-identical to default on every form control (verified by pixel
   diff) — a control that cannot be hovered is one the operator cannot confirm is
   live. Border only, so focus stays the loudest state. */
.ux-input:hover, .ux-select:hover, .ux-textarea:hover { border-color: var(--color-content-border-strong); }
.ux-check:hover i { border-color: var(--color-action-promise); }
.ux-switch:hover { border-color: var(--color-content-border-strong); }
/* LAW-04 — read-only keeps verifiable information. This state was absent from
   the stylesheet AND from all 19 pages, so it would have been invented at
   implementation time. Dashed edge reads as "look, don't type" without
   greying the value out. */
.ux-input:has([readonly]), .ux-textarea:has([readonly]) {
  background: var(--color-surface-page); border-style: dashed;
}
.ux-input [readonly], .ux-textarea [readonly] { color: var(--color-content-meta); cursor: default; }
.ux-input .ico, .ux-select .ico:last-child { color: var(--color-content-meta); }
.ux-select { cursor: pointer; }
.ux-select > span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ux-textarea { height: auto; min-height: 84px; padding: 8px 10px; align-items: flex-start; }
.ux-field--error .ux-input, .ux-field--error .ux-select { border-color: var(--color-status-blocking); }
.ux-err { font-size: 12px; color: var(--color-status-blocking); }
.ux-hint { font-size: 12px; color: var(--color-content-meta); }
.ux-check { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; min-height: 24px; }
.ux-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.ux-check i {
  width: 15px; height: 15px; flex: none; border: 1px solid var(--color-content-border-strong);
  border-radius: 2px; background: var(--color-surface-raised); display: grid; place-items: center;
  position: relative;
}
/* The 15px box is the visual, but it was also the click target — below WCAG
   2.5.8 AA (24x24). An inset pseudo-element grows the hit area to 25x25 without
   changing the drawn box or the row rhythm. */
.ux-check i::before { content: ""; position: absolute; inset: -5px; }
.ux-check i::after { content: ""; width: 7px; height: 4px; border-left: 1.5px solid var(--color-action-on-promise); border-bottom: 1.5px solid var(--color-action-on-promise); transform: rotate(-45deg) scale(0); transition: transform .12s ease; margin-top: -2px; }
.ux-check input:checked + i { background: var(--color-action-promise); border-color: var(--color-action-promise); }
.ux-check input:checked + i::after { transform: rotate(-45deg) scale(1); }
.ux-check input:indeterminate + i { background: var(--color-content-meta); border-color: var(--color-content-meta); }
.ux-check input:indeterminate + i::after { content: ""; width: 7px; height: 1.5px; border: 0; background: var(--color-surface-raised); transform: none; margin: 0; }
.ux-check input:focus-visible + i { outline: 2px solid var(--color-action-promise); outline-offset: 2px; }
.ux-check input:disabled + i { opacity: .45; }
.ux-check.is-off { color: var(--color-content-meta); cursor: not-allowed; }
.ux-switch { position: relative; width: 36px; height: 20px; flex: none; display: inline-block; }
.ux-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.ux-switch i {
  position: absolute; inset: 0; border-radius: 10px; background: var(--color-content-border-strong);
  transition: background-color .15s ease; pointer-events: none;
}
.ux-switch i::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--color-surface-raised); transition: transform .15s ease;
}
.ux-switch input:checked + i { background: var(--color-action-promise); }
.ux-switch input:checked + i::after { transform: translateX(16px); }
.ux-switch input:focus-visible + i { outline: 2px solid var(--color-action-promise); outline-offset: 2px; }
.ux-switch input:disabled + i { opacity: .45; }

/* ---------- buttons ---------- */
.ux-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 34px; padding: 0 14px; border: 1px solid var(--color-content-border);
  border-radius: var(--rounded-sm); background: var(--color-surface-raised);
  font-size: 13px; color: var(--color-content-ink); white-space: nowrap;
}
.ux-btn:hover { border-color: var(--color-content-border-strong); background: var(--color-surface-mineral); }
.ux-btn--primary { background: var(--color-action-promise); border-color: var(--color-action-promise); color: var(--color-action-on-promise); }
.ux-btn--primary:hover { background: var(--color-action-promise-hover); border-color: var(--color-action-promise-hover); }
.ux-btn--danger { background: var(--color-status-blocking); border-color: var(--color-status-blocking); color: var(--color-action-on-promise); }
.ux-btn--quiet { border-color: transparent; background: transparent; color: var(--color-content-meta); }
.ux-btn--quiet:hover { color: var(--color-content-ink); background: var(--color-surface-mineral); }
.ux-btn--sm { height: 28px; padding: 0 10px; font-size: 12px; }
.ux-btn:disabled, .ux-btn[aria-disabled=true] { opacity: .45; cursor: not-allowed; }
.ux-btn.is-danger-text { color: var(--color-status-blocking); border-color: transparent; background: transparent; }
.ux-btn.is-danger-text:hover { background: var(--color-surface-attention); }

/* row actions read as links, not a second toolbar (GR-01) */
.ux-rowacts { display: flex; align-items: center; gap: 2px; }
.ux-rowacts a, .ux-rowacts button, .ux-rowacts .is-off {
  display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 8px;
  border: 0; border-radius: var(--rounded-sm); background: transparent;
  font-size: 12px; color: var(--color-content-locator);
}
/* .is-off is a <span>, not an <a>/<button>: it needs the same box as its enabled
   siblings or it renders at the inherited 14px in a 23px box beside 12px/26px
   actions, and the withheld destructive action ends up the odd one out. */
.ux-rowacts a:hover, .ux-rowacts button:hover { background: var(--color-surface-mineral); color: var(--color-content-ink); }
.ux-rowacts .is-danger { color: var(--color-content-locator); }
.ux-rowacts .is-danger:hover { background: var(--color-surface-attention); color: var(--color-status-blocking); }
.ux-rowacts .is-off { color: var(--color-content-meta); opacity: .55; cursor: not-allowed; }
.ux-rowacts .is-off:hover { background: transparent; color: var(--color-content-meta); }

/* Filter-toolbar actions. These are page-level buttons, not row actions, and
   must not borrow .ux-rowacts: that rule (.ux-rowacts button, 0-1-1) outranks
   .ux-btn--primary (0-1-0) and stripped the primary fill, so 查询 and 重置 both
   rendered as two identical bare words — no fill, no border, 26px tall, in the
   row-action size. The filter bar also had no primary button at all on 8 of 14
   pages, which is LAW-01's "one primary action per task zone" going unmet. */
.ux-toolbaracts { display: flex; align-items: center; gap: 8px; }

/* ---------- table ---------- */
.ux-table-wrap { overflow-x: auto; max-width: 100%; }
.ux-table { width: 100%; border-collapse: collapse; min-width: 820px; font-variant-numeric: tabular-nums; }
.ux-table th {
  text-align: left; font-size: 12px; font-weight: 400; color: var(--color-content-meta);
  padding: 10px 14px; white-space: nowrap; border-bottom: 1px solid var(--color-content-border-strong);
}
.ux-table .ux-name { min-width: 168px; }
.ux-table td { padding: 12px 14px; vertical-align: middle; border: 0; white-space: nowrap; }
.ux-table td.ux-wrap { white-space: normal; }
.ux-table tbody tr { transition: background-color .12s ease; }
.ux-table tbody tr:hover { background: color-mix(in srgb, var(--color-surface-mineral-start) 70%, transparent); }
/* Longhand, not the `font` shorthand: the shorthand resets font-variant-numeric
   and line-height to initial, which silently stripped the tabular-nums that
   .ux-table sets — so the columns LAW-02 requires to align lost it. */
.ux-table .ux-idx { font-family: var(--ux-mono-cjk); font-size: 12px; color: var(--color-content-meta); width: 44px; }
.ux-table .ux-id { position: relative; }
.ux-table .ux-id::after {
  content: ""; position: absolute; left: 14px; bottom: 9px; width: 34px; height: 1px;
  background: linear-gradient(90deg, var(--color-content-border-strong), transparent);
}
.ux-table tbody tr:last-child .ux-id::after { display: none; }
.ux-table .ux-name { display: grid; gap: 2px; }
.ux-table .ux-name b { font-weight: 500; }
.ux-table .ux-name small { font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-meta); }
.ux-table .ux-num { text-align: right; font-family: var(--ux-mono-cjk); font-size: 13px; white-space: nowrap; }
.ux-table .ux-muted { color: var(--color-content-meta); font-size: 12px; }
.ux-table .ux-tight { width: 1%; white-space: nowrap; }
/* capacity bar: a groove inset into the row, not a floating gauge card. It never
   carries the verdict on its own — the exact number beside it is the fact, the
   bar only makes two values comparable at a glance (LAW-02). */
.ux-bar {
  display: inline-block; width: 64px; height: 4px; margin-right: 10px;
  vertical-align: middle; border-radius: var(--rounded-sm);
  background: color-mix(in srgb, var(--color-content-border-strong) 70%, transparent);
}
.ux-bar::after {
  content: ""; display: block; width: var(--pct, 0%); height: 100%;
  border-radius: inherit; background: var(--color-content-meta);
}
.ux-bar--over::after { background: var(--color-status-blocking); }
.ux-table .ux-pick { width: 1%; white-space: nowrap; padding-right: 0; }
.ux-checkcell { display: flex; align-items: center; gap: 10px; }
/* master/detail page: the detail table shares its width with the master column,
   so its horizontal rhythm runs one step tighter (20px cells instead of 28px). */
.ux-table--tight th, .ux-table--tight td { padding-left: 10px; padding-right: 10px; }
.ux-table--tight th:first-child, .ux-table--tight td:first-child { padding-left: 14px; }

/* ---------- status ---------- */
.ux-status { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; white-space: nowrap; color: var(--color-content-meta); }
/* the pixel marks carry their own stroke="none"; do not strip stroke from the
   whole status cell, or line icons in the same cell (lock, folder) vanish. */
.ux-status .ico { width: 16px; height: 16px; }
.ux-status[data-state=success] { color: var(--color-status-success); }
.ux-status[data-state=warning] { color: var(--color-status-warning); }
.ux-status[data-state=blocking] { color: var(--color-status-blocking); }
.ux-status[data-state=info] { color: var(--color-status-info); }
.ux-status[data-state=muted] { color: var(--color-content-meta); }

/* ---------- focus summary (the second dark mass) ---------- */
.ux-summary {
  position: relative; isolation: isolate; overflow: hidden; border-radius: var(--rounded-md);
  background: linear-gradient(112deg, var(--color-surface-focus), var(--color-surface-focus-end));
  color: var(--color-content-inverse); padding: 24px 26px;
}
.ux-summary > img {
  position: absolute; z-index: -1; right: -12%; top: -60%; width: 62%; height: 220%;
  object-fit: cover; opacity: .2; pointer-events: none;
}
.ux-summary .ux-kicker, .ux-summary .ux-muted { color: var(--color-content-inverse-meta); }
/* h1 too: on index.html this is the page title, so it is the document's h1.
   Same type treatment either way — the tag only positions it in the outline. */
.ux-summary h1, .ux-summary h2 { font-size: 20px; font-weight: 500; margin: 8px 0 0; }
.ux-metrics { display: flex; gap: 40px; flex-wrap: wrap; margin-top: 22px; }
.ux-metric { display: grid; gap: 4px; }
.ux-metric > span { font-size: 12px; color: var(--color-content-inverse-meta); }
.ux-metric strong { font: 34px var(--ux-mono-cjk); letter-spacing: -1px; line-height: 1.1; }
.ux-metric strong i { font: 14px var(--ux-mono-cjk); font-style: normal; color: var(--color-content-inverse-meta); margin-left: 5px; }
.ux-metric small { font-size: 12px; color: var(--color-content-inverse-meta); }

/* ---------- panels ---------- */
.ux-grid { display: grid; gap: 18px; }
.ux-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ux-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ux-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ux-grid--1 { grid-template-columns: minmax(0, 1fr); }
.ux-grid--main { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
.ux-card {
  background: linear-gradient(112deg, var(--color-surface-register-start), var(--color-surface-register-end));
  border-radius: var(--rounded-md); padding: 20px 22px;
}
/* h2 as well as h3: a card that is the first content under the page h1 has no
   level-2 parent to sit under, so its title is h2 there and h3 everywhere else.
   The two render identically — only the outline position differs. */
.ux-card h2, .ux-card h3 { font-size: 16px; font-weight: 500; margin: 0 0 4px; }
.ux-card .ux-kicker { display: block; margin-bottom: 6px; }
.ux-card p { margin: 10px 0 0; font-size: 13px; color: var(--color-content-meta); }
.ux-muted { color: var(--color-content-meta); font-size: 12px; }
.ux-mono { font-family: var(--ux-mono-cjk); font-variant-numeric: tabular-nums; }
.ux-rule { width: 38px; height: 2px; background: linear-gradient(90deg, var(--color-action-promise), transparent); margin: 16px 0; border: 0; }

/* state specimen: label + pixel shape + the rule it demonstrates */
.ux-state {
  border: 1px solid var(--color-content-border); border-radius: var(--rounded-sm);
  padding: 14px 16px; background: var(--color-surface-raised); display: grid; gap: 8px;
}
/* h3, not h4: these cards usually sit inside a heading'd container (ux-card h2,
   ux-register-head h2, ux-feedback h3), so h4 skipped a level. Where a group of
   cards is the top-level content of the page — no heading'd wrapper above it —
   the markup uses h2 instead, which this rule covers so both render alike. */
.ux-state h2, .ux-state h3 { margin: 0; font-size: 13px; font-weight: 500; }
.ux-state p { margin: 0; font-size: 12px; color: var(--color-content-meta); line-height: 1.6; }
.ux-state code { font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-locator); }

/* ---------- feedback / empty / error ---------- */
.ux-feedback { padding: 46px 24px; text-align: center; background: var(--color-surface-register-start); }
.ux-feedback .ico { width: 26px; height: 26px; color: var(--color-content-meta); }
.ux-feedback h3 { font-size: 16px; font-weight: 500; margin: 14px 0 6px; }
.ux-feedback p { margin: 0 auto 18px; font-size: 13px; color: var(--color-content-meta); max-width: 44ch; }
.ux-alert {
  display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px;
  background: var(--color-surface-attention); border-radius: var(--rounded-sm);
  border-left: 3px solid var(--color-status-warning);
}
.ux-alert .ico { color: var(--color-status-warning); }
.ux-alert p { margin: 0; font-size: 13px; }
.ux-alert .ux-muted { display: block; margin-top: 4px; }
.ux-alert--blocking { border-left-color: var(--color-status-blocking); }
.ux-alert--blocking .ico { color: var(--color-status-blocking); }
.ux-alert--info { border-left-color: var(--color-status-info); }
.ux-alert--info .ico { color: var(--color-status-info); }

/* Alerts sitting on a dark focus surface: the light-surface attention fill
   would render beige-on-beige. Invert to inverse ink over a translucent
   attention wash, keeping the same left rule. Token-only, no raw values. */
.ux-alert--on-dark {
  background: color-mix(in srgb, var(--color-surface-attention) 12%, transparent);
  border-left-color: var(--color-content-inverse-meta);
}
.ux-alert--on-dark p, .ux-alert--on-dark .ux-muted { color: var(--color-content-inverse); }
.ux-alert--on-dark .ico { color: var(--color-content-inverse-meta); }
.ux-alert--on-dark--blocking { border-left-color: var(--color-status-blocking); }
.ux-alert--on-dark--blocking .ico { color: var(--color-status-blocking); }

/* ---------- master / detail split (dict page) ---------- */
.ux-split { display: grid; gap: 18px; grid-template-columns: 240px minmax(0, 1fr); align-items: start; }
.ux-master { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--color-content-border); }
.ux-masteritem {
  position: relative; display: block; padding: 12px 18px; text-decoration: none;
  border-bottom: 1px solid var(--color-content-border);
}
.ux-masteritem:hover { text-decoration: none; background: color-mix(in srgb, var(--color-action-promise) 6%, transparent); }
.ux-masteritem.is-active { background: color-mix(in srgb, var(--color-action-promise) 10%, transparent); }
.ux-masteritem.is-active::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--color-action-promise);
}
.ux-masteritem b { font-weight: 500; font-size: 14px; }
.ux-masteritem small { display: block; font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-meta); margin-top: 3px; }

/* ---------- specimen row: an overlay specimen shown beside explanatory states ---------- */
.ux-specimen { display: grid; gap: 20px; grid-template-columns: 460px minmax(0, 1fr); align-items: start; }
.ux-specimen .ux-overlay { border-radius: var(--rounded-md); }
.ux-specimen .ux-drawer { width: 100%; }

/* ---------- tree (permission stress page) ---------- */
.ux-tree { font-size: 13px; }
.ux-tree ul { list-style: none; margin: 0; padding: 0 0 0 26px; }
.ux-tree > ul { padding-left: 0; }
.ux-tree li { position: relative; padding: 5px 0; }
.ux-tree ul ul > li::before {
  content: ""; position: absolute; left: -14px; top: 0; bottom: 0; width: 1px;
  background: var(--color-content-border);
}
.ux-tree ul ul > li:last-child::before { bottom: 50%; }
.ux-tree ul ul > li::after {
  content: ""; position: absolute; left: -14px; top: 18px; width: 11px; height: 1px;
  background: var(--color-content-border);
}
.ux-tree-row { display: flex; align-items: center; gap: 9px; }
.ux-tree-row .ico { color: var(--color-content-meta); }
.ux-tree-row .ux-muted { margin-left: auto; padding-right: 4px; }

/* ---------- dialog / drawer ---------- */
.ux-overlay {
  position: relative; border-radius: var(--rounded-lg); overflow: hidden;
  box-shadow: 0 24px 60px color-mix(in srgb, var(--color-surface-focus) 26%, transparent);
}
.ux-overlay::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: color-mix(in srgb, var(--color-surface-focus) 42%, transparent);
}
.ux-dialog {
  position: relative; z-index: 1; margin: 24px auto; max-width: 520px;
  background: linear-gradient(140deg, var(--color-surface-dialog-start), var(--color-surface-dialog-end));
  border-radius: var(--rounded-lg); padding: 24px 26px;
}
.ux-dialog h2 { font-size: 20px; font-weight: 500; margin: 0 0 4px; }
.ux-dialog .ux-kicker { display: block; margin-bottom: 6px; }
.ux-dialog hr { border: 0; border-top: 1px solid var(--color-content-border); margin: 16px 0; }
.ux-dialog dl { display: grid; grid-template-columns: 68px minmax(0, 1fr); gap: 10px 14px; margin: 0 0 4px; font-size: 13px; }
.ux-dialog dt { color: var(--color-content-meta); font-size: 12px; }
.ux-dialog dd { margin: 0; }
.ux-dialog-foot { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 20px; }
.ux-drawer {
  position: relative; z-index: 1; margin-left: auto; width: min(420px, 88%);
  min-height: 440px; background: linear-gradient(140deg, var(--color-surface-dialog-start), var(--color-surface-dialog-end));
  border-radius: var(--rounded-lg); padding: 24px 26px;
}
.ux-drawer-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }

/* ---------- tabs inside a register ---------- */
.ux-seg { display: flex; gap: 2px; padding: 3px; background: var(--color-surface-mineral); border-radius: var(--rounded-sm); }
.ux-seg button {
  border: 0; background: transparent; padding: 6px 12px; border-radius: 2px;
  font-size: 12px; color: var(--color-content-meta);
}
.ux-seg button[aria-pressed=true] { background: var(--color-surface-raised); color: var(--color-content-ink); }
.ux-tabs { display: flex; gap: 22px; border-bottom: 1px solid var(--color-content-border); }
.ux-tabs button {
  border: 0; background: transparent; padding: 10px 0 11px; font-size: 14px; color: var(--color-content-meta);
  border-bottom: 2px solid transparent;
}
.ux-tabs button[aria-selected=true] { color: var(--color-content-ink); border-bottom-color: var(--color-action-promise); }

/* ---------- definition rows ---------- */
.ux-dl { display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 0; margin: 0; font-size: 13px; }
.ux-dl > dt, .ux-dl > dd { padding: 11px 0; border-bottom: 1px solid var(--color-content-border); margin: 0; }
.ux-dl > dt { color: var(--color-content-meta); font-size: 12px; }
.ux-dl > dd { min-width: 0; overflow-wrap: anywhere; }
.ux-dl > dd:last-of-type, .ux-dl > dt:last-of-type { border-bottom: 0; }

/* ---------- form layout ---------- */
.ux-form { display: grid; gap: 18px 22px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ux-form .ux-span2 { grid-column: 1 / -1; }
.ux-form-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 16px 22px; border-top: 1px solid var(--color-content-border);
}
.ux-form-note { margin-right: auto; font-size: 12px; color: var(--color-content-meta); }

/* ---------- auth ---------- */
.ux-auth { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); min-height: 100vh; }
.ux-auth-art {
  position: relative; isolation: isolate; overflow: hidden; padding: 56px 56px 44px;
  background: linear-gradient(150deg, var(--color-surface-focus), var(--color-surface-brand));
  color: var(--color-content-inverse); display: flex; flex-direction: column; justify-content: space-between;
}
.ux-auth-wave {
  position: absolute; z-index: -1; right: -18%; top: -14%; width: 92%; height: 128%;
  object-fit: cover; opacity: .5; pointer-events: none;
}
/* Width must clear the 46px display type: `ch` resolves against this pane's
   own 14px font, so a ch-based cap is ~3x too narrow and the intended
   看见变化。/ 掌握每一刻。 break wraps mid-word. em is sized for the h1. */
.ux-auth-brandinner { max-width: 25em; }
.ux-auth-kicker { font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.8px; color: var(--color-brand-mark); }
.ux-auth-art h1 { font-size: 46px; line-height: 1.28; font-weight: 500; margin: 26px 0 20px; letter-spacing: 1px; }
.ux-auth-art p { font-size: 14px; line-height: 1.9; color: var(--color-content-inverse-meta); margin: 0; }
.ux-auth-rule { display: block; width: 42px; height: 2px; background: var(--color-brand-mark); margin: 30px 0 14px; }
.ux-auth-foot { font: var(--font-size-meta) var(--ux-mono-cjk); letter-spacing: 1.5px; color: var(--color-content-inverse-meta); }
.ux-auth-stage { display: grid; place-items: center; padding: 40px 32px; background: var(--color-surface-page); }
.ux-auth-card { width: min(380px, 100%); }
.ux-auth-card h2 { font-size: 24px; font-weight: 500; margin: 0 0 6px; }
.ux-auth-card .ux-field { margin-bottom: 18px; }
.ux-auth-card .ux-input { height: 40px; }
.ux-auth-card .ux-btn--primary { width: 100%; height: 40px; }

/* ---------- module hub ---------- */
.ux-plain { min-height: 100vh; }
.ux-hub { padding: 40px 44px 64px; max-width: 1280px; margin: 0 auto; }
.ux-hubgrid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(272px, 1fr)); }
.ux-hubcard {
  display: grid; gap: 8px; padding: 18px 20px; text-decoration: none;
  background: linear-gradient(112deg, var(--color-surface-register-start), var(--color-surface-register-end));
  border-radius: var(--rounded-md); border: 1px solid transparent; position: relative;
}
.ux-hubcard:hover { border-color: var(--color-content-border-strong); text-decoration: none; }
.ux-hubcard .ico { width: 20px; height: 20px; color: var(--color-content-locator); }
.ux-hubcard b { font-size: 14px; font-weight: 500; }
.ux-hubcard small { font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-meta); }
.ux-hubcard span { font-size: 12px; color: var(--color-content-meta); }
/* A subdirectory card takes the whole row. The nesting is a structural fact
   (日志管理 is parent_id 2, not a peer of 系统管理), so the grid should say so
   with the same weight the sidebar uses for depth — an indent and a left rule —
   rather than leaving the fact to a sentence in the group description. It also
   stops a nine-card group from ending on a one-card orphan row. */
.ux-hubcard--sub {
  grid-column: 1 / -1; padding-left: 24px;
  grid-template-columns: 20px minmax(0, 1fr) auto; align-items: center; column-gap: 12px;
}
.ux-hubcard--sub::before {
  content: ''; position: absolute; left: 0; top: 14px; bottom: 14px; width: 2px;
  background: var(--color-content-locator); border-radius: var(--rounded-sm);
}
.ux-hubcard--sub small { grid-column: 2; }
.ux-hubcard--sub span { grid-column: 3; grid-row: 1 / span 2; max-width: 46ch; text-align: right; }
.ux-hubgroup { margin-top: 34px; }
.ux-hubgroup > h2 { font-size: 16px; font-weight: 500; margin: 0 0 4px; display: flex; align-items: center; gap: 9px; }
.ux-hubgroup > h2 .ico { color: var(--color-content-locator); }
.ux-hubgroup > p { margin: 0 0 14px; font-size: 12px; color: var(--color-content-meta); }

/* ---------- icon specimen (icons.html) ----------
   LAW-06 says no framed persistent panels, so a cell is separated by the
   register's own row rhythm and a hairline, not by a card. The reserved state
   is text + weight, never colour alone (LAW-03): a cell that is merely
   lower-contrast would be the exact failure this page exists to rule out. */
.ux-icogrid { display: grid; gap: 0; grid-template-columns: repeat(auto-fill, minmax(212px, 1fr)); }
.ux-icocell {
  display: flex; align-items: center; gap: 12px; padding: 11px 14px;
  border-top: 1px solid var(--color-content-border);
  border-left: 1px solid var(--color-content-border);
}
.ux-icocell--reserved { color: var(--color-content-meta); }
/* the glyph itself goes to meta; the name stays ink so the label is still
   readable when the mark is receded */
.ux-icocell--reserved .ux-icoart { color: var(--color-content-meta); }
.ux-icocell--reserved .ux-icocell b { color: var(--color-content-meta); font-weight: 400; }
.ux-icoart {
  flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: var(--rounded-sm); background: var(--color-surface-raised);
  color: var(--color-content-ink);
}
.ux-icoart .ico { width: 22px; height: 22px; }
.ux-icocell > div { min-width: 0; flex: 1 1 auto; }
.ux-icocell b { display: block; font: 500 var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-ink); }
.ux-icocell small { display: block; font: var(--font-size-meta) var(--font-family-ui); color: var(--color-content-meta); margin-top: 2px; }
/* where the glyph actually sits, measured from the rendered DOM across all 19
   app-shell pages — not a count of which pages mention it, which is constant
   (every rail icon is on 9+ pages) and therefore says nothing. */
.ux-icoplace {
  flex: none; align-self: flex-start; margin-top: 2px; text-align: right;
  font: var(--font-size-meta) var(--font-family-ui); color: var(--color-content-locator);
  font-variant-numeric: tabular-nums;
}
.ux-icoplace:empty { display: none; }
.ux-icogroup { margin-bottom: 26px; }
.ux-icogroup-head {
  display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap;
  padding: 0 0 12px; border-bottom: 1px solid var(--color-content-border-strong);
}
.ux-icogroup-head h2 { font-size: 18px; font-weight: 500; margin: 5px 0 0; }
.ux-icogroup-head p { margin: 0; flex: 1 1 auto; }
/* the size ladder: one glyph, four optical sizes, to show the family holds at
   the 14px it actually ships at as well as at review size */
.ux-icosizes { display: flex; align-items: flex-end; gap: 26px; }
.ux-icosizes figure { margin: 0; text-align: center; }
.ux-icosizes figcaption { font: var(--font-size-meta) var(--ux-mono-cjk); color: var(--color-content-meta); margin-top: 8px; }

/* ---------- responsive ---------- */
@media (max-width: 1180px) {
  .ux-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ux-grid--main, .ux-grid--3, .ux-specimen { grid-template-columns: minmax(0, 1fr); }
  .ux-hubgrid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}
@media (max-width: 980px) {
  .ux-frame { grid-template-columns: 60px minmax(0, 1fr); }
  .ux-rail-word, .ux-rail-label, .ux-rail-foot, .ux-rail-caret, .ux-rail-tag { display: none; }
  .ux-rail-link, .ux-rail-leaf, .ux-rail-sec { justify-content: center; padding-left: 0; padding-right: 0; }
  .ux-work, .ux-split { grid-template-columns: minmax(0, 1fr); }
  .ux-notes { position: static; max-height: none; border-right: 0; border-bottom: 1px solid var(--color-content-border); }
  .ux-search { width: 150px; }
  .ux-auth { grid-template-columns: minmax(0, 1fr); }
  .ux-auth-art { display: none; }
}
@media (max-width: 680px) {
  .ux-grid--2, .ux-grid--4, .ux-form { grid-template-columns: minmax(0, 1fr); }
  .ux-form .ux-span2 { grid-column: auto; }
  .ux-hubgrid { grid-template-columns: minmax(0, 1fr); }
  .ux-summary { border-radius: 0; }
  .ux-canvas > .ux-page { padding: 18px 14px 0; }
  .ux-user-name, .ux-crumb-link { display: none; }
  .ux-summary { padding: 20px; }
  .ux-metrics { gap: 22px; }
  .ux-metric strong { font-size: 26px; }
  .ux-search { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
