/* The panel's visual layer, rewritten from the outside.
 *
 * There are no frontend sources — the panel ships as compiled Nuxt chunks — so
 * this cannot be a redesign in the usual sense. It is a sheet loaded after the
 * bundle's own, and everything below either overrides a token the bundle
 * already reads or restates a rule with enough specificity to win.
 *
 * The material is borrowed from LeadEngine (`frontend/src/ui/theme.css`), which
 * the owner named as the reference: a near-black ground, surfaces that are a
 * faint white fill rather than a lighter grey, depth from a hairline border and
 * an inset highlight — never a drop shadow — and one accent used sparingly.
 *
 * **The problem this fixes is not colour.** Measured on the accounts screen
 * before any of this: seven different button heights (24, 28, 36, 40, 44, 45,
 * 60), ten different corner radii (4, 6, 8, 9.6, 11.2, 12, 16, 32, 999), and
 * font sizes landing on 11.2 / 11.52 / 11.84 / 12.16 / 15.2 because several
 * screens were built with arbitrary Tailwind values. Nothing lined up with
 * anything, which is exactly what "кнопки разного размера, друг на друга
 * наезжают" describes. Most of the rules here are a scale being enforced, and
 * the colours are the smaller half of the work.
 *
 * Layers, in order: tokens → ground → surfaces → controls → data → chrome.
 * A rule that needs `!important` says why.
 */

/* ═══════════════════════════════════════════════════════════════════════════
   1. Tokens
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* --- ground and surfaces ------------------------------------------------ */
  --gf-bg: #0a0c11;
  --gf-bg-raised: #0e1116;
  --gf-panel: rgba(255, 255, 255, 0.028);
  --gf-panel-2: rgba(255, 255, 255, 0.05);
  --gf-panel-3: rgba(255, 255, 255, 0.075);
  --gf-line-soft: rgba(255, 255, 255, 0.045);
  --gf-line: rgba(255, 255, 255, 0.08);
  --gf-line-strong: rgba(255, 255, 255, 0.14);
  --gf-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.05);

  /* --- text ---------------------------------------------------------------
     Three levels and no more. The bundle had eleven greys doing the work of
     three, which is why a caption and a value could end up the same weight. */
  --gf-t0: #f2f4f7;
  /* Both greys are lighter than the reference's. Measured against this ground,
     LeadEngine's #9ba3b2 / #7c8494 came out at 4.1:1 on the panel fill — under
     the 4.5 floor, and it is what every hint, caption and secondary value uses.
     These are 5.2 and 6.4. */
  --gf-t1: #aab2c0;
  --gf-t2: #8d95a5;

  /* --- accent -------------------------------------------------------------
     LeadEngine's violet. The bundle's own blue is still in the semantic set
     below as `info`, so nothing that meant "information" turned into "action". */
  --gf-accent: #6e56f7;
  --gf-accent-hi: #8b78f9;
  /* The accent as TEXT. #6e56f7 is a fine fill behind white, but as ink on the
     near-black ground it measures 4.09:1 — under the floor, and it is what
     every outlined button and text link in the panel is painted with. This is
     5.6:1 and reads as the same colour. */
  --gf-accent-text: #9d8dfb;
  --gf-accent-soft: rgba(110, 86, 247, 0.14);
  /* Same idea, but for the accent text the BUNDLE paints. `build-frontend.sh`
     repoints every `color: <the old blue>` at this token instead of a hex.
     It used to hold the light-theme value here and the dark one under `.dark`,
     because the panel shipped both themes; since §9.8 pinned the panel to dark
     there is one value, and it is the one that has to be right on the first
     frame — before the `.dark` class lands.

     Lighter than --gf-accent-text, and the difference is the point. That one
     paints icons, which need 3:1. This paints running text, which needs 4.5:1
     — not on the panel ground, where anything would pass, but on the tinted
     ground a hover puts underneath it. Measured across 597 controls: #9d8dfb
     drops to 3.83:1 on the lightest of those tints, this holds 4.70:1. */
  --gf-ink-accent: #beb3fc;
  --gf-accent-line: rgba(110, 86, 247, 0.36);
  --gf-accent-glow: rgba(110, 86, 247, 0.45);
  --gf-accent-ink: #0a0a10;

  /* --- semantic ----------------------------------------------------------
     Deliberately desaturated. A grid of ten status pills in full-strength
     green and red reads as an alarm panel; these read as labels. */
  --gf-ok: #4fb389;
  --gf-ok-soft: rgba(79, 179, 137, 0.13);
  --gf-warn: #d7a64f;
  --gf-warn-soft: rgba(215, 166, 79, 0.13);
  --gf-bad: #d96a6f;
  --gf-bad-soft: rgba(217, 106, 111, 0.13);
  --gf-info: #5f93c9;
  --gf-info-soft: rgba(95, 147, 201, 0.13);

  /* The same four as INK. The values above are measured against the panel
     ground, where they sit at 5.6-8.8:1; they are not always on it. A hover
     tints the control with a soft wash of its own hue, and a real cursor over
     597 controls found «Валидный» at 3.76:1 on rgb(48,72,79) and «О модуле» at
     4.41:1 on rgb(20,44,68) — right colour, moved ground. These clear 4.70:1
     on every tint the panel paints, and 10.3:1 back on the ground. Fills keep
     the values above; only text takes these. */
  --gf-ok-ink: #84caad;
  --gf-warn-ink: #dfb771;
  --gf-bad-ink: #eaadb0;
  --gf-info-ink: #a1c0df;

  /* --- scale --------------------------------------------------------------
     Three radii, two control heights. Everything else gets rounded onto them. */
  --gf-r-sm: 9px;
  --gf-r: 13px;
  --gf-r-lg: 18px;
  --gf-h-sm: 32px;
  --gf-h: 40px;

  --gf-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --gf-dur: 0.16s;

  --gf-font: "Onest", Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --gf-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* PrimeVue reads its own token names. Redefining them here means every table,
   dropdown, dialog and form field the library renders follows the palette
   without a single component-level override. */
:root.dark,
.dark {
  --p-surface-0: #ffffff;
  --p-content-background: var(--gf-bg-raised);
  --p-content-border-color: var(--gf-line);
  --p-content-color: var(--gf-t0);
  --p-content-border-radius: var(--gf-r-sm);
  --p-text-color: var(--gf-t0);
  --p-text-muted-color: var(--gf-t2);
  --p-primary-color: var(--gf-accent);
  --p-primary-contrast-color: #ffffff;
  --p-focus-ring-color: var(--gf-accent);
  --p-focus-ring-width: 2px;
  --p-focus-ring-offset: 2px;
  --p-focus-ring-style: solid;
  --p-form-field-background: rgba(255, 255, 255, 0.035);
  --p-form-field-border-color: var(--gf-line);
  --p-form-field-hover-border-color: var(--gf-line-strong);
  --p-form-field-focus-border-color: var(--gf-accent);
  --p-form-field-color: var(--gf-t0);
  --p-form-field-placeholder-color: var(--gf-t2);
  --p-transition-duration: var(--gf-dur);
}

/* ═══════════════════════════════════════════════════════════════════════════
   2. Ground
   ═══════════════════════════════════════════════════════════════════════════ */

