/* components.css — buttons, tables, badges, forms, modals, logs, toasts */

/* ── Buttons ───────────────────────────────────────────────── */

button {
  font-family: inherit;
  font-size: .78rem;
  padding: 7px 13px;
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(39, 48, 74, .92), rgba(28, 34, 54, .96));
  color: var(--fg2);
  border: 1px solid rgba(108, 126, 167, .34);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .18), inset 0 1px 0 rgba(255, 255, 255, .045);
  cursor: pointer;
  transition: background var(--tr), color var(--tr), border-color var(--tr), box-shadow var(--tr), transform var(--tr);
}
button:hover:not(:disabled) {
  background: linear-gradient(180deg, rgba(54, 65, 96, .98), rgba(35, 43, 68, .98));
  color: var(--fg);
  border-color: rgba(129, 140, 248, .52);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .24), 0 0 0 2px rgba(99, 102, 241, .08);
  transform: translateY(-1px);
}
button:active:not(:disabled) { transform: translateY(0) scale(.98); }
button:disabled { opacity: .45; cursor: not-allowed; }

.btn-p {
  background: linear-gradient(135deg, #4f46e5, #3730a3);
  color: #fff;
  border-color: rgba(129, 140, 248, .72);
  box-shadow: 0 3px 12px rgba(49, 46, 129, .38), inset 0 1px 0 rgba(255, 255, 255, .12);
}
.btn-p:hover:not(:disabled) {
  background: linear-gradient(135deg, #4338ca, #3730a3);
  color: #fff;
  box-shadow: 0 4px 14px rgba(55, 48, 163, .38);
}

.btn-g {
  background: linear-gradient(135deg, #34d399, #059669);
  color: #fff;
  border-color: rgba(110, 231, 183, .72);
  box-shadow: 0 3px 14px var(--glow-g), inset 0 1px 0 rgba(255, 255, 255, .16);
}
.btn-g:hover:not(:disabled) { color: #fff; filter: brightness(1.08); }

.btn-d { background: transparent; color: var(--r); border-color: rgba(244, 63, 94, .38); }
.btn-d:hover:not(:disabled) { background: rgba(244, 63, 94, .12); color: var(--r); }

/* The standard action button. It is the button a field most often sits beside,
   so it takes the control height rather than being sized by its own padding —
   which is how it ended up 23px next to a 28px select in the Payment row.
   Deliberately dense spots (`.qp-btns`) already override this by specificity. */
.btn-s {
  min-height: var(--ctl-h);
  padding: 0 10px;
  font-size: .76rem;
  border-radius: 8px;
}

/* Credits pill in navbar — shows remaining credits, click to open Usage modal */
.credits-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 9px;
  font-size: .72rem;
  font-weight: 700;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--fg2);
  transition: all .15s;
}
.credits-pill:hover { border-color: var(--accent); color: var(--accent); }
.credits-pill .btn-icon { width: 14px; height: 14px; }

/* Modern controls used in the header, tab toolbars, and modal footers. */
.hdr-r button,
.tab-tools > button,
.modal-f > button {
  min-height: var(--ctl-h);
  border-radius: 9px;
  font-weight: 600;
  letter-spacing: .01em;
}

/* A button that carries a glyph is one flex row, so the icon cannot land on the
   label's baseline. Before this rule the two were separate inline boxes with no
   gap between them, and every header button worked around it by writing the
   space into its own label (`<span> Usage</span>`) — which is why the glyph read
   as touching the text. */
button:has(> .btn-icon) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.hdr-r button:not(.btn-p):not(.btn-g),
.tab-tools > button:not(.btn-p):not(.btn-g),
.modal-f > button:not(.btn-p):not(.btn-g) {
  background: rgba(255, 255, 255, .045);
  border-color: rgba(148, 163, 184, .28);
}

.hdr-r button:hover:not(:disabled),
.tab-tools > button:hover:not(:disabled),
.modal-f > button:hover:not(:disabled) {
  border-color: rgba(129, 140, 248, .6);
}

/* Lucide icons inside compact controls and async state buttons. */
.btn-icon,
.modal-close-icon,
.btn-refresh svg,
.ic-item button svg,
.step-btn svg,
.row-acts button svg,
.spin-icon {
  width: 14px;
  height: 14px;
  flex: none;
  stroke-width: 2.1;
  vertical-align: middle;
}
.btn-refresh,
.ic-item button,
.row-acts button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
.spin-icon { animation: spin .8s linear infinite; }


.cred-val {
  display: inline-flex;
  align-items: center;
  height: 17px;
  font-size: .72rem;
  vertical-align: -2px;
  white-space: nowrap;
}

/* Row-level refresh button — uniform size/shape across all account tables
   (CodeBuddy/CBCN/Qoder next to Credits).
   Spinner via .busy overlay keeps the glyph hidden while active. */
.btn-refresh {
  width: 17px;
  height: 17px;
  padding: 0;
  margin-left: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: .68rem;
  line-height: 1;
  vertical-align: -2px;
  border-radius: 4px;
  cursor: pointer;
}

/* Inline spinner for busy buttons */
button.busy { position: relative; color: transparent !important; }
button.busy::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px; height: 13px;
  border: 2px solid rgba(255, 255, 255, .28);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin .6s linear infinite;
}

/* ── Tables ────────────────────────────────────────────────── */

/* Short lists are padded by invisible rows from tables.js so every tab
   keeps the same row rhythm without stretching the table width. */
.tbl-wrap {
  overflow-x: auto;
}

.tbl-wrap table {
  width: 100%;
  border-collapse: collapse;
  font-size: .82rem;
}

.table-filler {
  visibility: hidden;
  pointer-events: none;
}
.table-filler td {
  height: 40px;
  padding: 10px 14px;
  border-bottom: 1px solid transparent;
}

.table-empty td.empty {
  height: 40px;
  padding: 10px 14px !important;
}


/* Centered cell/header (Status + Linked columns on account grids) */
th.ta-c, td.ta-c { text-align: center; }

.ta-c .badge { margin: 0 auto; }

.ta-c .app-circles { justify-content: center; }

thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  text-align: left;
  padding: 11px 14px;
  font-size: .67rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--fg2);
  background: #101422;
  border-bottom: 1px solid var(--bg4);
  white-space: nowrap;
}

tbody td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--bg3);
  color: var(--fg);
  vertical-align: middle;
}

tbody tr { transition: background var(--tr); }
tbody tr:hover { background: rgba(99, 102, 241, .08); }
tbody tr:last-child td { border-bottom: none; }

/* Rows appear with a short stagger; capped so long pages stay snappy */
tbody tr.in { animation: fadeUp 260ms both; }

td.em {
  font-family: var(--mono);
  font-size: .78rem;
  color: var(--acc2);
  font-weight: 500;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chk-col { width: 34px; }

/* ── Custom checkbox ────────────────────────────────────────────
   appearance:none boxes with an accent fill + tick (pseudo-elements
   don't render on <input>, so ticks/dashes are data-URI backgrounds).
   Covers tables (chk-col), .chk rows, .chk-inline and run cards. */
input[type=checkbox] {
  appearance: none;
  -webkit-appearance: none;
  width: 15px; height: 15px;
  margin: 0;
  flex: none;
  border-radius: 4px;
  border: 1px solid var(--bg4);
  background-color: rgba(21, 27, 45, .72);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0 0;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .28);
  cursor: pointer;
  transition: border-color var(--tr), background-color var(--tr),
              background-size .12s ease, box-shadow var(--tr);
}
input[type=checkbox]:hover { border-color: var(--acc2); }
input[type=checkbox]:checked {
  border-color: var(--acc);
  background-color: var(--acc);
  background-size: 11px 11px;
  box-shadow: 0 0 0 3px rgba(99, 102, 241, .16);
}
input[type=checkbox]:indeterminate {
  border-color: var(--acc2);
  background-color: rgba(99, 102, 241, .3);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E");
  background-size: 10px 10px;
}
input[type=checkbox]:disabled {
  opacity: .42;
  cursor: not-allowed;
  box-shadow: none;
}
input[type=checkbox]:focus-visible {
  outline: 2px solid var(--acc3);
  outline-offset: 1px;
}

.empty {
  text-align: center;
  padding: 42px 16px !important;
  color: var(--fg3);
  font-size: .8rem;
}

.row-acts { display: flex; gap: 5px; }

.pag {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 15px;
  border-top: 1px solid var(--bg3);
  font-size: .72rem;
  color: var(--fg3);
}

/* ── Badges ────────────────────────────────────────────────── */

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 99px;
  font-size: .63rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .5px;
  border: 1px solid transparent;
}
.badge.ok      { background: rgba(16, 185, 129, .13); color: var(--g);   border-color: rgba(16, 185, 129, .3); }
.badge.fail    { background: rgba(244, 63, 94, .13);  color: var(--r);   border-color: rgba(244, 63, 94, .3); }
.badge.ban     { background: rgba(244, 63, 94, .2);   color: #fda4af;    border-color: rgba(244, 63, 94, .42); }
.badge.run     { background: rgba(245, 158, 11, .13); color: var(--y);   border-color: rgba(245, 158, 11, .3); }
.badge.pending { background: var(--bg3);              color: var(--fg3); border-color: var(--bg4); }
/* §752: the two small badges the tables gained. `.cc-badge` is a country code — a
   fact, so the neutral variant — and `.attempt-badge` is the retry counter, amber
   from the second attempt on. Both are deliberately small: they sit beside a status
   pill and must not compete with it. */
/* §754: the relogin alert is the **same size as the 2FA badge**, because the operator
   asked for exactly that: *"badge need relogin terlalu besar iconnya.. buat sperti icon
   2fa saja besarnya"*. `.badge-2fa` is a 12px disc holding an 8px glyph; this mirrors
   it, with the amber of an alert rather than the green of a verified state. Sized in
   one place so the two cannot drift apart again. */
.relogin-alert {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* **16px, not 12 (§763).** Matching the 2FA disc exactly (§754) went one step too
     far: the operator's second look was *"alert badge need relogin skarang terlalu
     kecil"*. The 2FA badge is a *decoration* on an avatar — it marks a state beside a
     picture. This one is a **button** the operator has to find and hit, so it needs to
     read as a control: 16px disc, 10px glyph, the same shape and colour family. */
  width: 16px;
  height: 16px;
  border-radius: 50%;
  /* `--y` is this stylesheet's amber — the same colour `.badge.run` uses, so the
     alert reads as the same family of signal rather than a new one. */
  background: var(--y);
  color: #1a1205;
  cursor: pointer;
  flex: none;
  vertical-align: middle;
  transition: transform .12s ease, filter .12s ease;
}
.relogin-alert:hover { transform: scale(1.15); filter: brightness(1.1); }
.relogin-alert svg,
.relogin-alert .btn-icon { width: 10px; height: 10px; stroke-width: 2.5; }

/* **The proof on the Status cell (§846).** The green disc marks a row that answered a
   real chat turn; its tooltip quotes the reply, the model and the exit. Deliberately the
   same 16px shape as `.relogin-alert` above — one size for "a small control on the
   Status cell", so the two cannot drift (§763). Green rather than amber: this is a
   verified state, not an alert. */
.wb-proof {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--g);
  color: #06170d;
  cursor: pointer;
  flex: none;
  vertical-align: middle;
  transition: transform .12s ease, filter .12s ease;
}
.wb-proof:hover { transform: scale(1.15); filter: brightness(1.1); }
.wb-proof svg,
.wb-proof .btn-icon { width: 10px; height: 10px; stroke-width: 2.5; }

