:root {
  --bg: #0e1117;
  --bg-alt: #161b24;
  --panel: #1a2130;
  --border: #2a3347;
  --text: #e6e9f0;
  --muted: #8a93a6;
  --accent: #4f7cff;
  --accent-hover: #3d68e8;
  --green: #2ecc8f;
  --red: #ff5d6c;
  --amber: #ffb547;
  --radius: 10px;
  font-size: 15px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
}
.hidden { display: none !important; }
/* RBAC view-only mode: navigate() marks the body when the signed-in role holds
   the current page at view but not edit, and every mutating control carries
   edit-only. The server gate is the boundary; this only keeps the page honest. */
.view-only .edit-only { display: none !important; }
.role-matrix th.center, .role-matrix td.center { text-align: center; width: 5rem; }
.role-matrix tr.role-section th { background: var(--bg-alt); text-align: left; padding-top: .6rem; }
.role-matrix label { cursor: pointer; }
.role-matrix tr.role-action-row td.role-action-cell { padding-left: 2.2rem; color: var(--muted); }
/* The editor's "Add the permissions of" row: inline role boxes, not the stacked form labels. */
.role-sources { display: flex; flex-wrap: wrap; gap: .4rem .9rem; align-items: center; margin: .4rem 0 .8rem; }
.role-sources label { display: inline-flex; align-items: center; gap: .3rem; margin: 0; font-size: inherit; cursor: pointer; }
/* applyActionGates hides an ungranted action control with the hidden
   attribute; this keeps it hidden whatever display a class sets. */
[data-action][hidden] { display: none !important; }
.muted { color: var(--muted); font-size: .85rem; }
/* Keeps a labeled money figure ("total 22.39 USD") from breaking mid-phrase;
   a narrow Amount column then wraps at the separator between figures. */
.nowrap { white-space: nowrap; }
/* The Amount column: one labeled money figure per line, values aligned in their
   own column. No red/green here on purpose — the figures of a row are read
   together, so tinting only the first misrepresented the row. */
.amount-lines { display: grid; grid-template-columns: max-content max-content; gap: 0 .45rem; font-size: .85rem; }
.amount-lines > span { white-space: nowrap; }
/* A horizontal run of small facts (badges + muted text) above a detail body,
   wrapping rather than overflowing on a narrow modal. */
.meta-row { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; margin-bottom: .6rem; }
.asset-logo { width: 28px; height: 28px; object-fit: contain; border-radius: 6px; vertical-align: middle; background: rgba(255,255,255,.04); }
.asset-logo-cell { text-align: center; }
.error { color: var(--red); margin-top: .75rem; font-size: .9rem; }
/* The counterpart to .error for an outcome that succeeded. Kept separate rather
   than reusing .error so a green message can never be mistaken for a failure. */
.notice { color: var(--green); margin-top: .75rem; font-size: .9rem; }

h1 { font-size: 1.3rem; letter-spacing: .5px; }
h1 span { color: var(--accent); }
/* Sandbox deployments (QX_ENV local/staging, reported by ui-config as
   chain_env=testnet) get a lighter blue shell and a loud badge beside the
   brand so a staging console is never mistaken for production. */
body.sandbox {
  --bg: #16233f;
  --bg-alt: #1c2d50;
  --panel: #223760;
  --border: #35507f;
}
.env-badge { display: block; margin-top: .3rem; font-size: 1.45rem; font-weight: 800; color: var(--amber); letter-spacing: 1px; }
h2 { font-size: 1.4rem; margin-bottom: 1.2rem; }
h3 { font-size: 1rem; margin-bottom: .8rem; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: .5px; }

/* Login */
.login-wrap {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1.5rem; padding: 2rem 1rem;
}
.login-brand { width: min(320px, 80vw); height: auto; }
.login-card {
  width: 380px; background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 2rem;
}
.login-card h1 { margin-bottom: .3rem; }
.login-card p { margin-bottom: 1.2rem; }
label { display: block; font-size: .8rem; color: var(--muted); margin: .9rem 0 .3rem; }
input, select, textarea {
  width: 100%; padding: .6rem .8rem; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg-alt); color: var(--text);
  font-size: .95rem; outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