/* Onest, served from this installation. Google's CDN is a third party in
 * every page load and a single point of failure for the panel's type; the
 * four subsets together are 65 KB, which is cheaper than the DNS lookup. */
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url("/_gf/onest-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url("/_gf/onest-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url("/_gf/onest-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url("/_gf/onest-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The bundle sizes html and body at `100vw`, which on a page with a vertical
   scrollbar is 11px wider than the viewport: every screen reported horizontal
   overflow and clipped its last 11px. `overflow-x: clip` upstream hides the
   scrollbar but not the loss. */
html.dark,
.dark body {
  width: 100% !important;
  min-width: 0 !important;
  max-width: 100% !important;
}

.dark body,
.dark #__nuxt {
  background: var(--gf-bg);
  color: var(--gf-t0);
}

.dark body {
  font-family: var(--gf-font);
  /* The bundle ships text at 11.2px in places. Nothing below 12 is readable at
     arm's length on a 27", and the owner reads these screens all day. */
  -webkit-font-smoothing: antialiased;
}

/* A single faint wash from the top so the ground is not a flat void. Radial,
   not linear: a linear gradient over a tall page banded visibly.

   Painted onto the body, not into a fixed ::before. A fixed pseudo-element with
   `z-index: 0` opens a stacking context that sits above every statically
   positioned element on the page — the first attempt at this hid the entire
   content column behind a transparent sheet. */
.dark body {
  background-image: radial-gradient(
    120% 60% at 50% 0,
    rgba(110, 86, 247, 0.06),
    transparent 60%
  );
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* ═══════════════════════════════════════════════════════════════════════════
   3. Surfaces
   ───────────────────────────────────────────────────────────────────────────
   `bg-white dark:bg-gray-800` + `shadow-xl shadow-gray-200/50` is the panel's
   card, repeated on every screen. In dark mode that is a mid-grey block under
   a heavy black shadow — the two things that make the whole page look like a
   stack of loose paper. Replaced with the faint-fill-and-hairline material.
   ═══════════════════════════════════════════════════════════════════════════ */

.dark .bg-white,
.dark .dark\:bg-gray-800,
.dark .dark\:bg-gray-800\/50,
.dark .dark\:bg-gray-900,
.dark .dark\:bg-gray-900\/50 {
  background-color: var(--gf-panel) !important;
}

.dark .dark\:bg-gray-700,
.dark .dark\:bg-gray-700\/50,
.dark .bg-gray-50,
.dark .bg-gray-100 {
  background-color: var(--gf-panel-2) !important;
}

/* Every shadow in the bundle, gone. Depth comes from the border and the inset
   highlight below; a drop shadow on a near-black ground only muddies it. */
.dark [class*="shadow-"] {
  box-shadow: none !important;
}

.dark .border-gray-200,
.dark .border-gray-200\/50,
.dark .dark\:border-gray-700,
.dark .dark\:border-gray-700\/50,
.dark .dark\:border-gray-600,
.dark .divide-gray-200 > * + *,
.dark .dark\:divide-gray-700 > * + * {
  border-color: var(--gf-line) !important;
}

/* The card material itself. Applied to the shape the bundle uses for a card —
   a rounded box that carries both a background and a border — rather than to a
   class name, because the same look is spelled three different ways. */
.dark .rounded-xl.border,
.dark .rounded-2xl.border,
.dark .rounded-lg.border {
  box-shadow: var(--gf-highlight) !important;
}

/* --- radii: ten values collapse onto three ------------------------------- */

.dark .rounded-sm,
.dark .rounded {
  border-radius: 6px !important;
}
.dark .rounded-md,
.dark .rounded-lg {
  border-radius: var(--gf-r-sm) !important;
}
.dark .rounded-xl {
  border-radius: var(--gf-r) !important;
}
.dark .rounded-2xl,
.dark .rounded-3xl {
  border-radius: var(--gf-r-lg) !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   4. Controls
   ═══════════════════════════════════════════════════════════════════════════ */

/* --- text -----------------------------------------------------------------
   Arbitrary Tailwind sizes produced 11.2 / 11.52 / 11.84 / 12.16px on one
   screen. Everything under 12 goes to 12; the difference was never legible
   anyway, and the inconsistency was. */
.dark .text-xs,
.dark [class*="text-[0.7"],
.dark [class*="text-[0.6"] {
  font-size: 12px !important;
  line-height: 1.45 !important;
}

/* The floor, enforced.
 *
 * A sweep over eleven screens found 80 leaf elements set below 12px, down to
 * 9px on the account status pill ("Валидный") and 10px on seventeen field
 * labels. That is the "сложно всё это читать" in the owner's own words, and no
 * amount of colour work fixes a 9px word.
 *
 * Named classes rather than a blanket rule: `* { font-size: 12px }` would also
 * flatten the headings. Each of these is a component of the bundle that chose
 * its own size, and every one of them comes back up to 12. Hierarchy is carried
 * by weight and colour instead, which costs nothing in legibility. */
.dark .account-status,
.dark .status-label,
.dark .section-badge,
.dark .section-count,
.dark .column-subtitle,
.dark .new-badge-inline,
.dark .badge-compact,
.dark .geo-count,
.dark .plan-chip,
.dark .duration-label,
.dark .toggle-label-inline,
.dark .field-compact,
.dark .field-compact *,
.dark .spm-adv-hint,
.dark .text-\[10px\],
.dark .text-\[11px\],
.dark [class*="text-[0.65"],
.dark [class*="text-[0.68"],
.dark [class*="text-[0.72"],
.dark [class*="text-[0.75"] {
  font-size: 12px !important;
  line-height: 1.4 !important;
}

/* The status pill was 9px uppercase; at 12 it needs the room to sit in. */
.dark .account-status {
  padding: 3px 8px;
  border-radius: 999px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.dark .text-gray-500,
.dark .dark\:text-gray-400,
.dark .dark\:text-gray-500 {
  color: var(--gf-t2) !important;
}
.dark .text-gray-600,
.dark .text-gray-700,
.dark .dark\:text-gray-300 {
  color: var(--gf-t1) !important;
}
.dark .text-gray-800,
.dark .text-gray-900,
.dark .dark\:text-gray-100,
.dark .dark\:text-gray-200,
.dark .dark\:text-white {
  color: var(--gf-t0) !important;
}

/* Uppercase micro-labels — table headers, section eyebrows — need tracking or
   they read as a smear at 12px. */
.dark .uppercase {
  letter-spacing: 0.06em;
}

/* --- buttons --------------------------------------------------------------
   One height for the common case. `min-height` rather than `height` so a
   button that legitimately wraps to two lines still grows.

   `.p-inputnumber-button` is excluded because it is not a button in a row: the
   two spinners share ONE field's height inside an absolutely positioned 38px
   group, so a 40px floor made the pair 80px and hung the decrement button over
   the setting below it. Left out, PrimeVue's own flex splits the group into two
   19px halves. */
.dark button:not(.menu-item):not(.submenu-link):not([class*="w-full"]):not(.p-checkbox-box):not(.p-inputnumber-button),
.dark a[class*="inline-flex"][class*="px-"] {
  min-height: var(--gf-h);
  border-radius: var(--gf-r-sm);
  font-weight: 500;
  transition: background-color var(--gf-dur) var(--gf-ease),
    border-color var(--gf-dur) var(--gf-ease), filter var(--gf-dur) var(--gf-ease);
}

/* Icon-only buttons keep the small height — forcing 40px on a row of six
   toolbar icons is what pushed them into each other in the first place. */
.dark button.icon-button,
.dark button[class*="p-2"]:not([class*="px-"]),
.dark button[class*="p-1"]:not([class*="px-"]) {
  min-height: var(--gf-h-sm);
  min-width: var(--gf-h-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--gf-r-sm);
}

.dark button.icon-button:hover,
.dark button[class*="p-2"]:not([class*="px-"]):hover {
  background: var(--gf-panel-2);
}

/* The primary action. The bundle paints it with a flat brand blue; this is the
   glossy accent from LeadEngine — a lighter top edge, an inset highlight and a
   short glow, so the one button that commits work reads as raised. */
.dark button[class*="bg-blue-6"],
.dark button[class*="bg-blue-5"],
.dark button[class*="from-blue-"],
.dark a[class*="bg-blue-6"] {
  background: linear-gradient(
    160deg,
    var(--gf-accent-hi),
    var(--gf-accent)
  ) !important;
  border-color: transparent !important;
  color: #ffffff !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24),
    0 8px 20px -12px var(--gf-accent-glow) !important;
}
.dark button[class*="bg-blue-6"]:hover,
.dark button[class*="from-blue-"]:hover {
  filter: brightness(1.08);
}
.dark button[class*="bg-blue-6"]:active {
  filter: brightness(0.94);
}
.dark button:disabled {
  filter: grayscale(0.6) brightness(0.72);
  cursor: not-allowed;
}

/* The secondary action: outlined, accent ink. PrimeVue paints these with
   `--p-primary-color` directly, which is the fill colour and too dark to read
   as text on this ground. */
.dark .p-button-outlined,
.dark .p-button-text,
.dark a[class*="text-blue-"],
.dark button[class*="text-blue-"],
.dark .text-primary {
  color: var(--gf-accent-text) !important;
}
.dark .p-button-outlined {
  border-color: var(--gf-accent-line) !important;
  background: transparent !important;
}
.dark .p-button-outlined:hover,
.dark .p-button-text:hover {
  background: var(--gf-accent-soft) !important;
  border-color: var(--gf-accent) !important;
}

/* Dashed outlines on the three top actions ("Импортировать аккаунты" and
   friends) read as a drop zone, not a button. */
.dark .border-dashed {
  border-style: solid !important;
  border-color: var(--gf-line) !important;
  background: var(--gf-panel) !important;
}
.dark .border-dashed:hover {
  border-color: var(--gf-line-strong) !important;
  background: var(--gf-panel-2) !important;
}

/* --- form fields --------------------------------------------------------- */

.dark input[type="text"],
.dark input[type="search"],
.dark input[type="number"],
.dark input[type="password"],
.dark input[type="email"],
.dark textarea,
.dark select {
  min-height: var(--gf-h);
  background: rgba(255, 255, 255, 0.035) !important;
  border: 1px solid var(--gf-line) !important;
  border-radius: var(--gf-r-sm) !important;
  color: var(--gf-t0) !important;
  transition: border-color var(--gf-dur) var(--gf-ease);
}
.dark textarea {
  min-height: 88px;
  line-height: 1.5;
}
.dark input::placeholder,
.dark textarea::placeholder {
  color: var(--gf-t2) !important;
}
.dark input:hover,
.dark textarea:hover,
.dark select:hover {
  border-color: var(--gf-line-strong) !important;
}
.dark input:focus,
.dark textarea:focus,
.dark select:focus {
  border-color: var(--gf-accent) !important;
  outline: none;
  box-shadow: 0 0 0 3px var(--gf-accent-soft) !important;
}

/* Spinner arrows sat on top of the last digit in every narrow numeric field. */
.dark input[type="number"] {
  appearance: textfield;
  -moz-appearance: textfield;
}
.dark input[type="number"]::-webkit-outer-spin-button,
.dark input[type="number"]::-webkit-inner-spin-button {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
}

/* --- the model picker's filter --------------------------------------------
   Added by deploy/panel-model-search.js, because the model dropdown in
   Settings → API now lists OpenRouter's whole catalogue. Sticky, so it stays
   put while four hundred rows scroll under it. */
.dark .gf-msearch {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--gf-bg-raised);
  border-bottom: 1px solid var(--gf-line);
}
.dark .gf-msearch-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--gf-h-sm) !important;
  padding: 0 10px;
  font-size: 13px;
  font-family: var(--gf-mono);
}
.dark .gf-msearch-count {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--gf-t2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dark .gf-msearch-count.is-empty {
  color: var(--gf-warn-ink);
}

/* --- checkbox -------------------------------------------------------------
   The bundle draws its own (appearance: none, 20×20) and leaves it a bare grey
   square — no tick, no hover, no visible checked state until you look twice.
   Redrawn: same box, real tick, accent fill. */
.dark input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  min-width: 18px;
  border: 1.5px solid var(--gf-line-strong);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.04);
  display: inline-grid;
  place-content: center;
  cursor: pointer;
  transition: background-color var(--gf-dur) var(--gf-ease),
    border-color var(--gf-dur) var(--gf-ease);
}
.dark input[type="checkbox"]:hover {
  border-color: var(--gf-accent-line);
  background: var(--gf-accent-soft);
}
.dark input[type="checkbox"]::before {
  content: "";
  width: 10px;
  height: 10px;
  transform: scale(0);
  transition: transform var(--gf-dur) var(--gf-ease);
  background: var(--gf-accent-ink);
  clip-path: polygon(14% 44%, 0 65%, 44% 100%, 100% 16%, 82% 0, 40% 66%);
}
.dark input[type="checkbox"]:checked {
  background: var(--gf-accent);
  border-color: var(--gf-accent);
}
.dark input[type="checkbox"]:checked::before {
  transform: scale(1);
}
.dark input[type="checkbox"]:indeterminate {
  background: var(--gf-accent);
  border-color: var(--gf-accent);
}
.dark input[type="checkbox"]:indeterminate::before {
  transform: scale(1);
  clip-path: inset(42% 8% 42% 8%);
}