.cc-badge` is a country code — a
   fact, so the neutral variant — and `.attempt-badge` is the retry counter, amber
   from the second attempt on. Both are deliberately small: they sit beside a status
   pill and must not compete with it. */
/* §754: the relogin alert is the **same size as the 2FA badge**, because the operator
   asked for exactly that: *"badge need relogin terlalu besar iconnya.. buat sperti icon
   2fa saja besarnya"*. `.badge-2fa` is a 12px disc holding an 8px glyph; this mirrors
   it, with the amber of an alert rather than the green of a verified state. Sized in
   one place so the two cannot drift apart again. */
.relogin-alert {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* **16px, not 12 (§763).** Matching the 2FA disc exactly (§754) went one step too
     far: the operator's second look was *"alert badge need relogin skarang terlalu
     kecil"*. The 2FA badge is a *decoration* on an avatar — it marks a state beside a
     picture. This one is a **button** the operator has to find and hit, so it needs to
     read as a control: 16px disc, 10px glyph, the same shape and colour family. */
  width: 16px;
  height: 16px;
  border-radius: 50%;
  /* `--y` is this stylesheet's amber — the same colour `.badge.run` uses, so the
     alert reads as the same family of signal rather than a new one. */
  background: var(--y);
  color: #1a1205;
  cursor: pointer;
  flex: none;
  vertical-align: middle;
  transition: transform .12s ease, filter .12s ease;
}
.relogin-alert:hover { transform: scale(1.15); filter: brightness(1.1); }
.relogin-alert svg,
.relogin-alert .btn-icon { width: 10px; height: 10px; stroke-width: 2.5; }

/* **The proof on the Status cell (§846).** The green disc marks a row that answered a
   real chat turn; its tooltip quotes the reply, the model and the exit. Deliberately the
   same 16px shape as `.relogin-alert` above — one size for "a small control on the
   Status cell", so the two cannot drift (§763). Green rather than amber: this is a
   verified state, not an alert. */
.wb-proof {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--g);
  color: #06170d;
  cursor: pointer;
  flex: none;
  vertical-align: middle;
  transition: transform .12s ease, filter .12s ease;
}
.wb-proof:hover { transform: scale(1.15); filter: brightness(1.1); }
.wb-proof svg,
.wb-proof .btn-icon { width: 10px; height: 10px; stroke-width: 2.5; }

/* The exit a check left through — on every row that has one, banned included. Small and
   muted: it is a fact to read, not a control to hit. Truncated because a session id can
   be long, and the full value is in the tooltip. */
/* **The restriction badge (§849).** `.wb-ban` was referenced in the markup since §780 and
   never had a rule, so the ban mark inherited `.relogin-alert`'s amber — the same colour as
   "needs relogin", which is a different problem with a different fix. Red, because a
   restricted account is dead rather than fixable, and the operator needs to tell the two
   apart at a glance in a column of fifty rows. */
.wb-ban {
  background: var(--r);
  color: #fff;
}

.cc-badge { font-size: .6rem !important; font-weight: 700 !important; letter-spacing: .04em; padding: 1px 6px; }
.attempt-badge { font-size: .6rem; padding: 1px 4px; margin-left: 5px; vertical-align: middle; }

/* Running badge gets a live shimmer so activity is obvious at a glance */
.badge.run.live {
  background-image: linear-gradient(90deg, transparent, rgba(245, 158, 11, .28), transparent);
  background-size: 180% 100%;
  animation: shimmer 1.7s linear infinite;
}

/* ── Forms ─────────────────────────────────────────────────── */

.f { margin-bottom: 12px; }
/* `.f-label` is for a field whose control is a *group* — the channel picker's
   radio cards. `<label for=…>` cannot point at a `div`, so the heading is a span
   wearing the same style rather than a label that names nothing. */
.f label,
.f .f-label {
  display: block;
  font-size: .68rem;
  color: var(--fg3);
  margin-bottom: 5px;
  text-transform: uppercase;
  letter-spacing: .7px;
}
.f-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* `.inp` is the class the Seller Area's fields carry, and it had no rule at all
   — so all 21 of them (both toolbar searches and every field in the product and
   shop-settings forms) rendered as raw UA controls while the rest of the app was
   skinned. It belongs in this list rather than in a rule of its own, so a field
   is a field wherever it is written.

   `color-scheme: dark` is what makes the UA's *own* parts dark: the select's
   option popup, the search field's clear button, the date picker glyph. Chromium
   paints those from the system scheme otherwise, so they came out either glaring
   white or, against a dark pane, looking like part of the panel behind them. */
input[type=text], input[type=number], input[type=password], input[type=search],
select, textarea, .inp {
  width: 100%;
  padding: 9px 11px;
  border-radius: var(--rad);
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--fg);
  font-family: inherit;
  font-size: .8rem;
  color-scheme: dark;
  transition: border-color var(--tr), box-shadow var(--tr);
}

/* The height half of the recipe, which a multi-line field must not inherit.
   `:not(textarea)` rather than a later override on `textarea`, because `.inp`
   (0,1,0) outranks `textarea` (0,0,1) and the product description is exactly
   `textarea.inp` — it would have been pinned to 32px, and `min-height` could
   not have saved it either, for the same reason. */
input[type=text], input[type=number], input[type=password], input[type=search],
select, .inp:not(textarea) {
  height: var(--ctl-h);
  padding: 0 11px;
}
input::placeholder, textarea::placeholder { color: var(--fg3); }

/* The option list is painted by the UA, not by the select's own box, so it takes
   an explicit surface: left to the default it blends into the panel behind it,
   which is what a dropdown must never do. */
select option { background: var(--bg4); color: var(--fg); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px rgba(99, 102, 241, .14);
}
textarea { min-height: 108px; resize: vertical; font-family: var(--mono); font-size: .76rem; }
select { cursor: pointer; }

.chk { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.chk label { font-size: .78rem; color: var(--fg2); cursor: pointer; text-transform: none; letter-spacing: 0; margin: 0; }

/* ── Modals ────────────────────────────────────────────────── */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(4, 4, 8, .74);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.overlay.show { display: flex; animation: fadeIn 160ms both; }

.modal {
  width: 100%;
  max-width: 520px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--bg2);
  border: 1px solid var(--bg4);
  border-radius: var(--rad3);
  box-shadow: 0 24px 70px rgba(0, 0, 0, .6);
  animation: popIn 220ms cubic-bezier(.2, .9, .3, 1) both;
}
.modal.wide { max-width: 780px; }

.modal-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px 18px;
  border-bottom: 1px solid var(--bg3);
}
.modal-h h3 { font-size: .95rem; font-weight: 600; }
/* A modal header's `spacer` (the Domains dialogs' “+” buttons) pushes the
   action to the right the way the same class does on a tab toolbar — the rule
   lived only inside `.tab-tools`, so in a header the div was zero-width. */
.modal-h .spacer { flex: 1; }
/* Modal close (X) buttons: uniform red square, same style on every modal
   (Run, Config, Proxies, Scheduler, Log, Monitor, Report, …).
   Only targets close buttons — action buttons (like .btn-p, .btn-s, .btn-g, .modal-h-btn)
   keep their normal action button size and styling. */
.modal-h > button[data-act="close-modal"],
.modal-h > button:not(.btn-p):not(.btn-g):not(.btn-s):not(.btn-d):not(.modal-h-btn),
.modal-close-btn {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex: none;
  border-radius: 8px;
  background: #dc2626;
  border: 1px solid #ef4444;
  color: #fff;
  font-size: 1.3rem;
  line-height: 1;
  box-shadow: 0 3px 9px rgba(220, 38, 38, .28), inset 0 1px 0 rgba(255, 255, 255, .18);
}
.modal-h > button[data-act="close-modal"]:hover,
.modal-h > button:not(.btn-p):not(.btn-g):not(.btn-s):not(.btn-d):not(.modal-h-btn):hover,
.modal-close-btn:hover {
  background: #ef4444;
  border-color: #f87171;
  color: #fff;
  box-shadow: 0 4px 12px rgba(220, 38, 38, .38);
}
.modal-h > button .modal-close-icon,
.modal-close-btn .modal-close-icon,
.scheduler-close .modal-close-icon {
  width: 15px;
  height: 15px;
}

/* Modal header action buttons (e.g. Add User, Add iCloud) */
.modal-h-btn {
  height: 30px;
  min-height: 30px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .74rem;
  font-weight: 600;
  border-radius: 8px;
  line-height: 1;
}

/* Compact icon action buttons inside table cells (User table, etc.) */
.tbl-acts {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  justify-content: flex-end;
}
.tbl-acts button,
button.btn-row-act {
  width: 24px !important;
  height: 24px !important;
  min-height: 24px !important;
  max-height: 24px !important;
  padding: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  flex: none !important;
  border-radius: 6px !important;
  box-shadow: none !important;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(148, 163, 184, .25);
  color: var(--fg2);
}
.tbl-acts button:hover:not(:disabled),
button.btn-row-act:hover:not(:disabled) {
  background: rgba(255, 255, 255, .12);
  color: var(--fg);
  border-color: rgba(129, 140, 248, .5);
  transform: none;
}
.tbl-acts button.btn-d,
button.btn-row-act.btn-d {
  background: transparent;
  color: var(--r);
  border-color: rgba(244, 63, 94, .3);
}
.tbl-acts button.btn-d:hover:not(:disabled),
button.btn-row-act.btn-d:hover:not(:disabled) {
  background: rgba(244, 63, 94, .14);
  color: var(--r);
  border-color: rgba(244, 63, 94, .6);
  transform: none;
}
.tbl-acts button svg,
.tbl-acts button .btn-icon,
button.btn-row-act svg,
button.btn-row-act .btn-icon {
  width: 12px !important;
  height: 12px !important;
}
.scheduler-toggle {
  min-width: 88px;
}


.modal-b { padding: 18px; overflow-y: auto; flex: 1; }

/* A sentence in a dialog body saying what the button below it will and will not
   do — the same shape as `.cfg-note`, named for the dialog rather than for the
   config hub now that a second one needs it (the Outlook Generate wizard, which
   mints credentials and launches nothing). */
.modal-note { margin: 14px 0 0; font-size: .7rem; color: var(--fg3); line-height: 1.5; }

.modal-f {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding: 13px 18px;
  border-top: 1px solid var(--bg3);
}

/* ── Proxy key modal ───────────────────────────────────────── */
.proxy-add {
  padding: 12px;
  border: 1px solid var(--bg3);
  border-radius: var(--rad);
  background: var(--bg);
}
.proxy-add .f { margin-bottom: 9px; }
.proxy-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.proxy-actions button {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.proxy-actions button > .btn-icon {
  width: 14px;
  height: 14px;
  flex: none;
}
/* Card grid for proxy keys — replaces the cramped table layout. */
.proxy-grid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: 10px;
}
/* Pool health strip above the key grid: the two numbers that decide whether a
   launch has a key to use at all, plus how fresh the sweep is. */
.proxy-summary {
  margin-top: 12px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 9px 11px;
  border-radius: 10px;
  background: var(--bg3);
  border: 1px solid var(--bg4);
  font-size: .7rem;
  color: var(--fg2);
}
.proxy-summary-bad { border-color: rgba(244, 63, 94, .35); }
.proxy-summary .px-stat {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 2px 9px;
  border-radius: 99px;
  background: rgba(255, 255, 255, .04);
  color: var(--fg3);
  font-weight: 600;
}
.proxy-summary .px-stat b { font-size: .78rem; color: var(--fg); }
.proxy-summary .px-stat.ok b { color: var(--g); }
.proxy-summary .px-stat.warn b { color: var(--y); }
.proxy-summary .px-stat.bad b { color: var(--r); }
.proxy-summary .px-sweep { margin-left: auto; color: var(--fg3); font-size: .66rem; }

.proxy-empty {
  grid-column: 1 / -1;
  text-align: center;
  padding: 26px 10px;
  color: var(--fg3);
  font-size: .78rem;
  border: 1px dashed var(--bg4);
  border-radius: var(--rad);
}
.proxy-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 11px 12px;
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(39, 48, 74, .5), rgba(24, 30, 49, .65));
  border: 1px solid var(--bg4);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .16), inset 0 1px 0 rgba(255, 255, 255, .04);
  transition: border-color var(--tr), box-shadow var(--tr), transform var(--tr);
}
.proxy-card:hover {
  border-color: rgba(129, 140, 248, .45);
  box-shadow: 0 5px 14px rgba(0, 0, 0, .24);
  transform: translateY(-1px);
}
.proxy-card-bad {
  border-color: rgba(244, 63, 94, .3);
}
.proxy-card-bad:hover {
  border-color: rgba(244, 63, 94, .55);
}
.proxy-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.proxy-key-cell {
  font-size: .74rem;
  font-weight: 600;
  color: var(--acc2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.proxy-card .badge { flex: none; }
.proxy-progress {
  height: 5px;
  overflow: hidden;
  border-radius: 99px;
  background: var(--bg4);
}
.proxy-progress span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--acc), var(--cyan));
}
.proxy-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: .68rem;
  color: var(--fg2);
}
.proxy-meta small { color: var(--fg3); }
.proxy-remaining { color: var(--g); font-size: .68rem; font-weight: 600; }
.proxy-card-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 26px;
}
.proxy-checked { font-size: .62rem; color: var(--fg3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.proxy-muted { color: var(--fg3); }
.proxy-error {
  display: block;
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.proxy-card-acts {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: none;
}
.proxy-card-bottom .btn-s {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  flex: none;
}
.proxy-card-bottom .btn-s svg {
  width: 13px;
  height: 13px;
}

/* ── Config modal list items (SMS / iCloud / 9router) ──────── */
/* Rows used to be unstyled divs — give every list item the same raised
   card treatment as the proxy cards so the Config modal reads cleanly. */
.ic-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  margin-bottom: 6px;
  border-radius: 9px;
  background: linear-gradient(180deg, rgba(39, 48, 74, .5), rgba(24, 30, 49, .6));
  border: 1px solid var(--bg3);
  transition: border-color var(--tr), box-shadow var(--tr);
}
.ic-item:hover {
  border-color: rgba(129, 140, 248, .4);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .2);
}
.ic-item > div:first-child {
  min-width: 0;
}
/* .em inside a list item — td.em styles don't apply outside <td>. */
.ic-item .em {
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 600;
  color: var(--acc2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ic-item button {
  width: 26px;
  height: 26px;
  padding: 0;
  flex: none;
  border-radius: 7px;
}
.ic-item button svg {
  width: 13px;
  height: 13px;
}

/* ── Logs ──────────────────────────────────────────────────── */

.logbox {
  font-family: var(--mono);
  font-size: .7rem;
  line-height: 1.65;
  background: var(--bg);
  border: 1px solid var(--bg3);
  border-radius: var(--rad);
  padding: 11px 13px;
  height: 340px;
  overflow-y: auto;
  color: var(--fg2);
}
.logbox div { word-break: break-word; }
.logbox .ok   { color: var(--g); }
.logbox .err  { color: var(--r); }
.logbox .warn { color: var(--y); }

.acc-log-row td { padding: 0 !important; background: var(--bg); }
.acc-log {
  font-family: var(--mono);
  font-size: .67rem;
  line-height: 1.6;
  padding: 9px 13px;
  overflow-y: auto;
  color: var(--fg2);
  border-left: 2px solid var(--acc);
}
.acc-log .ok { color: var(--g); }
.acc-log .err { color: var(--r); }
.acc-log .warn { color: var(--y); }

/* The GitHub/CodeBuddy panel fades in. It used to appear only after the table
   re-fetched and rebuilt itself, so its arrival read as a jump; now that the
   chevron paints from the rows already in hand it lands within the click, and
   this keeps the entrance from snapping. Reduced-motion turns it off (base.css). */
.acc-panel { animation: acc-in .16s ease-out; }
@keyframes acc-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.arrow { display: inline-block; transition: transform var(--tr); }

/* ── Row accordion body (GitHub / CodeBuddy / Qoder) ────────────
   The panel classes below are shared by every expanded row; the Qoder credit
   detail is the newest user (see `qdDetailPanel`). Deliberately plain: this is
   dense reference data read one row at a time, so the layout is a wrapping grid
   of label/value cells rather than a column of prose. */
.acc-panel-in { padding: 9px 13px; display: flex; flex-direction: column; gap: 9px; }
.acc-cells {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}
.acc-cell {
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 7px 10px;
}
.acc-lbl {
  font-size: .6rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fg3);
  margin-bottom: 3px;
}
.acc-val { font-size: .82rem; font-weight: 600; font-family: var(--mono); color: var(--fg); }
.acc-hint { font-size: .64rem; color: var(--fg3); margin-top: 3px; }
.acc-block {
  border-top: 1px dashed var(--border);
  padding-top: 8px;
}
.acc-line { font-size: .72rem; color: var(--fg2); padding: 2px 0; }
.acc-line b { color: var(--g); font-family: var(--mono); }
.acc-none { color: var(--fg3); font-style: italic; }
.acc-tag {
  font-size: .58rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  margin-left: 5px;
  color: var(--fg3);
}
.acc-mono { font-family: var(--mono); font-size: .64rem; color: var(--fg3); }

/* ── Toasts ────────────────────────────────────────────────── */

.toasts {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast {
  min-width: 210px;
  max-width: 340px;
  padding: 10px 13px;
  border-radius: var(--rad);
  background: var(--bg-glass);
  border: 1px solid var(--bg4);
  border-left: 3px solid var(--acc);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
  font-size: .76rem;
  color: var(--fg);
  animation: popIn 220ms both;
}
.toast.ok      { border-left-color: var(--g); }
.toast.err     { border-left-color: var(--r); }
.toast.warn    { border-left-color: var(--y); }
.toast.loading {
  border-left-color: var(--p);
  border-color: rgba(99, 102, 241, 0.4);
  background: rgba(14, 18, 28, 0.95);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .55), 0 0 18px rgba(99, 102, 241, 0.2);
  display: flex;
  align-items: center;
  gap: 10px;
}
.toast.loading .toast-spinner {
  color: var(--p);
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.toast.out     { animation: fadeIn 180ms reverse both; }

/* ── Login ─────────────────────────────────────────────────── */

.login-box {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}
.login-card {
  width: 100%;
  max-width: 330px;
  padding: 26px;
  background: var(--bg2);
  border: 1px solid var(--bg3);
  border-radius: var(--rad3);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
  animation: popIn 260ms both;
}
.login-card h2 { font-size: 1.05rem; margin-bottom: 4px; text-align: center; }
.login-card p { font-size: .74rem; color: var(--fg3); text-align: center; margin-bottom: 18px; }

/* ── Responsive ────────────────────────────────────────────── */


/* ── Authoritative icon bounds ────────────────────────────────
   Brand SVG/PNG files have large intrinsic dimensions. Keep every
   rendered asset constrained by its UI context after Lucide upgrades. */
.tab-ic {
  width: 14px !important;
  height: 14px !important;
  min-width: 14px;
  min-height: 14px;
  overflow: hidden;
}
.tab-ic > img,
.tab-ic > svg {
  width: 9px !important;
  height: 9px !important;
  max-width: 9px !important;
  max-height: 9px !important;
  object-fit: contain;
}
.app-circle {
  width: 26px;
  height: 26px;
  min-width: 26px;
  min-height: 26px;
  overflow: visible;
}
.app-circle > img {
  width: 14px !important;
  height: 14px !important;
  max-width: 14px !important;
  max-height: 14px !important;
  object-fit: contain;
}
.app-circle.linked > img {
  width: 15px !important;
  height: 15px !important;
  max-width: 15px !important;
  max-height: 15px !important;
}
/* Platform icons rendered before account emails are intentionally smaller
   than Linked-column circles. */
.em > span[style*="inline-flex"] > .app-circle,
.em > span[style*="inline-flex"] .app-circle {
  width: 20px;
  height: 20px;
  min-width: 20px;
  min-height: 20px;
}
.em > span[style*="inline-flex"] > .app-circle > img,
.em > span[style*="inline-flex"] .app-circle > img,
.em > span[style*="inline-flex"] .app-circle.linked > img {
  width: 12px !important;
  height: 12px !important;
  max-width: 12px !important;
  max-height: 12px !important;
}
.rel-icon > img {
  width: 14px !important;
  height: 14px !important;
  max-width: 14px !important;
  max-height: 14px !important;
}


@media (max-width: 720px) {
  .overlay { padding: 8px; align-items: flex-end; }
  .modal {
    max-width: none;
    max-height: 92vh;
    border-radius: var(--rad3) var(--rad3) 0 0;
  }
  .modal-h, .modal-f { padding-inline: 14px; }
  .modal-b { padding: 14px; }

  .pag { padding: 8px 10px; }
}

/* ── Relation icons ────────────────────────────────────────── */

.rel-icon {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  cursor: default;
  position: relative;
}
.rel-icon img {
  width: 14px;
  height: 14px;
  opacity: .7;
  transition: opacity var(--tr), transform var(--tr);
  vertical-align: -2px;
}
.rel-icon:hover img {
  opacity: 1;
  transform: scale(1.15);
}
.rel-icon .rel-tip {
  display: none;
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 8px;
  border-radius: 5px;
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--fg2);
  font-size: .64rem;
  white-space: nowrap;
  z-index: 90;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .4);
  pointer-events: none;
}
/* Tooltips render in the #fx-tip fixed layer (never clipped). */
.rel-icon:hover .rel-tip, .app-circle:hover .rel-tip { display: none; }

/* ── Floating tooltip layer (fixed, above scroll containers) ─ */
#fx-tip {
  display: none;
  position: fixed;
  z-index: 99999;
  max-width: 300px;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--fg2);
  font-size: .68rem;
  line-height: 1.4;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .55);
  pointer-events: none;
}

/* ── App circles (Authorized App column) ───────────────────── */

.app-circles {
  display: inline-flex;
  gap: 5px;
  align-items: center;
}
.app-circle {
  position: relative;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--tr), box-shadow var(--tr);
  cursor: default;
}
.app-circle:hover {
  transform: scale(1.18);
  z-index: 2;
}
.app-circle img {
  width: 14px;
  height: 14px;
  filter: brightness(1.2);
}
.app-circle.gh {
  background: linear-gradient(135deg, #e2e4ea, #c9ccd4);
  border: 1px solid #d1d5db;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}
.app-circle.gh:hover { box-shadow: 0 0 12px rgba(0, 0, 0, .18); }
.app-circle.gh img { filter: none; }

/* Linked state is uniform across every platform: a light disc keeps each
   glyph legible (GitHub black, Qoder two-tone, CodeBuddy purple, 9router
   orange tile) and the green border/ring marks the active connection. Only
   the shared .app-circle.linked rule below styles it. */

/* Qoder official mark is two-tone (black + brand green) on transparent,
   so it sits on a light disc — same role as the light disc behind the
   black GitHub glyph. */
.app-circle.qd {
  background: linear-gradient(135deg, #ffffff, #eef0f5);
  border: 1px solid #d7dbe3;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}
.app-circle.qd:hover { box-shadow: 0 0 12px rgba(42, 219, 92, .28); }
.app-circle.qd img { filter: none; opacity: 1; }

/* Clawbrowser's official mark is a black tile with a light circle ("Side
   Bite") on transparent, so it takes the light disc too — on a dark disc the
   tile would be swallowed and only the light circle would read. */
.app-circle.cwb {
  background: linear-gradient(135deg, #ffffff, #eef0f5);
  border: 1px solid #d7dbe3;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
}
.app-circle.cwb:hover { box-shadow: 0 0 12px rgba(0, 0, 0, .3); }
.app-circle.cwb img { filter: none; opacity: 1; }

.app-circle.cb {
  background: linear-gradient(135deg, #1a237e, #283593);
  border: 1px solid #3949ab;
  box-shadow: 0 2px 8px rgba(26, 35, 126, .3);
}
.app-circle.cb:hover { box-shadow: 0 0 12px rgba(57, 73, 171, .35); }

.app-circle.gg {
  background: linear-gradient(135deg, #1b1b2e, #252538);
  border: 1px solid #3a3a4e;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
}
.app-circle.gg:hover { box-shadow: 0 0 12px rgba(66, 133, 244, .25); }

.app-circle.ic {
  background: linear-gradient(135deg, #1b2538, #22324e);
  border: 1px solid #3b82f6;
  box-shadow: 0 2px 8px rgba(59, 130, 246, .2);
}
.app-circle.ic:hover { box-shadow: 0 0 12px rgba(59, 130, 246, .35); }
.app-circle.ic img { filter: none; opacity: 1; }

.app-circle.none {
  background: var(--bg3);
  border: 1px dashed var(--bg4);
  opacity: .4;
}
.app-circle.none img { opacity: .3; filter: grayscale(1); }

/* App state fills: light disc + green ring = linked (clickable to detach
   where applicable); dark translucent slot = not linked yet (clickable to
   link). Both states read identically across every table. */
.app-circle.linked {
  background: linear-gradient(135deg, #ffffff, #eef0f5);
  border: 1px solid #22c55e;
  box-shadow: 0 0 0 2px rgba(74, 222, 128, .4);
}
.app-circle.linked img {
  opacity: 1;
  filter: none;
  width: 15px;
  height: 15px;
}
.app-circle.linked:hover { box-shadow: 0 0 0 3px rgba(74, 222, 128, .45); }

/* Banned app state: red border with small red X cross badge */
.app-circle.banned {
  position: relative !important;
  border: 1px solid #ef4444 !important;
  box-shadow: 0 0 0 2px rgba(239, 68, 68, .45) !important;
  background: linear-gradient(135deg, #2b181a, #1f1416) !important;
}
.app-circle.banned img {
  opacity: .55;
  filter: grayscale(0.6) brightness(0.9);
}
.app-circle.banned:hover {
  box-shadow: 0 0 0 3px rgba(239, 68, 68, .6) !important;
}
.app-circle .ban-x {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 12px;
  height: 12px;
  background: #ef4444;
  color: #ffffff;
  font-size: 8px;
  font-weight: 900;
  line-height: 12px;
  text-align: center;
  border-radius: 50%;
  border: 1.5px solid var(--bg1, #121218);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .6);
  pointer-events: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Failed app state: amber border with refresh icon for retry/reauth */
.app-circle.failed {
  position: relative !important;
  border: 1px solid #f59e0b !important;
  box-shadow: 0 0 0 2px rgba(245, 158, 11, .4) !important;
  background: linear-gradient(135deg, #2b2214, #1f1810) !important;
  cursor: pointer;
}
.app-circle.failed img {
  opacity: .6;
  filter: grayscale(0.5);
}
.app-circle.failed:hover {
  box-shadow: 0 0 0 3px rgba(245, 158, 11, .6) !important;
}
.app-circle .retry-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 12px;
  height: 12px;
  background: #f59e0b;
  color: #121218;
  border-radius: 50%;
  border: 1.5px solid var(--bg1, #121218);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .6);
  pointer-events: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.app-circle .retry-badge svg,
.app-circle .retry-badge .btn-icon {
  width: 8px;
  height: 8px;
}

/* 2FA Verified badge on linked GitHub icon */
.app-circle .badge-2fa {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 12px;
  height: 12px;
  background: #10b981;
  color: #ffffff;
  border-radius: 50%;
  border: 1.5px solid var(--bg1, #121218);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .6);
  pointer-events: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.app-circle .badge-2fa svg,
.app-circle .badge-2fa .btn-icon {
  width: 8px;
  height: 8px;
  color: #ffffff;
}

/* Running tab: icon-only action buttons */
.run-stop-badge,
.run-resume-badge,
.run-cancel-badge {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 22px;
  border-radius: 5px;
  font-size: .62rem;
  font-weight: 600;
  border: 1px solid transparent;
  vertical-align: middle;
}
.run-stop-badge svg,
.run-stop-badge .btn-icon,
.run-resume-badge svg,
.run-resume-badge .btn-icon,
.run-cancel-badge svg,
.run-cancel-badge .btn-icon {
  width: 12px;
  height: 12px;
}

/* Running → red stop button */
.run-stop-badge {
  border-color: rgba(74, 222, 128, .4);
  background: rgba(74, 222, 128, .12);
  color: #4ade80;
}
.run-stop-badge:hover {
  background: rgba(245, 158, 11, .18);
  border-color: rgba(245, 158, 11, .55);
  color: #f59e0b;
}

/* Stopped → amber resume button */
.run-resume-badge {
  border-color: rgba(245, 158, 11, .5);
  background: rgba(245, 158, 11, .15);
  color: #f59e0b;
  margin-right: 4px;
}
.run-resume-badge:hover {
  background: rgba(74, 222, 128, .15);
  border-color: rgba(74, 222, 128, .5);
  color: #4ade80;
}

/* Stopped → blue skip (re-queue) button */
.run-skip-badge {
  border-color: rgba(96, 165, 250, .5);
  background: rgba(96, 165, 250, .13);
  color: #60a5fa;
  margin-right: 4px;
}
.run-skip-badge:hover {
  background: rgba(96, 165, 250, .25);
}
.run-skip-badge svg,
.run-skip-badge .btn-icon {
  width: 12px;
  height: 12px;
}

/* Stopped → red cancel button */
.run-cancel-badge {
  border-color: rgba(239, 68, 68, .45);
  background: rgba(239, 68, 68, .12);
  color: #ef4444;
}
.run-cancel-badge:hover {
  background: rgba(239, 68, 68, .28);
}

/* Running → which exit the launch got. On the row rather than in the log: a run
   that silently left through the key pool has to be visible at a glance.
   "custom" is the accent (the operator's own proxy carried it); "managed" stays
   muted because it is the ordinary answer when no proxy is configured. */
.run-egress {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid var(--bg4);
  border-radius: 4px;
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--fg3);
  vertical-align: middle;
}
.run-egress.custom {
  border-color: rgba(129, 140, 248, .45);
  background: rgba(129, 140, 248, .12);
  color: var(--acc2);
}

/* Which engine carried the run — CB (Clawbrowser) or CF (Camoufox). On the row
   for the same reason the egress badge is: the engine used to be readable only
   from the "engine=cb" note inside the expanded log. CF takes an amber accent
   so a Camoufox run stands apart from the ordinary Clawbrowser one. */
.run-engine {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--bg4);
  border-radius: 4px;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .4px;
  color: var(--fg2);
  vertical-align: middle;
}
.run-engine.cf {
  border-color: rgba(249, 115, 22, .45);
  background: rgba(249, 115, 22, .12);
  color: #f97316;
}

/* The live view of a running browser (Clawbrowser Remote Control). A link, not
   a badge — it leaves the console — so it carries the accent colour only on
   hover and never looks like a status pill. The icon is the whole affordance at
   a glance; the tooltip names the dashboard and the account requirement. */
.run-live {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px;
  border: 1px solid rgba(56, 189, 248, .45);
  border-radius: 4px;
  background: rgba(56, 189, 248, .12);
  color: #38bdf8;
  font-size: .6rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.run-live:hover {
  background: rgba(56, 189, 248, .22);
  color: #7dd3fc;
}

.run-live svg {
  width: 11px;
  height: 11px;
}

/* The captured live view (`app.bot.liveview`). This one stays in the console: it
   is a polled frame, not a link out, so it reads as something to press rather
   than somewhere to go. */
.run-live-start {
  cursor: pointer;
}

/* While a view is up the cell reports it rather than drawing it — the frames are
   in the live-view dialog (§150a). Green, so it reads as running; pressing it
   stops the view, which is also what stops the capture on the machine holding
   the browser. */
.run-live-on {
  cursor: pointer;
  border-color: rgba(34, 197, 94, .45);
  background: rgba(34, 197, 94, .12);
  color: #4ade80;
}

.run-live-on:hover {
  border-color: rgba(248, 113, 113, .5);
  background: rgba(248, 113, 113, .12);
  color: #f87171;
}

/* ── The live-view dialog (§150a) ──

   Sized for a page, not for a table: the capture is a whole screen (1920x1080
   for both engines), and the two surfaces it used to be drawn in — a table cell
   and the 35%-wide URL Log pane — were small enough that reading a code or a
   consent screen off the picture was guesswork. The dialog takes as much of the
   viewport as it can and then scales the frame down to fit it, whole. */
.live-modal {
  width: min(1600px, 96vw);
  max-width: 96vw;
  /* A definite height, not just a cap: the frame's `max-height: 100%` resolves
     against the body's box, and a percentage max-height needs a *definite*
     containing block. With `height: auto` the body is content-sized and the
     percentage is dropped, which is how the frame could still overflow. */
  height: 88vh;
}

.live-modal-head {
  min-width: 0;
}

/* The address the run is on, immediately under the title and above the frame.
   A capture has no address bar of its own, so without this the picture is a
   page nobody can name — and it is the same line the URL Log pane ends on. */
.live-modal-url {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-top: 4px;
  font-size: .7rem;
  color: var(--fg2);
  overflow-wrap: anywhere;
}

.live-modal-url svg {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
}

.live-modal-url a {
  color: #7dd3fc;
  text-decoration: none;
}

.live-modal-url a:hover {
  text-decoration: underline;
}

.live-modal-none {
  color: var(--fg3);
  font-style: italic;
}

.live-modal-who {
  color: var(--fg3);
  font-weight: 400;
}

/* The picture, kept whole rather than cropped — a page that is half off screen
   would be a wrong answer about what the run is doing. The body takes whatever
   height the dialog has left (the modal is a fixed-height flex column) and the
   frame is scaled down inside it on *both* axes, so a 16:9 capture always lands
   in the box whole. `width: auto` / `height: auto` under a `max-width`/`max-height`
   pair is what preserves the aspect ratio: with `width: 100%; height: auto` the
   frame was as tall as its own width demanded and a short viewport simply cut
   the bottom off (`overflow: auto`), which is exactly the cropping reported. */
.live-modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px 16px;
  overflow: hidden;
}

.live-modal-frame {
  display: block;
  /* `min-width: 0` is load-bearing, not tidying. This frame is a *flex item* of
     the body above, and a flex item's automatic minimum size (`min-width: auto`)
     is its content size — the capture's own **1920px** intrinsic width — and a
     minimum outranks a maximum. Where the two are compared that way (Gecko and
     WebKit; Chrome's transferred-size handling happens to shrink instead) the
     frame stays 1920px wide inside a ~1500px box and the body's `overflow:
     hidden` keeps only its left edge — the operator's "only the left part is
     visible" (2026-09-25). Zero here hands the shrinking decision back to
     `max-width` / `max-height`, which is what scales the picture down whole,
     and Chrome — measured with a real 1920x1080 frame at five viewport sizes,
     unaffected either way — is where the earlier fit was checked.
     `min-height: 0` is the same statement for the cross axis. */
  min-width: 0;
  min-height: 0;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border: 1px solid rgba(56, 189, 248, .45);
  border-radius: 4px;
  background: #000;
}

/* A dash that has a reason behind it (AGENTS.md §149). Deliberately quiet: the
   reason is for the operator who is already asking *why there is no view*, and
   rows that can be watched must not be drowned out by rows that cannot. The
   dotted underline is the whole affordance — it says "there is something to
   hover" without turning a missing feature into a status badge. */
.live-why {
  color: var(--fg3);
  cursor: help;
  border-bottom: 1px dotted var(--fg3);
}

/* The fingerprint was never checked. Amber, not red: the run did not fail — it
   carried on with the egress probe alone — but nothing compared the profile it
   presented to the one asked for, which is the entire reason the verify step
   exists. Drawn only in that case; see `fingerprintBadge` in tables.js. */
.run-fp {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid rgba(251, 191, 36, .45);
  border-radius: 4px;
  background: rgba(251, 191, 36, .12);
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--y);
  vertical-align: middle;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Not linked: dark translucent "empty slot" — visible but clearly not an
   active connection; clickable to link. */
.app-circle.off {
  background: rgba(255, 255, 255, .055);
  border: 1px dashed rgba(148, 163, 184, .55);
  cursor: pointer;
}
.app-circle.off:hover {
  background: rgba(255, 255, 255, .1);
  box-shadow: 0 0 0 3px rgba(148, 163, 184, .22);
}

/* Logos render directly on the tinted fill — no white chip. A light halo
   + slight lift keeps dark marks (GitHub black, Qoder black half) visible
   on the translucent green, while colorful marks keep their identity. */
.app-circle.linked img {
  opacity: 1;
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .75)) drop-shadow(0 0 2px rgba(255, 255, 255, .35)) brightness(1.3);
  width: 15px;
  height: 15px;
}
.app-circle.linked img:hover { transform: scale(1.05); }

.app-circle.off img {
  opacity: .55;
  filter: grayscale(1) brightness(1.4);
  width: 12px;
  height: 12px;
}
.app-circle.off:hover img {
  opacity: .9;
  filter: grayscale(1) brightness(1.7);
}

.app-circle .rel-tip {
  display: none;
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 9px;
  border-radius: 5px;
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--fg2);
  font-size: .64rem;
  white-space: nowrap;
  z-index: 90;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
  pointer-events: none;
}


.app-circle.nr {
  background: linear-gradient(135deg, #0d9488, #0f766e);
  border: 1px solid #14b8a6;
  box-shadow: 0 2px 8px rgba(13, 148, 136, .25);
}
.app-circle.nr:hover { box-shadow: 0 0 12px rgba(20, 184, 166, .35); }
.app-circle.nr img { filter: brightness(1.6); }

/* The Shop disc carries the lifecycle in its fill, so a glance down the Routed
   column says which rows still sell: WHITE = stock (on the shelf), RED = sold
   (gone). The small STOCK/SOLD pill repeats it in words; the disc is the part
   that reads without stopping. Before this the fill was a single indigo tint
   for both states, which is what made "mark sold" look like nothing happened. */
.app-circle.shop-mark {
  /* Default is stock, so a row whose state was never written still reads as on
     the shelf — the convention crud._apply_destination writes on a first mark. */
  background: #ffffff !important;
  border: 1px solid #d7dbe3 !important;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.app-circle.shop-mark:hover { box-shadow: 0 0 12px rgba(148, 163, 184, .45); }
.app-circle.shop-mark svg,
.app-circle.shop-mark .btn-icon { width: 15px; height: 15px; color: #4b5563; }

.app-circle.shop-mark.shop-stock {
  background: #ffffff !important;
  border-color: #d7dbe3 !important;
}
.app-circle.shop-mark.shop-stock:hover { box-shadow: 0 0 12px rgba(148, 163, 184, .45); }
.app-circle.shop-mark.shop-stock svg,
.app-circle.shop-mark.shop-stock .btn-icon { color: #4b5563; }

.app-circle.shop-mark.shop-sold {
  background: linear-gradient(135deg, #ef4444, #dc2626) !important;
  border-color: #f87171 !important;
  box-shadow: 0 0 10px rgba(239, 68, 68, .45);
}
.app-circle.shop-mark.shop-sold:hover { box-shadow: 0 0 14px rgba(239, 68, 68, .65); }
.app-circle.shop-mark.shop-sold svg,
.app-circle.shop-mark.shop-sold .btn-icon { color: #ffffff; }

/* The rental fill (§584). Amber, because a rentable row is neither on the shelf
   (white) nor gone (red) — it is a third product, and an operator scanning the
   column has to tell "can be rented" from "can be bought" at a glance. */
.app-circle.shop-mark.shop-rent {
  background: linear-gradient(135deg, #f59e0b, #d97706) !important;
  border-color: #fbbf24 !important;
  box-shadow: 0 0 10px rgba(245, 158, 11, .45);
}
.app-circle.shop-mark.shop-rent:hover { box-shadow: 0 0 14px rgba(245, 158, 11, .65); }
.app-circle.shop-mark.shop-rent svg,
.app-circle.shop-mark.shop-rent .btn-icon { color: #1f1300; }

/* A locked disc is a *state*, not a disabled button (§589). The Shop disc on a row
   that is for rent stays amber — the row really is in the pool — but it carries no
   `data-act`, so it cannot be pressed, and it drops the hover lift that would promise
   a click. `cursor: not-allowed` is the only affordance, because the row is not
   "disabled", it is committed to the other product. */
.app-circle.locked {
  cursor: not-allowed;
  opacity: .82;
}
.app-circle.locked:hover { box-shadow: 0 0 10px rgba(245, 158, 11, .45); }
.app-circle.locked::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: repeating-linear-gradient(
    -45deg, rgba(0, 0, 0, .22) 0 2px, transparent 2px 5px);
  pointer-events: none;
}

/* The rent disc that replaces the AI Gateway disc on a mailbox table — same amber
   so the two controls for one product read as one idea. */
.app-circle.rent-mark {
  background: linear-gradient(135deg, #f59e0b, #d97706) !important;
  border-color: #fbbf24 !important;
  box-shadow: 0 0 10px rgba(245, 158, 11, .45);
}
.app-circle.rent-mark:hover { box-shadow: 0 0 14px rgba(245, 158, 11, .65); }
.app-circle.rent-mark svg,
.app-circle.rent-mark .btn-icon { width: 15px; height: 15px; color: #1f1300; }

/* Stock / Sold picker: the modal that the Shop disc opens on a marked row, and
   the same modal the Action menu opens for a whole selection. */
.shop-state-choice {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.shop-state-opt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--bg2);
  color: var(--fg);
  cursor: pointer;
  text-align: left;
  transition: border-color .15s, background .15s;
}
.shop-state-opt svg { width: 15px; height: 15px; }
.shop-state-opt .ss-title { font-weight: 700; font-size: .78rem; }
.shop-state-opt .ss-sub { font-size: .62rem; color: var(--fg3); line-height: 1.4; }
.shop-state-opt.stock svg { color: #6ee7b7; }
.shop-state-opt.stock:hover {
  border-color: rgba(16, 185, 129, .5);
  background: rgba(16, 185, 129, .08);
}
.shop-state-opt.sold svg { color: #fca5a5; }
.shop-state-opt.sold:hover {
  border-color: rgba(239, 68, 68, .5);
  background: rgba(239, 68, 68, .08);
}
/* The way out of Shop (§253) — full width under the stock/sold pair, because it
   is not a third state of the mark: it removes the mark. */
.shop-state-opt.unrouted { grid-column: 1 / -1; }
.shop-state-opt.unrouted svg { color: var(--fg3); }
.shop-state-opt.unrouted:hover {
  border-color: var(--line);
  background: var(--bg3);
}

/* ── Stepper & Cost Summary ────────────────────────────────── */

.stepper-wrap {
  margin-top: 5px;
}
.stepper {
  display: flex;
  align-items: stretch;
  gap: 3px;
  background: var(--bg);
  border: 1px solid var(--bg3);
  border-radius: var(--rad);
  padding: 3px;
}
.step-btn {
  background: var(--bg2);
  color: var(--fg);
  border: 1px solid var(--bg3);
  padding: 4px 8px;
  border-radius: calc(var(--rad) - 2px);
  font-size: .74rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  transition: background var(--tr), color var(--tr), border-color var(--tr);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
}
.step-btn:hover {
  background: var(--bg4);
  color: var(--fg);
}
.step-btn.primary {
  background: var(--acc);
  color: #fff;
  border-color: var(--acc);
  font-weight: 700;
}
.step-btn.primary:hover {
  filter: brightness(1.15);
}
.step-input {
  flex: 1;
  text-align: center;
  font-family: var(--mono);
  font-size: .95rem;
  font-weight: 700;
  background: transparent !important;
  border: none !important;
  color: var(--fg);
  min-width: 44px;
  padding: 0;
  appearance: textfield;
  -moz-appearance: textfield;
}
.step-input::-webkit-outer-spin-button,
.step-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ── Scheduler Throughput Estimate Card ── */
.sched-estimate-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin: 12px 0 14px 0;
  background: linear-gradient(135deg, rgba(99, 102, 241, .12), rgba(30, 27, 75, .32));
  border: 1px solid rgba(129, 140, 248, .3);
  border-radius: var(--rad);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), 0 2px 8px rgba(0, 0, 0, .2);
}
.sched-est-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: rgba(129, 140, 248, .2);
  color: #a5b4fc;
  border: 1px solid rgba(165, 180, 252, .25);
  flex: none;
}
.sched-est-icon svg {
  width: 17px;
  height: 17px;
}
.sched-est-content {
  flex: 1;
  min-width: 0;
}
.sched-est-title {
  font-size: .64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .7px;
  color: var(--acc2);
  margin-bottom: 2px;
}
.sched-est-text {
  font-family: var(--mono);
  font-size: .74rem;
  font-weight: 600;
  color: var(--fg);
  line-height: 1.4;
}

.cost-summary {
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--bg3);
  border-radius: var(--rad);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cost-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: .73rem;
}
.cost-row .lbl {
  color: var(--fg3);
}
.cost-row .val {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--fg2);
}
.cost-row .val.highlight {
  color: var(--acc);
  font-size: .84rem;
}
.cost-warning {
  margin-top: 4px;
  font-size: .68rem;
  font-weight: 600;
  color: var(--r);
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid rgba(239, 68, 68, 0.25);
  border-radius: calc(var(--rad) - 2px);
  padding: 5px 8px;
  text-align: center;
}

/* ── SMS tab: clickable provider label (opens edit modal) ──
   Neutralize the global <button> chrome (gradient bg, shadow, hover lift)
   so the label reads as plain text. */
.sms-name {
  background: none; border: none; box-shadow: none;
  padding: 0; margin: 0;
  font: inherit; font-size: .78rem; font-weight: 600;
  color: var(--fg); text-align: left; cursor: pointer;
}
.sms-name-cell .sms-name:hover,
.sms-name-cell .sms-name:active {
  /* must outrank the global button:hover gradient/shadow/lift */
  background: none; border-color: transparent; box-shadow: none;
  transform: none;
  color: var(--accent, #818cf8); text-decoration: underline;
}

/* ── Email tab: provider sources (plans/email-source.md) ──
   Same posture as the SMS tab's label, plus the two things this tab adds: the
   platform chips a supplier serves, and the checkbox list the Add/Edit modals
   draw from the provider catalog (key + the website it maps to). */
.email-name {
  background: none; border: none; box-shadow: none;
  padding: 0; margin: 0;
  font: inherit; font-size: .78rem; font-weight: 600;
  color: var(--fg); text-align: left; cursor: pointer;
}
.email-name:hover,
.email-name:active {
  background: none; border-color: transparent; box-shadow: none;
  transform: none;
  color: var(--accent, #818cf8); text-decoration: underline;
}
.email-chip {
  display: inline-block;
  margin: 1px 3px 1px 0;
  padding: 0 5px;
  border-radius: 3px;
  background: var(--bg3);
  color: var(--fg2);
  font-family: var(--mono);
  font-size: .64rem;
}
.email-acts { display: flex; gap: 4px; justify-content: flex-end; }
.email-plat { display: flex; flex-direction: column; gap: 4px; }
.email-plat-row {
  display: flex; align-items: center; gap: 6px;
  font-size: .72rem; color: var(--fg2);
}
.email-plat-row .hint { font-size: .64rem; }
/* The Add/Edit modals' custom-platform rows: a key → website pair the operator
   types when the supplier's own list does not carry the site they want to rent
   for. Grid rather than flex so the two inputs stay aligned as rows are added. */
.email-custom-list { display: flex; flex-direction: column; gap: 6px; }
.email-custom-row {
  display: grid; grid-template-columns: 1fr auto 1fr auto;
  align-items: center; gap: 6px;
}
.email-custom-row input {
  padding: 5px 8px; font-size: .72rem;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: 6px;
  color: var(--fg1); min-width: 0;
}
.email-price { min-height: 62px; }
.email-price-tbl { max-height: 300px; overflow: auto; }
.email-price-tbl table { width: 100%; }
.email-price-tbl th, .email-price-tbl td { padding: 4px 6px; font-size: .72rem; }

/* The Emails cell is now the way into the rent-history dialog — a count that is
   a button, so the whole inbox list is one click away instead of one accordion
   at a time. */
.email-hist-btn {
  background: none; border: none; box-shadow: none; padding: 0; margin: 0;
  font-family: var(--mono); font-size: .72rem; font-weight: 600;
  color: var(--fg); cursor: pointer;
}
.email-hist-btn:hover,
.email-hist-btn:active {
  /* must outrank the global button:hover gradient/shadow/lift */
  background: none; border-color: transparent; box-shadow: none;
  transform: none;
  color: var(--accent, #818cf8); text-decoration: underline;
}

/* ── Rent history dialog (#m-email-history) ──
   The accordion's replacement. It is wider than an ordinary dialog because it
   is a table; its rows scroll inside the body while the filters above and the
   pager + Action menu below stay put, and the Action menu opens *upward* — the
   dialog's bottom edge is where the screen ends. */
.modal.xl { max-width: 1180px; }
.email-hist-tools {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  padding: 11px 18px;
  border-bottom: 1px solid var(--bg3);
}
.email-hist-tools .hint {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .7rem; color: var(--fg3);
}
.email-hist-tools .tbl-filter { min-width: 168px; }
.email-hist-body { padding: 0; }
.email-hist-tbl { max-height: 52vh; overflow: auto; }
.email-hist-f { align-items: center; justify-content: space-between; }
.email-hist-f .pag { flex: 1; padding: 0; border-top: none; }
#m-email-history .bulk-dd .menu { top: auto; bottom: calc(100% + 8px); }
#m-email-history .bulk-dd .menu::before {
  top: auto; bottom: -5px;
  border-left: none; border-top: none;
  border-right: 1px solid rgba(165, 180, 252, .38);
  border-bottom: 1px solid rgba(165, 180, 252, .38);
}
/* The code is the point of the row: a button, so one click copies it. */
.email-code {
  background: none; border: none; box-shadow: none; padding: 0; margin: 0;
  font-family: var(--mono); font-size: .76rem; font-weight: 600;
  letter-spacing: .5px; color: var(--accent, #818cf8); cursor: pointer;
}
.email-code:hover, .email-code:active {
  background: none; border-color: transparent; box-shadow: none; transform: none;
  text-decoration: underline;
}

/* ── SMS tab: per-provider numbers accordion ── */
.sms-name-cell {
  display: flex; align-items: center;
  cursor: pointer; padding: 10px 8px; white-space: nowrap;
}
.sms-name-cell .arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 10px; height: 10px; margin-right: 6px; flex: none;
  color: var(--fg3);
  transition: transform .12s ease, color .12s ease;
}
.sms-name-cell .arrow svg { width: 10px; height: 10px; }
.sms-name-cell .arrow.rot { transform: rotate(90deg); }
.sms-row.open .sms-name-cell .arrow { color: var(--accent, #818cf8); }
.sms-detail td { padding: 6px 12px 14px; background: rgba(0,0,0,.18); }
.sms-nums {
  width: 100%; border-collapse: collapse;
  font-size: .72rem;
}
.sms-nums thead th {
  text-align: left; font-size: .62rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .6px;
  color: var(--fg3); padding: 4px 8px;
  border-bottom: 1px solid rgba(108, 126, 167, .25);
}
.sms-nums tbody td { padding: 4px 8px; color: var(--fg2); border-bottom: 1px solid rgba(108, 126, 167, .12); }
.sms-nums tbody tr:last-child td { border-bottom: none; }
.sms-nums-empty { padding: 10px 8px; color: var(--fg3); font-size: .72rem; }

/* ── Run modal: SMS source provider chips (run from the SMS tab) ── */
.sms-src-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-height: 34px; }
.sms-chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--bg3); border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 6px; padding: 4px 9px;
  font-size: .72rem; color: var(--fg2); white-space: nowrap;
}
.sms-chip small { color: var(--fg3); font-size: .6rem; }
.sms-chip-err { border-color: rgba(239, 68, 68, .45); color: var(--r); }
.sms-chip-muted { font-size: .64rem; color: var(--fg3); }

/* ── Multi-select lists and chips for Scheduler (styled as run-card tiles) ── */
.sc-multi-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 135px;
  overflow-y: auto;
  padding: 6px;
  background: rgba(15, 23, 42, .4);
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
}
.sc-chip-card {
  padding: 6px 10px !important;
}
.sc-chip-body {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}
.sc-chip-email {
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 500;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}
.sc-chip-badge {
  font-size: .62rem;
  color: var(--fg3);
  background: rgba(255, 255, 255, .06);
  padding: 1px 6px;
  border-radius: 4px;
  flex: none;
}

.sc-country-chips {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  padding: 6px;
  background: rgba(15, 23, 42, .4);
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
}
.sc-region-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.sc-region-tabs button {
  font-size: .63rem;
  padding: 2px 7px;
  border-radius: 5px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(148, 163, 184, .2);
  color: var(--fg2);
  cursor: pointer;
  transition: all var(--tr);
}
.sc-region-tabs button:hover {
  background: rgba(255, 255, 255, .1);
  color: var(--fg);
}
.sc-region-tabs button.active {
  background: var(--acc);
  color: #fff;
  border-color: var(--acc);
  font-weight: 600;
}

/* ── Queue proxy pool dialog (#m-q-pool) ────────────────────────────────
   The Run modal's pool control, lifted out of a toolbar popover that opened
   leftwards under the table. Chips stay dense: the operator is scanning ~200
   country codes, not reading cards, so the dialog is only slightly wider than
   a default modal and the grid fills the height it is given. */
.qp-modal { max-width: 660px; }
.qp-sub {
  font-size: .68rem;
  color: var(--fg3);
  margin-top: 3px;
  max-width: 56ch;
  line-height: 1.45;
}
.qp-body { display: flex; flex-direction: column; gap: 9px; }
.qp-search {
  width: 100%;
  font-size: .78rem;
  padding: 7px 10px;
  background: rgba(15, 23, 42, .55);
  border: 1px solid var(--bg4);
  border-radius: 8px;
  color: var(--fg);
}
.qp-search:focus { outline: none; border-color: var(--acc2); }
.qp-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.qp-btns { display: flex; gap: 5px; flex-wrap: wrap; }
.qp-btns .btn-s { font-size: .64rem; padding: 2px 8px; min-height: 0; }
.qp-count { font-size: .68rem; font-weight: 600; color: var(--acc2); }
.qp-chips { max-height: min(46vh, 340px); overflow-y: auto; }
.sc-cc-chip {
  padding: 6px 8px !important;
  display: flex !important;
  align-items: center !important;
  gap: 6px !important;
}
.sc-cc-code {
  font-family: var(--mono);
  font-weight: 700;
  font-size: .72rem;
  color: var(--fg);
}
.sc-cc-name {
  font-size: .65rem;
  color: var(--fg3);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sc-cc-chip .rc-tick {
  width: 14px !important;
  height: 14px !important;
  flex: none !important;
}
.sc-cc-chip.disabled {
  opacity: .3;
  pointer-events: none;
}

/* ── Run cards & logos sizing (guarantees cards & icons never break) ── */
.run-card {
  position: relative;
  display: flex !important;
  align-items: center;
  gap: 8px;
  margin: 0 !important;
  padding: 8px 12px;
  text-transform: none !important;
  letter-spacing: normal !important;
  font-size: .78rem;
  text-align: left;
  background: rgba(128, 128, 128, .05);
  color: var(--fg, #eee);
  border: 1px solid var(--bd, rgba(128, 128, 128, .28));
  border-radius: 9px;
  cursor: pointer;
  user-select: none;
  box-sizing: border-box;
  transition: border-color var(--tr, .18s), background-color var(--tr, .18s), box-shadow var(--tr, .18s);
}
.run-card:hover {
  border-color: var(--acc2, #4c9aff);
  background: rgba(99, 102, 241, .06);
}
.run-card:has(input:checked) {
  border-color: var(--acc2, #4c9aff);
  background: rgba(99, 102, 241, .09);
  box-shadow: inset 0 0 0 1px rgba(99, 102, 241, .18);
}
.run-card:has(input:disabled),
.run-card.disabled {
  opacity: .45 !important;
  cursor: not-allowed !important;
}
.run-card:has(input:disabled) input,
.run-card.disabled input {
  pointer-events: none !important;
}
/* The run cards hide their input, so a `radio` group needs the same treatment as
   the `checkbox` cards: without it the native dot renders beside the label. */
.run-card input[type=checkbox],
.run-card input[type=radio] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
  pointer-events: none;
}
.rc-logo,
.run-card .rc-logo {
  width: 20px !important;
  height: 20px !important;
  max-width: 20px !important;
  max-height: 20px !important;
  min-width: 20px !important;
  min-height: 20px !important;
  flex: none !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  overflow: hidden !important;
}
.rc-logo img,
.run-card .rc-logo img {
  width: 20px !important;
  height: 20px !important;
  max-width: 20px !important;
  max-height: 20px !important;
  object-fit: contain !important;
  display: block !important;
  flex: none !important;
}
.rc-logo img[src*="github.svg"],
.run-card .rc-logo img[src*="github.svg"] {
  filter: invert(1) brightness(1.8);
}
.run-cards {
  display: grid !important;
  grid-template-columns: repeat(2, 1fr) !important;
  gap: 8px !important;
}
.run-card .rc-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.run-card .rc-name {
  font-weight: 600;
  font-size: .78rem;
  line-height: 1.2;
  color: var(--fg);
}
.run-card .rc-via {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: .64rem;
  color: var(--fg3);
  font-weight: 500;
  line-height: 1;
}
.run-card:has(input:checked) .rc-via {
  color: var(--acc2);
}
.rc-mini-logo {
  width: 12px !important;
  height: 12px !important;
  max-width: 12px !important;
  max-height: 12px !important;
  object-fit: contain !important;
  display: inline-block !important;
  flex: none !important;
  filter: invert(1) brightness(1.8);
}
.run-card .rc-tick {
  width: 15px;
  height: 15px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid var(--bd, rgba(128, 128, 128, .4));
  box-sizing: border-box;
  transition: background-color var(--tr, .18s), border-color var(--tr, .18s);
}
.run-card:has(input:checked) .rc-tick {
  border-color: var(--acc2, #818cf8);
  background: var(--acc2, #818cf8);
  box-shadow: inset 0 0 0 3px rgba(15, 17, 26, .9);
}
.rc-state.soon {
  font-size: .62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: #a5b4fc;
  background: rgba(99, 102, 241, .16);
  border: 1px solid rgba(129, 140, 248, .35);
  padding: 2px 7px;
  border-radius: 5px;
  flex: none;
}

/* ── Wizard Bar for Scheduler ── */
.wizard-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 9px 16px;
  background: rgba(15, 23, 42, .32);
  border-bottom: 1px solid var(--bg4);
  user-select: none;
}
.w-step {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  opacity: .45;
  transition: all var(--tr);
}
.w-step:hover {
  opacity: .75;
}
.w-step.active {
  opacity: 1;
}
.w-step.completed {
  opacity: .85;
}
.w-step-num {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: .68rem;
  font-weight: 700;
  background: var(--bg3);
  color: var(--fg3);
  border: 1px solid var(--bg4);
  transition: all var(--tr);
}
.w-step.active .w-step-num {
  background: var(--acc);
  color: #fff;
  border-color: var(--acc);
  box-shadow: 0 0 8px rgba(99, 102, 241, .45);
}
.w-step.completed .w-step-num {
  background: rgba(16, 185, 129, .2);
  color: #34d399;
  border-color: rgba(52, 211, 153, .4);
}
.w-step-name {
  font-size: .72rem;
  font-weight: 600;
  color: var(--fg2);
}
.w-step.active .w-step-name {
  color: var(--fg);
}
.w-step-line {
  flex: 0 0 28px;
  height: 1px;
  background: var(--bg4);
}

/* ── Live Scheduler Running Dashboard ── */
.sched-live-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.sched-live-metrics {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.sc-metric {
  background: rgba(15, 23, 42, .45);
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  padding: 10px 8px;
  text-align: center;
}
.sc-metric-val {
  font-family: var(--mono);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--acc2);
  line-height: 1.1;
}
.sc-metric-lbl {
  font-size: .62rem;
  color: var(--fg3);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-top: 4px;
}
.sched-live-info {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px 14px;
  padding: 10px 14px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(148, 163, 184, .18);
  border-radius: var(--rad);
  font-size: .74rem;
  color: var(--fg2);
}
.sched-live-info-item {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.sched-live-info-item span {
  color: var(--fg3);
  font-size: .7rem;
  flex: none;
}
.sched-live-info-item b {
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sched-live-log-wrap {
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  background: rgba(10, 15, 30, .6);
  overflow: hidden;
}
.sched-live-log-h {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(15, 23, 42, .5);
  border-bottom: 1px solid var(--bg4);
  font-size: .68rem;
  font-weight: 600;
  color: var(--fg2);
}
.sched-live-log {
  max-height: 150px;
  overflow-y: auto;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: .68rem;
  line-height: 1.45;
  color: var(--fg2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sched-live-log .log-row {
  word-break: break-all;
}
.sched-live-log .log-row.err { color: var(--r); }
.sched-live-log .log-row.warn { color: var(--y); }
.sched-live-log .log-row.info { color: var(--fg2); }

/* ── Run Modal: Step 3 Review & Target Table ── */
.run-review-box {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.run-review-meta {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.run-meta-card {
  background: rgba(15, 23, 42, .45);
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  padding: 8px 10px;
  text-align: center;
}
.run-meta-val {
  font-family: var(--mono);
  font-size: .88rem;
  font-weight: 700;
  color: var(--fg);
  line-height: 1.2;
}
.run-meta-val.highlight {
  color: var(--acc2);
}
.run-meta-lbl {
  font-size: .62rem;
  color: var(--fg3);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-top: 3px;
}
.run-targets-tbl-wrap {
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  background: rgba(15, 23, 42, .3);
  overflow: hidden;
}
.run-targets-tbl-wrap table {
  width: 100%;
  border-collapse: collapse;
}
.run-targets-tbl-wrap th {
  background: rgba(255, 255, 255, .03);
  border-bottom: 1px solid var(--bg4);
  color: var(--fg3);
  font-size: .65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .5px;
  padding: 6px 10px;
  text-align: left;
}
.run-targets-tbl-wrap td {
  padding: 8px 10px;
  border-bottom: 1px solid rgba(148, 163, 184, .1);
  font-size: .74rem;
}
.run-targets-tbl-wrap tr:last-child td {
  border-bottom: none;
}
/* ── Browser engine (Configuration → General, Proxies) ───────────────────────
   The run cards once more, for the same reason as the pickers above: the choice
   is two sentences long — *uses this, ignores that* — which a `<select>` row
   cannot say. `auto-fit` rather than two fixed columns because a card whose
   launcher is not in the build yet carries a "Soon" chip and its group should
   take the width it needs. */
.run-cards.engine-cards {
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)) !important;
}
.run-card.engine.disabled { cursor: not-allowed !important; }
/* The base `.rc-via` is a two-word label on one line (`line-height: 1`), and it
   clips here: the engine's summary is a full clause. Without this the second
   line overlaps the first instead of wrapping. */
.run-card.engine .rc-via {
  line-height: 1.4 !important;
  white-space: normal;
}

/* The engine that owns each Proxies tab, and the dimmed state for the half the
   chosen engine *ignores*. `tab-off` does not hide or disable the tab: the
   operator can still inspect and edit that setting, and hiding it is exactly
   what would let a stored proxy look active while the engine never reads it.

   The chip is `engine-chip`, not `*-tab-*`: it is a badge *inside* a tab, and a
   name carrying `tab` would read as another tab model to
   There is only one tab model. */
.tab .engine-chip {
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 5px;
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: var(--fg3);
  background: rgba(148, 163, 184, .12);
  border: 1px solid var(--bg4);
  vertical-align: 1px;
}
.tab.tab-off { opacity: .5; }
.tab.tab-off:hover { color: var(--fg3); background: none; }
.tab.tab-off .engine-chip {
  color: #fca5a5;
  background: rgba(244, 63, 94, .12);
  border-color: rgba(244, 63, 94, .32);
}

/* Why the pane above is out of play. A banner rather than `.hint` text, because
   this is a consequence of a setting rather than help text — and it is the one
   place an operator learns that a saved value is being ignored on purpose. */
.engine-scope {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 12px;
  padding: 8px 10px;
  border: 1px solid var(--bg4);
  border-left: 3px solid var(--acc2, #818cf8);
  border-radius: var(--rad);
  background: rgba(99, 102, 241, .07);
  color: var(--fg2);
  font-size: .7rem;
  line-height: 1.5;
}
.engine-scope.off {
  border-left-color: rgba(244, 63, 94, .7);
  background: rgba(244, 63, 94, .06);
  color: var(--fg3);
}

.run-target-col {
  display: flex;
  align-items: center;
  gap: 8px;
}
.run-target-col img {
  width: 18px;
  height: 18px;
  object-fit: contain;
  flex: none;
}
.run-target-col img[src*="github.svg"] {
  filter: invert(1) brightness(1.8);
}

/* ═══════════════ Configuration Hub (sidebar modal) ═══════════════ */
.cfg-modal {
  max-width: 1100px !important;
  min-width: 0;
  min-height: min(80vh, 620px);
  height: min(85vh, 720px);
  display: flex;
  flex-direction: column;
}
/* Both Configuration modals are this shell; the shop one has fewer sections, so it
   asks for a narrower box. A class rather than an inline `max-width`, because an
   inline value loses to `applyRole()`'s `style.display` writes on the same
   element's subtree the moment a role changes — the same trap the header's three
   swapped pairs are commented about in `index.html`. */
.cfg-modal.narrow {
  max-width: 900px !important;
  height: min(80vh, 640px);
}
.cfg-layout {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.cfg-sidebar {
  width: 190px;
  min-width: 190px;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 10px;
  background: var(--bg);
  border-right: 1px solid var(--bg4);
  overflow-y: auto;
}
/* A sidebar entry, not a button that happens to sit in a column. Two things were
   wrong with the old one:

   1. It is a `<button>`, and base `button` carries a gradient, a drop shadow and
      an inset highlight. `.cfg-nav` reset `background` and `border` but not
      `box-shadow`, so every entry kept the button's shadow.
   2. The hover state never applied at all. `button:hover:not(:disabled)` is
      (0,2,1) and `.cfg-nav:hover` was (0,1,0), so hovering painted the base
      button's gradient over the sidebar instead. The `:not(:disabled)` below is
      not decoration — it is what gives this selector the weight to win.

   The active entry is marked by a rail on the left edge. A tinted rectangle is
   what the old style was, and in a column of tinted rows it says very little. */
.cfg-nav {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  /* No vertical padding: the control height is the box, exactly as for the other
     controls, so a sidebar entry is 32px rather than the 33px that 8px of padding
     around a 14.6px line box produces. */
  min-height: var(--ctl-h);
  padding: 0 10px 0 12px;
  border-radius: 8px;
  font-size: .76rem;
  font-weight: 500;
  color: var(--fg3);
  background: transparent;
  border: 1px solid transparent;
  box-shadow: none;
  cursor: pointer;
  transition: background var(--tr), color var(--tr);
  text-align: left;
  white-space: nowrap;
}
.cfg-nav::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 50%;
  width: 2px;
  height: 0;
  border-radius: 2px;
  background: var(--acc2);
  transform: translateY(-50%);
  transition: height var(--tr);
}
.cfg-nav:hover:not(:disabled) {
  background: rgba(255, 255, 255, .05);
  color: var(--fg);
  border-color: transparent;
  box-shadow: none;
}
.cfg-nav.active {
  background: rgba(99, 102, 241, .10);
  color: var(--fg);
  font-weight: 600;
}
.cfg-nav.active::before { height: 16px; }
.cfg-nav.active:hover:not(:disabled) {
  background: rgba(99, 102, 241, .14);
  color: var(--fg);
}
.cfg-nav svg,
.cfg-nav i {
  width: 15px;
  height: 15px;
  flex: none;
}
.cfg-nav-group-label {
  font-size: .62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .7px;
  color: var(--fg3);
  padding: 12px 10px 4px;
}
.cfg-content {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 18px 22px;
}
.cfg-section {}
.cfg-sec-title {
  font-size: .82rem;
  font-weight: 700;
  color: var(--fg);
  margin-bottom: 14px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--bg4);
}
.fm-layout-inline {
  display: grid;
  grid-template-columns: 170px 240px 1fr;
  gap: 0;
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  overflow: hidden;
  min-height: 380px;
}

/* ═══════════════ Monitoring Hub (tab modal) ═══════════════ */
/* The strip is the shared `.tabs`/`.tab` — one tab model, so there is nothing to
   restyle here. `.mon-pane` stays: a pane is not a tab. The three models this
   replaced (`.mon-tab` pills with a glow, `.platform-tab-btn` washes,
   `.ghm-tab` borders) each drew their active state differently, and two of them
   spent a shadow on it, which §6 allows only for overlays. */
.mon-pane {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.mon-pane .modal-b {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  max-height: min(70vh, 600px);
  overflow-y: auto;
}
#mon-log .logbox {
  flex: 1;
  min-height: 0;
  height: 100%;
  max-height: none;
  border: none;
  border-radius: 0;
}
#mon-log > .modal-b {
  height: 100%;
  max-height: min(70vh, 600px);
  overflow: hidden;
  padding: 0;
}
#mon-log > .modal-b > div {
  height: 100%;
}
#mon-report .tbl-wrap {
  flex: 1;
  min-height: 240px;
  overflow-y: auto;
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  background: var(--bg2);
}
#mon-report .stats-tbl {
  width: 100%;
  border-collapse: collapse;
}
#mon-report .stats-legend {
  margin-top: auto;
  padding-top: 10px;
  font-size: .64rem;
  color: var(--fg3);
  flex-shrink: 0;
}

/* ── Table Filters & Daily Active Runner ── */

.filter-group {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Filters carry the meaning of the table, so they keep their full labels and
     the decorative toolbar hint (which ellipsizes) absorbs the squeeze
     instead. Without this the four GitHub selects truncated to "Stat…". */
  flex: none;
}

/* The toolbar's and filter modal's controls. They used `--bg2` for their own
   surface while sitting on a `.panel`/`.modal` that is *also* `--bg2`, and their
   border was `var(--border)` — undefined, so nothing was drawn. A 1.00:1 field
   against its own panel: the control was invisible until focused. They now use
   the same recipe as every other field in the app (dark inset + `--bg4` edge). */
.tbl-filter {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--bg4);
  border-radius: 7px;
  padding: 0 8px;
  /* No `font-size` of its own: it carries no meaning over the field recipe, and
     setting it here only reached the `select`-based filters. On an
     `input[type=text].tbl-filter` the element+attribute selector (0,1,1) beat
     this class (0,1,0), so the text filters rendered a size larger than the
     selects in the very same toolbar. Inheriting makes the two agree. */
  font-family: inherit;
  cursor: pointer;
  outline: none;
  height: var(--ctl-h);
  color-scheme: dark;
  transition: border-color var(--tr);
}
.tbl-filter:focus {
  border-color: var(--acc);
}

/* The toolbar search shares its row with 28px controls (`.tbl-search` and
   `.tbl-filter` are both 28px), so the field keeps its full-height form look
   everywhere else and matches the row here. */
.tab-tools .inp {
  height: var(--ctl-h);
  padding: 0 10px;
  border-radius: 8px;
}

/* Created date range. `color-scheme: dark` is what makes the native picker
   glyph and calendar readable on the dark background — without it Chromium
   draws them black-on-black. */
.filter-range {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--fg3);
}
.filter-range input[type="date"].tbl-filter {
  color-scheme: dark;
  cursor: text;
  width: 128px;
  padding: 0 6px;
}
.filter-range input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .6;
}
.filter-range input[type="date"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}
.filter-range-sep { color: var(--fg3); font-size: .7rem; }

/* ── Table toolbar: search box (left) + filters button ────────── */

/* The pill wrapper around a transparent input, on the same surface as every
   other field (`--bg`). It used to be `--bg2` — the panel's own value — which
   stayed legible only through its edge; and once that panel's table filters
   (`.tbl-filter`) moved to `--bg`, the two controls in one toolbar no longer
   matched. Same recipe, one look. */
.tbl-search {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: var(--ctl-h);
  padding: 0 10px;
  border-radius: 8px;
  background: var(--bg);
  border: 1px solid var(--bg4);
  color: var(--fg3);
  transition: border-color var(--tr), background var(--tr);
}
.tbl-search:focus-within { border-color: var(--acc); background: var(--bg3); }
.tbl-search > svg { width: 13px; height: 13px; flex: none; }
.tbl-search input {
  border: 0;
  background: transparent;
  color: var(--fg);
  font-family: inherit;
  font-size: .8rem;
  width: 200px;
  outline: none;
}
.tbl-search input::placeholder { color: var(--fg3); }
.tbl-search.has-value { border-color: var(--acc); }

.filter-btn { display: inline-flex; align-items: center; gap: 6px; }
.filter-btn.on { border-color: var(--acc); color: var(--acc3); }
.filter-n {
  display: none;
  min-width: 15px;
  height: 15px;
  padding: 0 4px;
  border-radius: 99px;
  background: var(--acc);
  color: #fff;
  font-size: .6rem;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}
.filter-btn.on .filter-n { display: inline-block; }

/* The filters themselves, moved out of the toolbar into the modal: one labelled
   field per control, so what each dropdown narrows is written down instead of
   being carried in a `title` nobody hovers in a compact row of selects. */
.filter-modal {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.filter-field { display: flex; flex-direction: column; gap: 5px; }
.filter-field.span-all { grid-column: 1 / -1; }
.filter-lbl {
  font-size: .66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--fg3);
}
.filter-modal .tbl-filter { width: 100%; height: 32px; }
.filter-modal .filter-range { display: flex; align-items: center; gap: 7px; width: 100%; }
.filter-modal .filter-range input[type="date"].tbl-filter { width: 100%; flex: 1; }

/* Labelled for screen readers, silent on screen: the toolbar is compact and
   the calendar icon already says what the pair of dates is for. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Table footer: rows-per-page + pager ───────────────────────
   Both live in `.pag-right`, built by `initTableNav()`. `--bg4` is used for the
   outlines because it is a real token: `--border` is referenced 30+ times across
   this stylesheet but defined nowhere, so anything relying on it draws nothing. */

.pag-right { display: flex; align-items: center; gap: 12px; }

.per-page {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.per-page-lbl { color: var(--fg3); font-size: .68rem; }
.per-page select {
  appearance: none;
  -webkit-appearance: none;
  height: 27px;
  padding: 0 26px 0 9px;
  border-radius: 8px;
  border: 1px solid var(--bg4);
  background-color: var(--bg2);
  /* Chevron drawn inline: `appearance:none` removes the native one, and an icon
     element here would need a lucide pass on a control that never changes. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238492a6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 8px center;
  color: var(--fg);
  font-family: inherit;
  font-size: .7rem;
  cursor: pointer;
  transition: border-color var(--tr), background-color var(--tr);
}
.per-page select:hover { border-color: var(--acc); }
.per-page select:focus {
  outline: none;
  border-color: var(--acc);
  background-color: var(--bg3);
}

.pager { display: inline-flex; align-items: center; gap: 3px; }
.pg-btn,
.pg-num {
  min-width: 27px;
  height: 27px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg2);
  font-family: inherit;
  font-size: .7rem;
  cursor: pointer;
  transition: background var(--tr), color var(--tr), border-color var(--tr);
}
.pg-btn svg { width: 13px; height: 13px; }
.pg-btn:hover:not(:disabled),
.pg-num:hover:not(.active) { background: var(--bg3); color: var(--fg); }
.pg-num { border-color: var(--bg4); }
.pg-num.active {
  background: linear-gradient(135deg, var(--acc), #4338ca);
  border-color: var(--acc2);
  color: #fff;
  font-weight: 700;
  cursor: default;
}
.pg-gap { color: var(--fg3); font-size: .7rem; padding: 0 3px; user-select: none; }
.pg-btn:disabled { opacity: .3; cursor: not-allowed; }

.daily-scope-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 14px;
}

.daily-scope-card {
  padding: 12px;
  border-radius: 8px;
  background: var(--bg2);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: all var(--tr);
}
.daily-scope-card.active {
  border-color: var(--g);
  background: rgba(34, 197, 94, 0.08);
}
.daily-scope-card.disabled {
  opacity: .4;
  cursor: not-allowed;
  pointer-events: none;
}
.daily-scope-card .title {
  font-weight: 600;
  font-size: .8rem;
  color: var(--fg);
}
.daily-scope-card .sub {
  font-size: .68rem;
  color: var(--fg3);
  margin-top: 3px;
}

.daily-metrics-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 12px 0;
}
.daily-metric-card {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 8px;
  text-align: center;
}
.daily-metric-card .val {
  font-size: 1.1rem;
  font-weight: 700;
  font-family: var(--font-mono, monospace);
  color: var(--fg);
}
.daily-metric-card .lbl {
  font-size: .64rem;
  color: var(--fg3);
  text-transform: uppercase;
}

.daily-prog-track {
  height: 6px;
  background: var(--bg3);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 8px;
}
.daily-prog-bar {
  height: 100%;
  background: linear-gradient(90deg, var(--p), var(--g));
  width: 0%;
  transition: width 0.3s;
}

/* Manage Accounts cards (CodeBuddy tab) — one icon, copy, one action. */
.mng-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.mng-card-ic {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 9px;
  background: var(--bg2);
  color: var(--p);
}
.mng-card-ic svg { width: 16px; height: 16px; }
.mng-card-b { flex: 1; min-width: 0; }
.mng-card-t { font-size: .8rem; font-weight: 600; }
.mng-card-d {
  font-size: .68rem;
  color: var(--fg3);
  line-height: 1.45;
  margin-top: 2px;
}
.mng-card-d code {
  font-family: var(--font-mono, monospace);
  font-size: .66rem;
  color: var(--fg2);
}
.mng-inv-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 3px;
}
.mng-inv-row .inv-mail {
  color: var(--fg3);
  flex: none;
  max-width: 42%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mng-inv-row .inv-code {
  flex: none;
  font-family: var(--font-mono, monospace);
  font-size: .68rem;
  letter-spacing: .04em;
  color: var(--fg1, #e6edf3);
  background: #16202b;
  border-radius: 4px;
  padding: 1px 6px;
}
.mng-inv-row .inv-link { color: #7dd3a0; word-break: break-all; }
.mng-inv-row .inv-err { color: #f0a35e; }
/* The referral-code chip also shows inline in the CodeBuddy table — in the email
   cell and in the row's accordion — so it carries its own styling too. */
.inv-code {
  flex: none;
  font-family: var(--font-mono, monospace);
  font-size: .68rem;
  letter-spacing: .04em;
  color: var(--fg1, #e6edf3);
  background: #16202b;
  border: 1px solid rgba(255, 255, 255, .07);
  border-radius: 4px;
  padding: 1px 6px;
}
/* The parent code an account *bound* — deliberately tinted differently from the
   account's own `.inv-code` chip so the two are never mistaken for each other. */
.ref-by {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  font-family: var(--font-mono, monospace);
  font-size: .68rem;
  letter-spacing: .04em;
  color: var(--acc2, #818cf8);
  background: rgba(129, 140, 248, .12);
  border: 1px solid rgba(129, 140, 248, .38);
  border-radius: 4px;
  padding: 1px 6px;
}
.ref-by svg,
.ref-by .btn-icon {
  width: 11px;
  height: 11px;
  flex: none;
}

/* Status cell: badge + the persistent attempt counter, read as "SUCCESS 0x".
   Muted so it annotates the badge instead of competing with it; `0x` on a plain
   first-try success is still shown, because that is the fact worth seeing. */
.cb-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  white-space: nowrap;
}
.retry-n {
  flex: none;
  font-family: var(--font-mono, monospace);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--fg3);
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(148, 163, 184, .28);
  border-radius: 4px;
  padding: 1px 5px;
  cursor: help;
}

/* ── CodeBuddy Manage modal ──
   Same shape as the GitHub one (`ghm-*`): card grid, controls in card footers.
   The cron runner moved in from its own modal, so this is the only panel — the
   log card takes the leftover height instead of leaving a void under the cards. */
.ghm-modal.cbm-modal { max-width: 1040px; }
#cbm-daily-panel { flex: 1; min-height: 0; }
.cbm-fill { flex: 1; min-height: 0; }
.cbm-log { height: auto; max-height: none; flex: 1; min-height: 150px; }
/* The metric strip and the progress bar are already spaced for a modal body;
   inside a card they need to sit closer to the description. */
.ghm-card .daily-metrics-grid { margin: 2px 0 0; }
.ghm-card .daily-prog-track { margin-top: 2px; }
.ghm-card-t .badge { flex: none; white-space: nowrap; }

.daily-log-box {
  background: #080b10;
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 8px 10px;
  font-family: var(--font-mono, monospace);
  font-size: .68rem;
  height: 180px;
  overflow-y: auto;
  color: #abb2bf;
}
.daily-log-line {
  margin-bottom: 3px;
  word-break: break-all;
}
.daily-log-line .log-ts {
  color: var(--fg3);
  margin-right: 6px;
}
.daily-log-line.warning {
  color: var(--y);
}
.daily-log-line.error {
  color: var(--r);
}

/* ── URL Log Panel in Running Tab ── */
.acc-url-log {
  font-family: var(--font-mono, monospace);
  font-size: .65rem;
  color: #79c0ff;
  background: var(--bg);
}

/* ── GitHub Accordion ── */
.arrow {
  display: inline-block;
  transition: transform .15s ease;
}
.arrow[style*="rotate(90deg)"],
.arrow[data-expanded="true"] {
  transform: rotate(90deg) !important;
}
.url-log-line {
  margin-bottom: 3px;
  word-break: break-all;
  white-space: pre-wrap;
  line-height: 1.5;
}
.url-log-line .url-ts {
  color: var(--fg3);
  margin-right: 5px;
}
.url-log-line .url-val {
  cursor: help;
  text-decoration: underline dotted;
  text-decoration-color: rgba(121, 192, 255, 0.3);
}

/* ── Platform Settings & Format Variables ── */

/* The platform strip is the shared `.tabs`/`.tabs-scroll`/`.tab`. It keeps one
   thing of its own: the gap between the strip and the pane it switches, which
   the markup had before and the shared strip does not carry. */
#platform-cfg-tabs { margin-bottom: 16px; }

.plat-summary-card {
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 12px;
}
.plat-summary-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.plat-badge-list {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 6px 0;
}
.plat-field-badge {
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 2px 7px;
  font-family: var(--font-mono, monospace);
  font-size: .68rem;
  color: var(--fg);
}
.plat-code-preview {
  background: #080b10;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: var(--font-mono, monospace);
  font-size: .72rem;
  color: #79c0ff;
  word-break: break-all;
  white-space: pre-wrap;
  max-height: 140px;
  overflow-y: auto;
}

.var-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 6px;
}
.var-chip {
  background: var(--bg3);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 7px;
  font-family: var(--font-mono, monospace);
  font-size: .66rem;
  color: var(--p);
  cursor: pointer;
  user-select: none;
  transition: all var(--tr);
}
.var-chip:hover {
  border-color: var(--p);
  background: rgba(99, 102, 241, 0.15);
}
.var-chip.sep-chip {
  color: var(--y, #f59e0b);
  border-color: rgba(245, 158, 11, 0.35);
  background: rgba(245, 158, 11, 0.08);
  font-weight: 600;
}
.var-chip.sep-chip:hover {
  border-color: var(--y, #f59e0b);
  background: rgba(245, 158, 11, 0.2);
}

.field-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 6px;
  margin: 8px 0;
  padding: 8px 10px;
  background: var(--bg3);
  border-radius: 6px;
  border: 1px solid var(--border);
}
.field-checkbox-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .72rem;
  font-family: var(--font-mono, monospace);
  color: var(--fg);
  cursor: pointer;
}

/* ── GitHub Manage modal (tabbed, wide) ───────────────────────────────────
   The old modal was a 580px single column of stacked cards that showed nothing
   until a bulk run finished — indistinguishable from a hang. It now has room
   for a two-column action grid, a per-account progress view and its own Proxy
   and Queue tabs. */
.ghm-modal {
  max-width: 1180px;
  width: 94vw;
  height: 88vh;
  max-height: 88vh;
}
/* Actions and Proxy are short tabs: shrink the modal to its content instead of
   leaving half a screen of empty panel under the cards. Activity and Queue keep
   the tall layout because their lists grow into it. Toggled by `setGhTab`. */
.ghm-modal.ghm-fit {
  height: auto;
  min-height: 0;
}

/* The GitHub and Qoder manage modals use the shared `.tabs`/`.tab` strip, inside
   `.tabs-scroll` so four tab labels with pills still scroll on a phone-width
   modal instead of squashing. */
.ghm-pill {
  font-size: .62rem;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--bg3);
  color: var(--fg2);
  font-family: var(--mono);
  flex: none;
}
.ghm-pill.b-run { background: rgba(16, 185, 129, .18); color: var(--g); }

.ghm-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ghm-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ghm-panel[hidden] { display: none; }
/* Activity and Queue & Running should eat the leftover modal height instead of
   sitting as short boxes on top of a large void. */
#ghm-panel-activity,
#ghm-panel-queue { flex: 1; min-height: 0; }

.ghm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 12px;
  /* Equal heights across a row (and between rows) so the action cards read as
     one grid instead of a staggered pile — whatever the card count grows to. */
  align-items: stretch;
  grid-auto-rows: 1fr;
}

/* Two-column tab layout (Proxy form + result, Queue + Running). */
.ghm-2col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: 12px;
  align-items: stretch;
}
/* Queue/Running: give the two lists the leftover height instead of a fixed box. */
.ghm-2col-fill { flex: 1; min-height: 0; }
.ghm-2col-fill > .ghm-card { min-height: 0; }
.ghm-2col-fill .ghm-list { max-height: none; flex: 1; min-height: 120px; }

.ghm-card {
  padding: 14px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}
.ghm-card-h {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}
.ghm-card-h > button { flex: none; white-space: nowrap; }
/* The Activity header carries two actions (Stop, Clear): wrapped so the title
   block still takes the free space and neither button stretches. */
.ghm-card-btns { display: flex; flex: none; gap: 8px; }
.ghm-card-btns > button { flex: none; white-space: nowrap; }
/* Card footer: pinned to the bottom (`margin-top:auto`) so every primary button
   in a row sits on the same line, whether the card has an input or not. */
.ghm-card-f {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, .07);
  flex-wrap: wrap;
}
.ghm-card-f > input { flex: 1; min-width: 0; }
.ghm-card-f > button { flex: none; white-space: nowrap; }
/* One control height across the footer: an input is naturally ~4px taller than
   a `.btn-s`, and with `align-items:center` that difference nudged the primary
   button up or down by a few pixels from card to card. */
.ghm-card-f > input,
.ghm-card-f > select,
.ghm-card-f > button {
  height: 30px;
  min-height: 30px;
  padding-top: 0;
  padding-bottom: 0;
}
/* The Star card takes one repo per line, so its field is a textarea — three rows
   is what makes a pasted list readable. Kept out of the single-control height
   rule above (the button stays 30px) and below the global 108px default, which
   would make one card taller than its neighbours in the same row. */
.ghm-card-f > textarea {
  flex: 1;
  min-width: 0;
  min-height: 62px;
  resize: vertical;
  font-family: var(--mono);
  font-size: .74rem;
  line-height: 1.45;
}
.ghm-card-f > .ghm-help,
.ghm-card-f > .hint { margin-right: auto; }
.ghm-card-t {
  font-weight: 600;
  font-size: .82rem;
  display: flex;
  align-items: center;
  gap: 6px;
}
.ghm-card-d {
  font-size: .7rem;
  color: var(--fg3);
  line-height: 1.5;
  margin-top: 3px;
}
.ghm-card-d code {
  font-family: var(--mono);
  font-size: .68rem;
  padding: 0 3px;
  background: rgba(255, 255, 255, .06);
  border-radius: 3px;
}

.ghm-row { display: flex; gap: 8px; align-items: center; }
.ghm-row > input { flex: 1; min-width: 0; }

.ghm-field { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.ghm-field > label { font-size: .7rem; color: var(--fg2); }
.ghm-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.ghm-help { font-size: .66rem; color: var(--fg3); }

.ghm-switch { cursor: pointer; display: inline-flex; align-items: center; height: 30px; position: relative; }
/* Keep the real checkbox in the tab order — `display:none` would make the
   switch unreachable by keyboard and skip it for screen readers. */
.ghm-switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}
.ghm-switch input:focus-visible + span {
  outline: 2px solid var(--p);
  outline-offset: 2px;
}
.ghm-switch span {
  width: 38px;
  height: 20px;
  border-radius: 999px;
  background: var(--bg4);
  position: relative;
  transition: background 140ms;
}
.ghm-switch span::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--fg2);
  transition: transform 140ms;
}
.ghm-switch input:checked + span { background: var(--p); }
.ghm-switch input:checked + span::after { transform: translateX(18px); background: #fff; }

.ghm-result {
  font-family: var(--mono);
  font-size: .7rem;
  line-height: 1.6;
  padding: 10px;
  border-radius: 8px;
  background: rgba(0, 0, 0, .35);
  border: 1px solid rgba(255, 255, 255, .08);
  white-space: pre-wrap;
  flex: 1;
  min-height: 96px;
  overflow-y: auto;
}
.ghm-result-empty { color: var(--fg3); }
.ghm-result.ok { border-color: rgba(16, 185, 129, .4); }
.ghm-result.bad { border-color: rgba(239, 68, 68, .4); }

.ghm-progbar {
  height: 8px;
  border-radius: 999px;
  background: var(--bg4);
  overflow: hidden;
}
.ghm-progbar > span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--p), var(--g));
  transition: width 220ms ease;
}
/* The fill counts completed accounts, so on a run whose accounts take up to 90s
   each (a trial poll) it sits at zero for minutes and the bar reads as an empty
   track. `live` marks a run with work genuinely in flight, and the track then
   carries a moving stripe — a real loader, which is the allowance docs/design/DESIGN.md §8
   makes for `sweep` on an in-flight row. base.css turns the animation off under
   `prefers-reduced-motion`, which leaves a plain track. */
.ghm-progbar.live {
  background-image: repeating-linear-gradient(
    90deg, transparent 0 7px, rgba(148, 163, 184, .28) 7px 14px);
  animation: ghm-track 1.1s linear infinite;
}
@keyframes ghm-track {
  from { background-position-x: 0; }
  to { background-position-x: 14px; }
}
.ghm-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  font-size: .7rem;
  color: var(--fg3);
}
.ghm-stats b { color: var(--fg); font-family: var(--mono); font-weight: 600; }
.ghm-egress {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  font-family: var(--mono);
  font-size: .68rem;
  color: var(--fg3);
  padding: 7px 9px;
  border-radius: 7px;
  background: rgba(255, 255, 255, .03);
}
.ghm-egress b { color: var(--fg2); font-weight: 600; }

.ghm-log {
  flex: 1;
  min-height: 200px;
  padding: 12px;
  background: rgba(0, 0, 0, .35);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 8px;
  font-family: var(--mono);
  font-size: .7rem;
  line-height: 1.6;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
/* Log levels, in the same three colours the global panel uses (--y / --r), with
   the routine lines stepped back so a warning or an error is what stands out. */
.ghm-log .ln-dim  { color: var(--fg2); }
.ghm-log .ln-warn { color: var(--y); }
.ghm-log .ln-err  { color: var(--r); }

.ghm-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 34vh;
  overflow-y: auto;
}
.ghm-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 9px;
  border-radius: 6px;
  background: rgba(255, 255, 255, .03);
  font-size: .72rem;
}
.ghm-list-row .ghm-list-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ghm-list-row .ghm-list-meta { color: var(--fg3); font-family: var(--mono); font-size: .66rem; }
.ghm-empty { padding: 14px; text-align: center; color: var(--fg3); font-size: .72rem; }

/* ── Store workspace (the ConTools Telegram Shop, §10) ──────────────────────
   What is left here is only what the console does not already have. The shop's
   section strip is `.tabs` / `.tab` and its panes are `.tab-body` — the account
   strip's own markup, from layout.css — because a second set of rules for the
   same components is how the two areas drifted apart in the first place. The
   outer strip is the one that goes away while the shop is open (layout.css,
   `.shell.store-mode .panel > .tabs`). */

/* The one-line digest above each table: labels quiet, numbers readable. */
.store-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding: 11px 15px;
  border-bottom: 1px solid var(--bg3);
  font-size: .72rem;
  color: var(--fg3);
}
.store-facts b { color: var(--fg); font-family: var(--mono); font-size: .8rem; }

/* The Payments pane and its kin (2026-10-04). A channel is a text sub-tab of the
   Payments tab rather than a card-plus-modal: the strip says what is live, the
   stat cards say what the gateway holds, and the toolbar's *Connect* /
   *Disconnect* is the only control that writes the shop's choice. Stores and
   Settings borrow the same card grid so the three configuration panes read as
   one family. */
.pay-wrap { padding: 14px 15px 18px; display: grid; gap: 12px; }
/* The active channel, said once, with every verdict carrying its own timestamp:
   a green light with no age is a claim that cannot expire. */
.pay-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 10px 13px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: linear-gradient(180deg, rgba(40, 50, 78, .35), rgba(18, 22, 36, 0) 60%), var(--bg2);
  font-size: .73rem;
  color: var(--fg3);
}
.pay-strip-l { display: inline-flex; align-items: center; gap: 8px; color: var(--fg2); }
.pay-strip-l b { color: var(--fg); }
.pay-strip-i { white-space: nowrap; }
.pay-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fg3);
  box-shadow: 0 0 0 3px rgba(148, 163, 184, .12);
  flex: none;
}
.pay-dot.on { background: var(--g); box-shadow: 0 0 0 3px rgba(52, 211, 153, .16); }
/* The live payment test (§301): the QR the gateway minted for the operator's own
   bill, big enough to scan from a phone at arm's length, with the amount drawn
   under it — what was asked and what is about to be paid are one number on
   screen. The form and the running bill swap places inside the dialog. */
.pay-test-form { display: grid; gap: 12px; }
.pay-test-form.is-hidden { display: none; }
.pay-test-live { display: grid; gap: 12px; justify-items: center; text-align: center; }
.pay-test-live.is-hidden { display: none; }
.pay-test-qr { margin: 0; }
.pay-test-qr img {
  width: 240px;
  height: 240px;
  box-sizing: border-box;
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--bg4);
  border-radius: 8px;
  padding: 8px;
}
.pay-test-amt { font-size: 1.35rem; font-weight: 700; color: var(--fg); letter-spacing: -.02em; }
/* The channel shell (2026-10-04): logo-only tabs on top of the body, sharing one
   border, so the strip and the settings it switches between are one surface.
   The body is the old `m-seller-config` sidebar shape, kept at the tab's own
   level: a menu of the channel's sections and the section it selects. */
.pay-shell {
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: var(--bg2);
  overflow: hidden;
}
.pay-tabbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding-right: 12px;
  border-bottom: 1px solid var(--bg4);
  background: var(--bg2);
}
/* The one level of tabs below the area's own: the same `.tab` markup, quieter,
   so a channel is a tab wherever it is drawn. The label is the channel's name as
   plain text, the same word a screen reader gets from the `aria-label`. */
.pay-tabs {
  flex: 1;
  min-width: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  padding-top: 0;
}
.pay-tabs .tab { padding: 9px 14px 11px; }
.pay-live { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.pay-live .hint { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pay-body { display: flex; align-items: stretch; min-height: 300px; }
.pay-pane { display: flex; min-width: 0; width: 100%; }
.pay-pane.is-hidden { display: none; }
/* The tab is a *read* (2026-10-04, reshaped 2026-10-05): two equal spaces — the
   merchant's own column (the QRIS the bot holds, the connect switch, and the
   facts the gateway answered) and the account's own payments, so the money and
   the session that reads it are one glance apart. The one write on the picture is
   the corner icon, and the login is `#m-store-pay-login`, a two-step modal. */
