/* Admin section (#/admin/users|dashboards|bots). Tokens only, so light and dark both work.
   Dialogs are rendered on <body> by <modal>, so nothing inside one may hang off .adm: dialog content is styled
   through .adm-dlg (the wrapper of its body) and through the classes on its footer buttons. */

/* ------------------------------------------------------------------ section shell */
.adm { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.adm-tab { min-width: 0; }

/* One toolbar for the three tabs: title and status, then the tabs, on the leading side; the actions on the
   trailing side (nothing in a toolbar of this app is centred). The title block is a column of fixed width, so the
   tabs stay where they are when the status line changes length from tab to tab — the width of the search field
   plus the gap after it (--adm-lead / --adm-gap, shared with .adm-toolbar), so the tabs stand on the line the
   filter of 用户 starts on. In a window too narrow for all three the title column is the one that yields. */
.adm { --adm-lead: 320px; --adm-gap: 10px; }
.adm > .page > .adm-tb {
  display: grid; grid-template-columns: minmax(0, var(--adm-lead)) auto minmax(max-content, 1fr); column-gap: var(--adm-gap); align-items: center;
  transition: box-shadow var(--t) var(--ease);
}
/* the bar's hairline appears once content is under it (v-scrolled on the page) */
.adm > .page:not(.is-scrolled) > .adm-tb { box-shadow: none; }
.adm-tb .dash-h-t { min-width: 0; }
.adm-tb .dash-h-sub { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
.adm-tb .dash-h-a { justify-content: flex-end; flex-wrap: nowrap; }
.adm-tb .seg > a { padding: 0 14px; }
.adm-tb-short { display: none; }
/* a tablet-sized window: the status line keeps its figures (a column as wide as the longest of them), the filled
   button its verb */
@media (max-width: 1023px) {
  .adm { --adm-lead: 176px; }
  .adm-tb-more, .adm-tb-full { display: none; }
  .adm-tb-short { display: inline; }
}
/* The refresh button while it spins. `.loading` is also the shared name of a block placeholder (48px of padding,
   grey text): on a 28px button that made the bar 60px taller and pushed the page down for as long as it spun.
   The button also stays under the pointer, so it does not blink out of its hover look (a click then does nothing). */
.adm-tb .dash-h-a .icon-btn.loading { padding: 0; color: var(--text-2); pointer-events: auto; }
@media (hover: hover) { .adm-tb .dash-h-a .icon-btn.loading:hover { color: var(--text); } }

/* Segmented controls of this section (tabs, filter, sample language).
   The sliding thumb is the track's last child, so the shared divider rule (`.seg > * + *::before`) drew a tick
   on its leading edge. */
.adm .seg > .seg-thumb::before { content: none; }
/* Focus inside the track: the shared ring lies outside the segment, where the track's own overflow cuts it flat,
   and the selected segment drew none at all (its shadow is handed to the thumb). An inset ring is whole on both. */
.adm .seg > :focus-visible { box-shadow: inset 0 0 0 2px var(--tint); }
.adm .seg > :focus-visible::before, .adm .seg > :focus-visible + ::before { opacity: 0; }   /* no divider against the ring */
/* ...and it is there at once: the segments' shadow transition belongs to a thumb they no longer draw themselves */
.adm .seg > a, .adm .seg > button { transition-property: background-color, color; }
/* A filled button's own shadow (its top highlight) outranks the shared focus ring, so nothing was drawn when
   it had the focus. The footer buttons of dialogs are outside .adm (see the top of this file), hence their own
   classes. */
.adm .btn.primary:focus-visible, .btn.adm-busy.primary:focus-visible, .btn.adm-copy.primary:focus-visible, .btn.adm-done.primary:focus-visible { box-shadow: var(--ring); }
/* the same for the red text buttons (重置密码): `.btn.ghost.danger` clears the shadow, and the ring with it */
.adm .btn.ghost.danger:focus-visible, .btn.adm-foot-reset:focus-visible { box-shadow: var(--ring); }

/* footnote under a list or a column of cards, aligned to the text edge of the cards */
.adm-foot-note { margin: 12px 0 0; padding: 0 18px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); text-wrap: balance; }

/* ------------------------------------------------------------------ callouts */
.adm-callout { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: var(--radius-lg); font-size: var(--fs-md); line-height: var(--lh-body); color: var(--text-2); }
.card .adm-callout, .adm-dlg .adm-callout { border-radius: var(--radius); }   /* nested: one step tighter than its container */
.adm-callout > .icon { flex: none; margin-top: 1px; }
.adm-callout b { color: var(--text); font-weight: 600; }
.adm-callout .btn { flex: none; }
/* Short notes wrap to even lines: `pretty` only looks after Latin words, so Chinese left two or three characters
   alone on the last line (「…再查看（忘 / 了只能再重置）。」). */
.adm-callout ul { display: grid; gap: 4px; min-width: 0; }
.adm-callout li, .adm-callout.danger > .grow { text-wrap: balance; }
.adm-callout.info { background: var(--bg-sunken); }      /* an explanation, not a status: no colour */
.adm-callout.info > .icon { color: var(--text-3); }
.adm-callout.warn { background: var(--warn-soft); }
.adm-callout.warn > .icon { color: var(--warn); }
.adm-callout.danger { background: var(--danger-soft); align-items: center; }
.adm-callout.danger > .icon { color: var(--danger); margin-top: 0; }
/* term + definition pairs inside a callout */
.adm-defs { display: grid; gap: 4px; min-width: 0; margin: 0; }
.adm-defs > div { display: grid; grid-template-columns: 4.6em minmax(0, 1fr); column-gap: 8px; }
.adm-defs dt { color: var(--text); font-weight: 600; }
.adm-defs dd { margin: 0; text-wrap: pretty; }

/* ------------------------------------------------------------------ search + filter (one row, one height) */
.adm-toolbar { display: flex; align-items: center; gap: var(--adm-gap); flex-wrap: wrap; margin-bottom: 14px; }
.adm-search { position: relative; flex: 1 1 240px; max-width: var(--adm-lead); min-width: 0; }
.adm-search > .icon { position: absolute; left: 8px; top: 50%; margin-top: -7.5px; color: var(--text-3); pointer-events: none; }
.adm-search .input { height: var(--ctl-h); padding: 0 28px 0 29px; background: var(--fill-2); box-shadow: none; font-size: var(--fs-md); }
.adm-search .input::placeholder { color: var(--text-3); }
.adm-search .input:focus { background: var(--bg-elev); box-shadow: var(--ring); }
.adm-search-x { position: absolute; right: 4px; top: 50%; display: grid; place-items: center; width: 20px; height: 20px; margin-top: -10px; border-radius: 50%; }
.adm-search-x i { display: grid; place-items: center; width: 14px; height: 14px; border-radius: 50%; background: var(--text-4); color: var(--bg-elev); transition: background-color var(--t-fast) var(--ease); }
.adm-search-x:active i { background: var(--text-3); transition-duration: 0s; }
.adm-search-x::after { content: ""; position: absolute; inset: -4px; }   /* 20px to look at, the field's height to hit */
.adm-search-x:focus-visible { border-radius: 50%; }
.adm-search-x .icon { stroke-width: 2.6; }
@media (hover: hover) { .adm-search-x:hover i { background: var(--text-3); } }
.adm-toolbar .seg > button { gap: 4px; }
/* A count has room kept for it (two digits) only while it is unknown, so the filter is near its final width before
   the figures arrive. A count that is there is as wide as its digits: in a fixed slot 「8」 stood 4px further from
   its label than 「17」, and the slots made the filter too wide to share a row with the search on an 820px tablet. */
.adm-toolbar .seg .num:empty { min-width: 2ch; }
/* Where the rows are a list (tablets) the toolbar is as wide as the list: the search takes what the filter leaves.
   In a window too narrow for both on one row the filter gets the next row and fills it, as on a phone (it may
   grow as well, but beside the search its share of the spare room is a ten-thousandth: nothing). */
@media (min-width: 768px) and (max-width: 1023px) {
  .adm-search { flex-grow: 9999; max-width: none; }
  .adm-toolbar .seg { flex-grow: 1; }
}

/* ------------------------------------------------------------------ users: table (with a pointer, wide windows) */
.adm-users { table-layout: fixed; }
.adm-c-user { width: 30%; }
.adm-c-role { width: 140px; }
.adm-c-state { width: 150px; }
.adm-c-act { width: 116px; }
.adm-users.tight .adm-c-act { width: 44px; }
.adm-users td { vertical-align: middle; }
.adm-user { display: flex; align-items: center; gap: 10px; min-width: 0; }
.adm-user-n { display: flex; flex-direction: column; min-width: 0; }
.adm-user-dn { font-weight: 500; line-height: var(--lh-md); color: var(--text); }
.adm-user-un { display: block; font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 400; color: var(--text-3); }
/* (line-height 1: at the name's line-height the smaller text hangs below the line and makes its row 1px taller) */
.adm-you { font-size: var(--fs-sm); line-height: 1; font-weight: 400; color: var(--text-3); }
.adm-none { color: var(--text-4); }           /* a missing value: "—" */
/* a block inside the cell, not the cell: a clipped <td> paints its own bottom border, outside the row's hover
   wash, so the hairline under a hovered row had a lighter piece */
.adm-cell-clip { overflow: hidden; text-overflow: ellipsis; }
.adm-chips { display: flex; gap: 4px; min-width: 0; }
/* 管理员 is a privilege next to the role, not a second role: outlined, so the two never read as a pair of equals */
.chip.adm-admin { background: none; box-shadow: inset 0 0 0 var(--hair) var(--hairline-strong); color: var(--text-2); }
/* block-level, so the cell centres the 16px line itself (an inline box would sit on the cell's 14px baseline,
   1px below the centre of its neighbours) */
.adm-online { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-2); white-space: nowrap; }
.adm-online .dot { width: 7px; height: 7px; }
.adm-seen { display: block; overflow: hidden; text-overflow: ellipsis; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* the PM's role (the person the traders work for) is set in ink; the chip stays grey — blue would say "press me" */
.chip.adm-pm { color: var(--text); }

/* A stopped account: the row goes quiet, and 已停用 says why in a grey chip that keeps the chip's own ink (red
   is for a breach or a limit, and a stopped account is neither). */
.adm-off .avatar { opacity: .5; }
.adm-off .adm-user-dn, .adm-off .adm-cell-clip.text-2 { color: var(--text-3); }
.adm-off .chip:not(.adm-state) { background: var(--fill-2); box-shadow: none; color: var(--text-3); }

/* section rows: 在用 · 8 / 已停用 · 3 / 机器人 · 6 */
.adm-users .adm-grp td { padding: 6px 18px; background: var(--bg-sunken); font: 600 var(--fs-xs)/var(--lh-xs) var(--font); letter-spacing: .01em; color: var(--text-3); font-variant-numeric: tabular-nums; }
.adm-users tbody tr.adm-grp:hover { background: none; }

/* the row is the edit action: pressed state, focus ring, a chevron; 重置密码 arrives on hover or focus */
.adm-users tr.clickable:active:not(:has(.btn:active)) { background: var(--bg-active); transition-duration: 0s; }
.adm-users tr.clickable:focus-visible { outline: 2px solid var(--tint); outline-offset: -2px; box-shadow: none; }
.adm-row-end { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
.adm-chev { flex: none; color: var(--text-4); }
.adm-users .adm-row-end { gap: 6px; }
.adm-users .adm-row-end .btn { opacity: 0; transition: opacity var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); }
.adm-users tr:hover .adm-row-end .btn, .adm-users tr:focus-within .adm-row-end .btn { opacity: 1; }
@media (hover: none) { .adm-users .adm-row-end .btn { opacity: 1; } }

/* ------------------------------------------------------------------ users: list (tablet, phone) */
.adm-grp-h { margin: 20px 0 6px; padding: 0 16px; font: 600 var(--fs-sm)/var(--lh-sm) var(--font); letter-spacing: .01em; color: var(--text-3); font-variant-numeric: tabular-nums; }
.adm-toolbar + .adm-grp-h, .adm-callout + .adm-grp-h { margin-top: 4px; }
.adm-uitem {
  position: relative; display: flex; align-items: center; gap: 10px; width: 100%; min-height: 60px; padding: 9px 12px 9px 16px;
  text-align: left; color: var(--text); transition: background-color var(--t-fast) var(--ease);
}
/* hairlines start at the text edge: 16px + the 36px avatar + its 10px gap */
.adm-uitem + .adm-uitem::before { content: ""; position: absolute; left: 62px; right: 0; top: 0; height: var(--hair); background: var(--hairline); }
@media (hover: hover) { .adm-uitem:hover { background: var(--bg-hover); } }
.adm-uitem:active { background: var(--bg-active); transition-duration: 0s; }
.adm-uitem:focus-visible { border-radius: 0; box-shadow: inset 0 0 0 2px var(--tint); }
.adm-uitem-m { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.adm-uitem .adm-user-dn { line-height: var(--lh-body); }
.adm-uitem-r { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }

/* ------------------------------------------------------------------ loading: rows in their final shape */
/* In a table the skeleton rows are rows of the real table, under its real header (and under the first section
   row where there is one): the cell's own padding and hairline, and content as tall as a row's two text lines,
   so nothing moves when the answer arrives. */
.adm-sk-row { position: relative; display: flex; align-items: center; gap: 10px; height: calc(var(--lh-md) + var(--lh-sm)); }
.adm-dash-table .adm-sk-row { height: calc(var(--lh-body) + var(--lh-sm)); }
.table tbody tr.adm-sk-tr:hover { background: none; }
.adm-sk-row .skeleton { display: block; flex: none; width: 30px; height: 30px; }
.adm-sk-row .skeleton.round { border-radius: 50%; }
.adm-sk-row .skeleton.tile { border-radius: var(--radius-sm); }
.adm-sk-row > span { display: grid; gap: 7px; }
.adm-sk-row .skeleton.line { width: 132px; height: 10px; }
.adm-sk-row .skeleton.line + .line { width: 76px; }
/* the text of a section row (在用 · 8) while it is not known: a bar on the same line, so the row keeps its height */
.adm-sk-h, i.adm-sk-h.skeleton { display: inline-block; width: 48px; height: 8px; vertical-align: middle; }   /* (base.css makes i.skeleton a block) */
.adm-sk-h.skeleton { border-radius: var(--radius-xs); }
/* Where the rows are a list (tablets, phones) the skeleton is that list: 60px rows, hairlines from the text edge. */
.adm-sk { --sk-n: 6; --sk-inset: 62px; min-height: calc(60px * var(--sk-n)); }
.card-h ~ .adm-sk { --sk-n: 3; --sk-inset: 56px; }
.adm-sk > .adm-sk-row { height: 60px; padding: 0 16px; }
.adm-sk > .adm-sk-row + .adm-sk-row::before { content: ""; position: absolute; left: var(--sk-inset); right: 0; top: 0; height: var(--hair); background: var(--hairline); }
.adm-sk > .adm-sk-row .skeleton.round { width: 36px; height: 36px; }

/* ------------------------------------------------------------------ buttons that do not change size */
/* 复制 / 已复制 share one grid cell */
.btn.adm-copy { display: inline-grid; place-items: center; }
.adm-copy-s { grid-area: 1 / 1; display: inline-flex; align-items: center; gap: 4px; opacity: 0; transition: opacity var(--t-fast) var(--ease); }
.adm-copy-s.on { opacity: 1; }
/* busy: the label keeps its place, the spinner sits on top */
.adm-busy-l { display: inline-flex; align-items: center; gap: 6px; }
.adm-busy[aria-busy="true"] .adm-busy-l { visibility: hidden; }
.adm-busy > .spinner { position: absolute; left: 50%; top: 50%; margin: -7px 0 0 -7px; border-color: color-mix(in srgb, currentColor 28%, transparent); border-top-color: currentColor; }

/* ------------------------------------------------------------------ dialogs */
.adm-dlg { min-width: 0; }
.adm-dlg .field .hint { text-wrap: balance; }
.adm-dlg .input { text-overflow: ellipsis; }   /* a placeholder longer than its field (320px phones) ends in "…", not mid-character */
/* A hint that gives way to an error or a warning stays in the layout, unseen, and what replaces it lies over it.
   The hint is the longest of them at every width, so the field keeps one height and the dialog does not change
   height — and move — while it is being filled in. */
.adm-hints { display: grid; }
.adm-hints > * { grid-area: 1 / 1; align-self: start; }
.adm-hints-w { display: grid; row-gap: 2px; }
.adm-gone { visibility: hidden; }
.adm-nw { white-space: nowrap; }             /* a term that must not break at its hyphen */
/* an invalid field that has the focus keeps its red edge and gets a ring of the same colour */
.adm-dlg .field.invalid .input:focus { box-shadow: 0 0 0 1px var(--danger), 0 0 0 3.5px color-mix(in srgb, var(--danger-fill) 32%, transparent); }
.adm-req { margin-left: 3px; color: var(--text-3); }
.adm-form-grid { gap: 0 14px; }
.adm-dlg-who { display: flex; align-items: center; gap: 12px; padding: 2px 0 16px; margin-bottom: 16px; box-shadow: inset 0 calc(-1 * var(--hair)) 0 var(--hairline); }
.adm-dlg-who > .grow { display: flex; flex-direction: column; }
.adm-dlg-who b { font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; letter-spacing: -.01em; }

/* show / hide the password: an eye inside the field's trailing edge */
.adm-pw { position: relative; }
.adm-pw .input { padding-right: 32px; }
.adm-pw .icon-btn { position: absolute; right: 3px; top: 50%; width: 24px; height: 24px; margin-top: -12px; border-radius: calc(var(--radius-ctl) - 2px); color: var(--text-3); }
.adm-pw .icon-btn::after { content: ""; position: absolute; inset: -3px; }   /* the field's height to hit */

/* permissions: switches in one grouped block */
.adm-opts { display: block; width: 100%; border-radius: var(--radius); background: var(--bg-sunken); }
.adm-opt { position: relative; display: flex; align-items: center; gap: 12px; min-height: 54px; padding: 8px 12px 8px 14px; }
.adm-opt + .adm-opt::before { content: ""; position: absolute; left: 14px; right: 0; top: 0; height: var(--hair); background: var(--hairline); }
.adm-opt-t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.adm-opt-t b { font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 500; }
.adm-opt.danger .switch input:checked + i { background: var(--danger-fill); }
.adm-opt .switch:has(input:disabled) { opacity: .4; cursor: default; }
.adm-opt .switch input:disabled { cursor: default; }
/* a note under the block starts on the block's text edge; a note that stands alone starts on the fields' edge */
.adm-opts-note { margin: 0; text-wrap: pretty; }
.adm-opts + .adm-opts-note { margin-top: 6px; padding: 0 14px; }
.adm-reset-m { display: none; }               /* phones only (below): 重置密码 as a row of its own */

/* footer: a server error sits on the leading side, so the body never changes height. It opens with an icon (next
   to the red 重置密码 it would otherwise read as one run of red text) and is two lines at most — the height of the
   buttons, so the footer does not grow; the whole message is in its tooltip. */
.adm-foot-l { margin-right: auto; }
.adm-foot-reset { margin-left: -8px; }        /* a text button: its label, not its box, starts on the fields' edge */
.adm-err {
  flex: 1 1 0; min-width: 0; display: flex; align-items: flex-start; gap: 6px; margin-right: auto; align-self: center;
  max-height: var(--ctl-h); overflow: hidden; font-size: var(--fs-sm); line-height: var(--lh-xs); color: var(--danger);
}
.adm-foot-reset + .adm-err { margin-left: 12px; }
.adm-err > .icon { flex: none; }
.adm-err > span { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; min-width: 0; text-wrap: balance; }

/* one-time password */
.adm-secret { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 14px; }
.adm-secret-v {
  flex: 1 1 200px; display: flex; align-items: center; justify-content: center; min-height: 48px; padding: 8px 12px;
  border-radius: var(--radius); background: var(--bg-sunken); box-shadow: inset 0 0 0 var(--hair) var(--hairline);
  color: var(--text); font: 600 var(--fs-title2)/1.3 var(--mono); letter-spacing: .08em; text-align: center;
  overflow-wrap: anywhere; -webkit-user-select: all; user-select: all; cursor: text;
}
.adm-secret-v:focus-visible { border-radius: var(--radius); box-shadow: var(--ring); }
.adm-secret .btn { height: 48px; padding: 0 20px; border-radius: var(--radius); font-size: var(--fs-body); }

/* ------------------------------------------------------------------ dashboards */
.adm-dash-table { table-layout: fixed; }
.adm-c-dash { width: 220px; }
.adm-c-url { width: 26%; }       /* also the native table's 分组 column, so 说明 starts on the same x in both tables */
.adm-c-grp { width: 96px; }
.adm-c-sort { width: 76px; }
.adm-c-by { width: 124px; }
/* a right-aligned figure next to a left-aligned column: keep them a column gap apart */
.adm-dash-table th.num, .adm-dash-table td.num { padding-right: 24px; }
.adm-c-dact { width: 112px; }
/* these rows do nothing themselves (查看 and the trash are the targets): no wash under the pointer, which on 用户
   means "this row opens" */
.adm-dash-table tbody tr:hover { background: none; }
/* the last glyph of a row ends on the card's text edge: a 16px trash in a 28px button, a label in 8px of padding */
.adm-dash-table .adm-row-end { margin-right: -6px; }
.adm-dash-table .adm-row-end > .btn:last-child { margin-right: -2px; }
/* a row without the trash keeps its place free (28px button + 2px gap), so 查看 is on one edge in both tables */
.adm-dash-table .adm-row-end > .btn:only-child { margin-right: 30px; }
.adm-dash-t { display: flex; align-items: center; gap: 10px; min-width: 0; }
.adm-dash-t > div { min-width: 0; }
.adm-dash-id { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
.adm-dash-ico { display: inline-grid; place-items: center; flex: none; width: 30px; height: 30px; border-radius: var(--radius-sm); background: var(--fill-2); color: var(--text-2); }
.adm-dash-ico.native { background: var(--accent-soft); color: var(--link); }
.adm-dash-ico.lg { width: 40px; height: 40px; border-radius: var(--radius); }   /* at the head of its detail, the size of an avatar there */
/* a description takes the row's second line before it is cut (the row is two lines tall already) */
.adm-desc {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; white-space: normal;
  font-size: var(--fs-md); line-height: var(--lh-md); overflow-wrap: anywhere; text-wrap: balance;
}
.adm-url { font-size: var(--fs-sm); color: var(--text-2); }
.adm-url:hover { color: var(--link); }
.adm-by { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
/* delete: grey until the pointer is on it */
.icon-btn.adm-trash { color: var(--text-3); }
.icon-btn.adm-trash:active { background: var(--danger-soft); color: var(--danger); }
@media (hover: hover) { .icon-btn.adm-trash:hover { background: var(--danger-soft); color: var(--danger); } }

/* The same rows as a list (tablets and phones): the address or the description gets a line of its own. A row is
   one target with one trailing glyph: it opens the entry's detail, where the columns that have no room here are
   back and 删除 is a row of its own — not an icon a finger's width from the chevron. */
.adm-ditem {
  position: relative; display: flex; align-items: center; gap: 10px; width: 100%; min-height: 60px; padding: 8px 12px 8px 16px;
  text-align: left; color: var(--text); transition: background-color var(--t-fast) var(--ease);
}
.adm-ditem + .adm-ditem::before { content: ""; position: absolute; left: 56px; right: 0; top: 0; height: var(--hair); background: var(--hairline); }
@media (hover: hover) { .adm-ditem:hover { background: var(--bg-hover); } }
.adm-ditem:active { background: var(--bg-active); transition-duration: 0s; }
.adm-ditem:focus-visible { border-radius: 0; box-shadow: inset 0 0 0 2px var(--tint); }
.adm-ditem-m { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.adm-ditem-t { font-weight: 500; line-height: var(--lh-body); }
.adm-ditem-s { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }

/* An entry's detail (a dialog, so nothing here hangs off .adm): label / value pairs in one well, the values on
   one edge; a long address or description wraps and is never cut. Under it the rows that act — the shared
   .list-row on this dialog's wells, with the wells' 14px text edge. */
.adm-kv { margin: 0; }
.adm-kv > div { position: relative; display: grid; grid-template-columns: 4em minmax(0, 1fr); column-gap: 12px; padding: 11px 14px; font-size: var(--fs-body); line-height: var(--lh-body); }
.adm-kv > div + div::before { content: ""; position: absolute; left: 14px; right: 0; top: 0; height: var(--hair); background: var(--hairline); }
.adm-kv dt { color: var(--text-2); }
.adm-kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; text-wrap: balance; color: var(--text); }
.adm-kv dd.adm-kv-url { text-wrap: wrap; }      /* its lines break at the address's own joints (see DashDetail), not evened out */
.adm-kv dd.adm-none { color: var(--text-4); }
.adm-kv dd .mono { font-size: var(--fs-sm); }   /* an intranet address (host:port/) stays on one line on a phone */
.adm-rows { margin-top: 16px; overflow: hidden; }
.adm-rows > .list-row { padding-inline: 14px; border-radius: inherit; }

/* add dialog: 标题 + 排序 on one row, their hint on a full line below */
.adm-form-grid-dash { display: grid; grid-template-columns: minmax(0, 1fr) 120px; gap: 0 14px; }
.adm-form-grid-dash .field { margin-bottom: 6px; }
.adm-grid-hint { grid-column: 1 / -1; margin-bottom: 16px; }
.hint.adm-grid-hint.invalid { color: var(--danger); }
.adm-dlg .field .row > .btn { height: var(--field-h); }
.adm-dlg a.btn[aria-disabled="true"] { opacity: .4; pointer-events: none; }

/* icon picker: twelve quiet tiles, the chosen one tinted */
.adm-icons { display: grid; grid-template-columns: repeat(12, 1fr); gap: 4px; }
.adm-icons button {
  display: grid; place-items: center; height: 36px; border-radius: var(--radius-sm); color: var(--text-2);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
@media (hover: hover) { .adm-icons button:not(.active):hover { background: var(--fill-3); color: var(--text); } }
.adm-icons button:not(.active):active { background: var(--fill-2); transition-duration: 0s; }
.adm-icons button.active { background: var(--accent-soft); color: var(--link); box-shadow: inset 0 0 0 1.5px var(--tint); }
.adm-icons button:focus-visible { border-radius: var(--radius-sm); box-shadow: var(--ring); }

/* preview: the row exactly as the sidebar will show it */
.adm-preview { display: flex; align-items: center; gap: 12px; padding: 8px; border-radius: var(--radius); background: var(--bg-side); }
.adm-preview-l { flex: none; padding-left: 6px; font-size: var(--fs-sm); color: var(--text-3); }
.adm-preview-item {
  display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; max-width: 232px; min-height: 30px; padding: 0 8px;
  border-radius: var(--radius-sm); background: var(--selection); color: var(--text); font-size: var(--fs-md);
}
.adm-preview-item > .icon { color: var(--link); }

/* ------------------------------------------------------------------ group robots */
.adm-bots { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 16px; align-items: start; }
@media (max-width: 1280px) { .adm-bots { grid-template-columns: minmax(0, 1fr); } }
.adm-bots-l { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.adm-bots-l > .adm-foot-note { margin-top: -4px; }
.adm-group .card-h { gap: 10px; margin-bottom: 10px; }
.adm-group-t { min-width: 0; }
.adm-group-s { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); font-variant-numeric: tabular-nums; }
/* One text edge down the card: group name, robot names, address wells and the name field all start 42px in
   (the 32px avatar + its 10px gap); the hairlines between rows start there too. */
.adm-group { --edge: 42px; }
.adm-group > .adm-callout { margin-left: var(--edge); }   /* a failed load starts on that edge too */
.adm-hook { position: relative; display: flex; align-items: flex-start; gap: 10px; padding: 12px 0; }
.adm-hook::before { content: ""; position: absolute; left: var(--edge); right: 0; top: 0; height: var(--hair); background: var(--hairline); }
.adm-hook-b { min-width: 0; }
.adm-hook-n { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 0; min-height: 32px; }
.adm-hook-n b { font-weight: 500; min-width: 0; max-width: 100%; }
.adm-hook-meta { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); font-variant-numeric: tabular-nums; }
/* 创建于 … · 最近调用 …: each part stays whole, and where they do not fit on one line (320px phones) the second
   part takes the next. Every part carries its "·" in its own leading padding, and the row is pulled left by that
   padding under a clipping parent — so a part that starts a line shows no separator. */
.adm-hook-meta:has(> .adm-dots) { min-width: 0; overflow: hidden; }
.adm-dots { --sep: .9em; display: flex; flex-wrap: wrap; margin-left: calc(-1 * var(--sep)); }
.adm-dots > span { position: relative; padding-left: var(--sep); white-space: nowrap; }
.adm-dots > span::before { content: "\00b7"; position: absolute; left: 0; width: var(--sep); text-align: center; }
/* the trash glyph (16px in a 28px button) ends on the edge the address well and 添加 end on */
.adm-hook-a { display: flex; gap: 2px; flex: none; margin: 0 -6px 0 auto; }
/* the address well: its radius is the copy button's radius plus the 3px between them */
.adm-hook-url { display: flex; align-items: center; gap: 6px; margin-top: 6px; padding: 3px 3px 3px 10px; border-radius: var(--radius-sm); background: var(--bg-sunken); }
.adm-hook-url code { flex: 1; min-width: 0; display: flex; font-size: var(--fs-sm); color: var(--text-3); white-space: nowrap; }
.adm-url-h { min-width: 0; overflow: hidden; text-overflow: ellipsis; }   /* the fixed part yields; the key stays readable */
.adm-url-k { flex: none; color: var(--text); }
/* loading: the place and height of the name field, so a group with no robots does not move when it has answered */
.adm-hook-sk { min-height: 34px; padding: 4px 0 0 var(--edge); }
.adm-hook-sk .skeleton { display: block; height: 30px; border-radius: var(--radius-ctl); }
/* The list of groups while it is being read: each card is a group card's own boxes — the header (the 32px avatar
   beside a name line and a status line) and under it the name field's place (.adm-hook-sk) — so it is as tall as
   a group without robots at every size, and the first card does not move when the answer arrives. */
.adm-group-sk .card-h > i { display: block; flex: none; width: 32px; height: 32px; border-radius: var(--radius-sm); }
.adm-sk-lines { display: grid; align-content: center; gap: 7px; height: calc(var(--lh-headline) + var(--lh-sm)); }
.adm-sk-lines .skeleton.line { width: 132px; height: 10px; }
.adm-sk-lines .skeleton.line + .line { width: 76px; }
/* add a robot: field and button share one height; a rule above only when robots are listed */
.adm-add { position: relative; display: flex; gap: 8px; padding: 4px 0 0 var(--edge); }
.adm-hook + .adm-add { padding-top: 14px; }
.adm-hook + .adm-add::before { content: ""; position: absolute; left: var(--edge); right: 0; top: 0; height: var(--hair); background: var(--hairline); }
.adm-add > .btn { height: var(--field-h); }     /* the field's own height */

/* ------------------------------------------------------------------ migration help */
.adm-help { min-width: 0; }
.adm-steps { list-style: decimal; margin: 0 0 4px; padding-left: 22px; color: var(--text-2); font-size: var(--fs-md); line-height: var(--lh-body); }
.adm-steps li { margin-bottom: 8px; padding-left: 2px; text-wrap: pretty; }
.adm-steps li::marker { color: var(--text-3); font-variant-numeric: tabular-nums; font-weight: 600; }
.adm-steps b { color: var(--text); font-weight: 600; }
.adm-help code { padding: 1px 6px; border-radius: var(--radius-xs); background: var(--fill-2); font-family: var(--mono); font-size: .92em; color: var(--text); overflow-wrap: anywhere; }
/* old address → new address, as two lines of a diff (ink, not red / green: nothing here is a result) */
.adm-swap { margin: 8px 0 2px; border-radius: var(--radius-sm); background: var(--bg-sunken); font-family: var(--mono); font-size: var(--fs-sm); line-height: var(--lh-md); }
.adm-swap div { position: relative; padding: 7px 10px 7px 28px; overflow-wrap: anywhere; }
.adm-swap div::before { position: absolute; left: 10px; top: 7px; color: var(--text-3); }
.adm-swap .old { color: var(--text-3); text-decoration: line-through; }
.adm-swap .old::before { content: "\2212"; }
.adm-swap .new { color: var(--text); box-shadow: inset 0 var(--hair) 0 var(--hairline); }
.adm-swap .new::before { content: "+"; }
.adm-h { margin: 20px 0 8px; font: 600 var(--fs-md)/var(--lh-md) var(--font); letter-spacing: 0; color: var(--text); }
.adm-code-h { display: flex; align-items: center; gap: 8px; }
.adm-code-h h4 { flex: 1; min-width: 0; font: inherit; letter-spacing: inherit; }
.table.adm-wrap-table { table-layout: fixed; }
.table.adm-wrap-table td { padding: 7px 0 7px 12px; white-space: normal; vertical-align: top; font-size: var(--fs-sm); line-height: var(--lh-md); overflow-wrap: anywhere; }
.table.adm-wrap-table td:first-child { padding-left: 0; }
.table.adm-wrap-table tbody tr:hover { background: none; }
.table.adm-wrap-table .adm-mt { width: 118px; white-space: nowrap; color: var(--text); font-weight: 500; }
/* field names inside a description: the code voice in ink, no ground (several to a line would read as buttons) */
.table.adm-wrap-table code { padding: 0; background: none; white-space: nowrap; }
/* The sample: a well that keeps its shape; the text inside scrolls both ways and fades at every edge beyond
   which there is more of it (.more-l / -r / -t / -b are set from its scroll position). */
.adm-code { border-radius: var(--radius); background: var(--bg-sunken); overflow: hidden; }
.adm-code pre {
  --ml: 0px; --mr: 0px; --mt: 0px; --mb: 0px;
  margin: 0; padding: 12px 14px; max-height: 440px; overflow: auto; overscroll-behavior-x: contain;
  font: var(--fs-sm)/var(--lh-md) var(--mono); color: var(--text); white-space: pre; tab-size: 4;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--ml), #000 calc(100% - var(--mr)), transparent), linear-gradient(transparent, #000 var(--mt), #000 calc(100% - var(--mb)), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 var(--ml), #000 calc(100% - var(--mr)), transparent), linear-gradient(transparent, #000 var(--mt), #000 calc(100% - var(--mb)), transparent);
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
.adm-code pre.more-l { --ml: 28px; }
.adm-code pre.more-r { --mr: 28px; }
.adm-code pre.more-t { --mt: 20px; }
.adm-code pre.more-b { --mb: 28px; }
.adm-code .c { color: var(--text-3); }
.adm-code pre:focus-visible { border-radius: var(--radius); box-shadow: inset 0 0 0 2px var(--tint); }
.adm-code-note { margin-top: 6px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }

/* ------------------------------------------------------------------ phones */
@media (max-width: 767px) {
  /* One navigation bar: the back row (with the section title) and the section tabs under it. It lies over the
     scroller: clear while the page is at its top, material with a hairline once content is beneath. .adm-bar is
     the bar's surface and runs up behind the back row, so the two rows are one sheet of material with no seam. */
  .adm { position: relative; }
  .adm > .push-bar { position: absolute; top: 0; left: 0; right: 0; }
  .adm > .push-bar .push-t { opacity: 1; transform: translate(-50%, 0); }
  .adm-bar {
    position: absolute; z-index: 11; top: 0; left: 0; right: 0; padding: calc(44px + var(--safe-t)) var(--page-pad) 8px;
    -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: inset 0 calc(-1 * var(--hair)) 0 transparent;
    transition: background-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
  }
  .adm.is-under > .adm-bar { background: var(--material-bar); box-shadow: inset 0 calc(-1 * var(--hair)) 0 var(--hairline); }
  .adm-bar .seg { display: flex; width: 100%; }
  /* the page starts under the bar: back row + tabs (--ctl-h) + the bar's 8px + 12px of air */
  .adm > .page { padding-top: calc(44px + var(--safe-t) + var(--ctl-h) + 20px); scroll-padding-top: calc(44px + var(--safe-t) + var(--ctl-h) + 20px); }

  /* The section's actions are the back bar's trailing icons, as on every pushed page: plain glyphs in the link
     colour — add, where the tab has one, then refresh, which so keeps the trailing edge on every tab. (The shared
     .push-a makes them 40px with a 44px hit area; .loading is also the name of a block placeholder with 48px
     of padding, see .adm-tb above.) */
  .adm > .push-bar .icon-btn.loading { padding: 0; pointer-events: auto; }

  /* under the bars: the status line. It keeps the height of two lines — the figures, then the part that says
     more — so the first card starts on the same line on every tab; a status of one line sits in the middle. */
  .adm > .page > .adm-tb { display: block; margin-bottom: 12px; }
  .adm-tb h1 { display: none; }
  .adm-tb .dash-h-t { min-height: calc(var(--lh-sm) * 2); padding: 0 16px; }
  .adm-tb .dash-h-sub { white-space: normal; }
  .adm-tb-more { display: block; }             /* the second part of the status is a line of its own */
  .adm-tb-sep { display: none; }
  .adm-foot-note { padding: 0 16px; }

  /* 44px to touch behind the smaller shapes (::before — a button's ::after is its press veil) */
  .adm-hook-url .adm-copy::before, .adm-tab .empty .btn::before, .adm-callout .btn::before { content: ""; position: absolute; inset: -7px -4px; }
  .adm-opt .switch::after { content: ""; position: absolute; inset: -8px 0; }

  .adm-callout { font-size: var(--fs-sm); line-height: var(--lh-md); }
  .adm-callout.info { background: var(--bg-elev); }      /* on the grouped background a well would not show */
  .adm-defs > div { grid-template-columns: minmax(0, 1fr); }

  .adm-search { max-width: none; flex-basis: 100%; }
  .adm-search > .icon { left: 10px; }
  .adm-search .input { padding: 0 32px 0 32px; font-size: var(--fs-body); }   /* 16px: no zoom on focus */
  .adm-search-x { right: 6px; width: 24px; height: 24px; margin-top: -12px; }
  .adm-search-x::after { inset: -10px; }
  .adm-search-x i { width: 17px; height: 17px; }
  .adm-toolbar .seg { display: flex; width: 100%; }
  .adm-toolbar .seg > button { padding: 0 6px; }

  .adm-grp-h { margin-top: 22px; }

  /* dialogs */
  .adm-pw .input { padding-right: 44px; }
  .adm-pw .icon-btn { right: 6px; width: 32px; height: 32px; margin-top: -16px; }
  .adm-pw .icon-btn::after { inset: -6px; }
  .adm-opt { min-height: 60px; }
  .adm-reset-m { display: flex; align-items: center; justify-content: center; min-height: 48px; margin-top: 16px; color: var(--danger); font-size: var(--fs-body); transition: background-color var(--t-fast) var(--ease); }
  .adm-reset-m:active { background: var(--bg-active); transition-duration: 0s; }
  .adm-foot-reset { display: none; }
  .modal-f:has(> .adm-err) { flex-wrap: wrap; }
  .adm-err, .adm-foot-reset + .adm-err { flex: 1 1 100%; justify-content: center; max-height: none; margin-left: 0; line-height: var(--lh-sm); }
  .adm-err > .icon { margin-top: calc((var(--lh-sm) - 14px) / 2); }   /* the 14px glyph on the first line's centre */
  .adm-secret-v { font-size: var(--fs-title3); }
  .adm-secret .btn { flex: 1 1 100%; height: var(--ctl-h-lg); font-size: var(--fs-headline); font-weight: 600; }
  .adm-form-grid-dash { grid-template-columns: minmax(0, 1fr) 96px; }
  .adm-dlg .field .row > .btn { padding: 0 14px; }
  .adm-icons { grid-template-columns: repeat(6, 1fr); }
  .adm-icons button { height: 44px; }

  /* dashboards: the card title keeps one line, its note takes the next */
  .adm-tab > .card > .card-h { flex-wrap: wrap; row-gap: 2px; margin-bottom: 6px; }
  .adm-tab > .card > .card-h h3 { flex: 0 0 auto; white-space: nowrap; }
  .adm-tab > .card > .card-h .card-sub { flex: 1 1 100%; text-wrap: balance; }

  /* robots */
  .adm-group > .adm-callout { margin-left: 0; }   /* as the name field: the card's whole width */
  .adm-hook-a { margin-right: -10px; }            /* the glyph is 10px inside a 36px button */
  .adm-hook-n b { flex: 1; }
  .adm-hook-meta { order: 3; flex-basis: 100%; }
  .adm-hook-url { padding: 4px 4px 4px 12px; border-radius: var(--radius-lg); }   /* the button's 8px + the 4px around it */
  .adm-url-h { display: none; }                /* no room for a readable piece of the fixed part: show what tells robots apart */
  .adm-add, .adm-hook-sk { padding-left: 0; }  /* the name field needs the card's whole width */
  .adm-hook-sk { min-height: 48px; }
  .adm-hook-sk .skeleton { height: 44px; }
  .adm-hook + .adm-add::before { left: 0; }
  .adm-add > .btn { padding: 0 16px; }

  /* help: each msgtype / code is a stacked pair; the copy button stays with the title, the languages take a row */
  .table.adm-wrap-table tr, .table.adm-wrap-table td { display: block; width: auto; }
  .table.adm-wrap-table tr { padding: 8px 0; border-bottom: var(--hair) solid var(--hairline); }
  .table.adm-wrap-table tr:last-child { border-bottom: 0; }
  .table.adm-wrap-table td { padding: 0; border: 0; }
  .table.adm-wrap-table .adm-mt { width: auto; margin-bottom: 2px; }
  .table.adm-wrap-table code { white-space: normal; }   /* a long name may be wider than the card */
  .adm-code-h { flex-wrap: wrap; }
  .adm-code-h .seg { order: 3; display: flex; width: 100%; }
  .adm-code pre { max-height: 360px; }
}
/* the smallest phones: one size down, so the old host still fits on a line and the two addresses of the diff
   keep breaking at the same places (before the path, before the key) */
@media (max-width: 359px) {
  .adm-swap { font-size: var(--fs-xs); line-height: var(--lh-sm); }
}