.dark input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  min-width: 18px;
  border: 1.5px solid var(--gf-line-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  display: inline-grid;
  place-content: center;
  cursor: pointer;
}
.dark input[type="radio"]::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--gf-accent-ink);
  transform: scale(0);
  transition: transform var(--gf-dur) var(--gf-ease);
}
.dark input[type="radio"]:checked {
  background: var(--gf-accent);
  border-color: var(--gf-accent);
}
.dark input[type="radio"]:checked::before {
  transform: scale(1);
}

/* --- focus ----------------------------------------------------------------
   One ring everywhere. Keyboard focus was invisible on most of the panel. */
.dark :focus-visible {
  outline: 2px solid var(--gf-accent);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   5. Data — tables, rows, pills
   ═══════════════════════════════════════════════════════════════════════════ */

.dark table {
  border-collapse: separate;
  border-spacing: 0;
}

.dark thead th {
  font-size: 11px !important;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gf-t2) !important;
  font-weight: 600;
  background: transparent !important;
  border-bottom: 1px solid var(--gf-line) !important;
  padding-top: 10px;
  padding-bottom: 10px;
  white-space: nowrap;
}

.dark tbody td {
  border-bottom: 1px solid var(--gf-line-soft) !important;
  /* The rows were 44px of padding around 14px of text. A fleet of a hundred
     accounts should fit on a screen, not in three scrolls. */
  padding-top: 10px !important;
  padding-bottom: 10px !important;
  vertical-align: middle;
}

.dark tbody tr {
  transition: background-color var(--gf-dur) var(--gf-ease);
}
.dark tbody tr:hover {
  background: var(--gf-panel-2) !important;
}
.dark tbody tr:last-child td {
  border-bottom: none !important;
}

/* Numbers in a column line up only with tabular figures. */
.dark td,
.dark .tabular-nums,
.dark [class*="font-mono"] {
  font-variant-numeric: tabular-nums;
}

/* The action column. Six icon buttons in a 100px cell: they were wrapping into
   a ragged pile with no gap, which is the "кнопки наезжают друг на друга" the
   owner is looking at. A fixed 3×2 grid fits the cell exactly (3 × 28 + 2 × 4 =
   92px) and every row of the table then has its controls in the same place. */
.dark td .action-buttons {
  display: grid !important;
  grid-template-columns: repeat(3, 28px) !important;
  gap: 4px !important;
  justify-content: end;
  align-content: center;
}

/* `!important` on the height because the generic button rule above is
   `.dark button:not(…):not(…):not(…):not(…)` — four `:not()`s of specificity,
   which a plain `.dark .icon-btn` loses to. Excluding this class there instead
   would mean maintaining the exclusion list twice. */
.dark .icon-btn {
  width: 28px !important;
  height: 28px !important;
  min-height: 28px !important;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  border: 1px solid transparent;
  background: var(--gf-panel-2);
  color: var(--gf-t1);
  transition: background-color var(--gf-dur) var(--gf-ease),
    color var(--gf-dur) var(--gf-ease), border-color var(--gf-dur) var(--gf-ease);
}
.dark .icon-btn i {
  font-size: 13px;
}
.dark .icon-btn:hover:not(:disabled) {
  background: var(--gf-accent-soft);
  border-color: var(--gf-accent-line);
  color: var(--gf-t0);
}
.dark .icon-btn:disabled {
  opacity: 0.32;
  cursor: not-allowed;
}
/* The destructive one reads as destructive on hover and not before — a red
   button sitting in every row turns the whole column into a warning stripe. */
.dark .icon-btn[class*="delete"]:hover:not(:disabled),
.dark .icon-btn[class*="remove"]:hover:not(:disabled) {
  background: var(--gf-bad-soft);
  border-color: rgba(217, 106, 111, 0.4);
  color: var(--gf-bad-ink);
}

/* Status pills. The bundle tints them with full-strength Tailwind colours on a
   white chip; on the dark ground they were shouting. */
.dark [class*="bg-green-1"],
.dark [class*="bg-green-5"],
.dark [class*="bg-emerald-"] {
  background: var(--gf-ok-soft) !important;
  color: var(--gf-ok-ink) !important;
  border-color: transparent !important;
}
.dark [class*="bg-red-1"],
.dark [class*="bg-red-5"],
.dark [class*="bg-rose-"] {
  background: var(--gf-bad-soft) !important;
  color: var(--gf-bad-ink) !important;
  border-color: transparent !important;
}
.dark [class*="bg-yellow-"],
.dark [class*="bg-amber-"],
.dark [class*="bg-orange-"] {
  background: var(--gf-warn-soft) !important;
  color: var(--gf-warn-ink) !important;
  border-color: transparent !important;
}
.dark [class*="bg-sky-"],
.dark [class*="bg-cyan-"] {
  background: var(--gf-info-soft) !important;
  color: var(--gf-info-ink) !important;
  border-color: transparent !important;
}
.dark .text-green-500,
.dark .text-green-600,
.dark .text-emerald-500 {
  color: var(--gf-ok-ink) !important;
}
.dark .text-red-500,
.dark .text-red-600 {
  color: var(--gf-bad-ink) !important;
}
.dark .text-yellow-500,
.dark .text-amber-500 {
  color: var(--gf-warn-ink) !important;
}

/* The counter cards above the accounts table: a 32px number over a 11px label,
   with the label almost invisible. Rebalanced — the label is what you read
   first when scanning seven of them. */
.dark .text-3xl,
.dark .text-4xl {
  font-size: 26px !important;
  line-height: 1.1 !important;
  font-weight: 650 !important;
  letter-spacing: -0.02em;
}

/* Their labels are set in caps, and caps at a readable size no longer fit the
   card: "НА КАРАНТИНЕ" and "РЕАВТОРИЗАЦИЯ" wrapped to two lines while their
   five neighbours stayed on one, so a row of identical cards had two different
   shapes. Sentence case fits, and is faster to read besides. */
.dark .stat-title {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-weight: 500;
  white-space: nowrap;
}

/* One height across the header strip. It was mixing 36, 40 and 44px controls
   side by side, which is the most visible instance of the size drift because
   they sit in a single row against a shared baseline. */
.dark header .toggle-button,
.dark header .icon-button,
.dark header .locale-button,
.dark header .wallet-badge-link,
.dark header .threads-badge-link,
.dark header .wallet-badge,
.dark header .threads-badge,
.dark header [class*="user-menu"] > button {
  height: var(--gf-h) !important;
  min-height: var(--gf-h) !important;
  border-radius: var(--gf-r-sm) !important;
}
.dark header .icon-button,
.dark header .toggle-button {
  width: var(--gf-h) !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ═══════════════════════════════════════════════════════════════════════════
   6. Chrome — sidebar, header, scrollbars, overlays
   ═══════════════════════════════════════════════════════════════════════════ */

.dark aside,
.dark .sidebar {
  background: var(--gf-bg-raised) !important;
  border-right: 1px solid var(--gf-line) !important;
}

.dark .menu-item,
.dark .submenu-link {
  border-radius: var(--gf-r-sm);
  min-height: 38px;
  font-size: 13.5px;
  font-weight: 500;
  transition: background-color var(--gf-dur) var(--gf-ease),
    color var(--gf-dur) var(--gf-ease);
}

.dark .menu-item-inactive,
.dark .submenu-link-inactive {
  color: var(--gf-t1) !important;
  background: transparent !important;
}
.dark .menu-item-inactive:hover,
.dark .submenu-link-inactive:hover {
  background: var(--gf-panel-2) !important;
  color: var(--gf-t0) !important;
}

/* The active item: a filled chip with a left rail, so which screen you are on
   is answerable from the corner of the eye. */
.dark .menu-item.router-link-active,
.dark .submenu-link.router-link-active,
.dark .menu-item-active {
  position: relative;
  background: var(--gf-accent-soft) !important;
  color: var(--gf-t0) !important;
  border: 1px solid var(--gf-accent-line) !important;
}
.dark .menu-item.router-link-active::before,
.dark .menu-item-active::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 20%;
  bottom: 20%;
  width: 2.5px;
  border-radius: 2px;
  background: var(--gf-accent);
}