.pay-read { flex: 1; min-width: 0; display: grid; grid-template-columns: minmax(280px, 1fr) minmax(0, 3fr); gap: 18px; align-items: start; padding: 16px 18px 20px; }
@media (max-width: 980px) {
  .pay-read { grid-template-columns: minmax(0, 1fr); }
}
.pay-side { display: grid; gap: 12px; min-width: 0; align-content: start; }
/* §828: the account's own column, as one grid child. `.pay-read` is a two-column
   grid, so every direct child is a column — a second card added beside the first
   became a *third* child and wrapped into the narrow first column, clipping the
   history table's Amount and Status columns. One wrapper keeps both cards stacked
   in the wide column, which is where the money belongs. */
.pay-stack { display: grid; gap: 12px; min-width: 0; align-content: start; }
/* The cards stay side by side, two halves of the column (2026-10-05: *"stats ini
   tetap bersebelahan kocak 1/2 1/2 di sisi kiri.. bukan full"*). `auto-fit`
   with a 150px floor read as one card per row once the column became a quarter
   (two of those need 310px); a fixed pair keeps the facts together and each
   card's value wraps (`overflow-wrap: anywhere`) rather than overflowing. */
.pay-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.pay-stat {
  display: grid;
  gap: 4px;
  padding: 10px 12px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: linear-gradient(180deg, rgba(40, 50, 78, .28), rgba(18, 22, 36, 0) 46%), var(--bg2);
  min-width: 0;
}
.pay-stat-k {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fg3);
}
.pay-stat-v { font-size: .82rem; color: var(--fg); overflow-wrap: anywhere; }
/* The merchant's QRIS and its one write (2026-10-05): the corner icon opens the
   picker, and the parse stores both halves — the string on the gateway, the
   picture on the bot — in the same action. The slot is drawn either way: with the
   picture, or the quiet empty state that says a QRIS is missing, because this
   icon is the only place a QRIS is stored from now on. The tile is the column's
   own width and taller than before (2026-10-05, the operator's ask: *"preview qris
   juga tepotong. buat lebih besar previewnya"*, then *"preview qris buat lebih
   tinggi dan lebarnya full area kiri seperti stats"*), so the whole code reads at
   a glance and the column reads as one stack. */