/* Message bodies are the only multi-line field in the dashboard. */
textarea { font-family: inherit; resize: vertical; min-height: 6rem; }
/* Release & fail: the return address is read-only until Edit, beside it. */
.rf-address-row { display: flex; gap: .5rem; align-items: flex-start; }
/* An Abelian address is ~136 hex characters with no break points: wrap it
   anywhere so the whole address is visible across a few lines. */
.rf-address-row textarea {
  flex: 1; min-width: 0; min-height: 0; resize: none;
  font-family: monospace; font-size: .85rem; line-height: 1.45; word-break: break-all;
}
.rf-address-row textarea[readonly] { background: var(--bg); color: var(--muted); cursor: default; }
.rf-address-row button { flex: none; }
.totp-qr {
  display: flex; justify-content: center; margin: .6rem 0;
}
.totp-qr canvas {
  width: 200px; height: 200px; border-radius: 8px;
  background: #fff; border: 10px solid #fff;
}
.totp-secret {
  font-family: ui-monospace, monospace; background: var(--bg-alt); border: 1px dashed var(--border);
  padding: .8rem; border-radius: 8px; text-align: center; letter-spacing: 2px;
  word-break: break-all; margin: .5rem 0;
}

button {
  cursor: pointer; border: none; border-radius: 8px; padding: .6rem 1.1rem;
  font-size: .92rem; font-weight: 600; transition: background .15s;
}
button.primary { background: var(--accent); color: #fff; }
button.primary:hover { background: var(--accent-hover); }
button.ghost { background: transparent; color: var(--muted); border: 1px solid var(--border); }
button.ghost:hover { color: var(--text); border-color: var(--muted); }
button.full { width: 100%; margin-top: 1.1rem; }
button.small { padding: .3rem .7rem; font-size: .8rem; }
button.danger { background: transparent; border: 1px solid var(--red); color: var(--red); }
/* A button mid-request is greyed and relabelled by setPending, so a slow round
   trip reads as "working" instead of "nothing happened". */
button:disabled { opacity: .55; cursor: default; }
/* An input with a button hugging its right edge (the reset code + Resend).
   width:auto is needed because inputs are 100% wide by default. */
.field-row { display: flex; gap: .5rem; }
.field-row input { flex: 1; min-width: 0; width: auto; }

/* App shell */
.app { display: flex; min-height: 100vh; }
.sidebar {
  width: 220px; background: var(--bg-alt); border-right: 1px solid var(--border);
  padding: 1.5rem 1rem; display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
}
/* min-height: 0 lets the nav shrink below its content so it scrolls inside
   the fixed-height sidebar instead of pushing the footer out of the viewport. */
.sidebar nav {
  margin-top: 2rem; display: flex; flex-direction: column; gap: .25rem;
  flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: thin;
  padding-right: 2px; margin-right: -6px;
}
.sidebar nav a {
  padding: .55rem .8rem; border-radius: 8px; color: var(--text);
  cursor: pointer; text-decoration: none; font-weight: 500;
}
.sidebar nav a:hover { background: var(--panel); }
.sidebar nav a.active { color: #fff; background: var(--accent); }
/* Sidebar search + favourites. Every link carries a star at its end: a dim
   outline that brightens on hover, gold once favourited. While a search is on,
   non-matching links and empty groups are dropped and every remaining group is
   drawn open (its chevron too) without touching its saved state. */
.sidebar .nav-tools { display: flex; gap: 6px; margin: 10px 12px 0; }
.sidebar .nav-tools input { flex: 1; min-width: 0; width: auto; padding: 5px 8px; font-size: 12px; }
.sidebar .nav-fav-toggle {
  flex-shrink: 0; padding: 0 9px; border-radius: 6px; font-size: 14px; line-height: 1;
  background: var(--panel); color: var(--muted); cursor: pointer;
}
.sidebar .nav-fav-toggle:hover { color: #f5b301; }
.sidebar .nav-filter-empty { margin: .6rem 12px 0; font-size: 12px; }
.sidebar .nav-collapse-all {
  align-self: flex-start; margin: 6px 12px 0; padding: 2px 4px; border: 0; border-radius: 4px;
  background: transparent; color: var(--muted); font: inherit; font-size: 12px; cursor: pointer;
}
.sidebar .nav-collapse-all:hover { color: var(--text); text-decoration: underline; }
.sidebar .nav-tools ~ nav { margin-top: .8rem; }
.sidebar nav a { display: flex; align-items: center; gap: .4rem; }
.sidebar nav .nav-star {
  margin-left: auto; padding: 0 2px; color: var(--muted); opacity: .55; cursor: pointer;
  font-size: 13px; line-height: 1; transition: opacity .1s, color .1s;
}
.sidebar nav a:hover .nav-star, .sidebar nav .nav-star:focus-visible, .sidebar nav .nav-star.on { opacity: 1; }
.sidebar nav .nav-star:hover, .sidebar nav .nav-star.on { color: #f5b301; }
.sidebar nav .nav-filtered { display: none !important; }
.sidebar nav.filtering .nav-group-body.hidden { display: flex !important; }
.sidebar nav.filtering .nav-group-toggle .chevron { transform: none; }
/* Collapsible nav groups. Wrapping the links makes the GROUP the nav's flex
   item, so the group and its body each restate the nav's own .25rem gap or the
   links inside would lose their spacing. The chevron mirrors the fee-group and
   card-program-settings pattern. */
.sidebar nav .nav-group, .sidebar nav .nav-group-body { display: flex; flex-direction: column; gap: .25rem; }
.sidebar nav .nav-group-toggle {
  display: flex; align-items: center; gap: .5rem; width: 100%; text-align: left;
  background: transparent; color: var(--text); cursor: pointer;
  padding: .55rem .8rem; border-radius: 8px; font-size: .95rem; font-weight: 600;
}
.sidebar nav .nav-group-toggle:hover { background: var(--panel); }
.sidebar nav .nav-group-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sidebar nav .nav-group-toggle .chevron { color: var(--muted); flex-shrink: 0; transition: transform .15s; }
.sidebar nav .nav-group-toggle[aria-expanded="false"] .chevron { transform: rotate(-90deg); }
/* A group's links read as its children: they sit a clear step in from the
   header behind a rail, in a slightly smaller, lighter face than the bold
   headers, and an open group leaves a gap before the next header. The smaller
   face also gives back the width the indent costs in the 220px sidebar. */
.sidebar nav .nav-group-body {
  margin: 0 0 .45rem .85rem; padding-left: .55rem; border-left: 2px solid var(--border);
}
.sidebar nav .nav-group-body a {
  padding: .45rem .5rem; font-size: .875rem; font-weight: 400;
}
/* A group the operator collapsed while standing inside it still says where they
   are — otherwise the sidebar would show no active marker at all. */
.sidebar nav .nav-group.has-active > .nav-group-toggle { color: var(--accent); }
.sidebar-footer {
  display: flex; flex-direction: column; gap: .6rem; flex-shrink: 0;
  padding-top: .8rem; margin-top: .4rem; border-top: 1px solid var(--border);
}

main { flex: 1; padding: 2rem; max-width: none; min-width: 0; }
/* Tab bar: one tab per page opened this session. The negative margins span
   main's 2rem padding so the bar reads as the top edge of the work area. */
.tab-bar {
  display: flex; flex-wrap: wrap; gap: .3rem; position: sticky; top: 0; z-index: 6;
  background: var(--bg); border-bottom: 1px solid var(--border);
  margin: -2rem -2rem 1.2rem; padding: .6rem 2rem 0;
}
.tab {
  display: inline-flex; align-items: center; font-size: .85rem;
  border: 1px solid var(--border); border-bottom: 0; border-radius: 6px 6px 0 0;
  background: var(--panel); color: var(--muted);
}
.tab:hover { color: var(--text); }
.tab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.tab button { background: none; border: 0; color: inherit; cursor: pointer; padding: .35rem .5rem; font: inherit; }
.tab .tab-close { padding-left: .2rem; opacity: .7; }
.tab .tab-close:hover { opacity: 1; }
.tab button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tab-bar-close-all {
  margin-left: auto; align-self: center; margin-bottom: .3rem;
  background: none; border: 1px solid var(--border); border-radius: 6px;
  color: var(--muted); font-size: .8rem; padding: .3rem .6rem; cursor: pointer;
}
.tab-bar-close-all:hover { color: var(--text); border-color: var(--muted); }
.page-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; }
.page-head h2 { margin-bottom: 0; }
/* The tab bar already names the page, so each page's title heading (its first
   child, or the one inside a first-child page-head) is hidden; sub-section
   headings further down, as on Worker Settings, stay. The empty and no-access
   placeholders have no tab, so they keep theirs. */
main .page:not(#page-empty):not(#page-no-access) > h2:first-child,
main .page > .page-head:first-child > h2 { display: none; }
/* A page-head left with only its buttons keeps them at the right edge; one
   left with nothing collapses rather than leaving its margin behind. */
main .page > .page-head:first-child > h2 + * { margin-left: auto; }
main .page > .page-head:first-child:has(> h2:only-child) { display: none; }
/* Shared spacing below the explanatory paragraph under an admin page title. */
.page-desc { margin-top: .35rem; margin-bottom: 1.4rem; }
.panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: .8rem; }
.panel-head h3 { margin-bottom: 0; }
@media (max-width: 520px) {
  .panel-head { align-items: flex-start; flex-wrap: wrap; gap: .65rem; }
  .panel-head button { margin-left: auto; }
}

.panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1.2rem; margin-bottom: 1.2rem;
}
.grid-2 { display: grid; grid-template-columns: 3fr 2fr; gap: 1.2rem; }

/* Cards */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 1rem; margin-bottom: 1.2rem; }
.card {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 1rem 1.2rem;
}
.card .label { font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.card .value { font-size: 1.6rem; font-weight: 700; margin-top: .3rem; }
.card .value.green { color: var(--green); }

/* Tables */
.table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.table th {
  text-align: left; padding: .5rem .6rem; color: var(--muted); font-weight: 600;
  border-bottom: 1px solid var(--border); font-size: .75rem; text-transform: uppercase; letter-spacing: .5px;
}
.table td { padding: .55rem .6rem; border-bottom: 1px solid rgba(42,51,71,.5); }
.table tr:last-child td { border-bottom: none; }
.table tr.clickable { cursor: pointer; }
.table tr.clickable:hover td { background: rgba(79,124,255,.06); }
.mono { font-family: ui-monospace, monospace; font-size: .82rem; }

/* Asset display-order controls: drag handle, persisted position, and the
   keyboard/pointer Move Up/Down fallback all live in the first column. */
.asset-order-cell { white-space: nowrap; }
.asset-order-cell .drag-handle { cursor: grab; color: var(--muted); margin-right: .4rem; user-select: none; }
.asset-order-cell .order-pos { display: inline-block; min-width: 1.4rem; text-align: right; font-weight: 700; margin-right: .4rem; }
.asset-order-cell .reorder-btns button { padding: .1rem .45rem; font-size: .8rem; line-height: 1; }
#tbl-assets tr[draggable="true"] { cursor: grab; }
#tbl-assets tr.dragging { opacity: .5; background: rgba(79,124,255,.08); }

.badge { display: inline-block; padding: .15rem .55rem; border-radius: 20px; font-size: .72rem; font-weight: 700; }
.badge.on { background: rgba(46,204,143,.15); color: var(--green); }
.badge.off { background: rgba(255,93,108,.15); color: var(--red); }
.badge.amber { background: rgba(255,181,71,.15); color: var(--amber); }
.badge.blue { background: rgba(79,124,255,.15); color: var(--accent); }
/* Its own hue so a card-activity message is not read as a state (green/red). */
.badge.violet { background: rgba(167,139,250,.15); color: #a78bfa; }
.badge.net { background: rgba(138,147,166,.15); color: var(--muted); letter-spacing: .3px; }

a.link { color: var(--accent); text-decoration: none; }
a.link:hover { text-decoration: underline; }
.pos { color: var(--green); }
.neg { color: var(--red); }

/* Key/value detail view (transaction modal) */
.kv-section { margin-bottom: 1.1rem; }
.kv-section:last-child { margin-bottom: 0; }
.kv-section h4 {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .5px; color: var(--muted);
  font-weight: 700; margin-bottom: .4rem; padding-bottom: .3rem; border-bottom: 1px solid var(--border);
}
.kv-section h4.kv-section-head { display: flex; justify-content: space-between; align-items: center; gap: .6rem; }
.kv-row { display: grid; grid-template-columns: 128px 1fr; gap: .6rem; padding: .22rem 0; font-size: .85rem; align-items: baseline; }
.kv-row .kv-k { color: var(--muted); }
.kv-row .kv-v { word-break: break-all; }

.treasury-wallet-row .kv-v { display: flex; flex-wrap: wrap; gap: .55rem .8rem; align-items: center; }
.treasury-actions { display: inline-flex; flex-wrap: wrap; gap: .45rem; align-items: center; }
.treasury-refresh { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; justify-content: flex-end; }
.treasury-refreshed { font-size: .8rem; }
a.button-link {
  display: inline-block; border-radius: 8px; padding: .3rem .7rem;
  font-size: .8rem; font-weight: 600; text-decoration: none;
}
a.button-link.ghost { color: var(--muted); border: 1px solid var(--border); }
a.button-link.ghost:hover { color: var(--text); border-color: var(--muted); }
.treasury-qr { display: grid; gap: .9rem; justify-items: center; }
.treasury-qr img {
  width: 220px; height: 220px; border-radius: 8px; background: #fff;
  border: 12px solid #fff;
}
.treasury-address {
  width: 100%; padding: .75rem; background: var(--bg-alt);
  border: 1px solid var(--border); border-radius: 8px; word-break: break-all;
  text-align: center;
}

/* Fee rule groups (Fees page) */
.callout {
  background: rgba(79,124,255,.08); border: 1px solid rgba(79,124,255,.35);
  border-left: 3px solid var(--accent); border-radius: 8px;
  padding: .6rem .9rem; font-size: .85rem; margin-bottom: 1.2rem;
}
.callout code {
  font-family: ui-monospace, monospace; font-size: .8rem;
  background: var(--bg-alt); padding: .05rem .35rem; border-radius: 4px;
}
.fee-group { padding: .9rem 1.2rem; }
.fee-group-head { display: flex; justify-content: space-between; align-items: center; gap: .8rem; }
.fee-group-toggle {
  display: inline-flex; align-items: center; gap: .5rem;
  background: transparent; color: var(--text); padding: .3rem .4rem; font-size: 1rem;
}
.fee-group-toggle:hover { color: var(--accent); }
.fee-group-toggle .chevron { color: var(--muted); transition: transform .15s; }
.fee-group-toggle[aria-expanded="false"] .chevron { transform: rotate(-90deg); }
.fee-group-body { margin-top: .6rem; }

/* Filters */
.filters { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.filters input, .filters select { width: auto; flex: 1; min-width: 130px; }
.filters button { flex-shrink: 0; }

/* Wide data tables scroll horizontally inside their panel instead of
   stretching the page or being clipped. The pager lives OUTSIDE this wrapper
   so it never scrolls off to the side with the table. */
.table-scroll { width: 100%; overflow-x: auto; }

/* The pager stays pinned to the bottom of the viewport while its list is in
   view, so pagination is reachable without scrolling to the end of a long
   page. It sits on an opaque bar so rows scroll cleanly behind it. */
.pager {
  display: flex; gap: .6rem; align-items: center; justify-content: flex-end;
  font-size: .85rem; color: var(--muted);
  position: sticky; bottom: 0; z-index: 5;
  margin-top: .4rem; padding: .7rem 0 .2rem;
  background: var(--panel); border-top: 1px solid var(--border);
}
/* The jump input and size select sit inline with the Prev/Next buttons;
   inputs and selects are full-width by default, so both are sized here. */
/* The global label rule adds a top margin and a smaller font for stacked
   forms; inline in the pager both would drop the label below the buttons. */
.pager label { display: inline-flex; align-items: center; gap: .3rem; margin: 0; font-size: inherit; }
.pager input[type=number] { width: 4.5rem; padding: .3rem .4rem; font-size: .8rem; line-height: 1.2; }
.pager select { width: auto; padding: .3rem .4rem; font-size: .8rem; line-height: 1.2; }

/* Chart */
.chart { height: 220px; display: flex; align-items: flex-end; gap: 6px; padding-top: 1rem; }
.chart .bar-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.chart .bar { width: 100%; background: linear-gradient(180deg, var(--accent), #2b4bb8); border-radius: 4px 4px 0 0; min-height: 2px; }
.chart .bar-label { font-size: .62rem; color: var(--muted); white-space: nowrap; }

/* Modal */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,.6);
  display: flex; align-items: center; justify-content: center; z-index: 50;
}
.modal {
  width: 440px; max-height: 85vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0 1.5rem 1.5rem;
}
/* Wide variant for the modals that render a multi-column table rather than a
   form (the per-card vendor history reads). The max-width is load-bearing:
   .modal-backdrop has no padding, so a fixed 880px would otherwise run
   edge-to-edge on a laptop screen. Padding is deliberately NOT changed — the
   sticky .modal-header's negative margins are tied to it. */
.modal.wide { width: 880px; max-width: calc(100vw - 2rem); }
/* Sticky header: the close control stays reachable while a long form scrolls
   inside .modal's own overflow container. The modal's top padding lives here
   (not on .modal) — a negative top margin on a sticky box renders shifted
   down in Chrome and overlaps the first form label. */
.modal-header {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: flex-start; justify-content: space-between; gap: .75rem;
  background: var(--panel);
  margin: 0 -1.5rem .2rem; padding: 1.5rem 1.5rem .6rem;
}
.modal-header h3 { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.modal-close {
  flex: 0 0 auto; width: 2rem; height: 2rem; padding: 0; line-height: 1;
  font-size: 1.25rem; cursor: pointer;
  background: transparent; color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.modal-close:hover { color: var(--text); border-color: var(--muted); }
.modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.modal-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1.4rem; }
.checkbox-row { display: flex; align-items: center; gap: .5rem; margin-top: .9rem; }
.checkbox-row input { width: auto; }
.checkbox-row label { margin: 0; }

/* User-row Actions dropdown */
.action-menu { position: relative; display: inline-block; }
.action-menu-trigger[aria-expanded="true"] { color: var(--text); border-color: var(--accent); }
.action-menu-list {
  position: fixed; z-index: 30; min-width: 150px;
  max-height: calc(100vh - 16px); overflow-y: auto;
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: .3rem; display: flex; flex-direction: column; gap: 2px;
  box-shadow: 0 8px 24px rgba(0,0,0,.45);
}
.action-menu-list button {
  background: transparent; color: var(--text); text-align: left;
  padding: .45rem .7rem; border-radius: 6px; font-weight: 500; font-size: .85rem;
}
.action-menu-list button:hover { background: rgba(79,124,255,.15); }
.action-menu-list button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.action-menu-sep { height: 1px; background: var(--border); margin: .25rem .3rem; }
/* Keep per-row action buttons on one line in narrow / scrolling tables */
.row-actions { white-space: nowrap; }

/* Toast */
.toast {
  position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 100;
  background: var(--green); color: #06281a; font-weight: 600;
  padding: .8rem 1.2rem; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.4);
}
.toast.err { background: var(--red); color: #fff; }

/* Worker Settings */
.ws-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
.ws-control { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-alt); padding: 1rem; }
.ws-control-head { display: flex; justify-content: space-between; align-items: flex-start; gap: .75rem; }
.ws-control-title { font-weight: 600; color: var(--text); }
.ws-toggle { display: inline-flex; align-items: center; gap: .4rem; margin: 0; cursor: pointer; white-space: nowrap; }
.ws-toggle input { width: auto; margin: 0; }
.ws-toggle-state { font-size: .85rem; color: var(--muted); }
/* Corridors: finance's own Min/Max, edited inline in the table. */
.remit-limit input { width: 9rem; padding: .35rem .5rem; font-size: .85rem; }
.remit-limit-warn { display: block; margin-top: .2rem; font-size: .75rem; color: var(--amber); }
.remit-msg input { width: 18rem; padding: .35rem .5rem; font-size: .85rem; }
.remit-msg.narrow input { width: 9rem; }
.ws-control input[type="number"] { width: 100%; }
/* A read-only control's value is picked elsewhere, so it must not look like a
   field the operator failed to fill in: it is dimmed and its cursor says
   "not editable" rather than "click to type". */
.ws-control input[readonly] { background: var(--bg); color: var(--muted); cursor: not-allowed; }
/* The buttons that take you to the catalogue a read-only value is picked from. */
.ws-control-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0; }

/* Card Program Settings groups (Finance page). Each group is its own bordered,
   independently collapsible section so the three settings areas read as
   separate blocks instead of a cramped run of headings, at both desktop and
   narrow widths. The chevron mirrors the fee-group pattern. */
.cps-groups { display: flex; flex-direction: column; gap: 1rem; }
.cps-group { border: 1px solid var(--border); border-radius: var(--radius); padding: .85rem 1.1rem; }
.cps-group-head { margin: 0; font-size: 1rem; }
.cps-group-toggle {
  display: flex; align-items: center; gap: .5rem; width: 100%; text-align: left;
  background: transparent; color: var(--text); padding: .25rem 0;
  font-size: 1rem; font-weight: 600;
}
.cps-group-toggle:hover { color: var(--accent); }
.cps-group-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }
.cps-group-toggle .chevron { color: var(--muted); flex-shrink: 0; transition: transform .15s; }
.cps-group-toggle[aria-expanded="false"] .chevron { transform: rotate(-90deg); }
.cps-group-body { margin-top: .85rem; display: flex; flex-direction: column; gap: .85rem; }
/* The persistent 220px sidebar plus main/panel/group padding can leave the
   group body narrower than the default .ws-grid 280px column minimum at any
   viewport width, so a viewport breakpoint can't protect it. Cap the column
   minimum at the container width instead: columns stay 280px where they fit
   and shrink with the container where they don't. */
.cps-group .ws-grid { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
/* Sub-headings split one group body into labeled rows (e.g. the shared rate,
   then the top-up row, then the withdrawal row) without a second collapse
   level. */
.cps-subhead { margin: 0; font-size: .85rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

/* Raw JSON expansion rows (unified Visa
   transaction details). Content is written via textContent only — this style
   just keeps large hostile-looking payloads scrollable and monospaced. */
.raw-json {
  margin: 0; padding: .75rem 1rem; max-height: 420px; overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .8rem; line-height: 1.45; white-space: pre-wrap; word-break: break-word;
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  border: 1px solid var(--border); border-radius: var(--radius);
}

/* Treasury tier tabs (Withdrawal Main / Clean / Dirty wallet selector). */
.tier-tabs { display: flex; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.tier-tab {
  background: transparent; color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: .45rem .9rem; font-size: .85rem;
}
.tier-tab:hover { color: var(--text); border-color: var(--muted); }
.tier-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* App segment (QXipher / Abelian filter) in the sidebar. Matches the nav's
   idle/active treatment so it reads as part of the same control family. */
.app-segment {
  display: flex;
  gap: 4px;
  margin: 10px 12px 6px;
  padding: 3px;
  background: var(--panel);
  border-radius: 8px;
}
.app-segment button {
  flex: 1;
  padding: 5px 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.app-segment button:hover { color: var(--text); }
.app-segment button.active { color: #fff; background: var(--accent); }

/* Retained KYC submission detail (finance View detail modal) */
.kyc-doc-slot { margin: .5rem 0 .9rem; }
.kyc-doc { display: block; max-width: 100%; max-height: 360px; margin-top: .35rem; border: 1px solid var(--border); border-radius: 6px; }
.kyc-divider { border: 0; border-top: 1px solid var(--border); margin: 1rem 0; }