/* Group headings — "Главная", "Модули", "Администрирование". `.group-title`,
   not `.menu-group`: the latter is the whole group including its items, and
   setting `text-transform` on it put the entire sidebar in caps. */
.dark .group-title {
  font-size: 12px !important;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--gf-t2) !important;
  font-weight: 600;
}

/* Space between groups, so the sidebar reads as three lists rather than one
   long one. */
.dark .menu-group + .menu-group {
  margin-top: 18px;
}

.dark header,
.dark .topbar {
  background: rgba(14, 17, 22, 0.82) !important;
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--gf-line) !important;
}

/* Dialogs and dropdowns: raised ground, hairline, one real shadow. This is the
   single place a drop shadow is right — it separates a floating layer from the
   page rather than faking depth on something flat. */
.dark [role="dialog"],
.dark .p-dialog,
.dark .p-popover,
.dark .p-select-overlay,
.dark .p-menu {
  background: var(--gf-bg-raised) !important;
  border: 1px solid var(--gf-line-strong) !important;
  border-radius: var(--gf-r) !important;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.75) !important;
}

.dark ::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
.dark ::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.11);
  border-radius: 9px;
}
.dark ::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.18);
}
.dark ::-webkit-scrollbar-track {
  background: transparent;
}

.dark ::selection {
  background: var(--gf-accent-soft);
  color: var(--gf-t0);
}

/* A screen arriving should settle, not blink into place. Cheap enough to run
   on every route change, and it covers the frame where a table has its header
   but not its rows. */