.pay-qris-col { display: grid; gap: 6px; justify-items: center; width: 100%; }
.pay-qris-note { text-align: center; }
.pay-qris-fig { position: relative; margin: 0; width: 100%; }
.pay-qris-slot {
  width: 100%;
  height: 320px;
  box-sizing: border-box;
  display: grid;
  place-items: center;
  background: #fff;
  border: 1px solid var(--bg4);
  border-radius: 12px;
  overflow: hidden;
}
.pay-qris-slot img { width: 100%; height: 100%; object-fit: contain; padding: 12px; box-sizing: border-box; }
.pay-qris-none { display: grid; gap: 5px; justify-items: center; font-size: .66rem; color: #7c879c; }
.pay-qris-none svg { width: 38px; height: 38px; stroke-width: 1.7; opacity: .55; }
.pay-qris-edit {
  position: absolute;
  right: -10px;
  bottom: -10px;
  width: 32px;
  height: 32px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--bg4);
  border-radius: 50%;
  background: var(--bg2);
  color: var(--fg2);
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .42);
  transition: color var(--tr), border-color var(--tr), transform var(--tr);
}
.pay-qris-edit:hover:not(:disabled) {
  background: var(--bg2);
  color: var(--fg);
  border-color: var(--acc);
  transform: translateY(-1px);
}
.pay-qris-edit svg { width: 15px; height: 15px; stroke-width: 2.1; }