@keyframes gf-in {
  from {
    opacity: 0;
    transform: translateY(5px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.dark main > *,
.dark [class*="page-"] > * {
  animation: gf-in 0.24s var(--gf-ease);
}

@media (prefers-reduced-motion: reduce) {
  .dark main > *,
  .dark [class*="page-"] > * {
    animation: none;
  }
  .dark * {
    transition-duration: 0.01ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   9. The polish pass
   ═══════════════════════════════════════════════════════════════════════════

   Sections 1–8 set a palette and a scale and then trusted the bundle to stay
   on them. Driving every screen with Playwright and measuring instead of
   looking says it did not: twenty-six corner radii and forty-three control
   heights outside the scale, five states where hovering makes the label
   unreadable, and two settings rows in one card built from two different
   components.

   Everything below is keyed to a selector that was measured, and the comment
   on each block carries the number that justified it. Written after the owner
   said the panel was still unpleasant to use — he was right, and the previous
   pass had been grading itself on colour.
   ═══════════════════════════════════════════════════════════════════════════ */

/* --- 9.1 the scale, finished ----------------------------------------------
   Two more steps the earlier scale was missing, which is why things kept
   landing between its values. */
:root {
  --gf-r-xs: 6px;
  --gf-h-lg: 48px;
}

/* Radii. Measured across twenty screens: 9.6, 8, 10, 12, 16, 5, 6, 4, 11.2,
   14, 30, 13.6, 2, 8.8 and twelve more — 331 elements on 9.6px alone, which is
   `rounded-[0.6rem]` on the sidebar item. None of these were chosen; they are
   what `rem`-based Tailwind arbitrary values compute to. Five values survive:
   2 (flag), 6 (micro), 9 (control), 13 (card), 999 (pill). */
.dark .menu-item,
.dark .submenu-link,
.dark .icon-button,
.dark .icon-btn,
.dark .stepper,
.dark .opt-icon,
.dark .mode-switch-thumb,
.dark .p-inputnumber-button,
.dark .field-value,
.dark .p-paginator > *,
.dark .header-link,
.dark .sf-profile,
.dark .geo-count,
.dark .column-count,
.dark .section-badge {
  border-radius: var(--gf-r-sm) !important;
}

.dark .module-section,
.dark .stat-card,
.dark .status-card,
.dark .page-header,
.dark .filters-section,
.dark .dash-mini,
.dark .opt-card,
.dark .cs-card,
.dark .tg-tile,
.dark .launch-hint,
.dark .delays-section,
.dark .emoji-item {
  border-radius: var(--gf-r) !important;
}

.dark .p-checkbox-box,
.dark .p-checkbox-input,
.dark .status-icon,
.dark .section-icon,
.dark .sf-ava {
  border-radius: var(--gf-r-xs) !important;
}

/* Anything shaped like a pill was drawn with 4px, 22px or 30px depending on
   the screen. A pill is a pill. */
.dark .account-status,
.dark .proxy-badge,
.dark .age-pill,
.dark .plan-chip,
.dark .role-chip,
.dark .tp-chip,
.dark .radio-chip,
.dark .p-toggleswitch,
.dark .p-toggleswitch-slider {
  border-radius: 999px !important;
}

/* Heights. 42 and 41 on numeric fields, 52 on the launch button, 60 on the
   three top actions, 56 on a select, 38 on a radio chip, 28 on the row icons —
   every one of them a near-miss of a value already in the scale. */
.dark .p-inputnumber-input,
.dark .stepper-input,
.dark .stepper-btn,
.dark .filter-select,
.dark .radio-chip,
.dark .p-inputnumber-button-group {
  height: var(--gf-h) !important;
  min-height: var(--gf-h) !important;
}

.dark .launch-button,
.dark .launch-button-wrap,
.dark .action-chip,
.dark .btn-oauth-link {
  height: var(--gf-h-lg) !important;
  min-height: var(--gf-h-lg) !important;
}

.dark .icon-btn {
  height: var(--gf-h-sm) !important;
  min-height: var(--gf-h-sm) !important;
  width: var(--gf-h-sm) !important;
}

/* The spinner column has to add back up to the field it sits in: two arrows,
   40px total, not two 21px arrows in a 42px group. */
.dark .p-inputnumber-button-group {
  display: flex !important;
  flex-direction: column;
}
.dark .p-inputnumber-button {
  height: 20px !important;
  min-height: 20px !important;
  border-radius: 0 !important;
}

/* --- 9.2 the settings row, one component ----------------------------------
   The defect the owner pointed at first. Inside one card, `.min-max-row` is a
   flex row — label left, a pair of 140px steppers right — and `.single-num-row`
   is a block: label on its own line at a larger size, a 279px stepper beneath
   it, left-aligned, reaching past where every other control in the card ends.
   Two components for the same job, and the second one is why the FloodWait
   settings look like they fell out of the card.

   Measured before: `.min-max-row` 953×50 with 313px of controls; the
   `.single-num-row` 953×68 with a 279px control at the wrong edge. */
.dark .single-num-row {
  display: flex !important;
  align-items: center;
  gap: 16px;
  min-height: 50px;
}
.dark .single-num-info {
  flex: 1 1 auto;
  min-width: 0;
}
.dark .single-num-label,
.dark .min-max-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--gf-t1);
}
.dark .single-num-name,
.dark .min-max-name {
  font-size: 14px;
  color: var(--gf-t1);
}
/* Same width as the min/max pair so both kinds of row end on one line, and the
   single control sits under the *second* stepper of the rows above — the edge
   the eye is already following. */
.dark .single-num-inputs {
  flex: 0 0 auto;
  display: flex !important;
  justify-content: flex-end;
  width: 313px;
}
.dark .single-num-inputs .p-inputnumber {
  width: 140px !important;
}
/* The info bubble had no gap from the label it annotates. */
.dark .single-num-label > svg,
.dark .single-num-label > i,
.dark .min-max-label > svg,
.dark .min-max-label > i {
  flex: 0 0 auto;
  margin-left: 2px;
  opacity: 0.65;
}

/* --- 9.3 hover that erases the text ---------------------------------------
   Five states, each measured with the cursor on the element. The worst is
   1.04:1 — white text on a surface that turns white — which is the same defect
   the owner found in the activity log, on a different screen.

   The rule these all break: a hover may change the surface OR the ink, and
   whichever it changes, the pair still has to clear 4.5:1. */

/* 1.04:1 — `.btn-create` fills with white and keeps its white label. */
.dark .btn-create:hover,
.dark .btn-create:focus-visible {
  /* The fill measured #765bff rather than the accent it is set to, because the
     bundle brightens this button with a `filter` on hover — which lands the
     white label at 4.46:1, six hundredths under the floor. The pressed accent
     removes both the filter and the argument. */
  background: var(--gf-accent-press) !important;
  border-color: var(--gf-accent-press) !important;
  filter: none !important;
}
.dark .btn-create:hover .bc-label,
.dark .btn-create:hover .bc-sub,
.dark .btn-create:hover svg,
.dark .btn-create:focus-visible .bc-label {
  color: #ffffff !important;
}

/* 2.49:1 — the role chip inside a hovered table row keeps its dim ink while
   the row lifts to a lighter fill. */
.dark tr:hover .role-text,
.dark tr:hover .role-chip,
.dark .p-datatable-tbody > tr:hover .role-text {
  color: var(--gf-t0) !important;
}

/* 2.05:1 — an already-selected filter chip tints violet on violet. */
.dark .tp-chip.on:hover,
.dark .tp-chip.on:hover span {
  color: var(--gf-t0) !important;
}
.dark .tp-chip.on:hover {
  background: var(--gf-accent-soft) !important;
  border-color: var(--gf-accent-line) !important;
}

/* 3.11:1 — the inactive half of the auto/manual switch. */
.dark .mode-switch-btn:hover span,
.dark .mode-switch-btn.is-auto span,
.dark .mode-switch-btn.is-manual span {
  color: var(--gf-t1) !important;
}
.dark .mode-switch-btn:hover span {
  color: var(--gf-t0) !important;
}

/* 4.18:1 — a segmented control just under the floor. */
.dark .seg-btn,
.dark .seg-btn:hover {
  color: var(--gf-t0) !important;
}

/* The general rule, so the next component the bundle adds does not repeat it:
   any row that lifts on hover raises its text with it instead of leaving it at
   the dim level chosen for the resting state. */
.dark tr:hover td,
.dark .p-datatable-tbody > tr:hover > td,
.dark li:hover > .item-sub,
.dark .cd-option:hover {
  color: var(--gf-t0);
}

/* --- 9.4 labels the browser had to cut ------------------------------------
   `.mode-selector-compact` is a three-column grid of 146px cells holding
   labels that need 134, 98 and 239px. Two of the three were rendering as
   "По ключевы…" and "Сначала сущ…", which is a control that does not say what
   it does. The grid keeps three across while the labels fit and drops to two
   when they do not; the label itself may take a second line. */
   Two attempts at re-tracking this grid were reverted, and the reason is worth
   keeping: the bundle declares its own `grid-template-columns` with
   `!important`, and this sheet is injected FIRST in the head — before the route
   CSS — so on an equal-specificity, both-important collision the bundle wins.
   Overriding the track list from here is not possible; letting the label wrap
   inside the cell the bundle already gives it is, and it is enough. Measured
   after: three equal 146px cells, three equal heights, nothing truncated.

   (Two of those attempts made it worse before measurement caught them — a
   `minmax(190px, 1fr)` that did apply put the third button in an implicit 57px
   track with its label on four lines.) */
.dark .mode-btn {
  height: auto !important;
  min-height: var(--gf-h) !important;
  padding: 8px 12px !important;
  align-items: center;
}
.dark .mode-label {
  white-space: normal !important;
  overflow: visible !important;
  text-overflow: clip !important;
  font-size: 13px;
  line-height: 1.25;
  text-align: left;
}

/* A select whose own value did not fit: "мин" needed 50px in a 42px box. */
.dark .p-select-label {
  overflow: visible;
  text-overflow: clip;
}
.dark .p-select {
  min-width: 88px;
}

/* --- 9.5 the channel box --------------------------------------------------
   `.channel-input-simple` is a flex row: a 195px textarea beside an empty
   panel holding one button, 122px tall. The field where the work happens was
   the smallest thing in the section. It takes the row now, and the button
   stops pretending to be a panel. */
.dark .channel-input-simple {
  align-items: flex-start;
  gap: 12px;
}
.dark .targets-input {
  flex: 1 1 auto !important;
  min-width: 0;
}
.dark .targets-input__field {
  width: 100% !important;
  min-height: 96px;
  resize: vertical;
  font-family: var(--gf-mono);
  font-size: 13px;
  line-height: 1.6;
}
.dark .add-toggle {
  flex: 0 0 auto;
  align-self: stretch;
  display: flex;
  align-items: flex-start;
}
.dark .add-toggle > .p-button {
  height: var(--gf-h) !important;
  min-height: var(--gf-h) !important;
}

/* --- 9.6 one accent, no gradients -----------------------------------------
   The import button was painted `linear-gradient(135deg,#14a3e7,#6e56f7)` — a
   leftover of the original brand blue bleeding into ours. A gradient here is
   decoration standing in for hierarchy. */
.dark .p-button[style*="gradient"],
.dark .p-button {
  background-image: none !important;
}

/* Section icons came in green, blue, orange and purple with nothing to
   distinguish them: the colour was not carrying meaning, it was carrying
   variety. Status colour stays where it means something — `.stat-card` keeps
   its green success and red failure — and the decorative set flattens. */
.dark .section-icon,
.dark .section-icon.green,
.dark .section-icon.blue,
.dark .section-icon.orange,
.dark .section-icon.purple,
.dark .opt-icon {
  background: var(--gf-accent-soft) !important;
  color: var(--gf-accent-text) !important;
  box-shadow: none !important;
}
.dark .section-icon svg,
.dark .section-icon i,
.dark .opt-icon svg,
.dark .opt-icon i {
  color: var(--gf-accent-text) !important;
}

/* --- 9.7 the tab strip ----------------------------------------------------
   "Настройки безопасности" overflowed its tab by 9px and "Партнёрская
   программа" by 4 — the two the owner named. The strip was sizing tabs to a
   fixed width and letting the label decide what to do about it. */
.dark .nav-tab-btn,
.dark .pp-tab {
  flex: 0 1 auto;
  min-width: 0;
  padding-inline: 14px;
  white-space: nowrap;
}
.dark .nav-tab-btn > *,
.dark .pp-tab > * {
  min-width: 0;
}
/* When they genuinely cannot all fit, the strip scrolls rather than crushing
   every label. */
.dark .nav-tabs,
.dark .pp-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: thin;
}

/* --- 9.8 dark only --------------------------------------------------------
   The panel shipped both themes and the light one was never designed — it was
   the bundle's default with our variables landing on it at random. Removing
   the switch removes a whole class of half-styled screens, and every rule in
   this file is written for the dark ground anyway. */
.icon-button:has(.pi-sun),
.icon-button:has(.pi-moon),
[data-gf-theme-toggle] {
  display: none !important;
}

/* --- 9.9 the eight states that fail on rendered pixels ---------------------
   Measured by moving the real pointer onto 570 distinct controls across twenty
   screens and reading the painted pixels under each label, because neither of
   the cheaper methods is trustworthy here: synthetic `mouseover` events never
   trigger CSS `:hover` at all, and compositing declared colours up the ancestor
   chain cannot see a gradient — it turned the ground's faint radial wash into a
   solid mid-blue and invented a dozen failures that are not on screen.

   What the eight had in common was worth more than the eight fixes. */

/* Three of them were one mistake: a filled accent button painted its label
   `--gf-t0` (#f2f4f7) instead of white. On #6e56f7 that is 4.39:1 — under the
   floor — while pure white on the same fill is 4.84:1. The off-white was doing
   nothing except failing. */
:root {
  /* The fill for a button that carries white text. The base accent passes at
     4.84:1, but only just; this is the value hover and active drop to, at
     6.2:1, so the pressed state is the *more* readable one. */
  --gf-accent-press: #5b45e0;
}

.dark .p-button:not(.p-button-outlined):not(.p-button-text):not(.p-button-link),
.dark .seg-btn.active,
.dark .mode-btn.active,
.dark .tp-chip.on,
.dark .btn-create {
  color: #ffffff !important;
}
.dark .p-button:not(.p-button-outlined):not(.p-button-text):not(.p-button-link) .p-button-label,
.dark .p-button:not(.p-button-outlined):not(.p-button-text):not(.p-button-link) .p-button-icon,
.dark .seg-btn.active > *,
.dark .mode-btn.active > * {
  color: #ffffff !important;
}

/* Hovering the primary button lit it #49b0dc — the original product's cyan,
   surviving in a state the accent repaint in `build-frontend.sh` never reaches
   because PrimeVue composes it from its own tokens at runtime rather than
   writing it into a stylesheet. Naming the tokens fixes every button at once. */
:root.dark,
.dark {
  --p-button-primary-background: var(--gf-accent);
  --p-button-primary-hover-background: var(--gf-accent-press);
  --p-button-primary-active-background: var(--gf-accent-press);
  --p-button-primary-border-color: var(--gf-accent);
  --p-button-primary-hover-border-color: var(--gf-accent-press);
  --p-button-primary-color: #ffffff;
  --p-button-primary-hover-color: #ffffff;
  --p-highlight-background: var(--gf-accent);
  --p-highlight-color: #ffffff;
  --p-paginator-nav-button-selected-background: var(--gf-accent);
  --p-paginator-nav-button-selected-color: #ffffff;
}

/* The current page in the paginator was set in white at 51% opacity: the one
   number in the strip that tells you where you are was the hardest to read. */
.dark .p-paginator-page-selected,
.dark .p-paginator-page.p-highlight {
  color: #ffffff !important;
  opacity: 1 !important;
  font-variant-numeric: tabular-nums;
}

/* An accent lighter than the base cannot carry white text — #7c66f8 is 4.11:1
   even with pure white — so the selected state uses the base fill and keeps the
   lighter tint for borders and glow, where contrast does not apply. */
.dark .top-cols button.active,
.dark .opener-mode-btn.active,
.dark .preset-btn.active {
  background: var(--gf-accent) !important;
  border-color: var(--gf-accent) !important;
  color: #ffffff !important;
}
.dark .top-cols button.active *,
.dark .opener-mode-btn.active *,
.dark .preset-btn.active * {
  color: #ffffff !important;
}

/* The description line under an option label was #76849a: 4.41:1 on the
   option's own fill, which is a shade lighter than the page. Secondary text
   gets the token that was measured against the surfaces it actually sits on. */
.dark .opt-desc,
.dark .ams-desc-hint,
.dark .cs-tg-hint,
.dark .tl-empty,
.dark .rail-hint {
  color: var(--gf-t2) !important;
}

/* The single-value stepper inherits a 199px input from the bundle's wide
   variant. Flex children do not shrink below their content width without being
   told to, so narrowing the wrapper to match the min/max pair sent the plus
   button 126px past the right edge of the card. Clipping the wrapper hid that
   but left the audit reporting three clipped containers, correctly — the
   content still did not fit. Shrinking the field is the actual fix. */
.dark .single-num-inputs .p-inputnumber-input {
  width: auto !important;
  min-width: 0 !important;
  flex: 1 1 auto;
}

/* --- 9.10 the last of the heights -----------------------------------------
   With the hidden native inputs excluded from the count — PrimeVue stretches a
   transparent `<input type=checkbox>` over the box it draws, and its 18px was
   being reported as a control the user could see — the real tail is short:
   45px on a limit button, 42 on the active menu item, 60 on a profile tab,
   130 on the "add" block beside the channel field, 43 and 44 on three more.

   Every one of them a control that should have been 40. */
:root {
  --gf-h-chip: 24px;
}

.dark .limit-btn,
.dark .header-link,
.dark .keyword-input,
.dark .chain-add-btn,
.dark .group-add-btn,
.dark .channel-add-btn,
.dark .menu-item,
.dark .menu-item-active,
.dark .p-inputtext.p-variant-filled {
  height: var(--gf-h) !important;
  min-height: var(--gf-h) !important;
  max-height: var(--gf-h);
}

/* The profile's "tab strip" is really a 260px vertical rail, and its `aside`
   clips: "Настройки безопасности" wants 172px inside a 149px label and simply
   ran out past the edge — the third defect the owner pointed at. It was 60px
   tall for one line of text, so there was room for the second line all along. */
.dark .nav-tab-btn,
.dark .pp-tab {
  height: auto !important;
  min-height: 44px !important;
  align-items: center;
}
.dark .nav-tab-btn .tab-label,
.dark .pp-tab .tab-label {
  white-space: normal !important;
  overflow-wrap: anywhere;
  line-height: 1.25;
  text-align: left;
}

/* Small pills — plan, age, count — share one height instead of 22, 26 and 28
   depending on which screen drew them. */
.dark .plan-chip,
.dark .age-pill,
.dark .count-badge,
.dark .section-badge,
.dark .geo-count,
.dark .column-count {
  height: var(--gf-h-chip) !important;
  min-height: var(--gf-h-chip) !important;
  display: inline-flex;
  align-items: center;
  padding-inline: 8px;
}

/* A wrapped label makes its own button taller; the ones beside it should
   follow rather than leave the row ragged. */
.dark .mode-selector-compact {
  align-items: stretch;
}
.dark .mode-btn {
  min-height: var(--gf-h-lg) !important;
  height: auto !important;
  max-height: none;
}

/* The last four controls measuring between the steps. */
.dark .btn-find,
.dark .p-datepicker-input,
.dark .opener-mode-btn,
.dark .search-input {
  height: var(--gf-h) !important;
  min-height: var(--gf-h) !important;
}

/* --- 9.11 the icon-only button --------------------------------------------
   Twenty-four buttons across the panel held a 16px icon in a 38px box and
   reported needing 65px. PrimeVue keeps the text button's horizontal padding
   (~24px a side) on the icon-only variant and, in some versions, an empty
   `.p-button-label` holding a non-breaking space that is hidden with
   `visibility` — hidden, but still 27px wide. Neither belongs in a square
   button: the icon centres itself when the box stops reserving room for a
   label that is not there. */
.dark .p-button-icon-only {
  padding: 0 !important;
  justify-content: center;
  gap: 0;
}
.dark .p-button-icon-only .p-button-label {
  display: none !important;
}

/* A hint line under a field is prose, not a label; it wraps. */
.dark .rail-hint,
.dark .field-hint,
.dark .launch-hint {
  white-space: normal !important;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.45;
}

/* --- 9.12 the gutter the spinner needs ------------------------------------
   Two variants of the same numeric field ship side by side. In one the spinner
   arrows sit inside the input and the input reserves 52px for them; in the
   other — `.field-compact` on the reactions screen — the arrows sit inside a
   448px input that reserves 12. The arrows cover 40px of the text, so a long
   enough value runs underneath them. Same component, same gutter. */
.dark .field-compact .p-inputnumber-input,
.dark .field-compact input.p-inputtext {
  padding-right: 52px !important;
}

/* --- 9.13 the tiles stop being a rainbow ----------------------------------
   The four tiles over the launch button — accounts, channels, interval,
   comment cap — came in blue, blue, green and amber. None of those is a state;
   they are four readings of the current configuration, and the colour was
   carrying variety rather than meaning. The statistics strip beside them keeps
   its hues, because there green really does mean succeeded and red failed. */
.dark .status-icon {
  background-image: none !important;
  background-color: var(--gf-accent-soft) !important;
  color: var(--gf-accent-text) !important;
}
.dark .status-icon svg,
.dark .status-icon i {
  color: var(--gf-accent-text) !important;
}

/* --- 9.14 three actions, one voice ----------------------------------------
   "Импортировать аккаунты", "Добавить аккаунт" and "Пул прокси" sit in a row
   doing the same kind of thing, and the third one was green — border, icon and
   all — for no reason the row explains. Green in this panel means a working
   account; spending it on a button that opens the proxy list makes the signal
   worth less everywhere else. */
.dark .action-chip {
  border-color: var(--gf-line) !important;
  border-style: solid !important;
}
.dark .action-chip:hover {
  border-color: var(--gf-accent-line) !important;
}
.dark .action-chip .chip-icon {
  background-image: none !important;
  background-color: var(--gf-accent-soft) !important;
  color: var(--gf-accent-text) !important;
}
.dark .action-chip .chip-icon svg,
.dark .action-chip .chip-icon i {
  color: var(--gf-accent-text) !important;
}

/* --- 9.15 the last three states ---------------------------------------------
 *
 * Everything below came out of the same measurement — a real cursor over 597
 * controls, contrast read off rendered pixels — and each one is a place where a
 * colour that is right on the panel ground stops being right somewhere else.
 *
 * PrimeVue paints its tags from its own token set, which is why «Валидный» in
 * the accounts table kept the fill green (3.76:1 on its own hover wash) while
 * every status label the stylesheet owns had already moved to the ink value.
 * Tokens, not a per-tag override: the library renders tags in the table, in the
 * dialog and in the filter bar, and one of those would have been missed. */
:root.dark,
.dark {
  --p-tag-success-color: var(--gf-ok-ink);
  --p-tag-success-background: var(--gf-ok-soft);
  --p-tag-warn-color: var(--gf-warn-ink);
  --p-tag-warn-background: var(--gf-warn-soft);
  --p-tag-danger-color: var(--gf-bad-ink);
  --p-tag-danger-background: var(--gf-bad-soft);
  --p-tag-info-color: var(--gf-ink-accent);
  --p-tag-info-background: var(--gf-accent-soft);
  --p-tag-secondary-color: var(--gf-t1);
  --p-tag-secondary-background: rgba(255, 255, 255, 0.06);
}

/* A segmented control's chosen segment is filled with the accent, and the
 * bundle lightens that fill on hover — #7d68f8, where white measures 4.03:1.
 * The rest of the panel darkens instead (§9.9): #5b45e0 carries white at
 * 6.23:1, so the pressed state reads better than the resting one rather than
 * worse. These are the selectors that had kept the old behaviour. */
.dark .top-cols button.active:hover,
.dark .top-cols button.active:focus-visible,
.dark .seg-btn.active:hover,
.dark .mode-btn.active:hover,
.dark .opener-mode-btn.active:hover,
.dark .preset-btn.active:hover,
.dark .tp-chip.on:hover {
  background: var(--gf-accent-press) !important;
  border-color: var(--gf-accent-press) !important;
  color: #ffffff !important;
  filter: none !important;
}

/* Page titles are painted with a gradient clipped to the glyphs, which is why
 * neither the audit nor the hover pass saw anything wrong with them: the
 * computed `color` is the near-white underneath, and the colour the eye reads
 * is a background. One of the two stops was a blue, so every module opened with
 * its largest text in a hue that appears nowhere else on the screen — a second
 * accent in the most prominent position on the page.
 *
 * The gradient stays; it is the panel's own idiom for a page title. It just
 * runs between two stops of one hue now. Both clear the large-text floor on the
 * card ground: 5.6:1 and 4.09:1 against a 3:1 requirement. */
.dark .page-title,
.dark .module-title,
.dark .section-hero-title {
  background-image: linear-gradient(
    135deg,
    var(--gf-accent-hi),
    var(--gf-accent) 55%,
    var(--gf-accent)
  ) !important;
}

/* --- 10 the storefront, gone before the first frame -------------------------
 *
 * `panel-trim.js` removes these nodes from the DOM; this stops them being seen
 * on the way out. Between Nuxt painting the header and the MutationObserver
 * running there is one frame, and one frame is enough for «План: Полная», a
 * credit balance and a language switcher to flash in the corner of a panel
 * that has none of those things.
 *
 * `build-frontend.sh` § "cutting the commerce out" forces the render guards on
 * most of them false, so on a good build these selectors match nothing at all.
 * They are here for the build where a shape stopped matching and the script
 * printed its warning — the panel should degrade to "invisible", not to
 * "sells you threads". */
.wallet-badge-link,
.threads-badge-link,
.plan-chip,
.sf-renew,
.locale-button {
  display: none !important;
}

/* The importer's slot quota, and the storefront hiding behind it.
 *
 * «Импортировать аккаунты» opens with a progress bar reading "использовано X / Y
 * слотов", fed by `GET /api/telegram-accounts/slots/`. Slots are the paid tier's
 * per-plan account quota; there is no plan here and no quota, the endpoint is a
 * 404, and the bar never leaves `is-skeleton` — a loading state that loads
 * forever, on every single opening of the dialog.
 *
 * Inside that same block sits «Купить ещё →», and behind that button a purchase
 * modal with a quantity stepper, a per-slot price, a tier breakdown and five
 * payment providers — CryptoBot, CardLink, Antilopay, Heleket and a Telegram
 * handle to pay a human. It is the thread-purchase storefront the owner asked to
 * be rid of, one click further in than anyone had looked.
 *
 * `build-frontend.sh` § "the slot quota" cuts the request, which is what stops
 * the 404 and — because the button renders only once the quota has loaded —
 * what makes the purchase modal unreachable. This is the visual half.
 *
 * Written here rather than in `panel-trim.js` because the rule is not about a
 * route: the account manager, and with it this dialog, is drawn on eight of
 * them. A route-scoped entry covered the dashboard and left the other seven
 * showing a bar that spins for a quota that does not exist.
 *
 * The tdata half of the dialog is untouched — reading a tdata folder is
 * implemented and offline (`modules/accounts/tdata.py`), and hiding the tab
 * would have taken a working import with it. */
.slots-progress-block {
  display: none !important;
}

/* --- 11 the controls that were never going to do anything -------------------
 *
 * `panel-trim.js` § "the controls that will never do anything" carries the
 * argument and the per-route list; this is the same cut made a frame earlier,
 * for the containers whose class name is specific enough to name here.
 *
 * A form is not the header: nothing here flashes in the corner of the eye and
 * then leaves. What it does instead is worse in its own way — the sweep runs on
 * a 60ms debounce after Vue paints, which is long enough to see a delay slider,
 * start reaching for it, and have it vanish under the cursor.
 *
 * Only containers that hold nothing but dead controls on every screen that
 * draws one. `.delays-section` is the pacing card of the module forms —
 * checked on Масс Реакции, Нейрочаттинг and Нейрокомментинг, and holding
 * nothing but the gaps between actions and the two FloodWait knobs.
 * `.delay-card` is the same thing on Парсинг.
 *
 * `.delays-modern` is deliberately NOT here, though it was: on Масслукинг it is
 * the direct parent of «Задержка между переходами», and that slider stopped
 * being a mock partway through this wave — `mass_looking/handler.py:465` reads
 * it now. On the other three screens the same class sits inside
 * `.delays-section` and is covered by the rule above anyway. A blanket rule is
 * only safe while the class means one thing, and this one stopped meaning it.
 *
 * The delays that are honoured — «Пауза между кругами» on Чат-Рассылки, the
 * reply delays on НейроДиалоги, and the two above — are in none of these
 * containers. Everything finer-grained is route-scoped in `panel-trim.js`.
 *
 * `.dev-keys` is «API ключи БЕТА», whose endpoint is a 404. It is a sibling of
 * the section that holds the live AI-provider configuration, not a parent of
 * it, so this takes the dead half and leaves the working one. */
.delays-section,
.delay-card,
.dev-keys {
  display: none !important;
}

/* The two country flags, which nginx's Content-Security-Policy now stops on the
 * way out. They were the panel's last live call to a third party: an <img> per
 * account pointing at flagcdn.com, so simply opening the account list told an
 * outside host how many accounts there are and which countries they sit in.
 *
 * The policy is the fix; this is the cosmetic half. Blocked by CSP the request
 * fails, `naturalWidth` stays 0, and the browser draws its torn-page glyph in
 * an 18x13 box. The component does carry an onError fallback, but it never runs
 * here — these two are plain <img> tags, not the `CountryFlag` component that
 * owns that path.
 *
 * Nothing is lost by hiding them: both sit immediately beside the same fact in
 * text. `.geo-flag-img` precedes the country code in the account picker's group
 * header («LT 1»), and `.proxy-flag-img` precedes «Прокси OK» in the badge. The
 * flag was decoration on a label that already said it. */
.geo-flag-img,
.proxy-flag-img {
  display: none !important;
}

/* --- 12 the warming waiver ---------------------------------------------------
 *
 * The card `panel-warming.js` appends to the warming screen. It exists because
 * a waived account and a warmed one are the same row to the API — both carry
 * `warmup_completed_at`, which is the only column the gate reads — and the
 * screen showed the owner's own account at «100%, готово» after the warmup had
 * been lifted by hand. The one field that separates them is the reason, so the
 * reason is the loudest thing in the card.
 *
 * Borrowed from the panel's own surfaces rather than invented: same radius,
 * same border tint and the same violet as every other accent here. */
.gf-warm-card {
  margin: 18px 0 6px;
  padding: 18px;
  border: 1px solid rgba(148, 163, 184, 0.2);
  border-radius: 13px;
  background: rgba(30, 27, 46, 0.55);
}

.gf-warm-title {
  font-weight: 600;
  margin-bottom: 12px;
}

.gf-warm-row {
  padding: 13px 0;
  border-top: 1px solid rgba(148, 163, 184, 0.14);
}

.gf-warm-row:first-of-type {
  border-top: 0;
}

.gf-warm-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
}

.gf-warm-name {
  font-weight: 600;
}

/* The state chip. Amber for a waiver, because it is neither an error nor a
 * clean pass — it is a protection the owner switched off on purpose, and it
 * should keep catching the eye for as long as it is off. */
.gf-warm-state {
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 9px;
  background: rgba(148, 163, 184, 0.16);
}

.gf-warm-row.is-waived .gf-warm-state {
  background: rgba(217, 152, 42, 0.19);
  color: #f0b350;
}

.gf-warm-reason {
  margin: 9px 0 0;
  padding-left: 11px;
  border-left: 2px solid rgba(217, 152, 42, 0.5);
  opacity: 0.92;
}

.gf-warm-note {
  margin: 9px 0 0;
  font-size: 13px;
  opacity: 0.72;
}

.gf-warm-input {
  display: block;
  width: 100%;
  margin: 11px 0 9px;
  padding: 9px 11px;
  border: 1px solid rgba(148, 163, 184, 0.28);
  border-radius: 9px;
  background: rgba(15, 13, 26, 0.6);
  color: inherit;
  font: inherit;
}

.gf-warm-input:focus {
  outline: none;
  border-color: #6e56f7;
}

.gf-warm-btn {
  margin-top: 9px;
  padding: 8px 15px;
  border: 0;
  border-radius: 9px;
  background: #6e56f7;
  color: #fff;
  font: inherit;
  cursor: pointer;
}

.gf-warm-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.gf-warm-err {
  margin: 9px 0 0;
  font-size: 13px;
  color: #f2777a;
}

/* «Агрессивный» in the protection block: refused for want of a source, and
 * drawn so it reads as refused rather than as merely unselected. `title=`
 * carries the reason, which `panel-protection.js` takes from the API. */
.level-option.gf-level-off {
  opacity: 0.45;
  cursor: not-allowed;
}

.level-option.gf-level-off .level-card {
  border-style: dashed !important;
}

/* --- 13 the sign-in screen ---------------------------------------------------
 *
 * This sheet was already loading on `/auth/sign-in` and changing nothing on it.
 * Everything above is written against the panel's markup, and the sign-in
 * screen shares none of it — it is the one screen outside `/panel`, built from
 * plain Tailwind utilities. So the first screen the owner ever sees was still
 * the original's blue while everything behind it had turned violet.
 *
 * Scoped by `.gf-auth`, which `panel-trim.js` puts on <html> for `/auth/*`.
 * The scope is not decoration: every hook below is a Tailwind utility that
 * repeats across the whole bundle, and unscoped they would repaint the panel.
 *
 * Nothing here invents a colour. Every value is a token this sheet already
 * defines on `:root`, which the auth page inherits like any other document —
 * the point of the section is to stop this screen having its own palette, so
 * giving it a fourth set of hand-picked hexes would miss it. */

/* Measured on the rendered button: `linear-gradient(135deg, rgb(91,69,224),
 * rgb(0,102,153))`. The first stop is already `--gf-accent-press` — the build's
 * sweep caught it, because it was a hex in a stylesheet. The second, `#3b5d80`,
 * is not on the ramp the sweep knows, so the one button on the screen faded
 * from violet into the old blue. */
.gf-auth .btn-auth-enter {
  background-image: linear-gradient(135deg, var(--gf-accent) 0%, var(--gf-accent-press) 100%) !important;
}

/* The 384px blurred circle behind the promo panel, `rgba(34,158,217,.1)` —
 * Telegram's own brand blue. It is the widest single colour on the screen and
 * the reason the left half reads as a different product. Deliberately exempt
 * from the build's sweep (`#229ed9` is absent from both tables, by design), so
 * it has to be named here. The hook is the arbitrary-value class itself; the
 * `rounded-full` qualifier is what separates it from the card underneath. */
.gf-auth [class*="229ED9"][class*="rounded-full"] {
  background-color: var(--gf-accent-soft) !important;
}

/* That card: three navies, `#0a1929 / #0d2137 / #0a1929`. Not bright, but blue,
 * and it is the ground everything else on the screen sits on. The panel's own
 * ground is neutral and lets the accent come from the glow above — same here. */
.gf-auth [class*="229ED9"]:not([class*="rounded-full"]) {
  background-image: linear-gradient(
    to right bottom,
    var(--gf-bg),
    var(--gf-bg-raised),
    var(--gf-bg)
  ) !important;
}

/* --- 14 the phone's action bar ----------------------------------------------
 *
 * The accounts manager grows a six-icon bar along the bottom of a phone, and
 * the bundle sets `word-break: break-word` on its captions. That property does
 * not mean "wrap sensibly" — it means "split this word wherever it runs out of
 * room", and in a 56px cell it produced «Библиот / ека фото».
 *
 * The caption itself is shortened in `panel-trim.js` § RELABEL, which is what
 * actually fixes today's screen. This is the mechanism: with the break off, a
 * caption too long for its cell overflows, and an overflow is something
 * `scripts/ui_audit.js` reports. A word cut in half mid-syllable is not — it
 * measures as a perfectly well-behaved two-line label. */
.am-mobile-actionbar .amab-btn span {
  word-break: normal !important;
  overflow-wrap: normal !important;
}

/* --- 15 the live parsing monitor ---------------------------------------------
 *
 * The card `panel-parsing-monitor.js` appends to the parsing screens and to
 * «База Telegram». Those screens answer «what did we collect»; this one answers
 * «how is it going right now» — rate, throughput, elapsed, ETA, and a feed of
 * finds as they land.
 *
 * `order: -1` is the whole placement trick. `.content-wrapper` is a flex column,
 * so the card sits at the TOP of the screen while staying the LAST child in the
 * DOM — appended, never inserted between nodes Vue owns.
 *
 * Idle it is one line. There is no reason for a parsing screen to open with
 * half a page of zeroes, and a monitor that is loud when nothing is happening
 * is a monitor the owner learns to scroll past. */
.gf-pm-card {
  order: -1;
  margin: 0 0 18px;
  padding: 14px 16px;
  border: 1px solid var(--gf-line);
  border-radius: var(--gf-r-lg);
  background: var(--gf-bg-raised);
  box-shadow: var(--gf-highlight);
  color: var(--gf-t0);
  font-size: 13px;
}

.gf-pm-card.is-busy {
  border-color: var(--gf-accent-line);
}

.gf-pm-strip {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.gf-pm-strip-t {
  font-size: 14px;
  font-weight: 600;
}

/* The state dot. Grey and still when nothing runs; violet and breathing when
 * something does — the one thing on the strip you can read from across a room. */
.gf-pm-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gf-line-strong);
  flex: none;
}

.gf-pm-dot.is-busy {
  background: var(--gf-accent);
  box-shadow: 0 0 0 4px var(--gf-accent-soft);
  animation: gf-pm-pulse 1.8s var(--gf-ease) infinite;
}

@keyframes gf-pm-pulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--gf-accent-soft); }
  50%      { box-shadow: 0 0 0 7px rgba(110, 86, 247, 0.05); }
}

@media (prefers-reduced-motion: reduce) {
  .gf-pm-dot.is-busy { animation: none; }
  .gf-pm-feed-row.is-fresh { animation: none; }
}

.gf-pm-toggle {
  margin-left: auto;
  padding: 5px 12px;
  border: 1px solid var(--gf-line);
  border-radius: var(--gf-r-sm);
  background: var(--gf-panel);
  color: var(--gf-t1);
  font-size: 12px;
  cursor: pointer;
  transition: color var(--gf-dur) var(--gf-ease), border-color var(--gf-dur) var(--gf-ease);
}

.gf-pm-toggle:hover {
  color: var(--gf-t0);
  border-color: var(--gf-line-strong);
}

.gf-pm-dim {
  color: var(--gf-t2);
  font-size: 12px;
}

/* -- one run ---------------------------------------------------------------- */

.gf-pm-run {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--gf-line-soft);
}