/* The pane's one control row (2026-10-05, the operator's ask): *Test gateway* and
   the connect switch, each half the column's width, under the picture. The switch
   is the console's own `.f-switch`, boxed like a button so the two halves line up:
   off is the full reset — logout and the stored merchant data go with it. */
.pay-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: center; }
.pay-actions .btn-s { width: 100%; }
.pay-switch {
  height: var(--ctl-h);
  padding: 0 10px;
  border: 1px solid var(--bg4);
  border-radius: 8px;
  background: var(--bg2);
  box-sizing: border-box;
  justify-content: flex-start;
  transition: border-color var(--tr), color var(--tr);
}
.pay-switch:hover { border-color: var(--acc); color: var(--fg); }

/* The account's own payments (2026-10-05): the gateway's `/transactions` over the
   last week, one row per mutation — quiet rules, tabular numbers, and an empty
   state that is one sentence. */
.pay-hist { min-width: 0; border: 1px solid var(--bg4); border-radius: var(--rad2); background: var(--bg2); overflow: hidden; }
.pay-hist-h { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--bg4); }
.pay-hist-h h4 { margin: 0; font-size: .76rem; font-weight: 700; color: var(--fg2); }
.pay-hist-sum { margin-left: auto; font-size: .68rem; color: var(--fg3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-hist-refresh {
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--fg3);
  cursor: pointer;
  transition: color var(--tr), border-color var(--tr), background var(--tr);
}
.pay-hist-refresh:hover:not(:disabled) { color: var(--fg); border-color: var(--bg4); background: var(--bg3); }
.pay-hist-refresh svg { width: 14px; height: 14px; }
/* The table scrolls inside the card, with its header pinned (§738). */
.pay-hist-body { min-width: 0; padding: 0; overflow: auto; max-height: 440px; }
.pay-hist-tbl { width: 100%; border-collapse: collapse; font-size: .74rem; }
.pay-hist-tbl th {
  text-align: left;
  padding: 7px 12px;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fg3);
  border-bottom: 1px solid var(--bg4);
}
.pay-hist-tbl td { padding: 8px 12px; color: var(--fg2); border-bottom: 1px solid rgba(40, 50, 78, .45); }
/* §738: four columns for the four facts, with the header pinned while the table
   scrolls — the operator asked for a table ("buat table saja !") because rows drawn
   as flex lines read as log noise and could not be compared down a column. Numbers
   are tabular and right-aligned so the amounts line up between rows of different
   id lengths. */
.pay-hist-tbl thead th { position: sticky; top: 0; z-index: 1; background: var(--bg2); white-space: nowrap; }
.pay-hist-tbl tbody td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.pay-hist-tbl tbody tr:hover td { background: rgba(255, 255, 255, .03); }
.pay-hist-tbl .pay-hist-when { width: 34%; color: var(--fg3); }
.pay-hist-tbl .pay-hist-id { width: 30%; }
/* The merchant's id sits under ours in the same cell (§833): it is a secondary
   reference, so it is smaller and dimmer — present for reconciliation, never
   competing with the `CT-` id the buyer quotes. The cell is already `nowrap` with an
   ellipsis, so this line gets its own block to avoid inheriting that. */
.pay-hist-tbl .pay-hist-mref {
  display: block;
  font-size: .68rem;
  color: var(--fg3);
  font-weight: 400;
  letter-spacing: .01em;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pay-hist-tbl .pay-hist-amt { width: 22%; text-align: right; color: var(--fg); font-variant-numeric: tabular-nums; }
.pay-hist-tbl .pay-hist-st { width: 14%; text-align: right; }
.pay-hist-tbl tbody tr:last-child td { border-bottom: 0; }
.pay-hist-tbl tbody tr:hover { background: rgba(40, 50, 78, .22); }
.pay-hist-tbl th.pay-hist-amt { text-align: right; }
.pay-hist-tbl td.pay-hist-amt { text-align: right; color: var(--fg); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-hist-tbl td.pay-hist-when { color: var(--fg3); font-variant-numeric: tabular-nums; }
.pay-hist-empty { padding: 26px 14px; text-align: center; font-size: .74rem; color: var(--fg3); }

/* ShopeePay keeps the same two-column pane, with connection facts on the right. */
.shopeepay-status { align-content: start; }
.pay-shopee-status { display: flex; align-items: flex-start; gap: 9px; padding: 8px 0; color: var(--fg2); font-size: .8rem; line-height: 1.5; }
.pay-shopee-status .pay-dot { margin-top: 5px; }
.pay-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 0; }
.pay-facts > div { min-width: 0; padding: 10px 12px; border: 1px solid var(--bg4); border-radius: var(--rad2); background: var(--bg2); }
.pay-facts dt { margin-bottom: 5px; color: var(--fg3); font-size: .64rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.pay-facts dd { margin: 0; color: var(--fg); font-size: .78rem; overflow-wrap: anywhere; }
#store-pay-shopeepay-token { font-family: var(--mono); }
#store-pay-shopeepay-error:empty { display: none; }
#store-pay-shopeepay-error { color: var(--r-text); }
@media (max-width: 600px) {
  .pay-facts { grid-template-columns: minmax(0, 1fr); }
  #store-pay-pane-shopeepay .pay-card-f { flex-wrap: wrap; gap: 10px; }
  #store-pay-pane-shopeepay .pay-card-f > * { min-width: 0; }
  #store-pay-pane-shopeepay .pay-card-f .hint { flex: 1 1 150px; }
  #store-pay-pane-shopeepay .btn-s,
  #store-pay-pane-shopeepay .inp { min-height: 44px; }
  #store-pay-pane-shopeepay .ghm-switch { min-height: 44px; align-items: center; }
}

/* GoPay's login modal (2026-10-05): a two-step wizard inside one small modal —
   the phone, then the code in its own four cells with *Resend code* waiting under
   them. Each step is centred on its icon and one line, and the animation is the
   slide the operator asked for: the incoming step comes in from the right. */
.pay-login-b { display: grid; gap: 14px; }
.pay-login-steps { display: grid; }
.pay-login-step { display: grid; gap: 14px; animation: pay-login-in .26s cubic-bezier(.4, 0, .2, 1); }
@keyframes pay-login-in {
  from { opacity: 0; transform: translateX(22px); }
  to   { opacity: 1; transform: none; }
}
.pay-login-hero { display: grid; gap: 8px; justify-items: center; text-align: center; padding: 2px 0 0; }
.pay-login-ico {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--bg4);
  border-radius: 14px;
  background: radial-gradient(130% 130% at 30% 18%, rgba(129, 140, 248, .3), rgba(99, 102, 241, .1) 62%, rgba(0, 0, 0, 0) 100%), var(--bg2);
  color: var(--acc3);
}
.pay-login-glyph { width: 21px; height: 21px; stroke-width: 1.9; }
.pay-login-title { font-size: .86rem; font-weight: 650; color: var(--fg); }
.pay-login-hero p { margin: 0; }
.pay-login-btn { width: 100%; }
.pay-otp-cells { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; max-width: 264px; margin: 0 auto; }
.pay-otp-cell {
  width: 100%;
  height: 56px;
  padding: 0;
  text-align: center;
  font-size: 1.3rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  background: var(--bg2);
  border: 1px solid var(--bg4);
  border-radius: 12px;
  outline: none;
  transition: border-color var(--tr), box-shadow var(--tr);
}
.pay-otp-cell:focus { border-color: var(--acc2); box-shadow: 0 0 0 3px rgba(99, 102, 241, .22); }
.pay-otp-meta { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 20px; font-size: .72rem; color: var(--fg3); }
.pay-link {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--acc2);
  cursor: pointer;
  transition: color var(--tr);
}
.pay-link:hover:not(:disabled) { color: var(--acc3); }
.pay-login-note { min-height: 16px; text-align: center; }
.pay-login-foot { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-top: 1px solid var(--bd); }
.pay-quiet { padding: 16px 18px; display: grid; gap: 8px; }
.pay-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 12px;
  align-items: start;
}
.pay-card {
  display: grid;
  gap: 8px;
  padding: 12px 14px 14px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: linear-gradient(180deg, rgba(40, 50, 78, .28), rgba(18, 22, 36, 0) 46%), var(--bg2);
  min-width: 0;
}
/* §741: a card's title row — the name on the left, the channel's on/off switch on
   the right, using the same switch style as the manage modals' "Sticky per account".
   The switch is the only control in the corner so there is nothing else to mistake
   it for. */