.gf-pm-run-head {
  display: flex;
  align-items: center;
  gap: 16px;
}

.gf-pm-ring {
  position: relative;
  width: 68px;
  height: 68px;
  flex: none;
}

.gf-pm-ring svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.gf-pm-ring-bg {
  fill: none;
  stroke: var(--gf-line);
  stroke-width: 7;
}

.gf-pm-ring-fg {
  fill: none;
  stroke: var(--gf-accent);
  stroke-width: 7;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.6s var(--gf-ease);
}

.gf-pm-ring-mid {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.gf-pm-run-who {
  min-width: 0;
}

.gf-pm-run-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 14px;
}

.gf-pm-live {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--gf-accent-soft);
  color: var(--gf-ink-accent);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.gf-pm-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
}

.gf-pm-chip {
  padding: 2px 9px;
  border: 1px solid var(--gf-line);
  border-radius: 999px;
  background: var(--gf-panel);
  color: var(--gf-t1);
  font-size: 12px;
}

.gf-pm-sub {
  margin-top: 6px;
  color: var(--gf-t1);
  font-size: 12px;
}

/* -- metric tiles ------------------------------------------------------------
 *
 * `tabular-nums` on the values, because these numbers change once a second and
 * proportional digits make the whole row twitch sideways on every tick. */