.pay-card-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.pay-card-h h4 { margin: 0; font-size: .68rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--fg3); }
/* A card's footer row: the short note on the left, the save button on the right. */
.pay-card-f { display: flex; align-items: center; gap: 10px; }
.pay-card-f .hint { flex: 1; min-width: 0; font-size: .68rem; }
.pay-card-f .btn-s { flex: none; }
.pay-card h4 {
  margin: 0;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--fg3);
}
.pay-card-quiet { opacity: .78; }
.pay-field { display: flex; flex-direction: column; gap: 4px; font-size: .72rem; color: var(--fg3); }
/* §735: the API key reads as a status row — label left, "set/not set" badge and
   the replace switch on one line — because the field itself stays hidden until
   the operator asks for it. */
.pay-kq-keyrow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pay-kq-keyrow .badge { font-size: .66rem; }
.pay-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* The Buyer menu switches (§498): one row per optional destination, the label on
   the left and the switch on the right, so the column of switches lines up and the
   reader scans down the names rather than across ragged rows. */
.nav-switches { display: flex; flex-direction: column; gap: 2px; }
.nav-switches .f-switch {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  font-size: .78rem;
}
.nav-switches .f-switch + .f-switch { border-top: 1px solid var(--bg3); }
.nav-switches .f-switch-text { color: var(--fg2); }
.nav-switches .f-switch input:checked + .f-switch-track { background: var(--acc); }
.nav-switches .nav-empty { font-size: .72rem; color: var(--fg3); }

/* ── the Overview ─────────────────────────────────────────────────────────────
   The shop at a glance. Three tiers, deliberately different in weight rather than
   a row of identical cards: what needs a person (small, loud, clickable), the money
   over time (the one thing worth a chart), and everything else as quiet text. A grid
   of six equal stat tiles is the shape every generated dashboard takes, and it ranks
   nothing — here the thing that implies an action is the thing you see first. */
.store-overview { padding: 16px 18px 22px; }

.ov-attention { display: flex; flex-wrap: wrap; gap: 8px; }
.ov-attention-load { font-size: .75rem; color: var(--fg3); }
/* A count that needs a person, as the button that goes and deals with it. The
   rail on the left edge is what makes it read as "look here" rather than as one
   more chip: a row of identical neutral pills ranks nothing, which is the whole
   point of putting these two above the chart. */
.ov-attn {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  padding: 9px 14px 9px 16px;
  border-radius: 10px;
  border: 1px solid var(--bg4);
  background: linear-gradient(180deg, rgba(40, 50, 78, .5), rgba(28, 34, 54, .5));
  color: var(--fg2);
  font-size: .74rem;
  cursor: pointer;
  transition: border-color 120ms, color 120ms, transform 120ms;
  box-shadow: none;
}
.ov-attn::before {
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--acc2);
}
.ov-attn:hover:not(:disabled) {
  border-color: rgba(129, 140, 248, .55);
  color: var(--fg);
  box-shadow: none;
  transform: translateY(-1px);
}
.ov-attn b { font-family: var(--mono); font-size: 1.1rem; color: var(--fg); }
/* Only when there is something to do: a warning colour on a zero is noise. */
.ov-attn.warn {
  border-color: rgba(245, 158, 11, .42);
  background: linear-gradient(180deg, rgba(245, 158, 11, .14), rgba(245, 158, 11, .05));
}
.ov-attn.warn::before { background: var(--y); }
.ov-attn.warn b { color: var(--y); }
.ov-clear { font-size: .75rem; color: var(--fg3); padding: 7px 0; }

/* Cards, on four edges rather than three. They were `--bg3` on a `--bg2` pane,
   which is barely an edge; `--bg4` is the divider the rest of the app uses. The
   faint top wash gives the card a lit edge without spending a shadow (§6 keeps
   shadows for overlays). */
.ov-chart, .ov-panel {
  margin-top: 16px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: linear-gradient(180deg, rgba(40, 50, 78, .30), rgba(18, 22, 36, 0) 46%), var(--bg2);
}
.ov-chart header {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 13px 15px 0;
}
.ov-chart h3, .ov-panel h3 {
  margin: 0;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fg2);
}
.ov-chart-note {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg3);
  color: var(--fg3);
  font-size: .68rem;
}
/* A fixed height rather than Chart.js's aspect guess: the pane is wide, and the
   default 2:1 ratio would make the chart 300px tall on a wide screen. */
.ov-canvas { height: 200px; padding: 12px 15px 15px; }
.ov-canvas canvas { width: 100% !important; height: 100% !important; }
.ov-chart-off { padding: 14px 15px; font-size: .74rem; color: var(--fg3); }

.ov-split { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 14px; }
.ov-panel { padding: 13px 15px 15px; }
.ov-panel h3 { margin-bottom: 10px; padding-bottom: 9px; border-bottom: 1px solid var(--bg3); }
.ov-panel .store-facts { padding: 0; border: 0; }

/* The Shop card's figures as a two-column sheet rather than a run of inline
   spans: six label/value pairs strung along one line wrap to a different shape on
   every width and share no column, so nothing lines up to be read down. */
.ov-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 7px 14px; }
.ov-fact { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.ov-fact .ov-fact-k { font-size: .7rem; color: var(--fg3); }
.ov-fact b { font-family: var(--mono); font-size: .8rem; color: var(--fg); }

/* What sold: a rank, a proportional bar, and the two numbers. The bar is what
   turns five rows into a ranking at a glance — the figures alone have to be
   compared by reading them. Its width arrives as `--w` from the renderer, which
   is the only place that knows the largest row. */
.sold-row {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  grid-template-areas:
    "rank name rev"
    "rank bar  orders";
  gap: 3px 10px;
  align-items: center;
  padding: 8px 0;
  border-top: 1px solid var(--bg3);
  font-size: .74rem;
}
.sold-row:first-child { border-top: 0; }
.sold-row .sold-rank {
  grid-area: rank;
  align-self: start;
  font-family: var(--mono);
  font-size: .66rem;
  color: var(--fg3);
  text-align: right;
}
.sold-row .sold-name {
  grid-area: name;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg2);
}
.sold-row .sold-rev { grid-area: rev; font-family: var(--mono); color: var(--fg); font-weight: 600; }
.sold-row .sold-orders { grid-area: orders; font-size: .66rem; color: var(--fg3); text-align: right; }
.sold-bar {
  grid-area: bar;
  height: 3px;
  border-radius: 2px;
  background: var(--bg3);
  overflow: hidden;
}
/* The fill is an element rather than a `::after` sized by a custom property, so
   the renderer sets a plain `width` and the sheet holds no token that only exists
   at runtime (docs/design/DESIGN.md §4.3: a referenced token is defined in `:root`). */
.sold-bar > span {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--acc), var(--acc2));
}
.ov-empty { font-size: .74rem; color: var(--fg3); line-height: 1.5; }

/* ── the activity feed and the health panel (the shop's Monitoring) ─────────── */
.health-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 10px;
  margin-bottom: 20px;
}
.health-item {
  position: relative;
  padding: 11px 30px 11px 14px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad);
  background: linear-gradient(180deg, rgba(40, 50, 78, .28), rgba(18, 22, 36, 0) 52%), var(--bg2);
  font-size: .72rem;
}
/* The verdict as a light on the card, because the value it belongs to is a word:
   "running" and "stopped" are told apart by reading them, and the dot is what
   lets the panel be scanned instead. */
.health-dot {
  position: absolute;
  top: 13px;
  right: 13px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--fg3);
}
.health-dot[data-tone="ok"] { background: var(--g); box-shadow: 0 0 0 3px rgba(16, 185, 129, .12); }
.health-dot[data-tone="warn"] { background: var(--y); box-shadow: 0 0 0 3px rgba(245, 158, 11, .12); }
.health-item .health-k {
  display: block;
  font-size: .63rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--fg3);
  margin-bottom: 4px;
}
.health-item .health-v { color: var(--fg); font-family: var(--mono); font-size: .78rem; }
.health-item .health-note { display: block; margin-top: 4px; font-size: .66rem; color: var(--fg3); }

.activity { border-top: 0; }
.activity-row {
  display: grid;
  grid-template-columns: 8px 74px 90px minmax(0, 1fr) auto;
  gap: 11px;
  align-items: center;
  padding: 9px 2px;
  border-bottom: 1px solid var(--bg3);
  font-size: .72rem;
  transition: background var(--tr);
}
.activity-row:last-child { border-bottom: 0; }
.activity-row:hover { background: rgba(255, 255, 255, .025); }
/* Takes its colour from the inline `color` the renderer already sets on the kind,
   so the status palette stays in one place (`ACTIVITY_KINDS`). */
.activity-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  justify-self: center;
}
.activity-when { font-family: var(--mono); font-size: .67rem; color: var(--fg3); }
/* The kind as a chip in its own status colour: as bare uppercase text it was the
   same weight as the product name beside it, so the column that says *what
   happened* was the quietest thing in the row. */
.activity-kind {
  justify-self: start;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: .63rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  background: color-mix(in srgb, currentColor 14%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 34%, transparent);
}
.activity-what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg2); }
.activity-amt { font-family: var(--mono); font-size: .72rem; color: var(--fg); }

.help-steps { margin: 0 0 16px; padding-left: 20px; font-size: .78rem; color: var(--fg2); line-height: 1.55; }
.help-steps li { margin-bottom: 7px; }
.cfg-note { margin: 14px 0 0; font-size: .7rem; color: var(--fg3); line-height: 1.5; }

/* The console's one switch, used by every configuration toggle (the bot's run
   switch, a channel's active switch and Manual). It is a drawn control rather
   than the browser's checkbox: the native input stays in the DOM for state,
   keyboard and the harnesses, is visually hidden, and the track, thumb and label
   are what the operator sees. The focus ring moves to the track so §10's
   visible-indicator rule holds on a control whose box is no longer the input. */
.f-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .78rem;
  color: var(--fg2);
  cursor: pointer;
}
.f-switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.f-switch-track {
  position: relative;
  width: 30px;
  height: 17px;
  flex: none;
  border-radius: 999px;
  background: var(--bg4);
  border: 1px solid rgba(255, 255, 255, .07);
  transition: background var(--tr), border-color var(--tr);
}
.f-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--fg3);
  transition: transform var(--tr), background var(--tr);
}
.f-switch input:checked + .f-switch-track {
  background: var(--acc);
  border-color: var(--acc);
}
.f-switch input:checked + .f-switch-track .f-switch-thumb {
  transform: translateX(13px);
  background: #fff;
}
.f-switch input:focus-visible + .f-switch-track {
  outline: 2px solid var(--acc2);
  outline-offset: 2px;
}
@media (max-width: 720px) {
  .ov-split { grid-template-columns: 1fr; }
  .ov-facts { grid-template-columns: 1fr; }
  /* Two lines instead of squeezing five columns into a phone: the timestamp and
     the amount on the first, the kind and the product on the second. The dot
     keeps its own column across both, so the rail stays continuous. */
  .activity-row {
    grid-template-columns: 8px minmax(0, 1fr) auto;
    grid-template-areas:
      "dot when amt"
      "dot kind what";
    row-gap: 6px;
  }
  .activity-dot { grid-area: dot; align-self: start; margin-top: 5px; }
  .activity-when { grid-area: when; }
  .activity-amt { grid-area: amt; justify-self: end; }
  .activity-kind { grid-area: kind; }
  .activity-what { grid-area: what; text-align: right; }
}

/* ── Automation Playground (plan: plans/automation-playground.md) ────────────
   The third area's own surface and nothing else: a frame that *is* the browser, and
   the two cards beside it — the launch form and the session list. Everything else on
   the pane is the console's markup (`.tab-tools`, `table`, `.inp`, `.btn-s`), which
   is why this block is small and names only what does not already exist.

   The frame is drawn by polling the session's own `frame.jpg`, so the picture here is
   one JPEG at a time — no video element, no socket, and a session nobody is watching
   produces no frames at all (`features/playground.js`). */

.pg-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 12px;
  padding: 12px 15px 16px;
  align-items: start;
}
.pg-live { display: flex; flex-direction: column; gap: 8px; min-width: 0; }

.pg-frame-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 340px;
  background: #05070c;
  border: 1px solid var(--bg3);
  border-radius: 8px;
  overflow: hidden;
}
.pg-frame-wrap img { display: none; max-width: 100%; max-height: 68vh; }
.pg-frame-wrap img[src] { display: block; }
.pg-frame-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  color: var(--fg3);
  font-size: .74rem;
}
.pg-live-meta { min-height: 15px; font-family: var(--mono); }

.pg-side { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.pg-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 11px 12px;
  background: var(--bg2);
  border: 1px solid var(--bg3);
  border-radius: 8px;
}
.pg-card h4 { margin: 0; font-size: .76rem; font-weight: 600; color: var(--fg2); }
.pg-field { display: flex; flex-direction: column; gap: 4px; font-size: .68rem; color: var(--fg3); }
.pg-field .inp { width: 100%; }

/* Named for what it is: the panel's own list of sessions, not one of the console's
   searchable account tables (`.pg-table` would read as a sixth tab model — see
   `test_design_tokens.py::test_there_is_only_one_tab_model`). */
.pg-sessions { max-height: 320px; overflow: auto; }
.pg-sessions table { width: 100%; }
.pg-sessions th, .pg-sessions td { padding: 5px 6px; font-size: .7rem; }
.pg-row-acts { display: flex; gap: 4px; justify-content: flex-end; }
.pg-row-watched td { background: var(--bg3); }
.pg-note { color: var(--r-text); font-size: .66rem; }

/* The recorder (P2). REC is a *visible state*, not a hidden mode: the dot, the label
   and the frame's own border all say the same thing, because a panel that captures
   clicks and keys silently is the trap §7 names. */