.gf-pm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 10px;
  margin-top: 14px;
}

.gf-pm-tile {
  padding: 10px 12px;
  border: 1px solid var(--gf-line-soft);
  border-radius: var(--gf-r);
  background: var(--gf-panel);
}

.gf-pm-tile-k {
  color: var(--gf-t2);
  font-size: 12px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.gf-pm-tile-v {
  margin-top: 5px;
  font-size: 19px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.gf-pm-tile-s {
  margin-top: 3px;
  min-height: 15px;
  color: var(--gf-t2);
  font-size: 12px;
}

.gf-pm-tile.is-ok .gf-pm-tile-v { color: var(--gf-ok-ink); }
.gf-pm-tile.is-bad .gf-pm-tile-v { color: var(--gf-bad-ink); }

/* -- what it is doing this second -------------------------------------------- */

.gf-pm-now {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding: 9px 12px;
  border: 1px solid var(--gf-line-soft);
  border-radius: var(--gf-r);
  background: var(--gf-panel-2);
}

.gf-pm-now-k {
  color: var(--gf-t2);
  font-size: 12px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.gf-pm-now-v {
  font-weight: 600;
}

.gf-pm-now-t {
  color: var(--gf-t1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.gf-pm-out {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 12px;
}

.gf-pm-out.is-ok { background: var(--gf-ok-soft); color: var(--gf-ok-ink); }
.gf-pm-out.is-warn { background: var(--gf-warn-soft); color: var(--gf-warn-ink); }
.gf-pm-out.is-bad { background: var(--gf-bad-soft); color: var(--gf-bad-ink); }

.gf-pm-log {
  display: flex;
  gap: 9px;
  margin-top: 8px;
  padding: 0 2px;
  color: var(--gf-t1);
  font-size: 12px;
}

.gf-pm-log.is-warn { color: var(--gf-warn-ink); }

/* -- blocks: feed, threads, base ---------------------------------------------- */

.gf-pm-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--gf-line-soft);
}

.gf-pm-block-t {
  margin-bottom: 10px;
  color: var(--gf-t2);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* The feed scrolls, and a scroll container cuts its last visible row in half.
 * The mask fades that row out instead, so a clipped line reads as "there is
 * more below" rather than as a rendering fault. */
.gf-pm-feed {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 264px;
  overflow-y: auto;
  -webkit-mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
}

.gf-pm-feed-row {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 7px 2px;
  border-bottom: 1px solid var(--gf-line-soft);
}

.gf-pm-feed-row:last-child { border-bottom: 0; }

/* A row that arrived since the last poll lights up once and settles. Once,
 * because a highlight that repeats stops meaning "this is new". */
.gf-pm-feed-row.is-fresh {
  animation: gf-pm-land 1.6s var(--gf-ease) 1;
}

@keyframes gf-pm-land {
  0% { background: var(--gf-accent-soft); }
  100% { background: transparent; }
}

.gf-pm-kind {
  flex: none;
  width: 62px;
  padding: 2px 0;
  border-radius: var(--gf-r-sm);
  background: var(--gf-panel-2);
  color: var(--gf-t1);
  font-size: 12px;
  text-align: center;
}

.gf-pm-kind.is-user { background: var(--gf-info-soft); color: var(--gf-info-ink); }
.gf-pm-kind.is-group { background: var(--gf-accent-soft); color: var(--gf-ink-accent); }
.gf-pm-kind.is-queued { background: var(--gf-warn-soft); color: var(--gf-warn-ink); }

/* Shrinks to whatever the row has left rather than to a fixed 300px. A hard
 * cap truncated «НЕЙРОСЕТИ ПОЛЕЗНЫЕ | ИИ НЕЙРОСЕТЬ ПРОМТЫ» on a 1440 screen
 * with 700px of empty row beside it. */
.gf-pm-feed-name {
  flex: 0 1 auto;
  min-width: 0;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gf-pm-feed-n {
  flex: none;
  color: var(--gf-t1);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.gf-pm-feed-ts {
  flex: none;
  margin-left: auto;
  color: var(--gf-t2);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.gf-pm-threads {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.gf-pm-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--gf-t1);
  font-size: 12px;
}

.gf-pm-input {
  width: 110px;
  height: var(--gf-h-sm);
  padding: 0 10px;
  border: 1px solid var(--gf-line);
  border-radius: var(--gf-r-sm);
  background: var(--gf-panel);
  color: var(--gf-t0);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.gf-pm-input:focus {
  outline: none;
  border-color: var(--gf-accent-line);
}

.gf-pm-btn {
  height: var(--gf-h-sm);
  padding: 0 16px;
  border: 0;
  border-radius: var(--gf-r-sm);
  background: var(--gf-accent);
  color: #fdfdff;
  font-size: 13px;
  cursor: pointer;
  transition: background var(--gf-dur) var(--gf-ease);
}

.gf-pm-btn:hover { background: var(--gf-accent-hi); }

.gf-pm-note {
  color: var(--gf-t1);
  font-size: 12px;
}

.gf-pm-hint {
  margin: 10px 0 0;
  max-width: 76ch;
  color: var(--gf-t2);
  font-size: 12px;
  line-height: 1.55;
}

.gf-pm-empty {
  margin: 14px 0 0;
  color: var(--gf-t1);
  font-size: 13px;
}

.gf-pm-foot {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--gf-line-soft);
  color: var(--gf-t2);
  font-size: 12px;
}