.pg-rec { gap: 6px; }
.pg-rec .pg-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fg3);
  flex: 0 0 auto;
}
.pg-rec.on { border-color: #e5484d; color: #ffd7d7; }
.pg-rec.on .pg-dot { background: #e5484d; box-shadow: 0 0 8px #e5484d; }
.pg-frame-wrap.armed { border-color: #e5484d; box-shadow: inset 0 0 0 1px rgba(229, 72, 77, .35); }
.pg-frame-wrap:focus-visible { outline: 1px solid #e5484d; }
.pg-frame-wrap.armed img { cursor: crosshair; }

/* The optimistic cursor dot: drawn at once, confirmed by the row that follows. */
.pg-cursor {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border: 2px solid #e5484d;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  transition: opacity .4s ease-out;
}

/* The action card's head: the title on the left, the two export files on the right
   (§198 — the recap leaves the panel as JSON for a builder and CSV for a person). */
.pg-card-head { display: flex; align-items: center; gap: 6px; }
.pg-card-head h4 { flex: 1 1 auto; }
.pg-export { display: flex; gap: 4px; flex: 0 0 auto; }

.pg-act-label { color: var(--fg2); font-size: .7rem; margin-bottom: 2px; }

.pg-act-tools { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.pg-act-tools .pg-grow { flex: 1 1 140px; min-width: 120px; }
.pg-act-tools .pg-num { width: 52px; }
.pg-inline { display: inline-flex; align-items: center; gap: 3px; }

/* The action list: the same `.tbl-wrap` the console uses, scrolled rather than
   paginated — a recording is read as one run of actions, newest first. */
.pg-actions { max-height: 320px; overflow: auto; }
.pg-actions table { width: 100%; }
.pg-actions th, .pg-actions td { padding: 5px 6px; font-size: .7rem; vertical-align: top; }
.pg-sel { font-family: var(--mono); font-size: .68rem; word-break: break-all; }
.pg-val { font-family: var(--mono); font-size: .66rem; color: var(--fg3); word-break: break-all; }
.pg-chip {
  display: inline-block;
  margin: 2px 3px 0 0;
  padding: 0 4px;
  border-radius: 3px;
  background: var(--bg3);
  color: var(--fg3);
  font-family: var(--mono);
  font-size: .62rem;
}
.pg-shot-cell { width: 92px; }
.pg-shot { max-width: 88px; border: 1px solid var(--bg3); border-radius: 4px; display: block; }

@media (max-width: 980px) {
  .pg-grid { grid-template-columns: 1fr; }
}

/* ── the catalogue editors (§311) ──────────────────────────────────────────
   The variant form is sectioned — identity, price, the stock gate, the
   delivery — because one flat column of twelve fields with inline styles is
   what made it a form to get lost in. Sections reuse the Payments pane's card
   tone; the chips are the source table's own default delivery fields, so an
   operator picks columns instead of remembering their names. */
.cat-form { display: grid; gap: 12px; }
.cat-sec {
  display: grid;
  gap: 8px;
  min-width: 0;
  padding: 11px 13px 13px;
  border: 1px solid var(--bg4);
  border-radius: var(--rad2);
  background: var(--bg2);
}
.cat-sec > h4 {
  margin: 0;
  font-size: .63rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fg3);
}
.cat-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.cat-note { font-size: .66rem; color: var(--fg3); }
.cat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.cat-chip {
  font: inherit;
  font-size: .66rem;
  line-height: 1.6;
  padding: 2px 9px;
  border: 1px solid var(--bg4);
  border-radius: 99px;
  background: transparent;
  color: var(--fg2);
  cursor: pointer;
  transition: var(--tr);
}
.cat-chip:hover { border-color: var(--acc2); color: var(--fg); }
.cat-chip.on { border-color: var(--acc); color: var(--acc3); background: rgba(99, 102, 241, .14); }

/* A variant row under its family: tinted, indented, and aligned to the same
   columns as the family so price and stock sit where the header says they do. */
.cat-variant-row td { background: rgba(0, 0, 0, .16); }
.cat-variant-row td:first-child { padding-left: 24px; }

@media (max-width: 720px) {
  .cat-2 { grid-template-columns: 1fr; }
}


/* ── the Markdown editor (§402) ──────────────────────────────────────────────
   The operator: "saya ingin semua yang mengandung deskripsi dalam format MD saja
   editornya.. dan lengkap dengan preview". The toolbar sits above the textarea and
   the preview replaces it in place, so the editor never grows the modal and the
   field keeps the width and position it always had. */
.md-editor { display: flex; flex-direction: column; gap: 6px; }
.md-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.md-tools { display: flex; gap: 2px; flex-wrap: wrap; }
.md-tool {
  font: inherit;
  font-size: .68rem;
  line-height: 1;
  min-width: 24px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg3);
  color: var(--fg2);
  cursor: pointer;
}
.md-tool:hover { border-color: var(--acc2); color: var(--fg); }
.md-tabs { margin-left: auto; display: flex; gap: 2px; }
.md-tab {
  font: inherit;
  font-size: .66rem;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: var(--fg3);
  cursor: pointer;
}
.md-tab.active { background: var(--bg3); color: var(--fg); border-color: var(--acc2); }
.md-preview {
  min-height: 4.5rem;
  max-height: 18rem;
  overflow-y: auto;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  font-size: .74rem;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}
.md-preview a { color: var(--acc2); }
.md-preview code {
  font-family: var(--mono);
  font-size: .7rem;
  background: var(--bg3);
  padding: 1px 4px;
  border-radius: 3px;
}
.md-preview pre {
  font-family: var(--mono);
  font-size: .7rem;
  background: var(--bg3);
  padding: 8px;
  border-radius: 4px;
  overflow-x: auto;
}
.md-preview blockquote {
  margin: 4px 0;
  padding-left: 10px;
  border-left: 2px solid var(--acc2);
  color: var(--fg2);
}
/* §830: the two tags the preview did not draw. Both exist in the rendered HTML — the
   checks confirmed `<u>` and `.tg-spoiler` were present — but with no rule they looked
   like plain text, so an operator could not tell whether the syntax had worked. */
.md-preview u { text-decoration: underline; text-underline-offset: 2px; }
.md-preview .tg-spoiler {
  background: var(--bg4);
  color: transparent;
  border-radius: 3px;
  padding: 0 2px;
  transition: color .12s ease;
}
.md-preview .tg-spoiler:hover { color: var(--fg); }
/* An expandable quote reads as collapsible: a dashed edge and a caret in the margin,
   so it is visibly different from a plain quote even though the preview cannot fold. */
.md-preview blockquote[expandable] {
  border-left-style: dashed;
  position: relative;
}
.md-preview blockquote[expandable]::before {
  content: "▸";
  position: absolute;
  left: -2px;
  top: 0;
  font-size: .6rem;
  color: var(--acc2);
}
.md-empty, .md-error { color: var(--fg3); font-style: italic; }
.md-error { color: var(--red, #f87171); }


/* ── the product/tool icon (§401) ────────────────────────────────────────────
   The emoji is the one field of a family that only ever appears in Telegram, so
   the table shows it beside the name and the editor previews the actual button:
   without that, a typo stays invisible until a buyer reads it. */
.cat-icon {
  display: inline-block;
  min-width: 1.4em;
  margin-right: 4px;
  font-size: 1rem;
  line-height: 1;
  vertical-align: -1px;
}
.cat-icon-none { color: var(--fg3); font-size: .8rem; }
.cat-icon-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cat-icon-preview {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg3);
  font-size: .72rem;
  color: var(--fg);
}
.cat-icon-suggest { display: flex; gap: 3px; flex-wrap: wrap; }
.cat-icon-suggest button {
  font: inherit;
  font-size: .82rem;
  line-height: 1;
  padding: 3px 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
}
.cat-icon-suggest button:hover { border-color: var(--acc2); background: var(--bg3); }


/* ── Proton Configuration: the two sections (accordion) ──────────────────────
   Two independent switches, each with its own header checkbox. Growth recovery
   owns the eight task checkboxes; 2FA has no children. Unchecking a header
   disables everything inside it, and unchecking both leaves a bare signup. */
.pt-acc { border: 1px solid var(--brd, #2a2a33); border-radius: 8px; margin-bottom: 8px; }
.pt-acc-h { display: flex; align-items: center; gap: 10px; padding: 8px 10px; }
.pt-acc-l { display: flex; align-items: center; gap: 8px; flex: 1; cursor: pointer; font-size: .74rem; }
.pt-acc-note { font-size: .64rem; color: var(--fg3); }
.pt-acc-t { background: none; border: 0; color: var(--fg3); cursor: pointer; display: flex; padding: 2px 4px; }
.pt-acc-t:hover { color: var(--fg1); }
.pt-acc-b { padding: 2px 10px 10px 30px; border-top: 1px solid var(--brd, #2a2a33); }
.pt-acc-b > #pt-growth-tasks { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px; padding-top: 8px; }
.pt-acc-b label { font-size: .7rem; display: flex; align-items: center; gap: 6px; }
/* Collapsed: the body is hidden, the header still shows the state. */
.pt-acc.collapsed .pt-acc-b { display: none; }
.pt-acc.collapsed .pt-acc-t i, .pt-acc.collapsed .pt-acc-t svg { transform: rotate(-90deg); }
/* Disabled by its section switch: visibly inert, and the tasks stay checked so
   re-enabling the section restores exactly what the operator had. */
.pt-acc-b[data-off="1"] { opacity: .4; pointer-events: none; }


/* ── User Management: accordion + worker vitals (§504) ────────────────────────
   The modal shows one accordion per user and, inside it, the worker machines that
   user may use. `min-height` is deliberate: this modal's content is one line per
   user until an accordion is opened, and without a floor the dialog shrank to a
   strip on the screen as the list loaded and grew again when a row was expanded —
   which is what the operator asked to make "enak di pandang". `max-height` stays
   the shared 88vh so a long fleet still scrolls inside the modal. */
.modal.um-modal { min-height: min(72vh, 560px); }

.um-b { padding: 12px; }
.um-list { display: flex; flex-direction: column; gap: 8px; }
.um-empty { color: var(--fg3); font-size: .76rem; padding: 18px 6px; text-align: center; }
.um-empty.bad { color: var(--r); }
.um-refresh { font-size: .64rem; color: var(--fg3); white-space: nowrap; }

/* One user. The head is the click target for the whole row. */
.um-user {
  border: 1px solid var(--bg4);
  border-radius: 10px;
  background: var(--bg1);
  overflow: hidden;
}
.um-user.open { border-color: rgba(129, 140, 248, .45); }
.um-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  cursor: pointer;
  user-select: none;
}
.um-head:hover { background: rgba(255, 255, 255, .028); }
.um-caret { color: var(--fg3); display: inline-flex; flex: none; }
.um-user.open .um-caret { color: var(--acc2); }
.um-name { display: flex; align-items: baseline; gap: 5px; font-weight: 600; font-size: .8rem; }
.um-name small { color: var(--acc2); font-size: .62rem; font-weight: 600; }
.um-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.um-chip {
  font-size: .63rem;
  color: var(--fg2);
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--bg4);
  border-radius: 6px;
  padding: 1px 6px;
  white-space: nowrap;
}
.um-chip.bad { color: var(--r); border-color: rgba(239, 68, 68, .45); }
.um-user-acts { display: flex; align-items: center; gap: 3px; margin-left: 4px; }
.um-spacer { width: 24px; text-align: center; color: var(--fg3); font-size: .7rem; }

.um-body {
  border-top: 1px solid var(--bg4);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* One worker machine inside a user. */
.um-machine { border: 1px solid var(--bg4); border-radius: 9px; padding: 9px 10px; background: var(--bg2); }
.um-machine-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.um-mid { font-size: .68rem; color: var(--fg2); }
.um-mid-note { font-size: .63rem; color: var(--fg3); }
.um-machine-acts { margin-left: auto; display: flex; align-items: center; gap: 3px; }

/* The spec sheet: chips for the static facts, meters for the moving ones. */
.um-stats { margin-top: 8px; }
.um-stats-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.um-stats-title { display: inline-flex; align-items: center; gap: 5px; font-size: .7rem; font-weight: 600; color: var(--fg2); }
.um-age { margin-left: auto; font-size: .63rem; color: var(--fg3); }
.um-age.stale { color: var(--y); }
.um-spec { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
/* ── Mark variant (§514) ───────────────────────────────────────
   The variant a row is claimed by. A chip, not text, because it is a fixed
   assignment the operator made — the same kind of fact as the STOCK/SOLD pill
   beside it, and it reads at a glance in a grid where the neighbouring column is
   a live number. */
.variant-chip {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: .66rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--p);
  background: color-mix(in srgb, var(--p) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--p) 34%, transparent);
  white-space: nowrap;
}

.mv-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.mv-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.mv-row:hover { border-color: var(--p); background: color-mix(in srgb, var(--p) 6%, transparent); }
.mv-row input { accent-color: var(--p); }
.mv-label { flex: 1; font-size: .8rem; font-weight: 600; }
.mv-meta { font-size: .68rem; color: var(--fg3); font-variant-numeric: tabular-nums; }

.um-hint { font-size: .65rem; color: var(--fg3); margin: 2px 0 6px; }
.um-hint.warn { color: var(--y); }

.um-meters { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
.um-meter-top {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: .64rem;
  color: var(--fg3);
  margin-bottom: 3px;
}
.um-meter-top .mono { color: var(--fg2); }
.um-bar { height: 5px; border-radius: 999px; background: rgba(255, 255, 255, .07); overflow: hidden; }
.um-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--fg3); transition: width 240ms ease; }
/* Threshold tones: amber at 70%, red at 90%. The bar is the whole message, so it
   is the colour that has to carry it — the numbers sit above it. */
.um-bar.ok > span { background: var(--g); }
.um-bar.warn > span { background: var(--y); }
.um-bar.bad > span { background: var(--r); }

.um-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.um-acts-user { border-top: 1px dashed var(--bg4); padding-top: 9px; }


/* ── Run's worker badge + the fleet strip (§507) ──────────────────────────────
   The Running tab is where an operator asks "is it running, and on what". The
   badge resolves the run's `machine` against the fleet; `manager` is spelled out
   because since §502 it is the anomaly, not the default. */
.run-worker {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--bg4);
  border-radius: 4px;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .3px;
  color: var(--fg2);
  background: rgba(16, 185, 129, .10);
  border-color: rgba(16, 185, 129, .35);
  cursor: pointer;
}
.run-worker:hover { background: rgba(16, 185, 129, .2); }
.run-worker.manager {
  background: rgba(245, 158, 11, .12);
  border-color: rgba(245, 158, 11, .45);
  color: var(--y);
  cursor: help;
}
.run-worker.unknown {
  background: rgba(244, 63, 94, .12);
  border-color: rgba(244, 63, 94, .45);
  color: var(--r);
}

/* The fleet as one control beside Action (§512). The per-machine numbers moved into
   the modal it opens — a row of full-width chips above the table was too long. */
#run-fleet-btn .n {
  font-size: .62rem; font-weight: 700; color: var(--fg2);
  background: rgba(255, 255, 255, .07); border-radius: 5px; padding: 0 5px; margin-left: 2px;
}
#run-fleet-btn.warn { border-color: rgba(245, 158, 11, .55); }
#run-fleet-btn.warn .n { color: var(--y); background: rgba(245, 158, 11, .12); }

/* ── the fleet modal (§512): a table, so columns line up and compare ─────── */
.modal.fl-modal { max-width: 1000px; min-height: min(62vh, 420px); }
.fl-body { display: flex; flex-direction: column; gap: 10px; }
.fl-count { font-size: .72rem; font-weight: 600; color: var(--fg3); }
.fl-wrap { overflow-x: auto; border: 1px solid var(--bg4); border-radius: 10px; }
.fl-table { width: 100%; border-collapse: collapse; font-size: .7rem; }
.fl-table th {
  text-align: left; font-size: .6rem; text-transform: uppercase; letter-spacing: .5px;
  color: var(--fg3); font-weight: 700; padding: 7px 9px; border-bottom: 1px solid var(--bg4);
  white-space: nowrap; background: rgba(255, 255, 255, .02);
}
.fl-table td { padding: 7px 9px; border-bottom: 1px solid rgba(255, 255, 255, .04); vertical-align: top; }
.fl-table tr:last-child td { border-bottom: none; }
.fl-table .ta-r { text-align: right; }
.fl-row { cursor: pointer; }
.fl-row:hover { background: rgba(255, 255, 255, .035); }
.fl-name b { color: var(--fg1); font-size: .72rem; }
.fl-mid { font-size: .6rem; color: var(--fg3); font-family: ui-monospace, monospace; }
.fl-flag { font-size: .55rem; font-weight: 700; padding: 0 4px; border-radius: 4px; margin-left: 5px; }
.fl-flag.bad { color: var(--r); background: rgba(244, 63, 94, .14); }
.fl-low { color: var(--y); font-weight: 600; }

/* ── Machine detail modal (§507) ─────────────────────────────────────────── */
.modal.md-modal { max-width: 900px; min-height: min(66vh, 480px); }
.md-body { display: flex; flex-direction: column; gap: 10px; }
.md-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 760px) { .md-grid { grid-template-columns: 1fr; } }
.md-block { border: 1px solid var(--bg4); border-radius: 10px; padding: 10px; background: var(--bg1); }
.md-h {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: .72rem; font-weight: 700; color: var(--fg2);
  margin-bottom: 7px; padding-bottom: 6px; border-bottom: 1px solid var(--bg4);
}
.md-fleet { display: flex; flex-direction: column; gap: 4px; }
.md-fleet-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: .66rem; }
.md-fleet-row code { font-size: .64rem; color: var(--fg2); }
.md-fleet-row.this { border-left: 2px solid var(--acc2); padding-left: 6px; }
.um-machine-head[data-act="machine-detail"] { cursor: pointer; border-radius: 8px; }
.um-machine-head[data-act="machine-detail"]:hover { background: rgba(255, 255, 255, .03); }

/* The manager's own card (§510): a real machine, deliberately not in the worker fleet.
   Dashed so it reads as a different kind of thing from the fleet cards above it. */
.um-manager { border-style: dashed; }
.um-manager .um-head { cursor: default; }
.um-manager .um-head:hover { background: transparent; }

/* ── Proxy pool: a thin bar, a table of connections (§657) ─────────────────── */
.pool-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--bg2); margin-bottom: 8px;
}
.pool-bar-text { font-size: .72rem; color: var(--fg2); }
.pool-bar-spacer { flex: 1; }
.pool-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--fg3); opacity: .45;
}
.pool-dot.on { background: var(--g); opacity: 1; box-shadow: 0 0 6px var(--g); }

.pool-add-row { display: flex; gap: 6px; margin-bottom: 8px; }
.pool-add-provider {
  width: 150px; flex: none; font-size: .72rem; padding: 5px 8px;
  background: var(--bg2); border: 1px solid var(--line);
  border-radius: var(--r); color: var(--fg1);
}
.pool-add-input {
  flex: 1; min-width: 0; font-size: .72rem; padding: 5px 8px;
  background: var(--bg1); border: 1px solid var(--line);
  border-radius: var(--r); color: var(--fg1); font-family: monospace;
}

.pool-table {
  width: 100%; border-collapse: collapse; font-size: .72rem;
  border: 1px solid var(--line); border-radius: var(--r); overflow: hidden;
}
.pool-table th {
  text-align: left; font-weight: 600; font-size: .64rem; letter-spacing: .04em;
  text-transform: uppercase; color: var(--fg3); padding: 6px 10px;
  background: var(--bg2); border-bottom: 1px solid var(--line);
}
.pool-table td { padding: 6px 10px; border-bottom: 1px solid var(--line); }
.pool-table tr:last-child td { border-bottom: none; }
.pool-muted { color: var(--fg3); }
.pool-host { font-family: monospace; font-size: .68rem; }
/* Bandwidth left, straight in the row — no separate card above the table.
   Credentials are not in the table at all; they live in the Edit modal. */
.pool-bw { color: var(--fg); font-variant-numeric: tabular-nums; }
.pool-bw-err { color: var(--r); font-size: .64rem; }
.pool-row-act { display: flex; gap: 3px; justify-content: flex-end; }
.pool-empty { text-align: center; color: var(--fg3); padding: 14px; }

.pool-led {
  display: block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--fg3); opacity: .35;
}
.pool-led.ok { background: var(--g); opacity: 1; box-shadow: 0 0 5px var(--g); }
.pool-led.bad { background: var(--r); opacity: 1; }

/* Read-only provider observation moved into the connection row (§875): bandwidth
   left is one cell, so the card that used to sit above the table is gone. */
@media (max-width: 720px) {
  .pool-table { display: block; overflow-x: auto; }
}

/* §829: the icon strip above a Content field. Small, wrapping, and quiet — it is a
   convenience beside the textarea, not a second toolbar competing with the Markdown
   one below it. */

/* One content card at a time; `hidden` is the switch, so no transition is needed. */
.content-card { min-width: 0; }
.content-card .pay-card-f { margin-top: 4px; }
.content-saved { color: var(--fg3); }
