/*
 * DCManage Platform — interface styles.
 *
 * Written by hand and served from this origin. The platform runs on networks
 * that reach nothing but their own site, so there is no font service, no
 * icon service, no stylesheet host and no build step between this file and
 * the browser. Icons are inline SVG for the same reason.
 *
 * Direction is handled with logical properties (inline-start / inline-end)
 * so the Persian and English interfaces are one stylesheet with a different
 * `dir` attribute, rather than two that drift apart.
 *
 * The file is organised as a system rather than as a list of screens. Layer
 * one below is a set of raw ramps that nothing in the interface reads
 * directly; layer two names a job and points it at a step. That indirection
 * costs nothing at render time and is the reason a dark scheme is forty
 * re-pointed aliases rather than a second design. Below the token block, no
 * rule may contain a raw colour and — apart from a component's own intrinsic
 * geometry, such as the diameter of a status dot — no raw length either. A
 * value that is not a token is a value nobody is allowed to type, and that
 * is what stops the spacing scale creeping back to the two dozen ad-hoc
 * steps this file replaced.
 *
 * The governing document is docs/design-system.md. Where the two disagree,
 * the document is right and this file is a bug.
 *
 * Author: MAJID ISALOO
 */

/* Vendored Persian face. Local files only — no CDN on air-gapped nets. */
@font-face {
  font-family: "IRANSansWeb";
  src: local("IRANSans"), local("IRANSansWeb"),
       url("../fonts/IRANSansWeb.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IRANSansWeb";
  src: local("IRANSans Light"),
       url("../fonts/IRANSansWeb_Light.woff2") format("woff2");
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IRANSansWeb";
  src: local("IRANSans Medium"),
       url("../fonts/IRANSansWeb_Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IRANSansWeb";
  src: local("IRANSans Bold"),
       url("../fonts/IRANSansWeb_Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* The same files again, under a second name limited to Arabic script, for the
   Persian words an English page holds — a customer's name, a server label
   typed in Persian. The English stack had no Persian face, so those fell to
   whatever Arabic-script font the operating system had (Segoe UI's on
   Windows), visibly smaller and thinner than the Latin beside them. Leading
   the English stack with this name gives Persian characters the product's
   face while every Latin character skips it and keeps the system font; a
   page with no Persian on it never downloads the file. The full-range family
   above stays as it is, because a Persian page sets its Latin in IRANSans
   too. */
@font-face {
  font-family: "IRANSansWeb Persian";
  src: local("IRANSans"), local("IRANSansWeb"),
       url("../fonts/IRANSansWeb.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0600-06FF, U+200C-200F, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: "IRANSansWeb Persian";
  src: local("IRANSans Medium"),
       url("../fonts/IRANSansWeb_Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0600-06FF, U+200C-200F, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: "IRANSansWeb Persian";
  src: local("IRANSans Bold"),
       url("../fonts/IRANSansWeb_Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
  unicode-range: U+0600-06FF, U+200C-200F, U+FB50-FDFF, U+FE70-FEFF;
}

/* ═══════════════════════════════════════════════════════════════════════
   1. Tokens — layer one, the ramps
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Neutral ramp ─────────────────────────────────────────────────────
     One hue, held at 218°, so the neutrals never disagree about
     temperature. Adjacent pairs in the light half stay above roughly
     1.08:1, which is what makes them read as different steps rather than
     as the same colour with two names. The 400–600 band is the mid-tone
     range a dark scheme's surfaces are made of. */
  --gray-0:   #ffffff;
  --gray-25:  #fbfcfd;
  --gray-50:  #f5f7fa;
  --gray-100: #eaeef4;
  --gray-200: #dde3ec;
  --gray-300: #c6cedb;
  /* 350 exists for one job: secondary text on the dark scheme's HOVERED and
     ACTIVE surfaces. 400 measured 5.12:1 at rest and 4.20:1 the moment a row
     was pointed at, which is the same "less readable when you touch it"
     failure the retired --text-faint was deleted for. This step holds
     6.88 / 5.65 / 5.05:1 across surface, hover and active. */
  --gray-350: #9aa6b8;
  /* 3.73:1 on white, 3.47:1 on sunken and 3.20:1 on --bg. The third of those
     is why this step moved: --bg is the ground the page-header buttons and a
     standalone pager sit on, where the old value measured 2.85:1 and a
     control boundary WCAG asks 3:1 of had none. */
  --gray-400: #79859a;
  --gray-500: #6f7b8d;   /* 4.29:1 — disabled text, icon strokes, dark-theme edges */
  --gray-600: #556172;   /* 6.29:1 on white, 5.86:1 on sunken — secondary text */
  --gray-700: #3d4756;
  --gray-800: #29313d;
  --gray-900: #191f28;   /* 16.56:1 on white */
  --gray-950: #0f141b;

  /* ── Hue families ─────────────────────────────────────────────────────
     Each family carries a tint (50/100) for fills, a mid (400/500) for
     strokes and bars, and a dark (600/700) for text, so every meaning has
     a member that works as a foreground on light AND a member that works
     as a foreground on dark. The reds were chosen with their siblings
     rather than separately: on a panel whose red marks a customer who is
     offline, that is the wrong colour to have eyeballed. */
  --blue-50:  #eef4ff;  --blue-100: #dbe6fe;  --blue-200: #bed2fc;
  --blue-300: #93b4fa;  --blue-400: #5b8def;  --blue-500: #3b76e8;
  --blue-600: #2563eb;  --blue-700: #1d4ed8;  --blue-800: #1e40af;

  --red-50:   #fdf2f2;  --red-100:  #fbe2e2;  --red-200:  #f6c6c6;
  --red-300:  #eea0a0;  --red-400:  #e07070;  --red-500:  #cf4444;
  --red-600:  #b92d2d;  --red-700:  #a11f1f;  --red-800:  #851c1c;

  --amber-50: #fff8ea;  --amber-100:#ffeecd;  --amber-200:#fbdda3;
  --amber-300:#f3c266;  --amber-400:#e29a1f;  --amber-500:#c1760a;
  --amber-600:#a35a08;  --amber-700:#8a4b09;  --amber-800:#733d08;

  --green-50: #eaf7ef;  --green-100:#d2eedc;  --green-200:#a6ddbb;
  --green-300:#69c48f;  --green-400:#2fa261;  --green-500:#188044;
  --green-600:#136a38;  --green-700:#10562e;  --green-800:#0d4526;

  --cyan-50:  #e8f6fa;  --cyan-100: #cfebf2;  --cyan-200: #a1d8e5;
  --cyan-300: #66bfd2;  --cyan-400: #2b9cb6;  --cyan-500: #0e7490;
  --cyan-600: #0b5c74;  --cyan-700: #094a5e;  --cyan-800: #083c4c;

  /* ── Space ────────────────────────────────────────────────────────────
     Nine steps on a 4px base, in rem so they scale with the operator's own
     browser setting. The count is the point rather than any individual
     value: with 17px, 15px, 13px and 22px all in play no two blocks on a
     page can share an edge, so the eye never finds a repeating beat and
     the interface reads as hand-placed. The steps start tight because
     density is a feature here — an operator scanning 300 ports wants rows,
     not air. */
  --s-1: 0.125rem;  /*  2px */
  --s-2: 0.25rem;   /*  4px */
  --s-3: 0.375rem;  /*  6px */
  --s-4: 0.5rem;    /*  8px */
  --s-5: 0.75rem;   /* 12px */
  --s-6: 1rem;      /* 16px */
  --s-7: 1.25rem;   /* 20px */
  --s-8: 1.75rem;   /* 28px */
  --s-9: 2.5rem;    /* 40px */

  /* ── Type ─────────────────────────────────────────────────────────────
     Eight steps replacing eighteen. Differences below the threshold of
     perception were never communicating levels of importance, only that
     nobody had decided. The gap that actually needed filling is --fs-4: a
     card title previously had no size of its own and inherited body, which
     is the same size as, and on Segoe UI the same rendered weight as,
     every bolded value in its own table. Only --fs-1 and --fs-2 are close,
     and those two are separated by case, tracking and colour as well as
     size, never by size alone. The display steps are clamped so a page
     title scales continuously rather than stepping at a breakpoint. */
  /* --fs-1 was 11px in English and 12px in Persian. Eleven pixels was the
     size of every badge, tile label and column header on an English page,
     which is too small to read comfortably for the operators this panel is
     for; it is 12px in both languages now, and in English it is told apart
     from --fs-2 by case, tracking and colour, as the paragraph above says. */
  --fs-1: 0.75rem;                                      /* 12px — eyebrow, column header */
  --fs-2: 0.75rem;                                      /* 12px — meta line, badge, hint */
  --fs-3: 0.8125rem;                                    /* 13px — table cell, input, button, nav */
  --fs-4: 0.9375rem;                                    /* 15px — card title, lead paragraph */
  --fs-5: 1.125rem;                                     /* 18px — a metric whose value is a word */
  --fs-6: clamp(1.25rem, 1.05rem + 0.9vw, 1.375rem);    /* 20–22px — page title */
  --fs-7: clamp(1.5rem, 1.2rem + 1.4vw, 1.75rem);       /* 24–28px — a metric whose value is a number */
  --fs-8: clamp(1.75rem, 1.2rem + 2.6vw, 2.125rem);     /* 28–34px — the public hero */

  /* ── Weight ───────────────────────────────────────────────────────────
     Three, not seven. Segoe UI exposes 300/400/600/700 and Tahoma — the
     Persian fallback — exposes 400/700, so a ladder of 500/650/700/750/800
     renders as two levels on the most common operator platform while the
     author sees six on a Mac. That gap is the whole reason the panel
     "looks different" on the customer's machine. 500 is deliberately
     absent because it is a no-op on Segoe UI, which is the platform the
     argument is about. */
  --fw-regular: 400;
  --fw-medium: 600;
  --fw-bold: 700;

  /* ── Leading ──────────────────────────────────────────────────────────
     Tightens as size grows. A wrapped 22px heading set with prose leading
     reads as two unrelated lines, and that is worst in Persian, where the
     strings are longer and wrap sooner. */
  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-body: 1.55;

  /* ── Tracking and casing ──────────────────────────────────────────────
     Held in tokens specifically so the RTL rule below can zero them.
     Browsers apply letter-spacing between the glyphs of an Arabic-script
     run while keeping the joined initial, medial and final forms, so a
     tracked Persian label renders as a word whose connections have visibly
     broken — it reads as damaged text, not as a spaced label. Uppercase is
     simultaneously a no-op there. One rule at the root repairs the sidebar
     section, the tile label, the column header and the stacked-row label
     at once. */
  --track-eyebrow: 0.05em;
  --track-tight: -0.01em;
  --caps-eyebrow: uppercase;

  /* ── Structure ────────────────────────────────────────────────────────
     Four radii on a doubling ramp, plus a pill. Small enough that an
     instrument's corners only stop the eye catching rather than softening
     anything, large enough at the panel step that a card carrying a
     hand-sized shadow reads as a panel rather than as a sticker.

     These four values used to be declared twice: 3/5/8 here and 4/8/12 in
     a second :root at the foot of the file, so the ramp a reader found at
     the top of the token layer was not the ramp the browser used. The
     later values won and are the ones kept; there is one declaration now.
     --radius-4 is the card and tile step and is deliberately equal to
     --radius-3: they were separate while the two blocks disagreed. */
  --radius-1: 4px;
  --radius-2: 8px;
  --radius-3: 12px;
  --radius-pill: 999px;
  --hair: 1px;

  /* The direction a row-long gradient runs, start to end. Re-pointed in the
     RTL block, because linear-gradient() has no logical keyword. */
  --to-inline-end: to right;

  /* ── Families ─────────────────────────────────────────────────────────
     Persian UI uses vendored IRANSansWeb (local first). English / LTR keeps
     the system stack for Latin, led by the Arabic-script-only copy of the
     Persian face so Persian data on an English page is set in the product's
     face (see the @font-face blocks). Consolas stays in mono so Windows never
     falls to Courier New on identifiers. */
  --font: "IRANSansWeb Persian", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-fa: "IRANSansWeb", "SF Arabic", "Geeza Pro", "Segoe UI", "Noto Naskh Arabic",
             "Noto Sans Arabic", Tahoma, system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Mono",
               "DejaVu Sans Mono", "Liberation Mono", monospace;

  /* ═════ Layer two — the semantic aliases ═════
     These are what components read. The dark scheme further down is a copy
     of this block and nothing else. */

  /* ── Surfaces ─────────────────────────────────────────────────────── */
  --bg:              var(--gray-100);
  --surface:         var(--gray-0);
  --surface-raised:  var(--gray-25);
  --surface-sunken:  var(--gray-50);
  --surface-hover:   var(--gray-100);
  --surface-active:  var(--gray-200);

  /* ── Lines ────────────────────────────────────────────────────────────
     Three jobs, three tokens. --line-control is the boundary of anything
     clickable, which WCAG 1.4.11 asks to reach 3:1 against its
     surroundings: a default button on a white card whose border cannot be
     seen has no affordance at all. */
  --line:         var(--gray-200);   /* decorative: row dividers, card edge */
  --line-strong:  var(--gray-300);   /* structural: card header seam, thead rule */
  --line-control: var(--gray-400);   /* 3.73:1 on surface, 3.47:1 on sunken, 3.20:1 on --bg */

  /* ── Text ─────────────────────────────────────────────────────────────
     There is deliberately no third grey. The retired --text-faint carried
     every column header, tile caption, sidebar heading and stacked-row
     label — the most repeated text style in the product — at 3.07:1 on
     white and 2.81:1 inside a hovered row, which is to say it became less
     readable the moment the operator pointed at it. Labels get their
     recessive quality from size and casing now, not from a colour that
     cannot be read. --text-3 exists as a distinct alias so a future
     contrast pass has somewhere to aim. */
  --text:          var(--gray-900);
  --text-2:        var(--gray-600);   /* 6.29:1 on surface, 5.86:1 on sunken, 5.40:1 hovered */
  --text-3:        var(--gray-600);
  /* 4.00:1 on the sunken fill a disabled input takes, which is below the 4.5:1
     this block otherwise holds to. Disabled controls are exempt from WCAG
     1.4.3, and the exemption is stated here rather than left implicit, because
     the reason this token is allowed to be quiet is exactly the reason it must
     never be borrowed for text that is merely secondary. */
  --text-disabled: var(--gray-500);
  --text-invert:   var(--gray-0);

  /* ── Meaning ──────────────────────────────────────────────────────────
     Four hues chosen together in one lightness band rather than
     independently. Every value clears 4.5:1 on --surface, and because
     contrast is symmetric that also makes white text on a solid fill of
     the same hue clear 4.5:1 — so .btn--danger and .badge--danger are
     fixed by the same number. Measured on --surface: accent 6.70:1,
     danger 7.69:1, warn 6.79:1, ok 6.67:1, info 5.36:1. */
  --accent:        var(--blue-700);
  --accent-strong: var(--blue-800);
  --accent-tint:   var(--blue-50);
  --accent-edge:   var(--blue-200);

  --danger:        var(--red-700);
  --danger-strong: var(--red-800);
  --danger-tint:   var(--red-50);
  --danger-edge:   var(--red-200);
  --danger-fill:   var(--red-600);    /* meters and solid bars, where mass not text */
  /* The boundary of a destructive control that is NOT filled. --danger-edge
     is a badge's ring at 1.52:1 and cannot carry a button; this step holds
     4.60:1 on --surface, 4.29:1 on --surface-sunken and 3.95:1 on --bg, so
     the quiet danger button clears WCAG 1.4.11 on every ground it sits on. */
  --danger-control: var(--red-500);

  /* Ink is the primary action colour, and making it near-black rather than
     brand blue is the decision that lets the rest of the colour system
     ration. When the confirm button is quiet, the one destructive control
     on a page is the only saturated thing on screen and cannot be
     mis-clicked at three in the morning. */
  --ink:        var(--gray-900);
  --ink-hover:  var(--gray-800);
  --ink-invert: var(--gray-0);

  --warn:          var(--amber-700);
  --warn-tint:     var(--amber-50);
  --warn-edge:     var(--amber-200);
  --warn-fill:     var(--amber-500);

  --ok:            var(--green-600);
  --ok-tint:       var(--green-50);
  --ok-edge:       var(--green-200);
  --ok-fill:       var(--green-500);

  --info:          var(--cyan-500);
  --info-tint:     var(--cyan-50);
  --info-edge:     var(--cyan-200);
  --info-fill:     var(--cyan-400);

  /* ── Enforcement state ────────────────────────────────────────────────
     The single most consequential fact in this product — whether the
     platform will shut a real customer's port — used to be rendered in
     four contradictory colour languages across five files. An operator
     cannot learn what red means under those conditions, and painting the
     correctly-configured armed state as a danger alert teaches people to
     ignore red on the one screen where red is load-bearing.

     These two triples are now the only way to say it, everywhere. Armed is
     red because red here means "this changes customer connectivity";
     observing is deliberately colourless because it is the absence of
     that, not a success. */
  --state-armed:          var(--danger);
  --state-armed-tint:     var(--danger-tint);
  --state-armed-edge:     var(--danger-edge);
  --state-observing:      var(--text-2);
  --state-observing-tint: var(--surface-sunken);
  --state-observing-edge: var(--line-strong);

  /* ── Chart series ─────────────────────────────────────────────────────
     Blue against green is the classic deuteranopia collision, and it was
     being drawn on the one page whose entire purpose is telling those two
     apart — equally fatal to anyone printing or screenshotting it in
     greyscale. Blue against amber separates far better and still not
     enough on its own, so the second series is dashed. Hue is never the
     only channel; the dash is a token so that rule is enforceable rather
     than a comment. */
  --series-in:       var(--blue-700);   /* 6.70:1 on surface */
  --series-out:      var(--amber-500);  /* 3.58:1 on surface */
  --series-out-dash: 5 3;
  --series-overlay-dash: 1.5 3;
  --series-grid:     var(--line);
  --series-axis:     var(--text-2);

  /* ── Meter texture ────────────────────────────────────────────────────
     The same argument as the chart's dash, applied to the bar that carries
     the most consequential number in the product. A utilisation meter whose
     only difference between "fine", "watch this" and "over" is hue tells a
     deuteranope and a greyscale printout nothing, and on the services list
     the bar is the sole warning signal in the row. The two alert bands get a
     hatch, at different angles and pitches from each other, so the band is
     legible by texture before it is legible by colour. */
  --meter-hatch: rgba(255, 255, 255, .5);

  /* The unfilled part of the busy spinner drawn on a solid button, where the
     surrounding tokens are all foregrounds and none of them is the right
     value for a track. */
  --busy-track-invert: rgba(255, 255, 255, .35);

  /* ── Focus ────────────────────────────────────────────────────────────
     --focus clears 3:1 on every surface in both themes and the halo gives
     it a second edge so it survives landing on a tinted row. The rule that
     consumes it is the only focus rule in this file. */
  --focus:        var(--blue-600);   /* 5.17:1 on surface */
  --focus-halo:   rgba(37, 99, 235, .25);
  --focus-width:  2px;
  --focus-offset: 2px;

  /* ── Depth ────────────────────────────────────────────────────────────
     An instrument does not float. Cards are bordered and nearly flat, and
     the only real elevation belongs to things that genuinely sit above the
     page. --scrim exists because a sticky topbar needs a depth cue that a
     hairline at 1.13:1 against the page cannot give it. --shadow-overlay
     exists because the first dialog this product grows must not have to
     invent its own value. */
  --shadow-sm:      0 1px 2px rgba(15, 23, 42, .05);
  /* The one a card takes. A hairline does the separating; this only stops
     a page of white rectangles reading as one sheet. It was a literal in a
     second :root at the foot of the file, which is the one place a reader
     comparing elevations would not look. */
  --shadow-card:    0 1px 2px rgba(15, 23, 42, .04), 0 6px 20px -14px rgba(15, 23, 42, .18);
  --shadow:         0 1px 3px rgba(15, 23, 42, .06), 0 8px 24px -12px rgba(15, 23, 42, .12);
  --shadow-lg:      0 4px 12px rgba(15, 23, 42, .08), 0 24px 48px -24px rgba(15, 23, 42, .25);
  --shadow-sticky:  0 1px 0 var(--line), 0 6px 16px -12px rgba(15, 23, 42, .35);
  --shadow-overlay: 0 12px 32px -8px rgba(15, 23, 42, .18), 0 32px 64px -24px rgba(15, 23, 42, .35);
  --scrim:          rgba(15, 23, 42, .07);

  /* ── Density ──────────────────────────────────────────────────────────
     Derived from the space scale, so one attribute on <html> loosens the
     whole panel without touching a rule. Dense is the default because that
     is what this product is for; comfortable exists because 13px at 125%
     Windows scaling is small for an operator who is fifty-five, and an
     escape hatch nobody can reach is not a mitigation. */
  --cell-pad-y: var(--s-3);
  --cell-pad-x: var(--s-5);
  --card-pad:   var(--s-6);
  --rail-pad-y: var(--s-4);
  --control-h:  2rem;      /* 32px */

  /* ── Frame ────────────────────────────────────────────────────────────
     The gutter is one token consumed by both the topbar and the content
     beneath it, so they can never disagree about their inline edge again
     and the page title cannot read as subtly misaligned with the header. */
  --sidebar-w:  15rem;     /* 240px */
  --topbar-h:   3.25rem;   /* 52px */
  --gutter:     clamp(var(--s-5), 2vw, var(--s-7));
  --content-max: 90rem;    /* 1440px */

  /* ── Measure and field widths ─────────────────────────────────────────
     Character units only, so they scale with the body size instead of
     ignoring it. Field widths are maxima rather than fixed sizes: a
     hard-coded search width cannot shrink, so inside a card that clips
     overflow the submit button beside it is not pushed to a second line —
     it is simply gone. */
  --measure:       68ch;
  --measure-tight: 52ch;
  --w-xs:  8ch;
  --w-sm: 14ch;
  --w-md: 22ch;
  --w-lg: 34ch;

  /* ── Grid minimums ────────────────────────────────────────────────────
     Used only inside min(), never bare. auto-fit collapses empty tracks
     but never shrinks a fixed minimum, so a bare 340px floor is wider than
     a 320px phone and pushes the body sideways. */
  --col-narrow: 180px;
  --col:        230px;
  --col-wide:   300px;

  /* Minimum touch target, raised only where the pointer is coarse. */
  --tap: 44px;

  /* ── Rack elevation ───────────────────────────────────────────────────
     The rack keeps a dark ground in both themes: an equipment elevation is
     a picture of a physical object and reads correctly as dark against a
     light page. Its greys are tokens so the dark scheme can lighten it
     rather than let it disappear into the background. */
  --rack-body:   #2c3442;
  --rack-rail:   #232a36;
  --rack-line:   #3c4557;
  --rack-text:   #e7ecf5;   /* 10.90:1 on --rack-body */
  --rack-text-2: #9fb0c9;   /*  5.68:1 on --rack-body, 6.83:1 on the dark one */
  /* The label on every unoccupied unit, which on a half-empty rack is the
     most repeated string on the page. It measured 1.99:1 and 2.39:1 — under
     half the bar for 11px text — because this was the one block in the file
     with no ratios recorded beside it. They are recorded now. */
  --rack-free:   #93a0b8;   /*  4.75:1 on --rack-body, 5.71:1 on the dark one */

  /* The chassis itself. These were five literal hexes inside the component
     rules — Tailwind's blue/emerald/amber/violet 400s, a fifth hue family the
     product has no other member of — so the dark scheme lightened the rack's
     ground beneath them and their contrast against it silently changed. They
     are drawn from the ramps that already exist and re-pointed with the rest. */
  --rack-device-top:    #3d4759;
  --rack-device-bottom: #333c4c;
  --rack-device-edge:   #4b5468;
  --rack-device-kind:   rgba(255, 255, 255, .09);
  --rack-link-hover:    var(--blue-200);

  --rack-server:  var(--blue-300);
  --rack-switch:  var(--green-300);
  --rack-power:   var(--amber-300);
  --rack-passive: var(--cyan-300);
  --rack-blank:   var(--gray-500);

  --rack-switch-top:     #2f4a45;  --rack-switch-bottom:  #27403c;
  --rack-power-top:      #4a4030;  --rack-power-bottom:   #3d3628;
  --rack-passive-top:    #3b3950;  --rack-passive-bottom: #323046;
  --rack-blank-a:        #2f3644;  --rack-blank-b:        #343c4c;

  /* A machine that has left the datacenter still holds its unit. It reads
     amber and hatched rather than as another solid chassis, and the hatch is
     what keeps it apart from a PDU — the only other amber-edged thing in an
     elevation — without spending a sixth hue on it. Hover lifts the whole
     body to yellow, which is the one state in this component that answers a
     question by being pointed at. */
  --rack-out:            var(--amber-300);
  --rack-out-a:          #453a26;  --rack-out-b:          #3a3120;
  --rack-out-hover-a:    #6a5623;  --rack-out-hover-b:    #574719;
  --rack-out-hover-edge: var(--amber-400);
  --rack-out-text:       var(--amber-100);
  /* The text on that yellow hover body. The rack is dark in both schemes, so
     one value serves both and the dark blocks need no copy of it. */
  --rack-out-hover-text: var(--gray-0);
}

/* Persian and any other RTL locale. The three-property eyebrow recipe that
   makes an English label work — small, tracked, uppercase — contributes
   nothing but damage in Arabic script, so both harmful properties are
   neutralised at the root rather than patched per component. Persian is
   also set one step larger, because Arabic script carries meaning in
   strokes below the baseline that 13px does not resolve at the fallback
   faces most operators will actually have.

   Scoped to :root, and that scoping is load-bearing rather than tidiness.
   `dir` is stated on both <html> and <body>, so a bare [dir="rtl"] matched
   BOTH — and a custom property declared on an element beats the value it
   would otherwise inherit, so <body> re-declared --fs-3 at 14px on itself
   and every descendant. The comfortable density, which raises --fs-3 on
   <html>, therefore changed nothing at all on a Persian page: the one
   accessibility escape hatch in the product was dead in the language whose
   script needs it most. */
:root[dir="rtl"] {
  --track-eyebrow: 0;
  --track-tight: 0;
  --caps-eyebrow: none;
  --fs-3: 0.875rem;    /* 14px */
  --fs-2: 0.8125rem;   /* 13px */
  /* The eyebrow step, which English sets in capitals, needs no override:
     it is 12px at the root in both languages. */

  /* "Towards the end of the line", for a gradient that runs along a row.
     The gradient syntax has no logical keyword, so the direction is a token
     and flips here with everything else. */
  --to-inline-end: to left;

  /* Leading, for the same reason the size steps up. Arabic script puts
     marks above the line and dots below it, and IRANSans draws a taller
     ascender and a deeper descender than the Latin stack at the same em,
     so the Latin leading that reads as comfortable here reads as touching.
     Persian strings are also longer, so a heading wraps where its English
     twin does not and the tight step is the one that shows it first. Each
     value is one notch looser than its Latin counterpart; nothing else
     about the ladder changes. */
  --lh-tight: 1.3;
  --lh-snug: 1.5;
  --lh-body: 1.75;

  /* The medium weight, which in Persian was rendering as bold. The
     vendored face ships 300/400/500/700 and has no 600, and the CSS font
     matching rules resolve a requested 600 by looking upward first — so
     every label, button, table <strong> and card title asking for
     --fw-medium landed on the 700 file. Persian had two weights where
     English has three, and the level between body and heading did not
     exist. 500 is the face's own Medium and is what the token means. */
  --fw-medium: 500;
}

/* The comfortable density. Spacing loosens and the workhorse size steps up
   one notch; nothing else changes, because density is a property of the
   frame rather than of the type ladder. */
:root[data-density="comfortable"] {
  --cell-pad-y: var(--s-5);
  --cell-pad-x: var(--s-6);
  --card-pad:   var(--s-7);
  --rail-pad-y: var(--s-5);
  --control-h:  2.25rem;
  --fs-3: 0.875rem;
}
:root[dir="rtl"][data-density="comfortable"] { --fs-3: 0.9375rem; }

/* ═══════════════════════════════════════════════════════════════════════
   2. Dark scheme
   ═══════════════════════════════════════════════════════════════════════

   A re-pointing of the alias layer and nothing else: no ramp changes, no
   component rule changes, no template changes. That is only affordable
   because layer one already carries a light member of every hue family —
   the reason a dark scheme was blocked before was that all five semantic
   colours had been chosen as dark-on-light foregrounds, so there was
   nothing to promote.

   The OS signal is the default; an explicit choice on <html> wins in
   either direction, because an operator on a light desktop working in a
   dark operations room should be able to decide. The two blocks are
   duplicates — that is the price of having no build step — and
   tests/Feature/InterfaceShellTest.php fails the moment they differ by a
   single declaration. Every ratio below was measured against --surface. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    /* Surfaces — a cool graphite ramp with the same number of steps as the
       light one, so every component keeps its structure. */
    --bg:              #0d1218;
    --surface:         #161d26;
    --surface-raised:  #1c2531;
    --surface-sunken:  #111820;
    --surface-hover:   #232d3a;
    --surface-active:  #2a3542;

    --line:         #242e3b;
    --line-strong:  #2f3a49;
    --line-control: var(--gray-500);   /* 3.95:1 — control boundaries hold */

    --text:          var(--gray-100);  /* 14.57:1 */
    /* Measured against the surface a row is HOVERED on rather than the one it
       sits at rest on, because that is the state an operator reads it in:
       6.88:1 at rest, 5.65:1 hovered, 5.05:1 active. The 400 step held 5.12:1
       at rest and fell to 4.20:1 and 3.76:1 under the pointer, which is the
       defect the retired --text-faint was deleted for, restated in a
       different scheme. */
    --text-2:        var(--gray-350);
    --text-3:        var(--gray-350);
    --text-disabled: var(--gray-500);
    --text-invert:   #0d1218;

    /* Each hue promotes to its 300 step, the member that exists for
       exactly this. */
    --accent:        var(--blue-300);   /* 8.20:1 */
    --accent-strong: var(--blue-200);
    --danger:        var(--red-300);    /* 8.22:1 */
    --danger-strong: var(--red-200);
    --warn:          var(--amber-300);  /* 10.28:1 */
    --ok:            var(--green-300);  /*  7.99:1 */
    --info:          var(--cyan-300);   /*  8.04:1 */

    /* Tints and edges. These are the only literals in the alias layer, and
       they are literals rather than color-mix() so that the scheme does
       not depend on a function the light theme has managed to avoid
       depending on. Each is the hue mixed roughly 14% (tint) and 38%
       (edge) into --surface. */
    --accent-tint: #18263c;  --accent-edge: #33456b;
    --danger-tint: #2b1a1e;  --danger-edge: #5f3237;
    --warn-tint:   #2b2317;  --warn-edge:   #614421;
    --ok-tint:     #15261a;  --ok-edge:     #26492f;
    --info-tint:   #112630;  --info-edge:   #1d4a5c;

    /* Solid fills stay at a mid step with light text rather than inverting
       to a pale block, because a bright rectangle is the wrong visual
       weight for a destructive control on a dark page. */
    --danger-fill: var(--red-400);
    /* 5.43:1 on --surface and 5.72:1 on --surface-sunken — the unfilled
       destructive control's boundary, where --danger-edge is 1.61:1. */
    --danger-control: var(--red-400);
    --warn-fill:   var(--amber-400);
    --ok-fill:     var(--green-400);
    --info-fill:   var(--cyan-400);

    /* Ink inverts: the primary action becomes the lightest thing on
       screen, which keeps it quiet relative to a saturated destructive
       button in exactly the way the light scheme does. */
    --ink:        var(--gray-100);
    --ink-hover:  var(--gray-0);
    --ink-invert: #0d1218;

    --state-observing-tint: var(--surface-sunken);
    --state-observing-edge: var(--line-strong);

    /* The chart is the one surface that would otherwise stay light-theme
       forever. Because the series are drawn as CSS classes rather than
       read as custom properties in JavaScript, it follows for free. The
       pair separates less here than in the light theme, so the dash on the
       upload series is doing more of the work and must not be dropped. */
    --series-in:  var(--blue-300);
    --series-out: var(--amber-500);   /* 4.74:1 on surface */

    --focus:      var(--blue-400);    /* 5.25:1 */
    --focus-halo: rgba(91, 141, 239, .30);

    /* A shadow reads as nothing on a dark ground; the hairline does the
       separating and the scrim under the sticky topbar has to be much
       heavier to be a boundary at all. */
    --shadow-sm:      0 1px 2px rgba(0, 0, 0, .4);
    --shadow-card:    0 1px 2px rgba(0, 0, 0, .35), 0 6px 20px -14px rgba(0, 0, 0, .7);
    --shadow:         0 1px 3px rgba(0, 0, 0, .45), 0 8px 24px -12px rgba(0, 0, 0, .6);
    --shadow-lg:      0 4px 12px rgba(0, 0, 0, .5), 0 24px 48px -24px rgba(0, 0, 0, .7);
    --shadow-sticky:  0 1px 0 var(--line), 0 6px 16px -12px rgba(0, 0, 0, .8);
    --shadow-overlay: 0 12px 32px -8px rgba(0, 0, 0, .6), 0 32px 64px -24px rgba(0, 0, 0, .8);
    --scrim:          rgba(0, 0, 0, .5);

    /* The rack must stop being darker than the page around it, or it stops
       reading as a physical object sitting on a surface. The chassis bodies
       lift with it, or the elevation's internal contrast changes between the
       two schemes for no reason a reader could name. */
    --rack-body: #1e2734;
    --rack-rail: #18202b;
    --rack-line: #2e3948;
    --rack-device-top:    #313c4d;
    --rack-device-bottom: #2a3444;
    --rack-device-edge:   #3f4a5d;
    --rack-switch-top:     #253d39;  --rack-switch-bottom:  #1f3431;
    --rack-power-top:      #3d3527;  --rack-power-bottom:   #322c20;
    --rack-passive-top:    #302f43;  --rack-passive-bottom: #29273a;
    --rack-blank-a:        #262d3a;  --rack-blank-b:        #2b3341;
    --rack-out-a:          #3c3220;  --rack-out-b:          #322a1b;
    --rack-out-hover-a:    #5d4b1f;  --rack-out-hover-b:    #4b3d16;
  }
}

/* The identical body, applied when the operator has chosen dark
   explicitly. It is a verbatim copy of the block above, comments and all:
   the copy drifted once — the secondary text fell back to the 400 step that
   fails AA under the pointer and the rack chassis kept its light-theme
   greys — so a test now compares the two declaration by declaration. */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Surfaces — a cool graphite ramp with the same number of steps as the
     light one, so every component keeps its structure. */
  --bg:              #0d1218;
  --surface:         #161d26;
  --surface-raised:  #1c2531;
  --surface-sunken:  #111820;
  --surface-hover:   #232d3a;
  --surface-active:  #2a3542;

  --line:         #242e3b;
  --line-strong:  #2f3a49;
  --line-control: var(--gray-500);   /* 3.95:1 — control boundaries hold */

  --text:          var(--gray-100);  /* 14.57:1 */
  /* Measured against the surface a row is HOVERED on rather than the one it
     sits at rest on, because that is the state an operator reads it in:
     6.88:1 at rest, 5.65:1 hovered, 5.05:1 active. The 400 step held 5.12:1
     at rest and fell to 4.20:1 and 3.76:1 under the pointer, which is the
     defect the retired --text-faint was deleted for, restated in a
     different scheme. */
  --text-2:        var(--gray-350);
  --text-3:        var(--gray-350);
  --text-disabled: var(--gray-500);
  --text-invert:   #0d1218;

  /* Each hue promotes to its 300 step, the member that exists for
     exactly this. */
  --accent:        var(--blue-300);   /* 8.20:1 */
  --accent-strong: var(--blue-200);
  --danger:        var(--red-300);    /* 8.22:1 */
  --danger-strong: var(--red-200);
  --warn:          var(--amber-300);  /* 10.28:1 */
  --ok:            var(--green-300);  /*  7.99:1 */
  --info:          var(--cyan-300);   /*  8.04:1 */

  /* Tints and edges. These are the only literals in the alias layer, and
     they are literals rather than color-mix() so that the scheme does
     not depend on a function the light theme has managed to avoid
     depending on. Each is the hue mixed roughly 14% (tint) and 38%
     (edge) into --surface. */
  --accent-tint: #18263c;  --accent-edge: #33456b;
  --danger-tint: #2b1a1e;  --danger-edge: #5f3237;
  --warn-tint:   #2b2317;  --warn-edge:   #614421;
  --ok-tint:     #15261a;  --ok-edge:     #26492f;
  --info-tint:   #112630;  --info-edge:   #1d4a5c;

  /* Solid fills stay at a mid step with light text rather than inverting
     to a pale block, because a bright rectangle is the wrong visual
     weight for a destructive control on a dark page. */
  --danger-fill: var(--red-400);
  /* 5.43:1 on --surface and 5.72:1 on --surface-sunken — the unfilled
     destructive control's boundary, where --danger-edge is 1.61:1. */
  --danger-control: var(--red-400);
  --warn-fill:   var(--amber-400);
  --ok-fill:     var(--green-400);
  --info-fill:   var(--cyan-400);

  /* Ink inverts: the primary action becomes the lightest thing on
     screen, which keeps it quiet relative to a saturated destructive
     button in exactly the way the light scheme does. */
  --ink:        var(--gray-100);
  --ink-hover:  var(--gray-0);
  --ink-invert: #0d1218;

  --state-observing-tint: var(--surface-sunken);
  --state-observing-edge: var(--line-strong);

  /* The chart is the one surface that would otherwise stay light-theme
     forever. Because the series are drawn as CSS classes rather than
     read as custom properties in JavaScript, it follows for free. The
     pair separates less here than in the light theme, so the dash on the
     upload series is doing more of the work and must not be dropped. */
  --series-in:  var(--blue-300);
  --series-out: var(--amber-500);   /* 4.74:1 on surface */

  --focus:      var(--blue-400);    /* 5.25:1 */
  --focus-halo: rgba(91, 141, 239, .30);

  /* A shadow reads as nothing on a dark ground; the hairline does the
     separating and the scrim under the sticky topbar has to be much
     heavier to be a boundary at all. */
  --shadow-sm:      0 1px 2px rgba(0, 0, 0, .4);
  --shadow-card:    0 1px 2px rgba(0, 0, 0, .35), 0 6px 20px -14px rgba(0, 0, 0, .7);
  --shadow:         0 1px 3px rgba(0, 0, 0, .45), 0 8px 24px -12px rgba(0, 0, 0, .6);
  --shadow-lg:      0 4px 12px rgba(0, 0, 0, .5), 0 24px 48px -24px rgba(0, 0, 0, .7);
  --shadow-sticky:  0 1px 0 var(--line), 0 6px 16px -12px rgba(0, 0, 0, .8);
  --shadow-overlay: 0 12px 32px -8px rgba(0, 0, 0, .6), 0 32px 64px -24px rgba(0, 0, 0, .8);
  --scrim:          rgba(0, 0, 0, .5);

  /* The rack must stop being darker than the page around it, or it stops
     reading as a physical object sitting on a surface. The chassis bodies
     lift with it, or the elevation's internal contrast changes between the
     two schemes for no reason a reader could name. */
  --rack-body: #1e2734;
  --rack-rail: #18202b;
  --rack-line: #2e3948;
  --rack-device-top:    #313c4d;
  --rack-device-bottom: #2a3444;
  --rack-device-edge:   #3f4a5d;
  --rack-switch-top:     #253d39;  --rack-switch-bottom:  #1f3431;
  --rack-power-top:      #3d3527;  --rack-power-bottom:   #322c20;
  --rack-passive-top:    #302f43;  --rack-passive-bottom: #29273a;
  --rack-blank-a:        #262d3a;  --rack-blank-b:        #2b3341;
  --rack-out-a:          #3c3220;  --rack-out-b:          #322a1b;
  --rack-out-hover-a:    #5d4b1f;  --rack-out-hover-b:    #4b3d16;
}

:root[data-theme="light"] { color-scheme: light; }

/* ═══════════════════════════════════════════════════════════════════════
   3. Base
   ═══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

/* `hidden` means hidden. The browser's own [hidden] { display: none } is the
   weakest rule there is, so any component that sets a display — the chart
   legend's inline-flex items, for one — showed an element its markup had
   hidden: outside compare mode the legend drew a third, unlabelled dotted
   key. until-found is left to the browser, which reveals it on find. */
[hidden]:not([hidden="until-found"]) { display: none !important; }

body {
  font-family: var(--font);
  font-size: var(--fs-3);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  /* Digits line up everywhere by default. Without this a column of failure
     counts cannot be compared by shape, and an operator asking "which
     switch fails most" has to read every number instead of seeing it. */
  font-variant-numeric: tabular-nums;
}

body[dir="rtl"] { font-family: var(--font-fa); }

h1, h2, h3, h4 {
  margin: 0;
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  text-wrap: balance;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 2px; }

img, svg, canvas, video { max-inline-size: 100%; }
svg.icon { inline-size: 17px; block-size: 17px; flex: 0 0 auto; stroke-width: 1.9; }

/* A glyph whose meaning is "the way you came from" reverses with the
   reading direction. The pagination arrows were already flipped; the back
   arrow and the sign-out door are the same class of glyph and were missed,
   so in Persian the بازگشت button showed the universal glyph for "next"
   and the sign-out figure walked into the building rather than out of it. */
[dir="rtl"] .icon--directional { transform: scaleX(-1); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Focus — one rule, never overridden ────────────────────────────────
   `outline: none` appears nowhere in this file. This uses a real outline
   rather than a box-shadow so that it also lands on native checkboxes,
   which are the controls a border-recolouring rule silently loses: the
   permission editors are grids of dozens of bare inputs and are the
   highest-consequence screens in the product. :focus-visible rather than
   :focus, so a mouse click does not draw it. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
  box-shadow: 0 0 0 calc(var(--focus-width) + var(--focus-offset) + 2px) var(--focus-halo);
  border-radius: var(--radius-2);
}

/* Inside a track with `overflow: hidden` — the segmented control, the
   pager — an outward offset is clipped, so the ring turns inward there. */
.segmented > :focus-visible,
.pager__controls > :focus-visible { outline-offset: calc(var(--focus-offset) * -1.5); }

/* The tick itself is on-palette and, more to the point, visible: a bare
   checkbox has no author border for anything else to recolour. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }

/* Screen-reader-only text, for controls whose whole label is an icon. */
.sr-only {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* Bypass blocks. Every page opens with the brand link and up to fourteen
   navigation links before <main>, so a keyboard or switch operator
   re-traversed the whole sidebar on every single page load. The link is
   invisible until it takes focus, which is the one moment it is useful. */
.skip-link:focus {
  position: fixed; inset-block-start: var(--s-4); inset-inline-start: var(--s-4);
  z-index: 100;
  inline-size: auto; block-size: auto;
  margin: 0; padding: var(--s-4) var(--s-5);
  clip-path: none; overflow: visible;
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background: var(--surface); color: var(--text);
  font-size: var(--fs-3); font-weight: var(--fw-medium);
  box-shadow: var(--shadow-lg);
}

/* ═══════════════════════════════════════════════════════════════════════
   4. Type utilities
   ═══════════════════════════════════════════════════════════════════════

   A utility owns exactly one property. That rule did not exist before,
   which is why `.mono` silently changed size as well as family and two
   adjacent tiles in one grid rendered their hint at different sizes. Half
   a pixel was never the problem; the problem was that nothing said which
   class owns size, so every future combination was a coin flip. */

.muted { color: var(--text-2); }
.faint { color: var(--text-2); }   /* live in twenty-one templates; aliased, not deleted */
.nowrap { white-space: nowrap; }
.center { text-align: center; }

/* Family and figures only. The size is relative, so it composes with any
   component instead of overriding it. */
.mono { font-family: var(--font-mono); font-size: 0.95em; font-variant-numeric: tabular-nums; }

/* The one eyebrow. It used to be defined five times at three sizes, three
   weights and three tracking values, for what is conceptually one thing: a
   small label naming what sits below it. Nothing distinguished those roles
   conceptually, so nothing distinguishes them typographically — and 10.5
   against 11 against 11.5 is not a distinction a reader can perceive. */
.eyebrow {
  font-size: var(--fs-1);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  letter-spacing: var(--track-eyebrow);
  text-transform: var(--caps-eyebrow);
  color: var(--text-2);
}

/* The secondary line under a value — a serial, a datacenter name, a source
   id. It replaces eighteen inline font-size declarations, which is where a
   good half of the inline styles in this product came from. */
.meta { font-size: var(--fs-2); color: var(--text-2); }

/* Explanatory text. Standalone it had no margin at all and fell back to
   the browser's paragraph default, so six views pushed it back by hand at
   four different distances. Its rhythm belongs to the container now. */
.hint { margin: 0; color: var(--text-2); font-size: var(--fs-2);
        line-height: var(--lh-body); max-inline-size: var(--measure); }
.hint a { text-decoration: underline; text-underline-offset: 0.12em; }
.field .hint { margin-block-start: var(--s-3); }
.hint--inset { padding: var(--s-4) var(--s-5) 0; }
.hint--inset-follow { padding: 0 var(--s-5); }

/* Free prose in a cell or a panel, at a readable measure. This was being
   done with an absolute pixel max-width on a <td>, where it is advisory
   under automatic table layout and does not scale with the body size. */
.prose { max-inline-size: var(--measure-tight); }
.prose--preserve { white-space: pre-wrap; }
.prose--scroll { max-block-size: 32rem; overflow: auto; }
.prose--scroll-sm { max-block-size: 24rem; overflow: auto; }
.muted--measure { max-inline-size: 46rem; }
.faq__item--spaced { margin-block-start: var(--s-5); }
/* The support form's disclosure, which the admin pages draw without the
   public site's sheet: a target, not a 22px line of text. */
.faq__item > summary { display: flex; align-items: center; gap: var(--s-3); min-block-size: 1.5rem; cursor: pointer; }
input[type="file"] { max-inline-size: 100%; min-block-size: var(--control-h); font: inherit; font-size: var(--fs-3); }
.estate-site__head a { display: inline-flex; align-items: center; min-block-size: 1.5rem; }
.cluster--block-s3 { margin-block: var(--s-3); }
.cluster--block-s5 { margin-block: var(--s-5); }
.cluster--block-end-s5 { margin-block-end: var(--s-5); }

/* Setup instructions that reuse the public documentation HTML. Admin
   pages do not load site.css, so the measure, the code block and the
   warning callout are restated here against the same tokens. */
.guide { max-inline-size: var(--measure); }
.guide h3 {
  font-size: var(--fs-4);
  font-weight: var(--fw-bold);
  margin-block: var(--s-7) var(--s-4);
  color: var(--text);
}
.guide p, .guide li {
  font-size: var(--fs-3);
  line-height: var(--lh-body);
  color: var(--text-2);
  margin-block: 0 var(--s-4);
}
.guide ul { padding-inline-start: var(--s-7); margin-block: var(--s-4) var(--s-5); }
.guide li { margin-block-end: var(--s-3); }
.guide strong { color: var(--text); font-weight: var(--fw-bold); }
.guide pre {
  padding: var(--s-4) var(--s-5);
  margin-block: var(--s-4) var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
  font-family: var(--font-mono);
  font-size: var(--fs-2);
  line-height: 1.7;
  color: var(--text);
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
  /* A command or a configuration file is copied line for line, so its lines
     scroll rather than break: `word-break: break-all` split
     "dcmanage-proxy" in two. */
  white-space: pre;
  word-break: normal;
  overflow-wrap: normal;
  overflow-x: auto;
}
.guide code { font-family: var(--font-mono); font-size: 0.92em; direction: ltr; unicode-bidi: isolate; }
.guide :not(pre) > code {
  background: var(--surface-sunken);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-1);
  padding: 0.1em 0.35em;
}
.guide .callout {
  margin-block: var(--s-6);
  padding: var(--s-6);
  border-radius: var(--radius-2);
  border-inline-start: 3px solid var(--accent);
  background: var(--accent-tint);
}
.guide .callout p:last-child { margin-block-end: 0; }
.guide .callout--warn { border-inline-start-color: var(--warn); background: var(--warn-tint); }

/* A failure reason is the answer to the question the operator opened the
   page to ask. On the schedule page it was the smallest text on screen
   with colour carrying the whole signal — and colour is the channel most
   likely to be lost to a deficiency or a bad monitor. */
.reason {
  margin-block-start: var(--s-3);
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--danger);
  font-size: var(--fs-3);
  color: var(--danger);
}

/* ── Bidirectional correctness for technical values ────────────────────
   This is the highest-value block in the file measured by defects repaired
   per line, and it repairs them without editing a single template.

   `text-align: start` resolves against the element's OWN direction, so
   every <td dir="ltr"> aligns to the left while its Persian siblings align
   to the right: the switch capacity table had three consecutive numeric
   columns hugging the opposite edge from their own headers, and read as if
   two grids had been spliced together. match-parent is the property
   designed for exactly this case. The physical value is the fallback, and
   inside a [dir="rtl"] scope "right" IS the start edge, so it is correct
   rather than a leak.

   `.tech` is in this list and must stay in it. It sets `direction: ltr` on
   the element itself, which is what isolates the value — and that is exactly
   what makes the inherited `text-align: start` resolve to LEFT, so every
   .tech cell hugged the opposite edge from its Persian neighbours. The first
   version of this repair matched only the attribute form and only inside
   `table.data td`, which left the audit log's When column, the schedule's
   duration and half a dozen others start-aligned to the wrong side. */
[dir="rtl"] table.data td[dir="ltr"],
[dir="rtl"] table.data td.mono,
[dir="rtl"] .tech { text-align: right; }
@supports (text-align: match-parent) {
  table.data td[dir="ltr"], table.data td.mono, .tech { text-align: match-parent; }
}

/* Isolation for a machine string inside a sentence, without touching the
   alignment of the box it sits in. This is what stops `2026-01-01 →
   2026-02-01` rendering end-date-first: both dates are number runs, the
   arrow between them is a neutral that takes the RTL base level, and U+2192
   is not in BidiMirroring.txt — so unlike a parenthesis it does not flip,
   and a Persian operator sees an arrow running from the end of the billing
   cycle back to its beginning. It is also what stops `12.50 GB (85.3%)`
   rendering with reversed brackets and the unit on the wrong side. */
.tech { direction: ltr; unicode-bidi: isolate; }
/* For a bare figure, or a figure with Latin parts. Not for prose: inside this
   left-to-right isolate a Persian phrase such as «۱۹ ثانیه پیش» is laid out
   number-first from the left and reads backwards, and a Persian unit lands
   on the wrong side of its number. A quantity written in words gets no class
   at all; the page's tabular figures already line it up. */
.num  { font-variant-numeric: tabular-nums; direction: ltr; unicode-bidi: isolate; }

/* ═══════════════════════════════════════════════════════════════════════
   5. Layout primitives
   ═══════════════════════════════════════════════════════════════════════

   Five classes, and between them they absorb almost every inline style
   that used to be in the templates. The handful that survive are values
   the server computes — meter widths — and inline is the correct place for
   those. */

/* Vertical rhythm as a property of the container rather than a margin each
   child carries and each parent then cancels. */
.stack { display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--flow, var(--s-6)); }
/* Without these a long address or an unbreakable value set the width of
   the whole column, and the column ran past a phone's edge, where .main
   clips it out of sight. */
.stack > * { min-inline-size: 0; }
.stack--tight { --flow: var(--s-4); }
.stack--snug  { --flow: var(--s-5); }

/* .card keeps its own bottom margin so that a view which has not been
   swept still spaces correctly — but the moment a view wraps its blocks in
   a .stack, the container's gap takes over and the margin stands down.
   That is what makes the migration safe in pieces rather than a flag day. */
.stack > .card,
.stack > .tiles,
.stack > .alert,
.stack > .field { margin-block-end: 0; }
.card__body > :last-child { margin-block-end: 0; }

/* A horizontal group that wraps: the toolbar, the button row, the chart
   legend, the badge run. Wrapping is not optional and that is the point —
   a flex row with no flex-wrap holding a fixed-width input, a select and a
   submit button renders wider than a phone inside a card that clips rather
   than scrolls, so the Search button is not pushed to a second line, it is
   gone. */
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap, var(--s-4)); }
.cluster > * { min-inline-size: 0; }
.cluster--center  { justify-content: center; }
.cluster--end     { align-items: flex-end; }
.cluster--lg { --gap: var(--s-5); }
/* The gap is declared here as well as the token. Only .cluster read --gap,
   and most views write a bare .row-actions, so their buttons — two red ones
   on the proxies list — sat edge to edge. */
.cluster--row-actions, .row-actions {
  --gap: var(--s-3);
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--gap);
  max-inline-size: 100%;
}
.cluster--row-actions .btn, .row-actions .btn { white-space: nowrap; }

/* ── Dialog ──────────────────────────────────────────────────────────────
   The first real overlay this product grows. It uses the native element, so
   focus, Escape and the top layer are the browser's job rather than three
   more things to get wrong; --shadow-overlay was reserved for exactly this.
   Width is capped in ch rather than px so a form of labelled fields keeps
   its measure at any density, and the whole thing becomes a sheet on a
   narrow screen because a centred box with 2rem of margin is not a phone. */
.sheet {
  inline-size: min(48rem, calc(100vw - 2rem));
  /* dvh after vh: a phone's vh is the height with the toolbar retracted, so
     a sheet sized in it ran its footer — the Save button — under the
     browser's own chrome. The vh line stays for engines without dvh. */
  max-block-size: min(46rem, calc(100vh - 4rem));
  max-block-size: min(46rem, calc(100dvh - 4rem));
  padding: 0;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-overlay);
  overflow: hidden;
}
.sheet::backdrop { background: rgba(15, 23, 42, .45); }
/* A confirmation is one sentence, not a form. */
.sheet--confirm { inline-size: min(32rem, calc(100vw - 2rem)); }
.sheet--confirm .sheet__body p { margin: 0; }
.sheet--wide { inline-size: min(56rem, calc(100vw - 2rem)); }
.sheet-section { margin-block-end: var(--s-6); }
.sheet-section:last-child { margin-block-end: 0; }
.sheet-section__title {
  display: flex; align-items: center; gap: var(--s-3);
  margin: 0 0 var(--s-4);
  font-size: var(--fs-3); font-weight: var(--fw-medium);
}
.sheet-section__title svg { inline-size: 1rem; block-size: 1rem; color: var(--text-2); }
.network-hub__tabs {
  margin-block: 0 var(--s-6);
  max-inline-size: 100%;
  overflow-x: auto;
}
/* On a phone the five hub tabs wrap into rows rather than scroll, so the one
   being shown — on a Persian page the last, at the far inline end — is never
   off the edge. The track's own line colour shows through a hairline gap, so
   the cells keep their dividers across both rows. */
@media (max-width: 620px) {
  .network-hub__tabs.segmented {
    display: flex; flex-wrap: wrap; overflow: hidden;
    gap: var(--hair); background: var(--line);
  }
  .network-hub__tabs.segmented > * { flex: 1 1 auto; border-inline-start: 0; background: var(--surface); }
}
.sheet__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-5) var(--card-pad);
  border-block-end: var(--hair) solid var(--line);
}
.sheet__head h2 { margin: 0; }
.sheet__body {
  padding: var(--card-pad);
  overflow-y: auto;
  max-block-size: calc(min(46rem, 100vh - 4rem) - 8rem);
  max-block-size: calc(min(46rem, 100dvh - 4rem) - 8rem);
}
.sheet__foot {
  display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: flex-end;
  padding: var(--s-5) var(--card-pad);
  border-block-start: var(--hair) solid var(--line);
  background: var(--surface-sunken);
}
.sheet__foot .sheet__foot-start { margin-inline-end: auto; }
.sheet__close {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2rem; block-size: 2rem; padding: 0;
  border: 0; border-radius: var(--radius-2);
  background: transparent; color: var(--text-2); cursor: pointer;
}
.sheet__close:hover { background: var(--surface-hover); color: var(--text); }
@media (pointer: coarse) {
  .sheet__close { inline-size: var(--tap); block-size: var(--tap); }
}
/* A cross, drawn by turning the one glyph the set already has. */
.icon--close { transform: rotate(45deg); }
@media (max-width: 40rem) {
  .sheet {
    inline-size: 100vw; max-inline-size: 100vw;
    margin: auto 0 0; border-radius: var(--radius-3) var(--radius-3) 0 0;
    max-block-size: 90vh;
    max-block-size: 90dvh;
  }
  /* Anchored to the bottom edge, the footer sits over the home indicator on
     a phone without a home button unless it pads itself clear of it. */
  .sheet__foot { padding-block-end: calc(var(--s-5) + env(safe-area-inset-bottom, 0px)); }
  .sheet__body { max-block-size: calc(90vh - 8rem); max-block-size: calc(90dvh - 8rem); }
}

/* A form inside a sheet: fields stack, and a field that is an address gets
   the width an address needs rather than the width a number needs. */
.sheet .field { margin-block-end: var(--s-5); }
.sheet .field:last-child { margin-block-end: 0; }
.sheet input[type="text"], .sheet input[type="url"], .sheet select { inline-size: 100%; }
.sheet input[type="number"] { inline-size: 8rem; }

/* An actions column sizes to its buttons. Without this the column takes an
   even share of the table and the last button drops onto a line of its own,
   which reads as a different kind of action rather than a narrow column. */
.data td.cell--actions, .data th.cell--actions { inline-size: 1%; white-space: nowrap; }
.data td.cell--actions .row-actions { flex-wrap: nowrap; }
@media (max-width: 60rem) {
  .data td.cell--actions, .data th.cell--actions { inline-size: auto; white-space: normal; }
  .data td.cell--actions .row-actions { flex-wrap: wrap; }
}
.cluster--row-actions form, .row-actions form {
  display: inline-flex; min-inline-size: 0; margin: 0;
}
.cluster--chips { --gap: var(--s-2); }

.traffic-instrument { margin-block-end: var(--s-6); }
.traffic-instrument .card { margin-block-end: 0; }

/* The auto-fit grid, written once. The min() is the whole fix: auto-fit
   collapses empty tracks but never shrinks a fixed minimum, so a bare
   340px floor on a 320px phone pushes the body sideways — and written in
   an inline attribute, no media query in this file could reach it. With
   min() the floor becomes the container width whenever the container is
   narrower, and the grid degrades to one column on its own. */
.grid {
  display: grid;
  gap: var(--gap, var(--s-6));
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col), 100%), 1fr));
  align-items: start;
}
.grid--narrow { --col: var(--col-narrow); }
.grid--wide   { --col: var(--col-wide); }
.grid > * { min-inline-size: 0; }

/* The form grid the switch form already asked for by name and never got,
   so its CLI Access card stacked five full-width fields while the cards
   above and below it laid out in columns on the same screen. */
.form-grid {
  display: grid;
  gap: var(--s-5) var(--s-6);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col), 100%), 1fr));
}

/* The search and filter row, rebuilt from scratch six times with five
   different fixed field widths, none of which could shrink. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); min-inline-size: 0; }
.toolbar > * { min-inline-size: 0; }
.toolbar__field { flex: 1 1 var(--w-md); min-inline-size: var(--w-sm); max-inline-size: var(--w-lg); }

/* Field width utilities, content-relative so they shrink inside a wrapping
   toolbar instead of clipping the control beside them. */
.w-xs { inline-size: min(var(--w-xs), 100%); }
.w-sm { inline-size: min(var(--w-sm), 100%); }
.w-md { inline-size: min(var(--w-md), 100%); }
.w-lg { inline-size: min(var(--w-lg), 100%); }
.w-auto { inline-size: auto; }

/* A <form> wrapping a single row-level button. */
.form-inline { display: inline-flex; }

/* A flex child that takes the remaining room and may shrink below its
   content. Used where a label column sits beside a fixed-width control. */
.grow { flex: 1; min-inline-size: 0; }

/* A label-and-value line in a panel that is not a table. */
.kv-row {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: var(--s-2) var(--s-5);
  padding-block: var(--s-3);
  border-block-end: var(--hair) solid var(--line);
}
.kv-row:last-child { border-block-end: none; }
/* A value that does not fit beside its label takes the line under it, at
   the same edge, instead of either one being cut. Squeezed side by side, a
   count of 116 broke into "11" and "6" and a GRE range into "172.16.30.0"
   and "/24"; holding the token whole without wrapping let a long endpoint
   run past the card under its squeezed label. On a line of its own a
   machine value breaks inside a token only when it is wider than the card,
   a figure never, and prose wraps at its spaces. */
.kv-row > :first-child { flex: 1 1 auto; min-inline-size: 0; }
.kv-row > :last-child {
  margin-inline-start: auto; max-inline-size: 100%; min-inline-size: 0;
  overflow-wrap: anywhere; text-align: end; }
.kv-row > :last-child:is(.num, .tech, .mono) { overflow-wrap: break-word; }
.kv-row > :last-child.num { min-inline-size: auto; }
/* A count that links to its list is the row's only control. */
.kv-row .num a { display: inline-flex; align-items: center; justify-content: flex-end; min-inline-size: 1.5rem; min-block-size: 1.5rem; }

/* ═══════════════════════════════════════════════════════════════════════
   6. Shell
   ═══════════════════════════════════════════════════════════════════════ */

/* 100vh is kept immediately before 100dvh as the fallback. On iOS Safari
   and Android Chrome 100vh is the LARGEST viewport height, so a vertically
   centred card sits partly under the URL bar with the submit button as the
   part pushed off. */
.app { display: flex; min-block-size: 100vh; min-block-size: 100dvh; }

.sidebar {
  inline-size: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--surface);
  border-inline-end: var(--hair) solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; inset-block-start: 0;
  block-size: 100vh; block-size: 100dvh;
  /* The scroll belongs to the element that actually has a constrained
     height. It used to be declared on .sidebar__nav, from when that was a
     direct flex child — wrapping it in the drawer made its parent an
     auto-height <details>, so `flex: 1` became inert and `overflow-y: auto`
     never had a height to overflow. On an iPad in landscape, or any desktop
     window under about 720px tall, Schedule and Audit were then simply
     off the bottom of a sticky column that nothing could scroll. */
  overflow-y: auto;
}

/* The three regions share one inline inset. They used four different ones,
   so the single element visible on every page was internally misaligned
   and the group headings sat fourteen pixels to the side of the links they
   head, which made the grouping read as accidental. */
.brand, .sidebar__nav, .sidebar__footer { padding-inline: var(--s-5); }

.brand {
  display: flex; align-items: center; gap: var(--s-4);
  padding-block: var(--s-5);
  border-block-end: var(--hair) solid var(--line);
  color: inherit;
}
.brand:hover { text-decoration: none; color: inherit; }
.brand__mark {
  inline-size: 32px; block-size: 32px; border-radius: var(--radius-2);
  background: var(--accent); color: var(--text-invert);
  display: grid; place-items: center; flex: 0 0 auto;
}
.brand__mark svg { inline-size: 19px; block-size: 19px; }
.brand__text { min-inline-size: 0; line-height: var(--lh-snug); }
.brand__name { font-size: var(--fs-4); font-weight: var(--fw-bold); letter-spacing: var(--track-tight); }
.brand__name span { color: var(--accent); }
.brand__sub { font-size: var(--fs-1); color: var(--text-2); }

.sidebar__nav { padding-block: var(--s-4) var(--s-6); flex: 1; overflow-y: auto;
                display: grid; gap: var(--s-1); align-content: start; }

.sidebar__section {
  padding: var(--s-6) 0 var(--s-2);
  font-size: var(--fs-1); font-weight: var(--fw-medium);
  letter-spacing: var(--track-eyebrow); text-transform: var(--caps-eyebrow);
  color: var(--text-2);
}

.sidebar__link {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-2);
  color: var(--text-2);
  font-size: var(--fs-3);
  transition: background .12s, color .12s;
}
.sidebar__link:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.sidebar__link:active { background: var(--surface-active); }

/* Both selectors, so the templates can migrate from .is-active to
   aria-current at their own pace and the navigation is never unstyled in
   between. The current page carries a solid rule on its inline-start edge
   as well as a tint, so the state survives greyscale and a deficiency. */
.sidebar__link.is-active,
.sidebar__link[aria-current="page"] {
  /* Fading out along the row rather than filling it, so the marker reads as
     a highlight rather than as a second button sitting in the navigation.
     `to right` is resolved against the writing direction by the logical
     keyword, so Persian fades the other way without a second rule. */
  background: linear-gradient(to inline-end, var(--accent-tint), transparent 85%);
  color: var(--accent);
  font-weight: var(--fw-semibold);
  box-shadow: inset 2px 0 0 var(--accent);
}
[dir="rtl"] .sidebar__link.is-active,
[dir="rtl"] .sidebar__link[aria-current="page"] { box-shadow: inset -2px 0 0 var(--accent); }
.sidebar__link.is-active svg,
.sidebar__link[aria-current="page"] svg { color: var(--accent); }

.sidebar__footer {
  padding-block: var(--s-4);
  border-block-start: var(--hair) solid var(--line);
  font-size: var(--fs-1); color: var(--text-2);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s-4); flex-wrap: wrap;
}

/* The navigation drawer's summary is hidden above the breakpoint that
   needs it. Scoping only the `display: flex` inside the media query would
   leave the summary at its UA `display: list-item` on desktop, which
   renders a stray row above the navigation. */
.nav-drawer > summary { display: none; }

/* Above the breakpoint the drawer is not a drawer, and the navigation must
   be on screen whatever state the <details> happens to be carrying. Without
   this, an operator who collapsed the menu on an iPad in portrait and then
   rotated to landscape lost all fourteen links with no control left to bring
   them back, because the summary is hidden at this width. The script beside
   it sets `open` for engines that hide the contents through a mechanism
   author CSS cannot reach; this rule is the half that works with scripting
   off. */
@media (min-width: 901px) {
  .nav-drawer > .sidebar__nav { display: grid; }
  .nav-drawer::details-content { content-visibility: visible; block-size: auto; }
}

/* min-inline-size: 0 is what lets a flex child shrink below its table's
   min-content width. Without it the ports list inflates .main past the
   sidebar and the last column is clipped by overflow:hidden on the card,
   with no scrollbar to reach Lock / Shut / View. overflow-x: clip on the
   column — not on .app, which would break the sticky sidebar — keeps a
   wide table inside this pane. */
.main { flex: 1; min-inline-size: 0; max-inline-size: 100%; overflow-x: clip;
        display: flex; flex-direction: column; }

.topbar {
  /* A minimum rather than a fixed height. Under a coarse pointer the
     controls inside grow to the 44px tap target, and a segmented track with
     its own border renders 46px inside what was a hard 48px bar — two pixels
     of headroom, so any locale string or density change spilled out of a
     sticky element instead of expanding it. */
  min-block-size: var(--topbar-h);
  background: var(--surface);
  border-block-end: var(--hair) solid var(--line-strong);
  box-shadow: var(--shadow-sticky);
  display: flex; align-items: center;
  position: sticky; inset-block-start: 0; z-index: 30;
}

/* The bar's chrome spans the window; its contents share the content
   column's maximum width and gutter, so the user chip sits above the
   content it belongs to rather than far to its side on a wide monitor, and
   the page heading below it shares a start edge with the header at every
   width. */
.topbar__inner, .content {
  inline-size: 100%;
  max-inline-size: min(var(--content-max), 100%);
  margin-inline: auto;
  padding-inline: var(--gutter);
  min-inline-size: 0;
}
.topbar__inner { display: flex; align-items: center; justify-content: space-between;
                 gap: var(--s-5); min-block-size: var(--topbar-h); min-inline-size: 0; }
.content { padding-block: var(--gutter); flex: 1; }

/* The topbar no longer repeats the page title a few pixels smaller than
   the h1 beneath it — two elements competing for "what page am I on" is a
   hierarchy with no winner, and one copy used to disappear below 900px, so
   desktop and tablet disagreed about where page identity lives. It carries
   the trail instead, which is information the h1 does not have. */
.crumbs { display: flex; align-items: center; gap: var(--s-3); min-inline-size: 0;
          font-size: var(--fs-2); color: var(--text-2);
          overflow: hidden; white-space: nowrap; }
/* text-overflow acts on a block container's own inline content, not on its
   flex children, so declaring it on .crumbs alone hard-clipped the trail with
   no ellipsis — and because the section comes first and nothing gave the last
   crumb priority, the crumb destroyed was the one naming the page. On a
   375px phone the bar read "Administration /" and nothing else. The truncation
   moves onto the children, and the page name is the child that keeps its
   room. */
.crumbs > * { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; }
.crumbs > :first-child { flex: 0 1 auto; }
.crumbs__sep { color: var(--text-2); flex: 0 0 auto; overflow: visible; }
/* The page name carries dir="auto", so a Latin title is laid out and cut
   from its own end. match-parent keeps it beside the separator rather than
   at the far edge its own direction would send it to. */
.crumbs > :last-child { color: var(--text); font-weight: var(--fw-medium); flex: 1 1 auto;
                        text-align: match-parent; }
/* The parent list, when a page has one, is a way back and looks like the
   trail around it until it is pointed at. */
.crumbs a { color: inherit; }
.crumbs a:hover { color: var(--text); }
.crumbs a:focus-visible { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
/* The trail and each crumb clip for the ellipsis, which cut the parent
   link's focus ring down to a hairline. Each gives the ring room in padding
   that a negative margin of the same size takes back out of the layout, so
   nothing moves. A ring turned inward instead, as in the pager, is drawn
   through the letters of a bare text link. */
.crumbs { padding-block: var(--s-2); margin-block: calc(var(--s-2) * -1); }
.crumbs > * { padding: var(--s-2); margin: calc(var(--s-2) * -1); }

/* The actions keep the inline end when there is no trail before them, which
   is the dashboard. */
.topbar__actions { display: flex; align-items: center; gap: var(--s-4); flex: 0 0 auto;
                   margin-inline-start: auto; }

/* On a phone the bar holds a trail and five controls in 320px. The language
   switch keeps its globe and loses its word — the word stays in the
   accessibility tree, so it is still announced, in its own language — and
   below 360px the avatar chip, which is a picture of who is signed in rather
   than a control, steps out entirely so sign-out is never pushed off the
   edge. */
@media (max-width: 430px) {
  .topbar__inner { gap: var(--s-3); }
  .topbar__actions { gap: var(--s-2); }
  .topbar__lang { padding-inline: var(--s-4); }
  .topbar__lang-label {
    position: absolute;
    inline-size: 1px; block-size: 1px;
    padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}
@media (max-width: 360px) {
  .topbar__actions .user-chip { display: none; }
}

.user-chip {
  display: flex; align-items: center; gap: var(--s-3);
  /* Logical padding, so the RTL override that used to outrank the phone
     rule — leaving twelve pixels of dead space beside a hidden name on a
     Persian phone — is no longer needed and cannot come back. */
  padding-block: var(--s-2);
  padding-inline: var(--s-2) var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: var(--fs-2);
}
.avatar {
  inline-size: 24px; block-size: 24px; border-radius: 50%;
  background: var(--accent); color: var(--text-invert);
  font-size: var(--fs-1); font-weight: var(--fw-bold);
  display: grid; place-items: center; flex: 0 0 auto;
}

.page-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap; margin-block-end: var(--s-7);
}
.page-header h1 { font-size: var(--fs-6); margin-block-end: var(--s-2); }
.page-header p { margin: 0; color: var(--text-2); max-inline-size: var(--measure); }

/* ═══════════════════════════════════════════════════════════════════════
   7. Card
   ═══════════════════════════════════════════════════════════════════════ */

.card {
  /* Components respond to their container, not to the viewport. A card
     inside the dashboard's two-column grid is around 500px wide on a
     1400px monitor, so a viewport query says "desktop" while the content
     inside is as cramped as it would be on a phone. Container queries need
     no build step.

     Note for anything added later: this establishes containment, so a
     dropdown or a confirm dialog must be portalled to the shell rather
     than nested inside a card, or it will be trapped. */
  container-type: inline-size;
  container-name: panel;

  background: var(--surface);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-sm);
  margin-block-end: var(--s-6);
  overflow: hidden;
}

/* The card title gets a size of its own — the step the old ladder was
   missing between body and h1. It used to carry no font-size at all, which
   made it the same size as, and on Segoe UI the same rendered weight as,
   every <strong> in the rows beneath it: the container and its contents
   sat at one level, and a page of six cards read as one undifferentiated
   list. This is fixed against the markup that already exists, with no
   template edit. */
.card__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap;
  padding: var(--rail-pad-y) var(--card-pad);
  background: var(--surface-raised);
  border-block-end: var(--hair) solid var(--line-strong);
  font-size: var(--fs-4);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}
.card__header > * { min-inline-size: 0; }
/* The title is a real heading. The size step above manufactured a visual
   level between body and h1, and then every one of the forty-five instances
   in the product rendered it in a <span> — so a switch detail page six cards
   deep exposed one h1, no h2, and an outline claiming the page had a single
   section. The rule resets the UA's own size and margin so promoting the
   element changes the semantics and nothing else. */
.card__header > h2, .card__title,
.card__header > span:first-child { display: inline-flex; align-items: center; gap: var(--s-3);
                                   font-size: inherit; font-weight: inherit;
                                   line-height: inherit; margin: 0;
                                   letter-spacing: normal; }
.card__header svg { color: var(--text-2); }
/* Controls inside a header are controls, not heading text. */
.card__header .btn, .card__header input, .card__header select,
.card__header .badge, .card__header .count { font-size: var(--fs-3); font-weight: var(--fw-regular); }
.card__header .badge, .card__header .count { font-size: var(--fs-1); font-weight: var(--fw-medium); }

.card__body { padding: var(--card-pad); min-inline-size: 0; }

/* Explanatory prose directly in a card body gets the measure and the
   rhythm .hint already had. A bare <p> was taking the card's full width —
   1100px, around 170 characters, roughly two and a half times the line
   length anybody reads comfortably — and the browser's own 1em paragraph
   margin, which is a value not on this file's spacing scale. On Settings
   and on every page that explains what a switch does before offering it,
   the result was a wall the eye loses its place in. The measure and the
   step are the two tokens that already exist for this. */
.card__body > p { max-inline-size: var(--measure); margin-block: 0 var(--s-6); }

.card__body--flush {
  padding: 0;
  min-inline-size: 0;
  /* One column that may shrink to zero. A block box sizes to its table,
     then .card { overflow: hidden } clips Lock/Shut/View with no way to
     scroll to them. Grid minmax(0, 1fr) makes .table-wrap the scroller. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.card__header > * { min-inline-size: 0; max-inline-size: 100%; }
.card__header .toolbar { flex: 1 1 16rem; max-inline-size: 100%; }
.card__header .toolbar__field { min-inline-size: 0; max-inline-size: none; flex: 1 1 12rem; }

/* Only a header that holds a filter row stacks it under the title. Every
   card is a panel container, so this used to stack every header in any card
   narrower than 70rem — every half-width card on a 1440px screen — and put a
   lone count or a "View all" link on a line of its own under a one-word
   title. A header with a title and a badge, a count or a button keeps them on
   one row and wraps only when it has to. */
@container panel (max-width: 70rem) {
  .card__header:has(.toolbar, .toolbar__field) { align-items: flex-start; flex-direction: column; gap: var(--s-3); }
  .card__header .toolbar { inline-size: 100%; flex: 1 1 auto; }
}

/* ═══════════════════════════════════════════════════════════════════════
   8. Statistic tiles
   ═══════════════════════════════════════════════════════════════════════ */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-narrow), 100%), 1fr));
  gap: var(--s-5);
  margin-block-end: var(--s-6);
}

.tile {
  background: var(--surface);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5);
  display: flex; gap: var(--s-5); align-items: flex-start;
  color: inherit;
  min-inline-size: 0;
  transition: box-shadow .15s, transform .15s, border-color .15s;
}

/* A tile is often a link, and without this it inherits the accent colour
   and turns its own value blue — which is why two inline `color: inherit`
   attributes existed purely to undo a rule that should never have applied
   to them, a fix invisible to the next person who adds a tile. */
a.tile { color: inherit; }
a.tile:hover { box-shadow: var(--shadow); transform: translateY(-1px);
               border-color: var(--line-control); text-decoration: none; color: inherit; }
a.tile:active { transform: translateY(0); }
a.tile:focus-visible { border-color: var(--accent); }
a.tile[aria-current="page"] { border-color: var(--accent); }

/* min-inline-size: 0 is baked in rather than remembered. The tiles that
   burst were precisely the ones that forgot it: a hostname or an SNMP
   error string with no spaces makes the tile push its grid track wider
   than the viewport and scrolls the whole page sideways. It is
   data-dependent, so it gets reported as "the page is broken sometimes"
   rather than as a layout bug. */
/* The body fills the track whether or not the template remembered to name
   it. Only six of fourteen tiles in the product carried .tile__body, so in
   one .tiles grid two tiles wrapped their hint at the track width and two
   wrapped it at their own content width — the same component rendering two
   ways, which reads as a fault rather than as a decision. Naming the icon
   as the exception is the version a new tile cannot get wrong. */
.tile > * { min-inline-size: 0; }
.tile > :not(.tile__icon) { flex: 1; }
.tile__body { min-inline-size: 0; flex: 1; }

.tile__icon {
  inline-size: 36px; block-size: 36px; border-radius: var(--radius-2);
  display: grid; place-items: center; flex: 0 0 auto;
  background: var(--accent-tint); color: var(--accent);
}
.tile__icon svg { inline-size: 18px; block-size: 18px; }
/* --accent is declared rather than left to the base rule happening to be
   brand-coloured. It is emitted by the dashboard controller and the port
   page and used to match nothing, surviving by accident; the day
   .tile__icon gets a neutral default, that tile silently loses its meaning
   and nothing fails. */
.tile__icon--accent  { background: var(--accent-tint); color: var(--accent); }
.tile__icon--ok,
.tile__icon--success { background: var(--ok-tint);     color: var(--ok); }
.tile__icon--warning { background: var(--warn-tint);   color: var(--warn); }
.tile__icon--danger  { background: var(--danger-tint); color: var(--danger); }
.tile__icon--info    { background: var(--info-tint);   color: var(--info); }

.tile__label {
  font-size: var(--fs-1); font-weight: var(--fw-medium);
  letter-spacing: var(--track-eyebrow); text-transform: var(--caps-eyebrow);
  color: var(--text-2);
}

.tile__value {
  font-size: var(--fs-7);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  margin-block-start: var(--s-2);
  font-variant-numeric: tabular-nums;
  /* break-word rather than anywhere, because a value is usually a number and
     `anywhere` breaks inside one. In a 172px phone tile "128,451 / 131,072"
     split after the thousands separator and ran to four lines, which is a
     capacity figure an operator acts on rendered as something they have to
     reassemble. break-word still breaks a hostname that would otherwise
     overflow; it just prefers the spaces first. */
  overflow-wrap: break-word;
}

/* The variant the system was missing, and the reason ten inline font-size
   overrides existed at two ad-hoc sizes. .tile__value is sized for a
   number; every template that put a word in it shrank it by hand, arriving
   at 18px or 19px depending on which file was edited that day. That is how
   one card ended up rendering four tiles at 25/25/25/19px side by side on
   a grid, which reads as a rendering fault rather than a decision. */
.tile__value--word { font-size: var(--fs-5); letter-spacing: normal; }

/* Tier three on the dashboard: a row of counts is navigation with a number
   printed on it. A count has no state and nothing about it is urgent, so it
   gives up the shadow, the raised surface and the largest numeral step to
   the alarm panel and the live figures above it, and keeps the icon chip
   that says which kind of thing it counts. The modifier is on the grid
   rather than on .tile, because every other .tiles row in the product IS
   the content of its page and must keep its weight. */
.tiles--counts { gap: var(--s-5); margin-block-end: var(--s-7); }
.tiles--counts .tile { box-shadow: none; padding: var(--s-4) var(--s-5); }
.tiles--counts .tile__value { font-size: var(--fs-6); font-weight: var(--fw-medium); }
.tiles--counts a.tile:hover { box-shadow: var(--shadow-sm); transform: none; }

.tile__hint { font-size: var(--fs-2); color: var(--text-2);
              margin-block-start: var(--s-2); overflow-wrap: anywhere; }
.tile__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin-block-start: var(--s-3);
}
.tile__stat {
  font-size: var(--fs-2);
  color: var(--text-2);
}
.tile__stat strong {
  font-variant-numeric: tabular-nums;
  margin-inline-end: 0.25rem;
}
.tile__stat--ok strong { color: var(--ok); }
.tile__stat--warning strong { color: var(--warn); }
.tile__stat--muted strong { color: var(--text-2); }

/* ═══════════════════════════════════════════════════════════════════════
   9. Tables
   ═══════════════════════════════════════════════════════════════════════ */

.table-wrap {
  overflow-x: auto;
  max-inline-size: 100%;
  min-inline-size: 0;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-inline: contain;
  /* A scroll affordance drawn in CSS. A table that hides half of itself on
     a phone — including the Status column the page exists to answer — with
     no fade, no shadow and no hint that there is more, is the failure mode
     this file's own comment warned about and then shipped on 26 tables.
     The `local` layers are the surface masking each edge; the `scroll`
     layers are the shadows, pinned to the viewport of the scroller and
     therefore visible only while there is genuinely more to see. */
  background:
    linear-gradient(to right, var(--surface) 40%, transparent) 0 0 / 28px 100% no-repeat local,
    linear-gradient(to left,  var(--surface) 40%, transparent) 100% 0 / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,    var(--scrim), transparent) 0 0 / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--scrim), transparent) 100% 0 / 12px 100% no-repeat scroll;
  background-color: var(--surface);
}

table.data { inline-size: 100%; border-collapse: collapse; }
/* Action cells wrap; a nowrap Lock/Shut/View cluster is what pushed the
   ports table out of the shell. */
table.data td.col-actions { white-space: normal; }
table.data--auto { inline-size: auto; }

table.data th {
  text-align: start;
  padding: var(--s-3) var(--cell-pad-x);
  background: var(--surface-sunken);
  border-block-end: var(--hair) solid var(--line-strong);
  font-size: var(--fs-1); font-weight: var(--fw-medium);
  letter-spacing: var(--track-eyebrow); text-transform: var(--caps-eyebrow);
  color: var(--text-2);
  white-space: nowrap;
}
th.col-main { inline-size: 34%; }

table.data td {
  padding: var(--cell-pad-y) var(--cell-pad-x);
  border-block-end: var(--hair) solid var(--line);
  vertical-align: middle;
  font-size: var(--fs-3);
  line-height: var(--lh-snug);
}
table.data tbody tr:last-child td { border-block-end: none; }
table.data td strong { font-weight: var(--fw-medium); }

/* The label column of a two-column specification table — the allowance
   breakdown, the source-system panel, the server specification. */
table.data td.cell-key { font-weight: var(--fw-medium); color: var(--text-2); }

/* A numeric column aligns on its digits and ends at the same edge. */
table.data td.num, table.data th.num { text-align: end; }

/* Row hover only where a row actually leads somewhere. On an audit log
   with no link in any cell, every row lit up under the cursor and none did
   anything, which teaches the operator to distrust the panel's hover
   feedback everywhere else. :has() does this with no markup change; the
   explicit attribute is the fallback for an engine without it. */
table.data tbody tr:has(a[href]):hover,
table.data[data-rows="linked"] tbody tr:hover { background: var(--surface-hover); }

/* An interface whose machine has left the datacenter, and an interface no
   machine has ever been recorded on, are opposite facts that produce the same
   reading: link down, no traffic. One is a space for a new machine, the other
   a cable that belongs to a chassis still sitting in the rail. The edge
   carries the difference, so a port list answers "can I use this one" by
   colour instead of by reading the server column of every row.

   The same tone marks an out-of-datacenter machine in an equipment list.

   The mark sits on the first cell rather than the row: under
   border-collapse a row's own border belongs to the table, and the sticky
   first column on narrow screens paints its own opaque background over
   anything the row had. The cell keeps both. */
table.data tbody tr[data-port-state="out"] > :first-child,
table.data tbody tr.row--out > :first-child {
  box-shadow: inset 3px 0 0 var(--warn-fill);
}
[dir="rtl"] table.data tbody tr[data-port-state="out"] > :first-child,
[dir="rtl"] table.data tbody tr.row--out > :first-child {
  box-shadow: inset -3px 0 0 var(--warn-fill);
}
table.data tbody tr[data-port-state="out"] td,
table.data tbody tr.row--out td { background: var(--warn-tint); }
table.data tbody tr[data-port-state="out"]:hover td,
table.data tbody tr.row--out:hover td {
  background: color-mix(in srgb, var(--warn-fill) 14%, var(--warn-tint));
}
table.data tbody tr[data-port-state="unassigned"] > :first-child {
  box-shadow: inset 3px 0 0 var(--accent-edge);
}
[dir="rtl"] table.data tbody tr[data-port-state="unassigned"] > :first-child {
  box-shadow: inset -3px 0 0 var(--accent-edge);
}

/* One empty state. The same sentence used to render centred in a tall
   block on four pages and hard against the inline edge in a short row on
   twelve others, where it reads as a data row whose contents failed to
   load rather than as a designed state — and on a fresh install it is the
   entire content of Switches, Datacenters and Servers, so the
   inconsistency lands on the first impression.

   The templates are normalised on `.empty` now. The `td.muted[colspan]`
   selector stays as a backstop because it costs nothing, but it was making
   the inconsistency invisible in the browser and therefore permanent in the
   source: three spellings of one state rendered identically, so nothing ever
   drew attention to the two that were wrong. */
table.data td.empty,
table.data td.muted[colspan] {
  text-align: center;
  padding-block: var(--s-8);
  color: var(--text-2);
}

/* A designed empty state, with room for the action that would fill it.

   One component, three placements: a card body, a `<td colspan>` inside a
   table, and a bare block. There were about sixty-nine hand-written
   versions of this across a hundred and sixty loops — a centred sentence
   here, a muted cell hard against the inline edge there, an untranslated
   dash somewhere else — which is why "no rows" read as "this table failed
   to load" on half the pages in the product. The shape is fixed here so a
   view supplies only the three things that actually differ: which icon,
   which sentence, and whether there is one action that would fill it. */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-4); padding: var(--s-9) var(--s-6);
  text-align: center; color: var(--text-2);
}
.empty-state__icon {
  inline-size: 40px; block-size: 40px; border-radius: var(--radius-2);
  display: grid; place-items: center;
  background: var(--surface-sunken); border: var(--hair) solid var(--line);
  color: var(--text-2);
}
.empty-state__icon svg { inline-size: 20px; block-size: 20px; }
.empty-state__title { font-size: var(--fs-4); font-weight: var(--fw-medium); color: var(--text); }
.empty-state p, .empty-state__hint { margin: 0; max-inline-size: 46ch; }
/* One action, and it is never the loudest thing in a card: an empty table
   is not where the page's primary weight belongs. */
.empty-state__action { margin-block-start: var(--s-2); }

/* Inside a table the state is the whole row, so the cell drops the padding
   and the centring it would otherwise inherit from td.empty and lets the
   component own both. The vertical rhythm shortens, because a table that is
   empty inside a two-column dashboard card must not be nine steps tall. */
table.data td.empty > .empty-state,
table.data td > .empty-state {
  padding-block: var(--s-6);
}
table.data td:has(> .empty-state) { padding: 0; }

/* A long table becomes its own scroll pane with a frozen header, rather
   than sixty rows sliding under a header that has already left the screen.
   This is opt-in and it is the ONLY place the border model changes: a
   sticky header needs separated borders, and applying that to every table
   would alter how every border in the rack elevation and the capacity
   table resolves for no benefit. Everywhere else, .table-wrap's own
   overflow-x already makes overflow-y compute to auto, so the wrap is a
   scroll container sized exactly to its table and there is no vertical
   scroll for a sticky header to survive. */
.table-wrap--tall { max-block-size: min(70dvh, 720px); overflow-y: auto; }
.table-wrap--tall table.data { border-collapse: separate; border-spacing: 0; }
.table-wrap--tall table.data th { position: sticky; inset-block-start: 0; z-index: 2; }
.table-wrap--tall table.data td { border-block-end: var(--hair) solid var(--line); }

/* ═══════════════════════════════════════════════════════════════════════
   10. Badges, counts and tags
   ═══════════════════════════════════════════════════════════════════════

   Three components where there was one. A pill meaning "this switch is
   stale", a pill meaning "4 tasks" and a pill wrapping the raw value of a
   setting were identical, so the eye could not pre-sort a page into state
   and data — which is why the settings table read as if every value were a
   status. */

.badge {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: var(--s-1) var(--s-4);
  border-radius: var(--radius-pill);
  /* Every variant carries a border. The soft fills measure barely above
     1:1 against the surfaces badges actually land on, so on a hovered row
     the warning pill's shape disappeared entirely and colour-of-text
     became the sole status signal — the one channel a colour-blind
     operator cannot use. The neutral badge was worse: its fill was the
     same token as the table header and the pager bar it sits in, giving a
     chip with literally no shape. The border is drawn in --line-control
     rather than --line-strong because a boundary that identifies a component
     is what WCAG 1.4.11 asks 3:1 of, and --line-strong measured 1.58:1
     against the row behind it: every coloured variant cleared the bar and the
     neutral chip — the one that says "down", "not collecting", "inactive" —
     was the single status pill on the page with no perceivable outline. */
  border: var(--hair) solid var(--line-control);
  background: var(--surface-sunken);
  color: var(--text-2);
  font-size: var(--fs-1); font-weight: var(--fw-medium);
  line-height: 1.6;
  white-space: nowrap;
}
.badge .dot, .dot { inline-size: 7px; block-size: 7px; border-radius: 50%;
                    background: currentColor; flex: 0 0 auto; }

/* All six tones the templates emit are defined. --ok and --success are
   both bound because a rename would silently grey every up/active/fresh/
   enabled chip in the product the moment this file shipped.

   The success fill is deliberately kept rather than reduced to a dot: the
   most frequent scan in this product is "which of these ports is up"
   across a table of hundreds of rows, and a coloured block is a
   pre-attentive channel that a coloured dot is not. */
.badge--neutral { /* the base rule is the neutral tone; named for symmetry */ }
.badge--accent  { background: var(--accent-tint); border-color: var(--accent-edge); color: var(--accent); }
.badge--ok,
.badge--success { background: var(--ok-tint);     border-color: var(--ok-edge);     color: var(--ok); }
.badge--warning { background: var(--warn-tint);   border-color: var(--warn-edge);   color: var(--warn); }
.badge--danger  { background: var(--danger-tint); border-color: var(--danger-edge); color: var(--danger); }
.badge--info    { background: var(--info-tint);   border-color: var(--info-edge);   color: var(--info); }

/* A count is not a status. */
.count {
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 1.75em; padding-inline: var(--s-3);
  border-radius: var(--radius-1);
  background: var(--surface-sunken); border: var(--hair) solid var(--line);
  color: var(--text-2); font-size: var(--fs-2);
  font-variant-numeric: tabular-nums;
}

/* Metadata — a VLAN, a firmware string, a raw setting value — is neither a
   status nor a quantity. */
.tag {
  font-family: var(--font-mono); font-size: var(--fs-2);
  color: var(--text-2);
  background: var(--surface-sunken); border: var(--hair) solid var(--line);
  border-radius: var(--radius-1); padding: 1px var(--s-4);
  direction: ltr; unicode-bidi: isolate;
}
/* The same box holding words rather than a machine value: a role name, a
   word such as «داخلی» or «ذخیره‌سازی». The mono stack has no Persian face,
   so Persian set in it fell to a fallback with its letters spread into
   monospace cells, and the forced left-to-right box moved a sentence's full
   stop to its start. The words take the page's face and direction. A name an
   operator typed, which may be in either script, goes in a <bdi> inside it. */
.tag--text { font-family: inherit; direction: inherit; white-space: nowrap; }

/* The one way this product states whether it will change customer
   connectivity. Used in the sidebar footer, on Settings, and at the head
   of every page that can act — same shape, same colour, same words. */
.state-strip {
  display: flex; align-items: flex-start; gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border: var(--hair) solid var(--state-observing-edge);
  border-inline-start: 3px solid var(--state-observing);
  border-radius: var(--radius-2);
  background: var(--state-observing-tint);
  font-size: var(--fs-3);
  margin-block-end: var(--s-6);
}
/* Production enforcing is the normal state and reads calm; not enforcing
   is the one an operator must notice, so it carries the warning tone. */
.state-strip--armed {
  border-color: var(--ok-edge);
  border-inline-start-color: var(--ok);
  background: var(--ok-tint);
}
.state-strip--armed svg { color: var(--ok); }
.state-strip--off {
  border-color: var(--warn-edge);
  border-inline-start-color: var(--warn-fill);
  background: var(--warn-tint);
  color: var(--warn);
}
.state-strip strong { font-weight: var(--fw-bold); }
.state-strip svg { flex: 0 0 auto; margin-block-start: 2px; }

/* The compact form, for the sidebar footer where there is no room for a
   sentence. It is the same two colours saying the same two things. */
.state-chip {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: var(--s-1) var(--s-4);
  border-radius: var(--radius-pill);
  border: var(--hair) solid var(--state-observing-edge);
  background: var(--state-observing-tint);
  color: var(--state-observing);
  font-size: var(--fs-1); font-weight: var(--fw-medium);
  white-space: nowrap;
}
.state-chip--armed {
  border-color: var(--ok-edge);
  background: var(--ok-tint);
  color: var(--ok);
}
.state-chip--off {
  border-color: var(--warn-edge);
  background: var(--warn-tint);
  color: var(--warn);
}

/* ═══════════════════════════════════════════════════════════════════════
   11. Buttons
   ═══════════════════════════════════════════════════════════════════════ */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3);
  min-block-size: var(--control-h);
  padding: 0 var(--s-5);
  /* --line-control is 3.32:1 against the surface rather than 1.63:1. A
     default button's entire affordance is its border, and at half the
     ratio WCAG 1.4.11 asks of a control boundary a row of secondary
     actions reads as floating text on a white card. */
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background: var(--surface);
  color: var(--text);
  font: inherit; font-size: var(--fs-3); font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  cursor: pointer; white-space: nowrap;
  position: relative;
  transition: background .12s, border-color .12s, transform .06s;
}
.btn:hover { background: var(--surface-hover); border-color: var(--text-2);
             color: var(--text); text-decoration: none; }
/* A press state, which no component in the product had. */
.btn:active { background: var(--surface-active); transform: translateY(1px); }
/* Disabled is a state, not a dimmer. At .55 opacity a destructive button's
   label composited to 2.93:1 and the pager's arrows to 1.77:1 — erased
   rather than de-emphasised, and with no second channel to say why. Stating
   the state in tokens keeps every variant at one legible appearance and
   above 3:1, which is what a control the operator has to recognise before
   deciding it is unavailable needs. */
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 1; cursor: not-allowed; pointer-events: none;
  background: var(--surface-sunken); border-color: var(--line);
  color: var(--text-disabled);
}

/* The primary action is near-black, not brand blue. This is the rationing
   decision that makes the rest of the colour system work: when the confirm
   button is quiet, the one destructive control on the page is the only
   saturated thing on screen and cannot be mistaken for it at three in the
   morning. Blue stays for links and for the current cell of a segmented
   control, where it means "selected" rather than "do this". */
.btn--primary { background: var(--ink); border-color: var(--ink); color: var(--ink-invert); }
.btn--primary:hover { background: var(--ink-hover); border-color: var(--ink-hover); color: var(--ink-invert); }
/* Solid danger answers a destructive question; it never opens one. There
   are two of these in the product: the confirm control inside the shell's
   confirmation dialog, and the submit of the queue-purge form, where the
   scope is chosen and a phrase typed before the button does anything. It is
   the loudest thing the interface can say, and it is worth exactly as much
   as it is rare — twelve solid red Delete buttons on one board made the
   loudest colour the most common one, which is how an operator learns to
   stop seeing it. */
.btn--danger  { background: var(--danger); border-color: var(--danger); color: var(--text-invert); }
.btn--danger:hover { background: var(--danger-strong); border-color: var(--danger-strong); color: var(--text-invert); }

/* Every destructive control that merely OPENS that question — delete, lock
   a port, power off, purge, revoke, unlink — is this one instead: a default
   button's neutral surface and geometry, carrying the danger colour in its
   label and its boundary only. It reads as red at a glance without painting
   a red block into a row, and it fills on hover and on keyboard focus, so
   the moment the pointer or the focus ring is on it there is no doubt what
   it is. The label is --danger on --surface at 7.69:1 light and 8.22:1
   dark; the boundary is --danger-control, which clears 1.4.11 on all three
   grounds a button sits on. */
.btn--danger-quiet {
  background: var(--surface);
  border-color: var(--danger-control);
  color: var(--danger);
}
.btn--danger-quiet:hover,
.btn--danger-quiet:focus-visible {
  background: var(--danger); border-color: var(--danger); color: var(--text-invert);
}
.btn--danger-quiet:active {
  background: var(--danger-strong); border-color: var(--danger-strong); color: var(--text-invert);
}
.btn--danger-quiet[disabled], .btn--danger-quiet[aria-disabled="true"] {
  background: var(--surface-sunken); border-color: var(--line); color: var(--text-disabled);
}

/* Blue is a selection, never an action — the same thing it means in the
   segmented control and in a link. Four controls in the product wore
   .btn--primary to report a state rather than to offer an action ("this
   image is offered", "this port counts for billing", "this is the pane you
   are looking at"), which on a wiring table meant one primary action per
   row. They wear this instead, and the page's single primary weight goes
   back to the one control that commits something. --accent on
   --accent-tint is 6.07:1 light and 7.35:1 dark. */
.btn--selected {
  background: var(--accent-tint); border-color: var(--accent); color: var(--accent);
}
.btn--selected:hover {
  background: var(--accent-tint); border-color: var(--accent-strong); color: var(--accent-strong);
}

.btn--quiet   { border-color: transparent; background: none; color: var(--text-2); }
.btn--quiet:hover { background: var(--surface-hover); color: var(--text); }

/* Genuinely smaller at a fine pointer; it grows only under pointer: coarse. */
.btn--sm { min-block-size: 1.625rem; padding-inline: var(--s-4); font-size: var(--fs-2); }
.btn--icon { padding-inline: var(--s-4); }
.btn--block { inline-size: 100%; }

/* A device read takes seconds and the page gave no sign it had started, so
   an operator who saw no change pressed Test connection three more times
   and queued three more SNMP jobs against a switch the page had just said
   may be slow. aria-busy is both the visual state and the thing a screen
   reader announces. inset: 0 with auto margins centres the spinner without
   any physical offset, so it is correct in both directions. */
/* The power row is drawn with its own button class rather than .btn, so it
   is named here too; without it the one control whose press takes longest
   was the one control that never showed it had been pressed. */
:is(.btn, .power-btn)[aria-busy="true"] { pointer-events: none; color: transparent; }
:is(.btn, .power-btn)[aria-busy="true"] > * { visibility: hidden; }
:is(.btn, .power-btn)[aria-busy="true"]::after {
  content: "";
  position: absolute; inset: 0; margin: auto;
  inline-size: 1em; block-size: 1em;
  border: 2px solid var(--line-control);
  border-block-start-color: var(--accent);
  border-radius: 50%;
  animation: dcm-spin .6s linear infinite;
}
.btn--primary[aria-busy="true"]::after,
.btn--danger[aria-busy="true"]::after {
  border-color: var(--busy-track-invert);
  border-block-start-color: var(--text-invert);
}
@keyframes dcm-spin { to { transform: rotate(360deg); } }

.form-actions { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }
.form-actions--bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-block: var(--s-5) var(--s-7);
  padding: var(--s-5) var(--s-6);
  background: var(--surface-raised);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--accent) 18%, transparent);
}
.form-actions__copy {
  display: grid;
  gap: 0.2rem;
  min-inline-size: min(100%, 18rem);
}
.form-actions__copy strong {
  font-size: var(--fs-3);
}
.form-actions__copy .meta {
  margin: 0;
  color: var(--text-2);
  font-size: var(--fs-2);
}
.form-actions__btns {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}
.form-section-break {
  margin-block: 0 var(--s-5);
  padding-block: var(--s-4);
  border-block-start: 1px dashed var(--line);
}
.form-section-break p {
  margin: 0;
  color: var(--text-2);
  font-size: var(--fs-2);
}

/* ═══════════════════════════════════════════════════════════════════════
   12. Segmented control
   ═══════════════════════════════════════════════════════════════════════

   One control drawn as one control, and used by both places that need one.
   The traffic range picker was four identical buttons whose handler never
   marked the chosen one, so after clicking 7d the chart redrew and nothing
   on screen stated what period was on display — during a billing dispute
   that is the one fact that matters. */

.segmented {
  display: inline-flex; align-items: stretch;
  background: var(--surface);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-sm);
  /* Scrolls rather than clips: at 320px the hardware tabs cut the last
     count off with nothing to say it was there. */
  overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
  max-inline-size: 100%;
}
/* A gap, because a cell's label and its count are separate flex items and
   the space between them in the markup is dropped: the tabs read
   "Failed(3)" and «هشدار(56)». */
.segmented > * {
  flex: none; white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-2);
  min-inline-size: 2.125rem; min-block-size: 1.875rem;
  padding-inline: var(--s-5);
  border: 0; border-inline-start: var(--hair) solid var(--line);
  border-radius: 0; background: none;
  font: inherit; font-size: var(--fs-2); font-weight: var(--fw-regular);
  font-variant-numeric: tabular-nums;
  color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.segmented > :first-child { border-inline-start: 0; }
.segmented > :hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.segmented > :active { background: var(--surface-active); }
.segmented > [aria-current],
.segmented > [aria-pressed="true"],
.segmented > .is-current {
  background: var(--accent); color: var(--text-invert); font-weight: var(--fw-medium);
  /* The active cell's own colour supplies the separation, so the divider
     beside it would read as a seam through a solid block. */
  box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent);
}
/* A count inside the chosen cell. .muted paints it the secondary grey, which
   on the accent fill measured 1.07:1 — the selected tab's count simply
   vanished. It takes the cell's inverted colour instead. */
.segmented > :is([aria-current], [aria-pressed="true"], .is-current) :is(.muted, .meta) { color: inherit; }
.segmented > [aria-disabled="true"] {
  opacity: 1; pointer-events: none;
  background: var(--surface-sunken); color: var(--text-disabled);
}
.segmented svg { inline-size: 15px; block-size: 15px; }

/* The focus ring turns inward inside this track, which puts it on the cell's
   own background — and on the SELECTED cell that background is --accent,
   against which --focus measures 1.30:1 in the light scheme and 1.56:1 in the
   dark one. So tabbing onto the range that is currently on display, on the
   page an operator opens during a billing dispute, showed nothing at all.
   The inset ring in the inverted text colour gives the outline a second edge
   that the solid fill cannot swallow. */
.segmented > [aria-current]:focus-visible,
.segmented > [aria-pressed="true"]:focus-visible,
.segmented > .is-current:focus-visible,
.pager__page.is-current:focus-visible {
  box-shadow: inset 0 0 0 var(--focus-width) var(--text-invert),
              inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent);
}

/* ═══════════════════════════════════════════════════════════════════════
   13. Pagination
   ═══════════════════════════════════════════════════════════════════════ */

/* Standalone, the pager is a self-contained bar. Most paginated pages put
   it in a bare <div> after the card closes, where card-footer styling — a
   top border, a sunken fill, no sides, no radius — rendered as a detached
   grey strip almost indistinguishable from the page background, floating
   under the table it belongs to. That is exactly the loose-parts reading
   the segmented track was built to answer.

   Rather than editing four views and hoping the fifth never moves, the
   component takes responsibility for both placements. The nested selector
   is a DESCENDANT, not a child: the one correct placement in the product
   puts links() inside .card__body--flush, which makes it a grandchild. */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap;
  padding: var(--s-4) var(--card-pad);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  margin-block-start: var(--s-5);
}
.card .pager {
  border: 0; border-block-start: var(--hair) solid var(--line);
  border-radius: 0; box-shadow: none; background: var(--surface-sunken);
  margin: 0;
}

.pager__summary { color: var(--text-2); font-size: var(--fs-2); }

/* The track scrolls rather than being clipped. Its cells carry a 2.125rem
   floor and do not shrink, so a thirteen-element window is about 487px of
   min-content — wider than a 375px phone, inside a .card that hides its
   overflow. The measured result was that pages 9 onward AND the next-page
   arrow were painted past the card's edge with no page scroll to reach them:
   on Audit, Servers, Services, Switch ports, Administrators, API keys and
   Security the operator could see page one and could not leave it. In RTL at
   320px seven of the fourteen controls landed at negative x.

   overflow-x: auto keeps the segmented appearance and makes the tail
   swipeable; the steps are pinned below so the control that must never be
   the clipped one cannot be. */
.pager__controls {
  display: inline-flex; align-items: stretch;
  max-inline-size: 100%;
  background: var(--surface);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}

/* `.pager__controls .btn` is here on purpose. The simple paginator can
   still drop .btn pills into this track, where they double their borders
   against it and have their corners sliced off by the overflow.
   Neutralising them here repairs that view whether or not its markup is
   rewritten, which matters because it renders the moment any controller
   switches to simplePaginate(). */
.pager__page, .pager__step, .pager__gap, .pager__controls .btn {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  min-inline-size: 2.125rem; min-block-size: 1.875rem; padding: 0 var(--s-4);
  border: 0; border-inline-start: var(--hair) solid var(--line);
  border-radius: 0; background: none; box-shadow: none;
  font-size: var(--fs-2); font-weight: var(--fw-regular);
  font-variant-numeric: tabular-nums;
  color: var(--text-2); white-space: nowrap;
  transition: background .12s, color .12s;
}
.pager__controls > :first-child { border-inline-start: 0; }
.pager__page:hover, .pager__step:hover, .pager__controls .btn:hover {
  background: var(--surface-hover); color: var(--text); text-decoration: none; }
.pager__page:active, .pager__step:active { background: var(--surface-active); }
.pager__page.is-current, .pager__controls .btn--primary {
  background: var(--accent); color: var(--text-invert); font-weight: var(--fw-medium);
  box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent); }
.pager__gap { color: var(--text-2); }
.pager__step svg { inline-size: 15px; block-size: 15px; }
/* The arrows point along the reading direction, so they flip with it. */
[dir="rtl"] .pager__step svg { transform: scaleX(-1); }
.pager [aria-disabled="true"] {
  opacity: 1; pointer-events: none;
  background: var(--surface-sunken); color: var(--text-disabled);
}

/* The framework ships pagination markup for a utility-class framework this
   platform does not use; if one of its views is ever rendered, its unsized
   icons fill the viewport. Contain them. */
.pager svg, nav[role="navigation"] svg { inline-size: 15px; block-size: 15px; }

/* ═══════════════════════════════════════════════════════════════════════
   14. Forms
   ═══════════════════════════════════════════════════════════════════════ */

.field { margin-block-end: var(--s-5); }
.field--grow  { flex: 1 1 var(--w-md); }
.field--fixed { flex: 0 1 var(--w-xs); }

/* The label is at least as loud as the value typed into it. It used to be
   smaller than its own input — the element that says what the field is was
   the second-smallest thing in the group — and its compensating 650 weight
   collapsed to plain bold on Segoe UI and to bold-or-nothing in Tahoma. */
.field > label {
  display: block;
  margin-block-end: var(--s-3);
  font-size: var(--fs-3); font-weight: var(--fw-medium);
  color: var(--text);
}

/* An <input> with no type is a text field, and 28 of the forms write it
   that way; they rendered as the browser's own 22px box. */
input:not([type]), input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="search"], input[type="url"],
input[type="tel"], select, textarea {
  inline-size: 100%;
  min-inline-size: 0;
  max-inline-size: 100%;
  min-block-size: var(--control-h);
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  font: inherit; font-size: var(--fs-3);
  color: var(--text); background: var(--surface);
  transition: border-color .12s;
}
input:hover, select:hover, textarea:hover { border-color: var(--text-2); }
/* The field's own focus state: its border and a soft halo, on every focus
   including a click, because a text field is where the caret is and the
   operator must see which one that is. The keyboard ring comes from the one
   :focus-visible rule in the base section and is not restated here — the
   copy that used to sit in the v0.8 section outranked the inward ring on the
   segmented track, and its field rule came after the invalid state below
   and painted an invalid field blue the moment it was focused, which is
   exactly when the operator is trying to fix it. */
input:where(:not([type="checkbox"], [type="radio"])):focus, select:focus, textarea:focus {
  border-color: var(--focus);
  box-shadow: 0 0 0 3px var(--focus-halo);
}
input[disabled], select[disabled], textarea[disabled] {
  background: var(--surface-sunken); color: var(--text-disabled); cursor: not-allowed; }
/* The invalid state is drawn on two channels, because a red border alone is
   colour-only signalling and the operator it fails is the one who most needs
   to find the field. The message itself is rendered under the control by
   <x-field-error>, wired through aria-describedby, so the state is also
   readable rather than merely visible. */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--danger);
  border-inline-start-width: 3px;
  background: var(--danger-tint);
}
input[aria-invalid="true"]:focus, select[aria-invalid="true"]:focus, textarea[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 28%, transparent);
}
::placeholder { color: var(--text-2); opacity: 1; }

/* The message belonging to one field, as opposed to the page-level summary.
   A form of fifteen controls that reports "a rule was broken" at the top and
   marks nothing is a form the operator has to solve by elimination. */
.field-error {
  display: flex; align-items: flex-start; gap: var(--s-3);
  margin-block-start: var(--s-3);
  font-size: var(--fs-2);
  color: var(--danger);
}
.field-error svg { inline-size: 14px; block-size: 14px; flex: 0 0 auto; margin-block-start: 2px; }

.check { display: flex; gap: var(--s-4); align-items: flex-start;
         font-weight: var(--fw-regular); cursor: pointer; }
.check input { inline-size: auto; min-block-size: 0; margin-block-start: 3px; flex: 0 0 auto; }
/* The one checkbox pattern, inside a field as well as outside one.
   `.field > label` outranks .check, so a checkbox written in a field became a
   bold block with the box on the text's top edge and no gap — the fix used to
   exist for the sign-in page alone. */
.field > label.check { display: flex; font-weight: var(--fw-regular); margin-block-end: 0; }
.field > label.check + label.check { margin-block-start: var(--s-3); }

/* A group of checkboxes under one caption is a field like any other. The
   browser's own fieldset is a grooved frame with its caption cut into the
   border, which nothing else on a form looks like; here the frame goes and
   the legend is set as the field's label. */
fieldset.field { margin-inline: 0; padding: 0; border: 0; min-inline-size: 0; }
fieldset.field > legend {
  padding: 0; margin-block-end: var(--s-3);
  font-size: var(--fs-3); font-weight: var(--fw-medium); color: var(--text);
}
fieldset.field > .hint { margin-block: 0 var(--s-4); }

/* A choice between mutually exclusive outcomes, drawn as one block per
   option so that the thing being chosen is the resulting state rather than
   the position of two independent switches whose conjunction decides it. */
.choice { display: grid; gap: var(--s-4); margin-block-end: var(--s-5); }
.choice__option {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.choice__option:hover { border-color: var(--line-control); background: var(--surface-hover); }
.choice__option input { inline-size: auto; min-block-size: 0; margin-block-start: 3px; flex: 0 0 auto; }
.choice__option:has(input:checked) { border-color: var(--accent); background: var(--accent-tint); }
/* The one option that arms the platform against real customer ports is the
   only saturated thing in the group, and only once it is the live choice. */
.choice__option--armed:has(input:checked) { border-color: var(--danger); background: var(--danger-tint); }
.choice__title { display: block; font-weight: var(--fw-medium); }
.choice__desc  { display: block; font-size: var(--fs-2); color: var(--text-2);
                 margin-block-start: var(--s-1); max-inline-size: var(--measure); }

/* ═══════════════════════════════════════════════════════════════════════
   15. Alerts
   ═══════════════════════════════════════════════════════════════════════ */

/* Derived from the semantic tokens rather than from eight hard-coded
   hexes, which is why alert--danger and badge--danger used to be two
   different reds for the same state on the same page. The borders sat
   barely above 1:1 against both their own fill and the page, so an alert
   read as a faintly tinted rectangle rather than a bounded notice; a
   hairline cannot be made visible without being ugly, so the boundary
   moves to a bar on the inline-start edge in the full-strength hue. */
.alert {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-4) var(--s-5);
  margin-block-end: var(--s-6);
  border: var(--hair) solid var(--line-strong);
  border-inline-start: 3px solid var(--text-2);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
  color: var(--text);
  font-size: var(--fs-3);
}
.alert svg { flex: 0 0 auto; margin-block-start: 2px; }
.alert strong { font-weight: var(--fw-bold); }
.alert ul { margin: var(--s-3) 0 0; padding-inline-start: var(--s-7); }
.alert--success { background: var(--ok-tint);     border-color: var(--ok-edge);
                  border-inline-start-color: var(--ok);     color: var(--ok); }
.alert--danger  { background: var(--danger-tint); border-color: var(--danger-edge);
                  border-inline-start-color: var(--danger); color: var(--danger); }
.alert--warning { background: var(--warn-tint);   border-color: var(--warn-edge);
                  border-inline-start-color: var(--warn);   color: var(--warn); }
.alert--info    { background: var(--info-tint);   border-color: var(--info-edge);
                  border-inline-start-color: var(--info);   color: var(--info); }

/* ═══════════════════════════════════════════════════════════════════════
   16. Meter
   ═══════════════════════════════════════════════════════════════════════ */

/* One utilisation bar with one set of thresholds. Traffic quota — the most
   important number in this product — was a 5px square-cornered borderless
   bar on the services list and a 6px bordered pill on switch detail, and
   they changed colour at different percentages, so amber did not mean one
   thing across two pages. The thresholds live in the template as a class
   rather than as a colour ternary, so there is one place to change them:
   warning at 70, danger at 85. */
.meter {
  block-size: 6px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-block-start: var(--s-2);
  background: var(--surface-sunken);
  border: var(--hair) solid var(--line);
}
/* The band is the fill's own colour, so the gradient further down — drawn
   from currentColor — is drawn in the band's hue. Nothing used to set a
   colour here, and that gradient came later at the same specificity, so
   every bar on every page was painted in the inherited text ink: a healthy
   service and one over its quota looked the same, and the hatch was gone. */
.meter__fill { block-size: 100%; border-radius: var(--radius-pill);
               color: var(--ok-fill); background-color: currentColor;
               transition: inline-size .3s; }
/* Hue is never the only channel — the rule the chart already obeys with its
   dash, applied here. The two alert bands take a hatch, and they take
   different ones from each other, so "watch this" and "over" are told apart
   in greyscale, on a printout and by an operator who cannot separate amber
   from red. On the services list this bar is the only warning signal in the
   row: its sibling prints raw gigabytes, not a state word. The hatch is a
   property rather than a background, so the layer that draws the gradient
   can stack it on top. */
.meter__fill--warning {
  color: var(--warn-fill);
  --meter-texture: repeating-linear-gradient(45deg,
    var(--meter-hatch) 0 2px, transparent 2px 6px);
}
.meter__fill--danger {
  color: var(--danger-fill);
  --meter-texture: repeating-linear-gradient(135deg,
    var(--meter-hatch) 0 3px, transparent 3px 6px);
}
.meter__fill { background-image: var(--meter-texture, none); }

/* ═══════════════════════════════════════════════════════════════════════
   17. Code, secrets and the permission editor
   ═══════════════════════════════════════════════════════════════════════ */

/* One code box. It must wrap rather than scroll, because a half-visible
   crontab line copied by eye is a broken one — and then two views rebuilt
   it inline, one of them with overflow-x: auto, the exact behaviour it was
   written to prevent. */
.code-block {
  padding: var(--s-4) var(--s-5);
  margin-block: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
  font-family: var(--font-mono); font-size: var(--fs-2); line-height: 1.7;
  color: var(--text);
  direction: ltr; unicode-bidi: isolate; text-align: start;
  /* Wraps at a space or a slash where it can and inside a token only where
     it must. break-all split "Authorization" and the host name at any
     letter on a phone even with a space a few characters away; anywhere
     still breaks a token that has no room, such as an issued key. */
  white-space: pre-wrap; word-break: normal; overflow-wrap: anywhere;
}
.code-block code { background: none; padding: 0; font: inherit; }

/* The one string in the product that can never be retrieved again, and the
   only one that had no copy control — while the crontab line, which is
   regenerable at any moment, has had a full clipboard implementation
   twelve files away all along. */
.secret { display: flex; gap: var(--s-4); align-items: center; flex-wrap: wrap; }
.secret .code-block { flex: 1 1 var(--w-lg); min-inline-size: 0; margin: 0; background: var(--surface); }

/* Two-factor enrolment: the QR code beside the steps, stacking on a phone.
   The code is black on white in both themes on purpose — many scanners
   cannot read an inverted one — so its colours are literal, not tokens. */
.enrol {
  display: grid; gap: var(--s-6); align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}
.qr {
  inline-size: min(15rem, 100%); aspect-ratio: 1;
  border: var(--hair) solid var(--line); border-radius: var(--radius-2);
  background: #fff; overflow: hidden;
}
.qr svg { display: block; inline-size: 100%; block-size: 100%; }
.qr__light { fill: #fff; }
.qr__dark { fill: #000; }

/* A key printed in groups wraps between groups, never inside one: a group
   split across two lines is read back as two. */
.code-block--groups { word-break: normal; overflow-wrap: normal; }

/* Recovery codes read in two columns where there is room for them, and
   one on a phone, never breaking a code across a line. */
.recovery-codes {
  display: grid; gap: var(--s-2) var(--s-6);
  grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
  margin: 0; padding: 0; list-style: none;
}
.recovery-codes li { white-space: nowrap; }

/* The one-time code field: digits spaced for reading back. */
.otp-input { letter-spacing: 0.2em; font-family: var(--font-mono); max-inline-size: 14rem; }

/* The account chip in the top bar is a link to the account page. */
a.user-chip { color: inherit; text-decoration: none; }
a.user-chip:hover { border-color: var(--line-strong); }

.perm-group { border: var(--hair) solid var(--line); border-radius: var(--radius-2);
              margin-block-end: var(--s-5); overflow: hidden; }
.perm-group__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap; padding: var(--s-4) var(--s-5);
  background: var(--surface-sunken); border-block-end: var(--hair) solid var(--line-strong);
}
.perm-group__title { font-size: var(--fs-3); font-weight: var(--fw-bold); }
.perm-group__desc  { color: var(--text-2); font-size: var(--fs-2);
                     margin-block-start: var(--s-1); max-inline-size: var(--measure); }
.perm-row { display: flex; gap: var(--s-5); align-items: flex-start;
            padding: var(--s-3) var(--s-5); border-block-end: var(--hair) solid var(--line); }
.perm-row:last-child { border-block-end: none; }
.perm-row:hover { background: var(--surface-hover); }
.perm-row--plain { border-block-end: none; padding-inline: 0; }
.perm-row input[type="checkbox"] { margin-block-start: 3px; flex: 0 0 auto; }
.perm-row__label { display: block; }
.perm-row__key { font-family: var(--font-mono); font-size: var(--fs-1); color: var(--text-2);
                 margin-inline-start: var(--s-3); direction: ltr; unicode-bidi: isolate; }
.perm-row__desc { display: block; font-weight: var(--fw-regular);
                  font-size: var(--fs-2); color: var(--text-2); margin-block-start: var(--s-1); }

/* A permission that no feature implements is marked, because an
   access-control screen is a promise about what will and will not happen.
   An operator who grants one to a junior and then checks it works learns
   that the checkboxes are decorative, and the day one of them starts
   working that lesson is dangerous. */
.perm-row[data-planned] .perm-row__label { color: var(--text-2); }
.perm-row[data-planned] input { opacity: .55; }
.perm-group__actions { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.perm-group__actions .btn { min-block-size: var(--tap); min-inline-size: var(--tap); }

/* ═══════════════════════════════════════════════════════════════════════
   18. Traffic chart
   ═══════════════════════════════════════════════════════════════════════ */

/* The plot itself now lives in assets/css/chart.css and is drawn by
   assets/js/traffic-chart.js, which reads --series-in and --series-out and
   redraws at the container's measured width. The drawing rules that used to
   sit here — .series, .series--in, .series--out, .chart__grid and
   .chart__axis — had no user left after that and are gone; a stylesheet that
   still describes the previous implementation is how the next person to
   touch the chart adopts the vocabulary it was replaced for.

   What stays here is the frame the shell owns: the summary line above the
   plot, and the legend, both of which are ordinary markup in the port view
   rather than anything the script builds. The colours are the same tokens
   the script reads, so the key and the line it labels cannot disagree. */
.chart { inline-size: 100%; block-size: 240px; display: block; }
.chart__summary { margin-block-end: var(--s-4); font-size: var(--fs-3); color: var(--text-2); }

.legend { display: flex; flex-wrap: wrap; gap: var(--s-6);
          margin-block-start: var(--s-4); font-size: var(--fs-2); color: var(--text-2); }
/* The key is drawn as a border so it matches the stroke it represents,
   including the dash — a filled swatch is thicker than the line it labels
   and cannot express a dash at all. */
.legend__key { display: inline-block; inline-size: 20px; block-size: 0;
               border-block-start: 2px solid currentColor; vertical-align: middle;
               margin-inline-end: var(--s-3); }
.legend__item { display: inline-flex; align-items: center; }
.legend__item--in  { color: var(--series-in); }
.legend__item--out { color: var(--series-out); }
.legend__item--out .legend__key { border-block-start-style: dashed; }
.legend__item--overlay { color: var(--series-in); }
.legend__item--overlay .legend__key { border-block-start-style: dotted; border-block-start-width: 2px; }
.legend__item span:last-child { color: var(--text-2); }

/* ═══════════════════════════════════════════════════════════════════════
   19. Rack elevation
   ═══════════════════════════════════════════════════════════════════════

   The rack keeps its dark ground in both themes: an equipment elevation is
   a picture of a physical object, and it reads correctly as dark against a
   light page. Its greys are the --rack-* tokens declared with the rest, so
   the dark scheme can lighten it rather than let it disappear into the
   background. */

.rack { border: 2px solid var(--line-control); border-radius: var(--radius-2);
        overflow: hidden; background: var(--rack-body);
        max-inline-size: 35rem; box-shadow: var(--shadow-lg); }
.rack__unit { display: flex; align-items: stretch;
              border-block-end: var(--hair) solid var(--rack-line); min-block-size: 26px; }
/* A 1U chassis is the whole-chassis link, and at 26px it was a target a
   finger misses. Under a coarse pointer every unit is a full target. */
@media (pointer: coarse) {
  .rack__unit { min-block-size: var(--tap); }
}
.rack__unit:last-child { border-block-end: none; }
.rack__number { flex: 0 0 40px; display: grid; place-items: center;
                font-family: var(--font-mono); font-size: var(--fs-1);
                color: var(--rack-text-2); background: var(--rack-rail);
                border-inline-end: var(--hair) solid var(--rack-line); }
.rack__slot { flex: 1; display: flex; align-items: center;
              padding: var(--s-1) var(--s-5); min-inline-size: 0; }
.rack__slot--free { color: var(--rack-free); font-size: var(--fs-1); }

.rack__device {
  position: relative;
  flex: 1; display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); min-inline-size: 0;
  background: linear-gradient(180deg, var(--rack-device-top) 0%, var(--rack-device-bottom) 100%);
  border: var(--hair) solid var(--rack-device-edge); border-radius: var(--radius-2);
  padding: var(--s-2) var(--s-5);
  color: var(--rack-text); font-size: var(--fs-2); font-weight: var(--fw-medium);
}
.rack__device a { color: var(--rack-text); text-decoration: none; }
/* The whole chassis is the link, not the 14px line of its name: a 1U
   machine is otherwise a target a finger cannot hit. */
.rack__device-name a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.rack__device a:hover { color: var(--rack-link-hover); }
.rack__device-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rack__device-meta {
  display: inline-flex; align-items: center; gap: var(--s-4); flex: 0 0 auto;
  color: var(--rack-text-2); font-weight: var(--fw-regular);
  font-size: var(--fs-1);
}
/* Only the height ("2U") is a machine value. The kind beside it is a word —
   «سرور», «بلنک» — and the mono stack has no Persian face, so it was drawn in
   a fallback with its letters spread into cells. */
.rack__device-meta > [dir="ltr"] { font-family: var(--font-mono); }
.rack__device-kind {
  text-transform: var(--caps-eyebrow); letter-spacing: var(--track-eyebrow);
  padding: 1px var(--s-3); border-radius: var(--radius-1);
  background: var(--rack-device-kind);
}

/* Equipment is coloured by what it is: the question at an elevation is
   usually "what is next to this space", and a wall of identical grey
   rectangles answers it slowly. An inline-start edge carries the colour so
   the type is readable down the stack without reading a word of it. */
.rack__device--server  { border-inline-start: 3px solid var(--rack-server); }
.rack__device--switch  { border-inline-start: 3px solid var(--rack-switch);
  background: linear-gradient(180deg, var(--rack-switch-top) 0%, var(--rack-switch-bottom) 100%); }
.rack__device--fex     { border-inline-start: 3px dashed var(--rack-switch);
  background: linear-gradient(180deg, var(--rack-switch-top) 0%, var(--rack-switch-bottom) 100%);
  opacity: 0.92; }
.rack__device--power   { border-inline-start: 3px solid var(--rack-power);
  background: linear-gradient(180deg, var(--rack-power-top) 0%, var(--rack-power-bottom) 100%); }
.rack__device--passive { border-inline-start: 3px solid var(--rack-passive);
  background: linear-gradient(180deg, var(--rack-passive-top) 0%, var(--rack-passive-bottom) 100%); }

/* A blanking plate is a real thing occupying a real unit, but it is not
   equipment: it should read as filled space, not as a device. */
.rack__device--blank {
  border-inline-start: 3px solid var(--rack-blank);
  background: repeating-linear-gradient(135deg,
    var(--rack-blank-a) 0 7px, var(--rack-blank-b) 7px 14px);
  color: var(--rack-text-2);
}

/* A machine marked out of the datacenter. The unit is still taken — the
   chassis is in the rail — but it is taken by something nobody can reach, and
   planning a new machine into that space needs somebody to collect the old
   one first. Amber and hatched says "occupied, but not by a working machine"
   at a glance; hover turns the body yellow, which is how an operator confirms
   the one they are pointing at. */
.rack__device--out {
  border-inline-start: 3px dashed var(--rack-out);
  background: repeating-linear-gradient(135deg,
    var(--rack-out-a) 0 8px, var(--rack-out-b) 8px 16px);
  color: var(--rack-out-text);
}
.rack__device--out .rack__device-meta { color: var(--rack-out-text); }
.rack__device--out a { color: var(--rack-out-text); }
.rack__device-kind--out {
  background: color-mix(in srgb, var(--rack-out) 28%, transparent);
  color: var(--rack-out-text);
}

/* Hover lights the unit the pointer is in, including the empty continuation
   row of a multi-unit chassis — the unit is what the operator is asking
   about, and on a continuation row there is nothing else to answer with. The
   whole chassis cannot light as one object from CSS alone: a rack reads
   top-down, so the row carrying the name is the LAST of its units in the
   document and no sibling selector reaches back up to it. */
.rack__device--out:hover,
.rack__unit:hover .rack__device--out {
  background: repeating-linear-gradient(135deg,
    var(--rack-out-hover-a) 0 8px, var(--rack-out-hover-b) 8px 16px);
  border-color: var(--rack-out-hover-edge);
  border-inline-start-color: var(--rack-out-hover-edge);
  color: var(--rack-out-hover-text);
}
.rack__unit:hover .rack__device--out a { color: var(--rack-out-hover-text); }

.rack-column { min-inline-size: 0; display: grid; gap: var(--s-4); }
.rack__legend {
  display: flex; align-items: center; gap: var(--s-3);
  margin: 0; color: var(--text-2); font-size: var(--fs-1);
}
.rack__legend-key {
  inline-size: 1.1rem; block-size: 0.7rem; flex: 0 0 auto;
  border-radius: var(--radius-1);
  border-inline-start: 3px dashed var(--rack-out);
}
.rack__legend-key--out {
  background: repeating-linear-gradient(135deg,
    var(--rack-out-a) 0 6px, var(--rack-out-b) 6px 12px);
}

/* The continuation rows of a multi-unit chassis: the same body, no border
   across the seam, so a 4U machine reads as one block rather than four. */
.rack__device--cont { border-block-start: 0; border-block-end: 0; min-block-size: 20px; }

/* Self-collapsing, because the breakpoint that used to govern this was
   derived from the wrong quantity. It was computed from the rack's own
   minimum plus a gap plus the rack's own maximum — which sizes the rack
   twice and never asks what the SECOND column needs. The rack sits at its
   35rem stop, so between 901px and about 1150px the equipment lists beside
   it were measured at 91px and 192px wide: a card whose header read
   "Servers" stacked over its count, with its table permanently scrolled.
   Stating both tracks' real minima lets the grid drop to one column at the
   width where two stop being readable, whatever the sidebar is doing. */
.rack-layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(28rem, 100%), 1fr));
  gap: var(--s-7);
  align-items: start;
}

/* ═══════════════════════════════════════════════════════════════════════
   20. Sign-in and the public page
   ═══════════════════════════════════════════════════════════════════════ */

.signin { min-block-size: 100vh; min-block-size: 100dvh;
          display: flex; align-items: center; justify-content: center; padding: var(--s-7); }
.signin--top { align-items: flex-start; padding-block-start: 8vh; }

.signin__card {
  inline-size: 100%; max-inline-size: 25rem;
  background: var(--surface);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-lg);
  padding: var(--s-8);
}
.signin__brand { display: flex; flex-direction: column; align-items: center;
                 gap: var(--s-5); margin-block-end: var(--s-3); }
.signin__brand .brand__mark { inline-size: 44px; block-size: 44px; border-radius: var(--radius-3); }
.signin__brand .brand__mark svg { inline-size: 25px; block-size: 25px; }
.signin__title { font-size: var(--fs-5); font-weight: var(--fw-bold);
                 letter-spacing: var(--track-tight); }
.signin__title span { color: var(--accent); }
.signin__sub { text-align: center; color: var(--text-2);
               margin-block-end: var(--s-7); font-size: var(--fs-3); }

/* "Remember me" is one line, so the tick centres on it. The rule that keeps
   a .check inside a .field a flex row now lives with .check itself. */
.signin .check { align-items: center; }
.signin .check input { margin-block-start: 0; }

/* The language switch, set apart from the form. It sat directly under the
   full-width submit button as a bare link, close enough to read as a second
   action of the form rather than a choice about the page. */
.signin__alt {
  display: flex; justify-content: center;
  margin-block-start: var(--s-7);
  padding-block-start: var(--s-6);
  border-block-start: var(--hair) solid var(--line);
}
.signin__locale {
  display: inline-flex; align-items: center; gap: var(--s-3);
  min-block-size: 2rem;
  padding: var(--s-2) var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text-2);
  font-size: var(--fs-2); font-weight: var(--fw-medium);
}
.signin__locale:hover {
  color: var(--text); background: var(--surface-hover);
  border-color: var(--line-control); text-decoration: none;
}
.signin__locale svg.icon { inline-size: 15px; block-size: 15px; }
@media (pointer: coarse) { .signin__locale { min-block-size: var(--tap); } }

/* The error pages share the sign-in card: the same frame the operator
   already knows, a large status number, one sentence of what happened and
   one way out. */
.error-page { text-align: center; }
.error-page .signin__brand { margin-block-end: var(--s-5); }
.error-page__code {
  margin: 0;
  font-size: var(--fs-8); font-weight: var(--fw-bold);
  line-height: 1; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.error-page__title { margin-block: var(--s-4) 0; }
.error-page__detail { margin: var(--s-5) 0 0; color: var(--text-2); font-size: var(--fs-3); }
.error-page__reference { margin: var(--s-4) 0 0; color: var(--text-2); font-size: var(--fs-2); }
.error-page__reference code { overflow-wrap: anywhere; }
.error-page__actions {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-4);
  margin-block-start: var(--s-7);
}

/* The public page's own styles used to live here — a hero and a feature
   grid, written when the root of the site was one screen. It is a whole
   marketing site now, with its own stylesheet, and keeping a second
   definition of .hero and .feature in this file meant the two silently
   fought: the site's hero inherited a 42rem cap from here and rendered at
   a third of its width. Those rules moved to site.css, which is the only
   place that draws them. */

/* ═══════════════════════════════════════════════════════════════════════
   21. Responsive
   ═══════════════════════════════════════════════════════════════════════

   Most of the work is already done above without a breakpoint, by two
   structural rules: nothing sets a bare minimum width — every auto-fit
   grid goes through .grid or .form-grid and their minmax(min(…, 100%)) —
   and min-inline-size: 0 is a property of the components that need it
   rather than something each caller remembers. Together those retire the
   400px breakpoint that used to exist here entirely. */

/* Tablet — the sidebar narrows before it collapses. Derived: a 240px
   sidebar plus two gutters plus a minimum readable working column stop
   fitting. Because --gutter is one clamp() consumed by both
   .topbar__inner and .content, they cannot drift apart. */
@media (max-width: 1100px) { :root { --sidebar-w: 13.25rem; } }

/* Small tablet and large phone. Derived from the rack layout: its first
   column plus a gap plus the rack's own maximum stop fitting at 880px.
   Declared once, here, so changing the rack cannot silently desynchronise
   two copies of the same number.

   The navigation becomes a real disclosure. It used to be a scrolling row
   of chips with the scrollbar hidden, the section headings hidden, and no
   script anywhere to scroll the active chip into view — so most of the
   navigation sat behind an affordance that advertised nothing, and the row
   still started at Dashboard when you were on the Audit page, which means
   the interface misreported where you were. A <details> drawer needs no
   script, keeps the grouping, and names the current section in its own
   summary. */
@media (max-width: 900px) {
  :root { --topbar-h: 3rem; }
  .app { flex-direction: column; }
  .sidebar {
    inline-size: 100%; flex: none; block-size: auto; position: static;
    border-inline-end: 0; border-block-end: var(--hair) solid var(--line-strong);
  }
  .brand { padding-block: var(--s-4); }
  .brand__sub { display: none; }

  .nav-drawer > summary {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-4);
    padding: var(--s-4) var(--s-5);
    min-block-size: var(--tap);
    border-block-start: var(--hair) solid var(--line);
    font-size: var(--fs-3); font-weight: var(--fw-medium);
    cursor: pointer; list-style: none;
  }
  .nav-drawer > summary::-webkit-details-marker { display: none; }
  .nav-drawer > summary .icon { transition: transform .15s; }
  .nav-drawer[open] > summary .icon { transform: rotate(180deg); }
  .sidebar__nav { max-block-size: 60dvh; }
  .sidebar__footer { padding-block: var(--s-3); }

  /* Fewer rows of chrome before the page heading. The enforcement chip moves
     up beside the brand rather than taking a row of its own under the menu,
     and the version, which a phone has no use for at a glance, steps out of
     the bar (Settings still states it). The drawer's own top rule draws the
     line under the shared row. */
  .sidebar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
  .sidebar > .brand { grid-column: 1; grid-row: 1; border-block-end: 0; }
  .sidebar > .sidebar__footer { grid-column: 2; grid-row: 1; border-block-start: 0; }
  .sidebar > .nav-drawer { grid-column: 1 / -1; grid-row: 2; }
  .sidebar__footer > :not(.state-chip) { display: none; }

  .card__header, .card__body { padding-inline: var(--s-5); }
  table.data th, table.data td { padding-inline: var(--s-4); }

  .rack-layout { grid-template-columns: 1fr; }
  .rack { max-inline-size: none; }

  /* The identifying column stays put while the rest scrolls, so a
     seven-column table is readable by swipe instead of by memory. This
     needs no markup change, which is why it is the default and stacking is
     not: most tables have no data-label attributes, so making stacking the
     default would render a column of anonymous grey blocks.

     The separation is drawn with box-shadow rather than a border, because
     under border-collapse the border belongs to the table and does not
     travel with the sticky cell. The inset shadow restores the row rule
     that the cell's own opaque background would otherwise paint over. */
  table.data:not([data-freeze="off"]) :is(th, td):first-child {
    position: sticky; inset-inline-start: 0; z-index: 1;
    background: var(--surface);
  }
  table.data:not([data-freeze="off"]) th:first-child { z-index: 3; background: var(--surface-sunken); }
  [dir="ltr"] table.data:not([data-freeze="off"]) :is(th, td):first-child {
    box-shadow: inset 0 -1px 0 var(--line), 1px 0 0 var(--line); }
  [dir="rtl"] table.data:not([data-freeze="off"]) :is(th, td):first-child {
    box-shadow: inset 0 -1px 0 var(--line), -1px 0 0 var(--line); }

  /* The frozen cell's own opaque background and shadow would erase the
     out-of-datacenter mark exactly where a narrow screen needs it most —
     the identifying column is the only one still on screen. Both are
     restored here, carrying the column separator with them. */
  table.data:not([data-freeze="off"]) tbody
    :is(tr[data-port-state="out"], tr.row--out) > :first-child {
    background: var(--warn-tint);
  }
  [dir="ltr"] table.data:not([data-freeze="off"]) tbody
    :is(tr[data-port-state="out"], tr.row--out) > :first-child {
    box-shadow: inset 3px 0 0 var(--warn-fill), inset 0 -1px 0 var(--line), 1px 0 0 var(--line); }
  [dir="rtl"] table.data:not([data-freeze="off"]) tbody
    :is(tr[data-port-state="out"], tr.row--out) > :first-child {
    box-shadow: inset -3px 0 0 var(--warn-fill), inset 0 -1px 0 var(--line), -1px 0 0 var(--line); }
  /* Gated the same way the full-row hover is, and for the same reason. On the
     audit log no cell holds a link, so between 621px and 900px — tablet
     width, where the column is frozen but the table has not yet stacked —
     hovering a row painted exactly one cell and left the other five at rest,
     which reads as a rendering fault rather than as feedback. */
  table.data tbody tr:has(a[href]):hover td:first-child,
  table.data[data-rows="linked"] tbody tr:hover td:first-child {
    background: var(--surface-hover); }

  /* Opt-in stacking at laptop/tablet width. Tables that set data-stack
     (ports, servers, services, switches, monitoring) become labelled cards
     instead of inflating the shell. Tables without it keep the frozen first
     column above. overflow-x:auto on .table-wrap remains the backstop.

     Each cell is a line: its label floated to the start of the page, its
     value flowing at the end. It was a flex row with space-between, which
     made the label, every text run and every child element a separate item
     spread across the row — a name at the centre and its datacenter at the
     far edge, a figure and its percentage apart, and a usage bar with no
     content width collapsed to its two-pixel border. Floated, the value is
     ordinary inline content: a figure and its unit stay on one line, a
     secondary line (.meta) stacks under the value at the same edge, and a
     long token wraps beside the label instead of setting the table's width.
     The fixed table layout is the other half of that: content can no longer
     widen a stacked table past its card. */
  table.data[data-stack] { table-layout: fixed; }
  table.data[data-stack] thead { display: none; }
  table.data[data-stack] tbody tr {
    display: block; padding: var(--s-4) var(--s-5);
    border-block-end: var(--hair) solid var(--line); }
  table.data[data-stack] tbody td[data-label] {
    display: flow-root;
    padding: var(--s-2) 0; border: none; text-align: end;
    overflow-wrap: anywhere; }
  /* The label is set in the page's face, whatever the cell's. A cell that is
     .mono gave its label the monospace stack, which has no Persian face, so
     «نام هاست» was drawn letter by letter in separate cells. Its line height
     is the value's, so the two read as one line. */
  table.data[data-stack] tbody td[data-label]::before {
    content: attr(data-label);
    /* The physical value first, for an engine without the logical keywords
       (Chrome before 118, which is where Windows 7 stops); the label's side
       follows the page's direction, which is what both lines say. */
    float: left; float: inline-start; max-inline-size: 50%;
    margin-inline-end: var(--s-5);
    text-align: start;
    font-family: var(--font); font-style: normal;
    font-size: var(--fs-1); font-weight: var(--fw-medium);
    line-height: calc(var(--fs-3) * var(--lh-snug));
    letter-spacing: var(--track-eyebrow); text-transform: var(--caps-eyebrow);
    overflow-wrap: normal;
    color: var(--text-2); }
  [dir="rtl"] table.data[data-stack] tbody td[data-label]::before {
    float: right; float: inline-start;
    font-family: var(--font-fa); direction: rtl; }
  /* A value cell that is left to right in a Persian page — .tech, .num,
     dir="ltr": a size, an address, a file name, a figure — keeps its own
     direction, so "100.00 GB" is not reordered to "GB 100.00" and a minus
     stays in front of its figure. In that cell the start of the line is the
     left, so the label is floated to its end (the page's start) and the value
     is aligned to its start (the page's end). Forcing the cell to the page's
     direction instead put the label in the right place and the value in the
     wrong order. .num is in the list because it sets the same direction:
     without it a figure cell swapped its label and value with every other
     line of the card. */
  [dir="rtl"] table.data[data-stack] tbody td[data-label]:is(.tech, .num, [dir="ltr"]) { text-align: start; }
  [dir="rtl"] table.data[data-stack] tbody td[data-label]:is(.tech, .num, [dir="ltr"])::before { float: right; float: inline-end; }
  /* A machine value keeps a token whole where it can: a host name or a
     dotted key that does not fit beside the label moves under it rather than
     being cut in two, and breaks inside the token only when it is wider than
     the whole line. Prose keeps the cell's anywhere. */
  table.data[data-stack] tbody td[data-label]:is(.tech, .mono),
  table.data[data-stack] tbody td[data-label] :is(.tech, .mono) { overflow-wrap: break-word; }
  /* A row of parts — a count and its badge, a set of buttons — is a flex box
     beside the label, and its items started at the label's side while every
     other value sat at the far edge. They line up with the values instead. */
  table.data[data-stack] tbody td[data-label] > :is(.cluster, .row-actions, .cluster--row-actions) {
    justify-content: flex-end; }
  /* A bar takes a line of its own under the label and the figure, at the
     full width of the row. */
  table.data[data-stack] tbody td[data-label] > .meter { clear: both; }
  table.data[data-stack] tbody td:not([data-label]) {
    display: block; padding: var(--s-2) 0; border: none; text-align: start; }
  table.data[data-stack] tbody td.empty { display: block; text-align: center; }
  /* Written to outrank the frozen-column rules above. At their weight this
     reset lost to them, and every stacked card kept a rule under its first
     field and another beside it. It sets no alignment: at this weight a
     text-align here beat the labelled cell's own, and the first line of every
     card sat beside its label while the rest sat at the far edge. An
     unlabelled first cell is already aligned to the start above. The frozen
     column's opaque background goes too: on a zebra-striped card it painted
     the first line as a white strip across the tinted row. */
  [dir] table.data[data-stack] :is(th, td):first-child {
    position: static; box-shadow: none; background: transparent; }
  /* An out-of-datacenter row is marked on the card as a whole. On the first
     cell the edge was drawn under that line's label, with the frozen
     column's hairlines beside it; the row is a block here, so it can carry
     the tint and the edge itself. */
  [dir] table.data[data-stack] tbody :is(tr[data-port-state="out"], tr.row--out) > :first-child {
    box-shadow: none; }
  table.data[data-stack] tbody :is(tr[data-port-state="out"], tr.row--out) {
    background: var(--warn-tint); box-shadow: inset 3px 0 0 var(--warn-fill); }
  [dir="rtl"] table.data[data-stack] tbody :is(tr[data-port-state="out"], tr.row--out) {
    box-shadow: inset -3px 0 0 var(--warn-fill); }
  /* A free port is marked the same way, with its accent edge on the card. */
  [dir] table.data[data-stack] tbody tr[data-port-state="unassigned"] > :first-child {
    box-shadow: none; }
  table.data[data-stack] tbody tr[data-port-state="unassigned"] {
    box-shadow: inset 3px 0 0 var(--accent-edge); }
  [dir="rtl"] table.data[data-stack] tbody tr[data-port-state="unassigned"] {
    box-shadow: inset -3px 0 0 var(--accent-edge); }
  /* [dir] so the cells outrank the linked-row hover above, which would paint
     the first line of the card in the neutral hover alone. */
  table.data[data-stack] tbody :is(tr[data-port-state="out"], tr.row--out):hover,
  [dir] table.data[data-stack] tbody :is(tr[data-port-state="out"], tr.row--out):hover > td {
    background: color-mix(in srgb, var(--warn-fill) 14%, var(--warn-tint)); }
}

/* Phone. Density tokens tighten rather than every rule being rewritten,
   which is the mechanism that replaces the inline font-size overrides that
   used to beat this media query and leave the "small" tiles larger than
   the "large" ones. */
@media (max-width: 620px) {
  :root { --cell-pad-x: var(--s-4); --card-pad: var(--s-5); }

  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-4); }
  .user-chip { padding-inline: var(--s-2); }
  .user-chip > span:not(.avatar) { display: none; }

  .page-header { flex-direction: column; align-items: stretch; }
  /* The wrapper, not the button. Most of the controls on the switch page
     are each inside their own <form>, which is a shrink-to-fit flex item,
     so `width: 100%` on the button resolves against the form's content
     width and does nothing — while a bare <a class="btn"> is a direct flex
     child and does go full width. That is why the action row rendered as
     two ragged rows of small buttons followed by one wide one. */
  .page-header .toolbar > *, .page-header .cluster > *, .page-header .row-actions > *,
  .form-actions > * { flex: 1 1 100%; }
  .page-header .btn, .form-actions .btn { inline-size: 100%; }

  .pager { flex-direction: column; align-items: stretch; }
  .pager__controls { justify-content: center; }
  /* The label takes the room beside its tick rather than wrapping to a line
     of its own and leaving the tick alone above it. Only a row that also
     holds a select wraps, so the select can have the full width. */
  .perm-row__label { flex: 1 1 0; min-inline-size: 0; }
  .perm-row:has(select) { flex-wrap: wrap; }
  .perm-row select { inline-size: 100%; }
  .perm-group__header { flex-direction: column; align-items: flex-start; }
  .rack__number { flex-basis: 32px; }
  .signin__card { padding: var(--s-7) var(--s-6); }
}

/* Below this the two-column tile grid stops helping. At 375px each track is
   171px, which after the padding and the 36px icon leaves 98px for a value
   set at 24px — so a capacity figure ran to four lines. The base rule's
   auto-fit already knows how to do this; it just had to stop being
   overridden. */
@media (max-width: 430px) {
  .tiles { grid-template-columns: 1fr; }
}

/* Orthogonal to width: a finger is not a mouse, and the question is the
   pointer rather than the viewport. A tablet with a finger at 900px and a
   phone need the same target; a small desktop window does not. Gating on
   width would leave 26px controls next to a per-row button that shuts a
   customer's port on exactly the device most likely to mis-tap it — and
   doing it this way is also what lets the desktop instrument stay
   genuinely dense. */
@media (pointer: coarse) {
  .btn, .btn--sm, .segmented > *, .pager__page, .pager__step,
  .pager__controls .btn, .sidebar__link, .check { min-block-size: var(--tap); }
  .btn--sm { padding-inline: var(--s-5); }

  /* The fields, which the first version of this block left out. A phone
     rendered a 32px search box beside a 44px submit button in the same
     toolbar, and the permission editors — the highest-consequence screens in
     the product, and grids of dozens of bare checkboxes — presented a 13px
     native tick inside a 32px row. A finger is not a mouse applies to the
     control being typed into as much as to the one being pressed.

     The tick selectors are written out per container because .check input,
     .choice__option input and .perm-row input each set inline-size: auto to
     stop the box stretching to the form width, and a bare element selector
     loses to all three. */
  input:not([type]), input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="date"], input[type="search"], input[type="url"],
  input[type="tel"], input[type="file"], select, textarea { min-block-size: var(--tap); }
  input[type="checkbox"], input[type="radio"],
  .check input[type="checkbox"], .check input[type="radio"],
  .choice__option input[type="checkbox"], .choice__option input[type="radio"],
  .perm-row input[type="checkbox"], .perm-row input[type="radio"] {
    inline-size: 24px; block-size: 24px; min-block-size: 24px;
  }
  .check input, .choice__option input,
  .perm-row input[type="checkbox"] { margin-block-start: calc((var(--tap) - 24px) / 2); }
  .perm-row, .choice__option { min-block-size: var(--tap); }

  /* Height alone is not a target. The page-number and prev/next cells keep a
     2.125rem inline floor whatever the pointer is, so they rendered 34px
     wide by 44px tall — under the 44px WCAG 2.5.5 asks of the controls most
     likely to be used one-handed. */
  .segmented > *, .pager__page, .pager__step { min-inline-size: var(--tap); }
}

/* ═══════════ Live list filters ═══════════ */

/* The list filters replace a region of the page as the operator types.
   Dimming it says the answer on screen is one keystroke out of date without
   moving anything — a spinner in place of the rows would make the list jump
   on every character, which is worse than a stale row for 200ms. */
[data-live-region].is-updating {
  opacity: .55;
  transition: opacity .12s;
}

@media (prefers-reduced-motion: reduce) {
  [data-live-region].is-updating { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   14. Depth and colour
   ═══════════════════════════════════════════════════════════════════════

   Everything above this line was measured. Contrast ratios, a neutral ramp
   held at one hue, six families each carrying a foreground that works on
   light and one that works on dark. None of that changes here, and nothing
   below overrides a colour that carries meaning.

   What this section adds is the part measurement does not give you: an
   interface that looks like somebody cared. Depth on the surfaces an
   operator spends the day on, a family colour per kind of equipment so a
   page announces what it is about before it is read, and a chart that looks
   like a chart rather than like two lines on graph paper.

   Two rules it keeps to.

   **Status colour is never decorative.** Green, amber and red mean up,
   watch and broken, and they are not spent on making a heading look nice.
   The families introduced here — violet, indigo, teal, fuchsia — carry no
   state at all, which is exactly why they are free to say "this is a rack".

   **Nothing here changes a contrast ratio.** Every tint below is a
   background behind text that already had a measured foreground, or a
   surface at the same lightness as the one it replaces. */

:root {
  /* Four families with no meaning attached, for saying which *kind* of
     thing a page is about. Deliberately not green/amber/red. Fuchsia is the
     services': they were amber, the warning hue, so the Services tile on the
     dashboard read as a warning about services. 600 holds 6.51:1 on white. */
  --violet-50: #f4f1fe;  --violet-100:#e8e2fd;  --violet-200:#d4c8fb;
  --violet-400:#8b6df0;  --violet-600:#6d43d4;  --violet-700:#5a34b8;

  --indigo-50: #eef1fe;  --indigo-100:#dee3fd;  --indigo-200:#c3ccfa;
  --indigo-400:#7b8ef0;  --indigo-600:#4a5fd4;  --indigo-700:#3c4db3;

  --teal-50:   #e9f7f5;  --teal-100:  #d0eeea;  --teal-200:  #a3ddd6;
  --teal-400:  #2ea79a;  --teal-600:  #12796f;  --teal-700:  #0e615a;

  --fuchsia-50:#fcf1fa;  --fuchsia-100:#f8e1f3; --fuchsia-200:#f0c4e7;
  --fuchsia-400:#d062b8; --fuchsia-600:#a3268e; --fuchsia-700:#861d74;

  /* What each kind of equipment is coloured as. Named for the thing rather
     than for the hue, so a page says `--kind-rack` and the decision about
     which colour that is lives here alone. */
  --kind-site:    var(--cyan-600);    --kind-site-tint:    var(--cyan-50);
  --kind-rack:    var(--violet-600);  --kind-rack-tint:    var(--violet-50);
  --kind-switch:  var(--blue-700);    --kind-switch-tint:  var(--blue-50);
  --kind-server:  var(--indigo-600);  --kind-server-tint:  var(--indigo-50);
  --kind-host:    var(--teal-600);    --kind-host-tint:    var(--teal-50);
  --kind-service: var(--fuchsia-600); --kind-service-tint: var(--fuchsia-50);

  /* The weight between medium and bold. It was being asked for by name in
     three places and resolving to nothing. */
  --fw-semibold: 600;

  /* A wash for the top of a card header. Barely there on purpose: enough to
     stop a stack of cards reading as one undifferentiated sheet, not enough
     to become a colour anybody notices. */
  --wash: linear-gradient(180deg, var(--surface-sunken) 0%, var(--surface) 100%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* The tints are rebuilt rather than reused. A #f4f1fe panel on a #161d26
       surface is a white rectangle; what the dark scheme needs is the same
       hue at the surface's own lightness. */
    --kind-site-tint:    #0d2e38;
    --kind-rack-tint:    #241a3d;
    --kind-switch-tint:  #18263c;
    --kind-server-tint:  #1a2144;
    --kind-host-tint:    #0d2f2c;
    --kind-service-tint: #321a2f;

    --kind-site:    var(--cyan-200);
    --kind-rack:    var(--violet-200);
    --kind-switch:  var(--blue-300);
    --kind-server:  var(--indigo-200);
    --kind-host:    var(--teal-200);
    --kind-service: var(--fuchsia-200);
  }
}

:root[data-theme="dark"] {
  --kind-site-tint:    #0d2e38;
  --kind-rack-tint:    #241a3d;
  --kind-switch-tint:  #18263c;
  --kind-server-tint:  #1a2144;
  --kind-host-tint:    #0d2f2c;
  --kind-service-tint: #321a2f;

  --kind-site:    var(--cyan-200);
  --kind-rack:    var(--violet-200);
  --kind-switch:  var(--blue-300);
  --kind-server:  var(--indigo-200);
  --kind-host:    var(--teal-200);
  --kind-service: var(--fuchsia-200);
}

/* ── Cards ──────────────────────────────────────────────────────────────
   A header that is a shade lighter than its body, and an optional hairline
   of family colour across the top. The strip is the whole trick: four cards
   down a page, each with a different one, and the eye finds the section it
   wants without reading a single heading. */
.card__header {
  background: var(--wash);
}

.card--kind { position: relative; }
.card--kind::before {
  content: "";
  position: absolute;
  inset-block-start: 0; inset-inline: 0;
  block-size: 3px;
  /* Strong at the start of the line and fading towards its end, in either
     reading direction; a 90deg strip put the strong end at the inline end of
     every Persian card. */
  background: linear-gradient(var(--to-inline-end), var(--kind, var(--accent)), transparent);
}

.card--site   { --kind: var(--kind-site); }
.card--rack   { --kind: var(--kind-rack); }
.card--switch { --kind: var(--kind-switch); }
.card--server { --kind: var(--kind-server); }
.card--host   { --kind: var(--kind-host); }

/* ── Tiles ──────────────────────────────────────────────────────────────
   The icon chip gets a gradient and a ring. It is the one element on a
   dashboard that is allowed to be decorative, because it carries no
   information a colour-blind operator could lose — the label beside it says
   the same thing in words. */
.tile__icon {
  background: linear-gradient(145deg, var(--accent-tint), var(--surface));
  box-shadow: inset 0 0 0 1px var(--accent-edge);
}
.tile__icon--ok,
.tile__icon--success { background: linear-gradient(145deg, var(--ok-tint), var(--surface)); box-shadow: inset 0 0 0 1px var(--ok-edge, var(--ok-tint)); }
.tile__icon--warning { background: linear-gradient(145deg, var(--warn-tint), var(--surface)); box-shadow: inset 0 0 0 1px var(--warn-edge, var(--warn-tint)); }
.tile__icon--danger  { background: linear-gradient(145deg, var(--danger-tint), var(--surface)); box-shadow: inset 0 0 0 1px var(--danger-edge, var(--danger-tint)); }
.tile__icon--info    { background: linear-gradient(145deg, var(--info-tint), var(--surface)); box-shadow: inset 0 0 0 1px var(--info-edge, var(--info-tint)); }
.tile__icon--accent  { background: linear-gradient(145deg, var(--accent-tint), var(--surface)); box-shadow: inset 0 0 0 1px var(--accent-edge, var(--accent-tint)); color: var(--accent); }

.tile__icon--site    { background: linear-gradient(145deg, var(--kind-site-tint), var(--surface));    color: var(--kind-site); }
.tile__icon--rack    { background: linear-gradient(145deg, var(--kind-rack-tint), var(--surface));    color: var(--kind-rack); }
.tile__icon--switch  { background: linear-gradient(145deg, var(--kind-switch-tint), var(--surface));  color: var(--kind-switch); }
.tile__icon--server  { background: linear-gradient(145deg, var(--kind-server-tint), var(--surface));  color: var(--kind-server); }
.tile__icon--host    { background: linear-gradient(145deg, var(--kind-host-tint), var(--surface));    color: var(--kind-host); }
.tile__icon--service { background: linear-gradient(145deg, var(--kind-service-tint), var(--surface)); color: var(--kind-service); }

/* A tile that is a link lifts a little further and picks up its family
   colour at the edge, so the thing under the pointer is unambiguous. */
a.tile:hover { box-shadow: var(--shadow); }

/* ── Tables ─────────────────────────────────────────────────────────────
   The header sticks, because an operator scrolling 350 ports loses the
   column names within one screen and then counts columns with a finger. */
/* No backdrop-filter. The header's background is opaque, so a filter behind
   it had nothing to filter and no name anybody could give it — but it did
   promote every sticky header in the product to its own compositing layer,
   which is exactly the condition the topbar note further down this file
   records as a reverted bug: Chrome composites the page in the wrong place
   after a scroll. The effect was invisible and the cost was the known
   defect, so it is removed rather than documented. */
.data thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  background: var(--surface-sunken);
}

/* The hover stays scoped to rows that lead somewhere, which is the rule
   section 9 states and argues for. Written blanket here — and again at the
   foot of the file — it lit every row of the audit log, where no cell is a
   link and nothing happens on click, which is exactly how an operator
   learns to disbelieve the panel's hover feedback on the tables where it
   does mean something. */
.data tbody tr { transition: background-color .12s; }
.data tbody tr:has(a[href]):hover,
.data[data-rows="linked"] tbody tr:hover { background: var(--surface-hover); }

/* ── Badges ─────────────────────────────────────────────────────────────
   A ring instead of a border, so a run of badges reads as a set of pills
   rather than as a row of boxes. */
.badge {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 22%, transparent);
}

/* ── The page header ────────────────────────────────────────────────────
   A quiet gradient behind the title, which is the one place on every page
   where a little presence costs nothing and is noticed. */
.page-header h1 {
  background: linear-gradient(120deg, var(--text) 0%, var(--text-2) 130%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Falls back to a plain colour where background-clip on text is not
   supported, rather than to invisible text. */
@supports not (background-clip: text) {
  .page-header h1 { color: var(--text); }
}

/* ── The chart ──────────────────────────────────────────────────────────
   The area fills are drawn by traffic-chart.js into gradients it defines;
   these are the parts that belong in a stylesheet. */
.chart__canvas .chart-grid { stroke: var(--line); opacity: .55; }
.chart__canvas .chart-line { fill: none; stroke-linecap: round; stroke-linejoin: round; }

@media (prefers-reduced-motion: no-preference) {
  .chart__canvas .chart-line { transition: opacity .15s; }
}

/* ── Presence ───────────────────────────────────────────────────────────
   The interface above is correct, legible and completely flat: white cards
   on a grey field, and nothing on the page has any weight. An operator
   spends their whole day here, and a customer is shown it before they buy.
   What follows gives the surfaces somewhere to sit.

   All of it is background and shadow. Not one rule below changes a text
   colour, so every ratio measured in section 1 still holds. */

body.operator {
  /* A field rather than a flat fill. Two stops, four per cent apart, so it
     reads as depth rather than as a gradient anybody notices. Scoped for the
     same reason the primary button is: the marketing site has its own. */
  background:
    radial-gradient(1200px 600px at 15% -10%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 70%),
    radial-gradient(900px 500px at 100% 0%, color-mix(in srgb, var(--kind-host, #12796f) 4%, transparent), transparent 65%),
    var(--bg);
  background-attachment: fixed;
}

/* A card's elevation, in one place. It was stated here and again at the
   foot of the file with different numbers; the later one won, so this pair
   of declarations described an appearance the product never had. The token
   is the survivor and the dark scheme re-points it, which is what the
   literals here could not do — a shadow written in --gray-900 is invisible
   on a graphite ground, so on the dark theme the cards were flat. */
.card { box-shadow: var(--shadow-card); }

/* The strip reads as part of the card rather than as a line drawn on it. */
.card--kind::before { block-size: 4px; opacity: .9; }

/* Tiles carry their family across the whole surface, not only in the icon.
   Four per cent is enough for the eye to group them and not enough to
   fight the number on top. */
.tile {
  background:
    linear-gradient(160deg, color-mix(in srgb, var(--tone, var(--accent)) 7%, var(--surface)) 0%, var(--surface) 60%);
}

.tile__icon {
  inline-size: 42px; block-size: 42px;
  border-radius: var(--radius-3);
}
.tile__icon svg { inline-size: 21px; block-size: 21px; }

.tile--server  { --tone: var(--kind-server); }
.tile--switch  { --tone: var(--kind-switch); }
.tile--site    { --tone: var(--kind-site); }
.tile--rack    { --tone: var(--kind-rack); }
.tile--host    { --tone: var(--kind-host); }
.tile--service { --tone: var(--kind-service); }
.tile--ok, .tile--success { --tone: var(--ok); }
.tile--warning { --tone: var(--warn); }
.tile--danger  { --tone: var(--danger); }
.tile--info    { --tone: var(--info); }
.tile--accent  { --tone: var(--accent); }

/* The value is the thing somebody came to read. */
.tile__value { letter-spacing: var(--track-tight); }

/* ── The shell ──────────────────────────────────────────────────────────
   The sidebar is where an operator's eye returns between every task, and
   it was a white rectangle. The active item now says which page you are on
   from the corner of the eye rather than only on inspection. */
.sidebar {
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-sunken) 100%);
}

/* ── Badges and meters ──────────────────────────────────────────────────
   A meter is a percentage somebody is going to act on, so it gets the one
   gradient in the interface that is genuinely functional: the bar changes
   colour along its own length, which makes "nearly full" visible before
   the number is read. */
/* The hatch is the top layer, the gradient beneath it, and the gradient runs
   towards the end of the line: the fill is anchored at the inline start, so
   its full-strength end is the edge that moves. */
.meter__fill,
.meter > span {
  background-image: var(--meter-texture, none), linear-gradient(var(--to-inline-end),
    color-mix(in srgb, currentColor 78%, transparent),
    currentColor);
  border-radius: var(--radius-pill);
}

/* ── Tables ─────────────────────────────────────────────────────────────
   Alternating rows, very faintly. At 350 ports the eye loses its line
   without them and an operator reads the wrong row's status. */
.data tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface-sunken) 55%, transparent); }
.data tbody tr:nth-child(even):has(a[href]):hover,
.data[data-rows="linked"] tbody tr:nth-child(even):hover { background: var(--surface-hover); }

/* ── Buttons ────────────────────────────────────────────────────────────
   There was a `.operator .btn--primary` here that repainted the primary
   action as an accent-blue gradient inside the panel and nowhere else, so
   Save was blue on Settings and near-black on the sign-in page, the portal
   and the marketing site — the same control, three appearances, decided by
   whichever layer was written last. It is gone. Section 1.5 of the design
   system is the one rule now, and it applies to every layer: the primary
   action is --ink everywhere, and the saturation budget belongs to the
   destructive control. */

/* ═══════════════════════════════════════════════════════════════════════
   Server command bar
   ═══════════════════════════════════════════════════════════════════════ */

.command-bar {
  display: grid;
  gap: var(--s-6);
  padding: var(--s-6);
  margin-block-end: var(--s-6);
  background: var(--surface);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-sm);
}
.command-bar__head {
  display: flex; flex-wrap: wrap; gap: var(--s-5);
  align-items: flex-start; justify-content: space-between;
}
.command-bar__head-actions {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  align-items: center;
}
.command-bar__title {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-3); font-weight: var(--fw-medium);
}
.command-bar__leds { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.power-led {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  min-block-size: 2.75rem;
}
.power-led__dot {
  inline-size: .75rem; block-size: .75rem; border-radius: 50%;
  background: var(--text-disabled);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-disabled) 25%, transparent);
}
.power-led--ok .power-led__dot {
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 28%, transparent);
}
.power-led--off .power-led__dot {
  background: var(--text-3);
}
.power-led--busy .power-led__dot {
  background: var(--accent);
  animation: ilo-pulse 1s ease-in-out infinite;
}
.power-led--down .power-led__dot {
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 28%, transparent);
}
.power-led__label {
  display: block; font-size: var(--fs-1); color: var(--text-3);
  text-transform: var(--caps-eyebrow); letter-spacing: var(--track-eyebrow);
}
.power-led strong { font-size: var(--fs-3); }

.port-chips {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s-3);
}
.port-chip {
  display: grid; gap: 2px;
  min-inline-size: 10rem; min-block-size: 2.75rem;
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
  text-decoration: none; color: inherit;
}
.port-chip:hover { background: var(--surface-hover); }
.port-chip--up { border-inline-start: 3px solid var(--ok); }
.port-chip--down { border-inline-start: 3px solid var(--text-disabled); }
.port-chip__name { font-weight: var(--fw-medium); }
.port-chip__meta, .port-chip__switch {
  font-size: var(--fs-1); color: var(--text-3);
}

.command-bar__gate, .command-bar__warn {
  margin: 0; font-size: var(--fs-2); color: var(--text-2);
}
.command-bar__form { display: grid; gap: var(--s-4); }
.power-actions {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-3);
}
.power-btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3);
  inline-size: 100%;
  min-block-size: 2.75rem; padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background: var(--surface);
  font: inherit; font-weight: var(--fw-medium); cursor: pointer;
  color: var(--text);
}
.power-btn:hover { background: var(--surface-hover); }
.power-btn svg { inline-size: 16px; block-size: 16px; }
/* Tokens rather than literals: the literal green-800 text measured 2.04:1
   on the dark scheme's surface, where every other colour on the row had
   been re-pointed and this one had not. */
.power-btn--on {
  background: var(--ok-tint);
  border-color: var(--ok-edge);
  color: var(--ok);
}
.power-btn[disabled] {
  cursor: not-allowed;
  background: var(--surface-sunken); border-color: var(--line);
  color: var(--text-disabled);
}
/* The same quiet danger as .btn--danger-quiet, expressed in this control's
   own geometry: neutral surface, danger label, danger boundary, filling
   under the pointer and under the focus ring. Force off and cycle are the
   two power actions that interrupt a running machine, and they now read the
   way every other destructive control in the product reads. */
.power-btn--danger {
  background: var(--surface);
  border-color: var(--danger-control);
  color: var(--danger);
}
.power-btn--danger:hover:not([disabled]),
.power-btn--danger:focus-visible:not([disabled]) {
  background: var(--danger); border-color: var(--danger); color: var(--text-invert);
}
@media (max-width: 40rem) {
  .power-actions { grid-template-columns: 1fr 1fr; }
}

/* A device panel: one thing the machine can be told to do, with its own
   heading, its own icon and its own state. They were loose rows under the
   power buttons, which put a disc drive and a console in the same visual
   sentence as "force off". */
.device-panel {
  display: grid; gap: var(--s-4);
  margin-block-start: var(--s-5);
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
}
.device-panel__head { display: flex; align-items: flex-start; gap: var(--s-4); }
.device-panel__icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2.25rem; block-size: 2.25rem; flex: 0 0 auto;
  border-radius: var(--radius-2);
  background: var(--surface);
  border: var(--hair) solid var(--line);
  color: var(--text-2);
}
.device-panel__icon svg { inline-size: 18px; block-size: 18px; }
.device-panel__title { margin: 0; font-size: var(--fs-3); }
.device-panel__state { margin: var(--s-1) 0 0; color: var(--text-2); }
.device-panel__controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.device-panel__controls--stack { flex-direction: column; align-items: stretch; }
.device-panel__form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); flex: 1 1 22rem; }
.device-panel__form select { flex: 1 1 16rem; min-inline-size: 0; }
.device-panel__form--iso {
  flex-direction: column;
  align-items: stretch;
  flex: 1 1 auto;
}
.device-panel__form-actions {
  display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center;
}
.device-panel__hint { margin: 0; color: var(--text-2); font-size: var(--fs-2); }

/* Media + console as a pair on wide viewports; stack on narrow. */
.device-panel-grid {
  display: grid;
  gap: var(--s-4);
  margin-block-start: var(--s-5);
  grid-template-columns: 1fr;
}
.device-panel-grid > .device-panel { margin-block-start: 0; min-inline-size: 0; }
@media (min-width: 56rem) {
  .device-panel-grid { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: stretch; }
}

/* ISO shelf: one selectable row per image, family + detail + size. */
.iso-shelf {
  margin: 0;
  padding: 0;
  border: 0;
  min-inline-size: 0;
}
.iso-shelf__toolbar {
  margin-block-end: var(--s-3);
}
.iso-shelf__query {
  inline-size: 100%;
  max-inline-size: none;
  min-block-size: 2.35rem;
  padding: 0.4rem 0.75rem;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-2);
}
.iso-shelf__lock-hint {
  margin: 0 0 var(--s-2);
  padding-inline: var(--s-2);
}
.iso-shelf--locked .iso-shelf__list {
  opacity: 0.55;
}
.iso-shelf--locked .iso-shelf__row {
  cursor: not-allowed;
}
.iso-shelf--locked .iso-shelf__row:hover { background: transparent; }
.iso-shelf__row.is-mounted {
  opacity: 1;
  background: color-mix(in srgb, var(--ok) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--ok) 40%, var(--line));
}
.iso-shelf__list {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-2);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface);
}
.iso-shelf__pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  margin-block-start: var(--s-3);
}
.iso-shelf__page-label {
  min-inline-size: 3.5rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  font-size: var(--fs-2);
}
.iso-shelf__page-btn { min-inline-size: 5.5rem; }
.iso-shelf__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: center;
  margin: 0;
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid transparent;
  border-radius: var(--radius-2);
  cursor: pointer;
  color: var(--text);
}
.iso-shelf__row[hidden] { display: none !important; }
.iso-shelf__row:hover { background: var(--surface-hover); }
.iso-shelf__row:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.iso-shelf__row input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.iso-shelf__radio {
  inline-size: 1rem;
  block-size: 1rem;
  border-radius: var(--radius-pill);
  border: 2px solid var(--line-strong, var(--line));
  background: var(--surface-raised);
  box-shadow: inset 0 0 0 0 var(--accent);
}
.iso-shelf__row:has(input:checked) .iso-shelf__radio {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 0.28rem var(--accent);
}
.iso-shelf__body { display: grid; gap: 0.15rem; min-inline-size: 0; }
.iso-shelf__family { font-size: var(--fs-3); line-height: 1.2; }
.iso-shelf__detail {
  color: var(--text-2);
  font-size: var(--fs-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.iso-shelf__size {
  font-size: var(--fs-2);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ilo-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-block: var(--s-5) var(--s-3);
}
.ilo-section-head h3 { margin: 0; }
.ilo-filter {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.ilo-filter__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.25rem 0.65rem;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--fs-2);
}
.ilo-filter__chip:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.ilo-filter__chip.is-active {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 50%, var(--line));
  color: var(--text);
  font-weight: var(--fw-medium);
}
.ilo-filter__chip--ok.is-active {
  background: color-mix(in srgb, var(--ok, #1b7f4a) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--ok, #1b7f4a) 45%, var(--line));
}
.ilo-filter__chip--failed.is-active {
  background: color-mix(in srgb, var(--danger, #b42318) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--danger, #b42318) 40%, var(--line));
}
.ilo-filter__n {
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}
a.badge { text-decoration: none; cursor: pointer; }
a.badge:hover { filter: brightness(0.96); }

.ilo-pager { margin-block: var(--s-3) var(--s-5); }

/* Controller log actions: three equal tiles in one row. */
.ilo-action-grid {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
}
@media (min-width: 48rem) {
  .ilo-action-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.ilo-action {
  display: grid;
  gap: var(--s-4);
  align-content: space-between;
  min-inline-size: 0;
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface-sunken);
}
.ilo-action__head { display: flex; align-items: flex-start; gap: var(--s-3); }
.ilo-action__icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2.25rem; block-size: 2.25rem; flex: 0 0 auto;
  border-radius: var(--radius-2);
  background: var(--surface);
  border: var(--hair) solid var(--line);
  color: var(--text-2);
}
.ilo-action__icon svg { inline-size: 18px; block-size: 18px; }
.ilo-action__title { margin: 0; font-size: var(--fs-3); }
.ilo-action__hint {
  margin: var(--s-1) 0 0;
  color: var(--text-2);
  font-size: var(--fs-2);
  line-height: 1.4;
}
.ilo-action--reset { --panel-tone: var(--amber-500); }
.ilo-action--danger { --panel-tone: var(--danger, #b42318); }
.ilo-action--reset .ilo-action__icon {
  color: color-mix(in srgb, var(--amber-500) 80%, var(--text));
  border-color: color-mix(in srgb, var(--amber-500) 35%, var(--line));
}
.ilo-action--danger .ilo-action__icon {
  color: color-mix(in srgb, var(--danger, #b42318) 75%, var(--text));
  border-color: color-mix(in srgb, var(--danger, #b42318) 30%, var(--line));
}
.ilo-action .btn { justify-self: start; }

/* Inside a card the panel is already framed, so it drops its own fill. */
.device-panel--inline { background: transparent; margin-block: var(--s-4); }
@media (max-width: 40rem) {
  .device-panel__form, .device-panel__controls { flex-direction: column; align-items: stretch; }
  .device-panel__form select, .device-panel__controls .btn { inline-size: 100%; }
  .device-panel__form-actions { flex-direction: column; align-items: stretch; }
  .device-panel__form-actions .btn { inline-size: 100%; }
  .ilo-action .btn { inline-size: 100%; justify-self: stretch; }
}

/* The link a settled console task hands back. */
.device-task__open {
  display: inline-flex; align-items: center; gap: var(--s-3);
  margin-block-start: var(--s-3);
  padding: var(--s-2) var(--s-4);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background: var(--surface);
  font-weight: var(--fw-medium);
  text-decoration: none;
}
.device-task__open svg { inline-size: 16px; block-size: 16px; }
.btn--ghost {
  background: transparent;
}

.ilo-loading {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-4);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
}
.ilo-spinner {
  inline-size: 1.25rem; block-size: 1.25rem;
  border: 2px solid var(--line);
  border-block-start-color: var(--accent);
  border-radius: 50%;
  animation: ilo-spin .6s linear infinite;
}
.ilo-skel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(7rem, 100%), 1fr));
  gap: var(--s-3);
}
.ilo-skel__chip {
  min-block-size: 4rem;
  border-radius: var(--radius-2);
  background: linear-gradient(90deg, var(--surface-sunken), var(--surface-hover), var(--surface-sunken));
  background-size: 200% 100%;
  animation: ilo-skel 1.2s ease-in-out infinite;
}
/* A chip row keeps its distance from the card under it. */
.ilo-chips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(7.5rem, 100%), 1fr));
  gap: var(--s-3);
  margin-block-end: var(--s-6);
}
.ilo-chip {
  display: grid; gap: 2px;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  text-decoration: none; color: inherit;
  min-block-size: 2.75rem;
}
.ilo-chip__count { font-size: var(--fs-5); font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }
.ilo-chip__label { font-size: var(--fs-1); color: var(--text-3); }
.ilo-chip--success { border-block-start: 3px solid var(--ok); }
.ilo-chip--info { border-block-start: 3px solid var(--info); }
.ilo-chip--warning { border-block-start: 3px solid var(--warn); }
.ilo-chip--danger { border-block-start: 3px solid var(--danger); }
.ilo-chip--neutral { border-block-start: 3px solid var(--line); }
.log-table { overflow-x: auto; }
.log-clear { display: grid; gap: var(--s-5); }
.log-clear__actions { gap: var(--s-4); margin-block-end: var(--s-2); }
.ilo-pager { margin-block: var(--s-3) var(--s-5); }

@keyframes ilo-spin { to { transform: rotate(360deg); } }
@keyframes ilo-pulse {
  50% { opacity: .45; }
}
@keyframes ilo-skel {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ilo-spinner, .ilo-skel__chip, .power-led--busy .power-led__dot { animation: none; }
}

.fex-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
  gap: var(--s-2);
}
.fex-jack {
  display: grid;
  place-items: center;
  min-block-size: 2.5rem;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface);
  font-size: var(--fs-1);
  font-variant-numeric: tabular-nums;
}
/* A used jack in the product's own success colour, which follows the dark
   theme and matches every other "in use" state on the page. */
.fex-jack--used {
  border-block-start: 3px solid var(--ok);
  background: var(--ok-tint);
}
.fex-jack--free {
  border-block-start: 3px solid var(--line);
}
.fex-jack--blocked {
  border-block-start: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
}
.fex-jack--unknown {
  border-block-start: 3px solid var(--line);
  background: var(--surface-sunken);
  color: var(--text-3);
}
.fex-jack--mine {
  border-block-start: 3px solid var(--accent);
  background: var(--accent-tint);
  font-weight: var(--fw-medium);
}
.fex-jack--mine_blocked {
  border-block-start: 3px solid var(--danger);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  font-weight: var(--fw-medium);
}
.fex-flow { min-inline-size: 0; display: grid; gap: var(--s-5); }
.fex-flow__rail { min-inline-size: 0; }
.fex-topo {
  min-inline-size: 0;
  padding: var(--s-5);
  border-radius: var(--radius-3);
  border: var(--hair) solid var(--line);
  background:
    radial-gradient(120% 80% at 12% 0%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 55%),
    linear-gradient(180deg, color-mix(in srgb, var(--surface-sunken) 70%, var(--surface)), var(--surface));
  overflow: hidden;
}
.fex-topo__stage {
  display: grid;
  gap: var(--s-4);
  justify-items: center;
}
.fex-topo__building {
  width: min(100%, 28rem);
  display: grid;
  gap: 0;
}
.fex-topo__roof {
  display: block;
  height: 0.85rem;
  margin-inline: 12%;
  border-radius: var(--radius-2) var(--radius-2) 0 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 55%, #1e293b), color-mix(in srgb, var(--accent) 25%, #334155));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent);
}
.fex-topo__hall {
  position: relative;
  padding: var(--s-4);
  border-radius: 0 0 var(--radius-3) var(--radius-3);
  border: var(--hair) solid var(--line);
  background:
    repeating-linear-gradient(
      90deg,
      color-mix(in srgb, var(--line) 55%, transparent) 0 1px,
      transparent 1px 2.4rem
    ),
    linear-gradient(180deg, color-mix(in srgb, #0f172a 6%, var(--surface)), var(--surface));
}
.fex-topo__glow {
  position: absolute;
  inset: auto 18% -30% 18%;
  height: 4rem;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 28%, transparent), transparent 70%);
  pointer-events: none;
}
.fex-topo__spine {
  width: 2px;
  height: 1.25rem;
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 50%, transparent), var(--accent));
  animation: fex-topo-pulse 2.4s ease-in-out infinite;
}
.fex-topo__rack {
  width: min(100%, 24rem);
  display: grid;
  grid-template-columns: 1.1rem 1fr;
  gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--radius-3);
  border: var(--hair) solid var(--line);
  background: color-mix(in srgb, var(--surface-sunken) 55%, var(--surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 4%, transparent);
}
.fex-topo__rails {
  display: grid;
  gap: 0.35rem;
  align-content: stretch;
}
.fex-topo__rails span {
  display: block;
  height: 0.35rem;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text-3) 35%, transparent);
}
.fex-topo__stack {
  display: grid;
  gap: var(--s-2);
  min-inline-size: 0;
}
.fex-topo__node {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: center;
  min-inline-size: 0;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-2);
  border: var(--hair) solid var(--line);
  background: var(--surface);
}
.fex-topo__node--dc {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
  box-shadow: 0 8px 24px -18px color-mix(in srgb, var(--accent) 60%, transparent);
}
.fex-topo__node--switch {
  border-inline-start: 3px solid color-mix(in srgb, #0ea5e9 70%, var(--line));
}
.fex-topo__node--server {
  border-inline-start: 3px solid var(--accent);
  background: color-mix(in srgb, var(--accent-tint) 55%, var(--surface));
}
.fex-topo__icon {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
  color: var(--text-2);
}
.fex-topo__node--dc .fex-topo__icon,
.fex-topo__node--server .fex-topo__icon {
  background: var(--accent-tint);
  color: var(--accent);
}
.fex-topo__kicker {
  display: block;
  font-size: var(--fs-1);
  color: var(--text-3);
  letter-spacing: var(--track-eyebrow);
  text-transform: var(--caps-eyebrow);
}
.fex-topo__node strong {
  display: block;
  min-inline-size: 0;
  overflow-wrap: anywhere;
}
@keyframes fex-topo-pulse {
  0%, 100% { opacity: 0.45; transform: scaleY(0.92); }
  50% { opacity: 1; transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
  .fex-topo__spine { animation: none; }
}
.fex-flow__unit {
  min-inline-size: 0;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface);
}
.fex-flow__unit--host {
  border-color: var(--accent-edge);
  box-shadow: inset 3px 0 0 var(--accent);
}
.fex-flow__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-inline-size: 0;
}
.fex-flow__head code {
  margin-inline-start: var(--s-2);
  padding: 1px 6px;
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
}
.fex-flow__face {
  min-inline-size: 0;
  overflow-x: auto;
}
.fex-flow .fex-grid {
  grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
}
.fex-flow .fex-jack { min-block-size: var(--tap); min-inline-size: var(--tap); }

.estate-tree {
  list-style: none;
  margin: 0;
  padding: 0;
  min-inline-size: 0;
}
.estate-tree ul {
  list-style: none;
  margin: var(--s-2) 0 0;
  padding-inline-start: var(--s-5);
  border-inline-start: var(--hair) solid var(--line);
}
.estate-tree__node { padding-block: var(--s-2); min-inline-size: 0; }
.estate-tree__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  min-inline-size: 0;
}
.estate-tree__label {
  margin-block-start: var(--s-3);
  font-size: var(--fs-2);
}

.toolbar--tight {
  gap: var(--s-2);
  margin-block-start: var(--s-3);
}
.toolbar--tight .toolbar__field {
  flex: 1 1 10rem;
  min-inline-size: 8rem;
}

.mac-lookup {
  display: grid;
  gap: var(--s-6);
  margin-block-end: var(--s-6);
}
@media (min-width: 60rem) {
  .mac-lookup { grid-template-columns: minmax(0, 1.6fr) minmax(16rem, 0.7fr); align-items: start; }
}
/* The aside is itself the card, so a grid gap on it opened a band between
   its header and its body; only the main column stacks its blocks. */
.mac-lookup__main { min-inline-size: 0; display: grid; gap: var(--s-6); align-content: start; }
.mac-lookup__aside { min-inline-size: 0; }
.mac-lookup__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--s-4);
}
.mac-lookup__bar .field { flex: 1 1 10rem; min-inline-size: 0; margin-block-end: 0; }
.mac-lookup__go { flex: 0 0 auto; }
.mac-lookup__recent {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  min-inline-size: 0;
  padding: var(--s-3) 0;
  border-block-end: var(--hair) solid var(--line);
  text-decoration: none;
  color: inherit;
}
.mac-lookup__recent:last-child { border-block-end: 0; }
.mac-lookup__recent:hover { color: var(--accent); }
.mac-lookup__recent.is-active { font-weight: var(--fw-medium); background: var(--surface-sunken); margin-inline: calc(var(--s-3) * -1); padding-inline: var(--s-3); border-radius: var(--radius-2); }
.mac-path {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
}
.mac-path__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  min-inline-size: 0;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface-sunken);
}
.mac-path__hop {
  display: grid;
  gap: 2px;
  min-inline-size: 0;
}
.mac-path__label {
  font-size: var(--fs-1);
  color: var(--text-3);
}
.mac-path__arrow.icon { color: var(--text-3); inline-size: 14px; block-size: 14px; }
.mac-lookup__raw { margin-block-start: var(--s-4); }
.mac-lookup__raw summary {
  cursor: pointer;
  font-size: var(--fs-2);
  color: var(--text-2);
}
.mac-lookup__hits {
  display: grid;
  gap: var(--s-6);
  margin-block-end: var(--s-6);
}
.mac-lookup__misses {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-3);
}
.mac-lookup__misses li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  min-inline-size: 0;
}

.acl-board { display: grid; gap: var(--s-5); }
.acl-port {
  min-inline-size: 0;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface-sunken);
}
.acl-port__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-block-end: var(--s-3);
}
.acl-block { display: grid; gap: var(--s-4); }
.acl-block--split {
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  align-items: start;
}
.acl-pane {
  display: grid;
  gap: var(--s-3);
  justify-items: start;
  min-inline-size: 0;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface);
}
.acl-pane--list { border-inline-start: 3px solid var(--accent); }
.acl-pane--groups { border-inline-start: 3px solid var(--line-control); }
.acl-pane__label {
  margin: 0;
  font-size: var(--fs-1);
  font-weight: var(--fw-semibold);
  color: var(--text-2);
  letter-spacing: var(--track-eyebrow);
  text-transform: var(--caps-eyebrow);
}
.acl-group { display: grid; gap: var(--s-3); justify-items: start; width: 100%; }
.acl-group + .acl-group {
  padding-block-start: var(--s-4);
  border-block-start: var(--hair) solid var(--line);
}
.acl-group__members {
  display: grid;
  gap: var(--s-3);
  width: 100%;
  padding-inline-start: var(--s-4);
  border-inline-start: 2px solid var(--line);
}
.acl-pane > .toolbar,
.acl-pane > .acl-composer,
.acl-group__members > .toolbar,
.acl-group__members > .acl-composer { justify-self: stretch; }
.acl-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  max-inline-size: 100%;
  min-block-size: var(--tap);
  padding-inline: var(--s-3);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface);
  white-space: nowrap;
}
.acl-chip--acl, .acl-chip--group { min-block-size: 2rem; }
.acl-chip--acl {
  border-color: var(--accent-edge);
  background: var(--accent-tint);
  color: var(--accent);
}
.acl-chip--group {
  background: var(--surface-sunken);
}
.acl-chip--addr { background: var(--surface); }
/* A line shown as the switch holds it, with nothing to press. */
.acl-chip--rule {
  border-style: dashed;
  background: var(--surface-sunken);
  color: var(--text-2);
}
.acl-chip--deny { border-color: var(--danger-edge); color: var(--danger); }
.acl-pane > .acl-entries { justify-self: stretch; }
.acl-chip__label {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.acl-chip__form { display: inline-flex; margin: 0; }
.acl-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--tap);
  min-block-size: var(--tap);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--danger);
  cursor: pointer;
}
.acl-chip__remove:hover,
.acl-chip__remove:focus-visible {
  color: var(--danger-strong);
  background: var(--danger-tint);
}
.acl-chip__remove .icon { inline-size: 14px; block-size: 14px; }

.net-board { display: grid; gap: var(--s-6); }
.net-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
  gap: var(--s-4);
}
.net-card {
  display: grid;
  gap: var(--s-3);
  min-inline-size: 0;
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface-sunken);
}
.net-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.net-card__name { min-inline-size: 0; }
.net-card__actions {
  justify-self: start;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.estate-board { display: grid; gap: var(--s-6); }
.estate-site__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-block-end: var(--s-3);
}
.estate-chip {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  max-inline-size: 100%;
  min-block-size: var(--tap);
  padding: var(--s-2) var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: inherit;
  text-decoration: none;
}
.estate-chip--link:hover { border-color: var(--accent-edge); background: var(--surface-hover); }
.estate-chip__name {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}


/* ---------------------------------------------------------------- overview

   What is wrong right now, above what the platform holds. When nothing is
   wrong the alarm list collapses to one quiet line, which is where it should
   spend most of its life — a panel that shouts every day is a panel nobody
   reads on the day it matters. */
/* The dashboard had one weight for three different jobs: the alarm panel,
   the four count tiles and the six state cards all arrived as bordered
   boxes of similar size, and the count tiles actually shouted loudest —
   their value was the 24–28px step while a live figure such as "failed
   today" sat two steps below it at 20–22px. So the page led with the number
   of servers the estate contains, which nobody asks twice, and whispered
   the number of jobs that failed this morning, which is asked every day.

   Nothing moves and nothing is regrouped below. Three tiers are simply made
   to look like three tiers:

     1  the alarm panel — what is wrong right now
     2  the state cards — the live figures, which carry the big numerals
     3  the count tiles — navigation with a number on it, flattened

   The quiet alarm line stays quiet, because it is the state this panel
   should spend most of its life in. */
.alarms {
  margin-block: var(--s-5) var(--s-7);
  padding: var(--s-5) var(--s-6);
  border: var(--hair) solid color-mix(in srgb, var(--danger) 30%, var(--line));
  border-radius: var(--radius-3);
  background: color-mix(in srgb, var(--danger) 5%, var(--surface));
}
/* A rail on the reading edge rather than a louder fill: the tint already
   says which family this is, and the panel has to gain presence without
   spending more of the red the port controls need. */
.alarms:not(.alarms--quiet) {
  border-inline-start: 3px solid var(--danger);
  box-shadow: var(--shadow-sm);
}
.alarms--quiet {
  display: flex; align-items: center; gap: var(--s-3);
  margin-block: var(--s-5) var(--s-6);
  padding: var(--s-4) var(--s-5);
  font-size: var(--fs-2);
  border-color: color-mix(in srgb, var(--ok) 28%, var(--line));
  background: color-mix(in srgb, var(--ok) 5%, var(--surface));
  color: var(--text-2);
}
.alarms--quiet svg { inline-size: 18px; block-size: 18px; color: var(--ok); }
.alarms__head { display: flex; align-items: center; gap: var(--s-3); margin-block-end: var(--s-4); }
.alarms__head strong { font-size: var(--fs-4); color: var(--text); }
.alarms__head svg { inline-size: 18px; block-size: 18px; color: var(--danger); }
.alarms__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.alarm { display: flex; align-items: baseline; gap: var(--s-3); }
.alarm__dot {
  inline-size: 8px; block-size: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--warn); translate: 0 -1px;
}
.alarm--danger .alarm__dot { background: var(--danger); }
.alarm a { color: inherit; }

/* A 20rem floor, so the six cards lay out three and three at 1440px with the
   sidebar beside them. At 17rem four fitted, and the second row was two
   cards and two empty tracks — three without the consoles card. */
.overview {
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  margin-block-end: var(--s-8);
}
.overview__card {
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  padding: var(--s-5) var(--s-5) var(--s-6);
  min-inline-size: 0;
}
/* The heading is separated from the figures by a hairline rather than by
   more space: six cards of this size need their titles to group as a band
   across the row, and a rule does that where a gap only makes the page
   taller. */
.overview__head {
  display: flex; align-items: center; gap: var(--s-3);
  margin-block-end: var(--s-5); padding-block-end: var(--s-4);
  border-block-end: var(--hair) solid var(--line);
}
.overview__head h2 { margin: 0; font-size: var(--fs-3); }
.overview__head svg { inline-size: 18px; block-size: 18px; color: var(--text-2); }
.overview__more { margin-inline-start: auto; display: inline-flex; align-items: center; min-block-size: 1.5rem; font-size: var(--fs-1); }
.overview__figures { display: flex; gap: var(--s-7); margin-block-end: var(--s-5); }
/* Tier two takes the numeral step the count tiles used to hold. A figure
   here is a live measurement — queued, failed today, disk — and it is the
   thing on this page worth reading from across a desk. */
.figure__value {
  font-size: var(--fs-7); font-weight: var(--fw-bold); line-height: var(--lh-tight);
  letter-spacing: var(--track-tight); font-variant-numeric: tabular-nums;
}
.figure__label { font-size: var(--fs-1); color: var(--text-2); }
.figure--ok .figure__value { color: var(--ok); }
.figure--warning .figure__value { color: var(--warn); }
.figure--danger .figure__value { color: var(--danger); }
.overview__rows { margin: 0; display: grid; gap: var(--s-2); }
.overview__rows > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-4);
}
.overview__rows dt { margin: 0; color: var(--text-2); min-inline-size: 0; flex: 1 1 auto; }
/* The value may shrink. Held at flex: 0 0 auto a row whose value is a
   sentence — "12 reached through it", under the proxy's badge — took its
   full measure and squeezed the term beside it until a SOCKS endpoint
   broke across two lines mid-address, inside a card wide enough to hold
   it. The value wraps first now, which is the one of the two that reads
   correctly wrapped. */
.overview__rows dd {
  margin: 0; text-align: end; font-variant-numeric: tabular-nums;
  flex: 0 1 auto; min-inline-size: 0;
}
/* A machine value has no spaces to break at, so an endpoint such as
   socks5://10.20.0.9:1080 set its own minimum width and ran out of the card
   on a phone. It may break anywhere inside a card; the full value is still
   the text, so copying it is unaffected. */
:is(.overview__card, .integration-card) .mono { overflow-wrap: anywhere; min-inline-size: 0; }
/* An endpoint under a proxy's name is shown whole or cut at its end, never
   broken at an arbitrary character inside the address; the full value is in
   its title, and on the proxies page. */
.overview__rows .mono { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; overflow-wrap: normal; }

/* ------------------------------------------------------------------- shelf

   The media shelf. Its header carried an undefined class, so the title, the
   address and two buttons stacked raw against each other with nothing
   arranging them — which is what "cramped and ugly" was.

   The facts under it are three numbers, and three numbers in a plain grid
   read as the start of a table rather than as a summary. They get a strip of
   their own, separated from the listing below it. */
.shelf__identity { display: flex; align-items: center; gap: var(--s-4); min-inline-size: 0; }
.shelf__badge {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2.5rem; block-size: 2.5rem; flex: 0 0 auto;
  border-radius: var(--radius-2);
  background: color-mix(in srgb, var(--accent, #2563eb) 10%, var(--surface));
  color: var(--accent, #2563eb);
}
.shelf__badge svg { inline-size: 20px; block-size: 20px; }
.shelf__url {
  margin: var(--s-1) 0 0; color: var(--text-2); font-size: var(--fs-1);
  overflow-wrap: anywhere;
}

.shelf__facts {
  display: grid; gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  margin-block-end: var(--s-5);
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
}
.shelf__fact { min-inline-size: 0; }
.shelf__fact-label {
  font-size: var(--fs-1); letter-spacing: var(--track-eyebrow); text-transform: var(--caps-eyebrow);
  color: var(--text-2); margin-block-end: var(--s-1);
}
.shelf__fact-value { font-size: var(--fs-4); font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }
.shelf__fact-of { color: var(--text-2); font-weight: var(--fw-regular); margin-inline-start: var(--s-2); }

/* The listing heading needs air above it; without it the table ran straight
   into the summary and the two read as one block. */
.shelf__section {
  margin-block: var(--s-5) var(--s-3);
  padding-block-start: var(--s-5);
  border-block-start: var(--hair) solid var(--line);
  font-size: var(--fs-3);
}
.shelf__section:first-child { margin-block-start: 0; padding-block-start: 0; border-block-start: 0; }

/* ------------------------------------------------------- server edit cards

   The edit page was five identical white cards with a bold line at the top
   of each, which gave an operator nothing to aim at: the controller
   credentials and the delete button looked the same from a metre away. Each
   section now carries its own tint and icon, so the page can be navigated by
   colour before it is read. */
.form-card__header {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-block-end: var(--hair) solid var(--line);
  background: color-mix(in srgb, var(--tone, var(--accent, #2563eb)) 6%, var(--surface));
}
.form-card__header .card__title { margin: 0; }
.form-card__icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2rem; block-size: 2rem; flex: 0 0 auto;
  border-radius: var(--radius-2);
  background: color-mix(in srgb, var(--tone, var(--accent, #2563eb)) 14%, var(--surface));
  color: var(--tone, var(--accent, #2563eb));
}
.form-card__icon svg { inline-size: 17px; block-size: 17px; }
/* A card that already declares a kind lends its colour to the header, so the
   stripe at the top and the header underneath it are one decision rather
   than two that disagree. */
.card--kind .form-card__header { --tone: var(--kind, var(--accent, #2563eb)); }
.form-card--identity { --tone: #2563eb; }
.form-card--controller { --tone: #7c3aed; }
.form-card--ports { --tone: #0891b2; }
.form-card--sensors { --tone: #059669; }
.form-card--danger { --tone: var(--danger); }

/* -------------------------------------------------------------- inline form

   A control, its button and its explanation on one line. These were raw —
   a full-width select with a button stuck under it and a bare label above,
   which is what every "attach a port / bind a sensor / action port" row
   looked like. */
.inline-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin-block-start: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  background: var(--surface-sunken);
}
.inline-form > label { font-weight: var(--fw-medium); color: var(--text-2); margin: 0; flex: 0 0 auto; }
.inline-form > select, .inline-form > input:not([type="hidden"]) {
  flex: 1 1 14rem; min-inline-size: 0; max-inline-size: 30rem;
}
.inline-form > .btn { flex: 0 0 auto; }
.inline-form + .hint { margin-block-start: var(--s-2); }

/* ================================================================ v0.8 look

   A visual refresh, not a redesign: the token system underneath — the
   ramps, the contrast arguments, the spacing scale — stands. What changes
   is the surface. Radii come up to modern weights, cards get a real
   elevation, the primary action gets depth, controls get a visible focus
   glow, and the three device panels each carry the colour of what they do.
   Everything routes through the existing tokens, so the dark scheme gets
   the same refresh for free.

   What used to open this block was a second :root and a second set of card
   and button rules, sitting three thousand lines below the ones they were
   overriding. The refresh is unchanged on screen; its four token values
   now live in the token layer and its card elevation in section 14, so
   there is one declaration of each and a reader who looks up a radius gets
   the number the browser actually uses.

   The primary button's gradient stayed where section 14 put it, scoped to
   `.operator`. Restated here unscoped it also reached the marketing site's
   call to action and turned the near-black it was designed as into accent
   blue — precisely the side effect section 14's own comment describes and
   scopes against. The panel's primary action is unchanged; the public
   page's is back to the colour it was drawn in. */

/* Focus is drawn by the base :focus-visible rule and the field rule beside
   the invalid state; neither is restated here. */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  border-radius: var(--radius-2);
  transition: border-color .12s, box-shadow .12s;
}
.acl-composer {
  display: grid;
  gap: var(--s-2);
  width: 100%;
  justify-self: stretch;
}
.acl-composer__label {
  margin: 0;
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
  color: var(--text-2);
}
.acl-composer__bar {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  min-block-size: 2.75rem;
  padding-block: 3px;
  padding-inline-start: var(--s-3);
  padding-inline-end: 3px;
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  transition: border-color .12s, box-shadow .12s;
}
.acl-composer__bar:focus-within {
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 4px var(--focus-halo);
}
.acl-composer__icon {
  display: grid;
  place-items: center;
  color: var(--text-3);
  flex: 0 0 auto;
}
.acl-composer__icon .icon { inline-size: 16px; block-size: 16px; }
.acl-composer__input {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: 0;
  margin: 0;
  padding: var(--s-2);
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  outline: none;
  font-variant-numeric: tabular-nums;
}
.acl-composer__bar:focus-within .acl-composer__input,
.acl-composer__input:hover,
.acl-composer__input:focus,
.acl-composer__input:focus-visible {
  border: 0;
  background: transparent;
  box-shadow: none;
  outline: none;
}
.acl-composer__go {
  flex: 0 0 auto;
  min-block-size: 2.25rem;
  padding-inline: var(--s-5);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--text-invert);
  font-size: var(--fs-2);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  white-space: nowrap;
}
.acl-composer__go:hover { filter: brightness(1.06); }
.acl-composer__go:focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: 2px;
}
.iso-shelf__query {
  border-radius: var(--radius-pill);
}

/* The table header's ground and the row transition are stated once, in
   section 14 beside the sticky-header rule that owns them. */

/* The sidebar's current page is a pill, not an underline. */
.sidebar__link { border-radius: var(--radius-2); margin-inline: var(--s-3); }
.sidebar__link.is-active, .sidebar__link[aria-current="page"] {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* Every icon inside an action button sits in a soft chip of that button's
   own colour, which is what makes a row of actions scannable before it is
   readable. currentColor keeps the chip agreeing with every variant and
   with the dark scheme. */
.device-panel__controls .btn svg {
  inline-size: 16px; block-size: 16px;
}

/* The three device panels carry the colour of what they do: the console is
   the accent, the disc drive is cyan, resetting the controller is amber.
   The icon grows into a small illustration rather than a glyph. */
.device-panel { border-radius: var(--radius-3); }
.device-panel__icon {
  inline-size: 2.75rem; block-size: 2.75rem;
  border: none;
  background: color-mix(in srgb, var(--panel-tone, var(--accent)) 12%, var(--surface));
  color: var(--panel-tone, var(--accent));
}
.device-panel__icon svg { inline-size: 22px; block-size: 22px; }
.device-panel--console { --panel-tone: var(--accent); }
.device-panel--media { --panel-tone: var(--cyan-500); }
.device-panel--reset { --panel-tone: var(--amber-500); }
/* The console panel's primary button used to restate a border mixed from
   the accent, to match a gradient declared beside it. Both are gone; the
   button takes the one primary appearance section 14 defines. */

/* In-page HTML5 console. Src is assigned after the task settles. */
.console-frame {
  margin-block-start: var(--s-4);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  overflow: hidden;
  background: #0b1017;
  color-scheme: dark;
}
.console-frame__toolbar {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  align-items: center; justify-content: space-between;
  padding: var(--s-3) var(--s-4);
  border-block-end: var(--hair) solid var(--line);
  background: var(--surface);
}
.console-frame__hint {
  margin: 0;
  flex: 1 1 12rem;
  color: var(--text-2);
  font-size: var(--fs-2);
}
.console-frame__actions {
  display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: flex-end;
}
.console-frame__viewport {
  display: block;
  inline-size: 100%;
  block-size: min(70vh, 48rem);
  border: 0;
  background: #0b1017;
  color-scheme: dark;
}
.console-frame__viewport[hidden] { display: none; }
:root[data-theme="light"] .console-frame,
:root[data-theme="light"] .console-frame__viewport {
  background: #111820;
}

/* Topbar: a hairline shadow so scrolling has a horizon. Blur was tried and
   reverted — backdrop-filter promotes the sticky bar to its own layer and
   Chrome composites the page in the wrong place after a scroll. */
.topbar { box-shadow: 0 1px 0 var(--line), 0 4px 12px -10px rgba(15, 23, 42, .25); }

/* ── Server estate board ─────────────────────────────────────────────── */
.estate-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  margin-block-end: var(--s-6);
}
.estate-toolbar__count {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
}
.estate-toolbar__num {
  font-size: var(--fs-7);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.estate-toolbar__label { color: var(--text-2); font-size: var(--fs-3); }
.estate-toolbar__search { flex: 1 1 16rem; max-inline-size: 28rem; margin-inline-start: auto; }


.estate-filter {
  display: grid;
  gap: var(--s-5);
  margin-block-end: var(--s-5);
  padding: var(--s-5) var(--s-6);
  background: var(--surface-raised);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
}
.estate-filter__sites {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
.estate-filter__status {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
.estate-filter__search {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--s-3);
  width: 100%;
  max-inline-size: 40rem;
}
.estate-filter__search .toolbar__field,
.estate-filter__search input[type="search"] {
  flex: 1 1 14rem;
  min-inline-size: 12rem;
  max-inline-size: none;
  min-block-size: 2.5rem;
  margin: 0;
}
.estate-filter__search .btn {
  flex: 0 0 auto;
  min-block-size: 2.5rem;
  padding-inline: 1.1rem;
  align-self: stretch;
}
.estate-toolbar--slim {
  padding-block: var(--s-3);
  margin-block-end: var(--s-5);
  border: none;
  background: transparent;
  box-shadow: none;
}
.estate-toolbar__scope {
  color: var(--text-2);
  font-size: var(--fs-3);
}

.server-board {
  display: grid;
  gap: var(--s-5);
  /* minmax(0, …), not 1fr: a bare 1fr has the widest card as its floor,
     and one long note in a card made every card on the board 790px wide,
     cut off at the edge of the page at every width. */
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1100px) {
  .server-board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* A column whose blocks sit at the top. As a grid, a card stretched to its
   neighbour's height spread the spare height over every row, so a short
   card's name sat lower than the one beside it; the footer takes the spare
   space instead and lines up along the row. */
.server-card {
  position: relative;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6);
  background: var(--surface-raised);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
}
.server-card:hover {
  border-color: var(--accent-edge);
  box-shadow: 0 8px 24px -18px rgba(15, 23, 42, .45);
}
.server-card--out,
.server-card--disabled {
  opacity: 0.55;
  background: color-mix(in srgb, var(--surface-sunken) 70%, var(--surface-raised));
}
.server-card--out:hover,
.server-card--disabled:hover {
  opacity: 0.72;
  box-shadow: none;
  transform: none;
}
.status-pill--muted {
  background: var(--surface-sunken);
  color: var(--text-2);
  border-color: var(--line);
}
@media (prefers-reduced-motion: reduce) {
  .server-card { transition: none; }
}
.server-card__hit {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
}
.server-card__head,
.server-card__facts,
.server-card__notes,
.server-card__foot { position: relative; z-index: 1; }
.server-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
}
.server-card__hostname {
  margin: 0;
  font-size: var(--fs-5);
  font-weight: var(--fw-bold);
  letter-spacing: var(--track-tight);
}
.server-card__hostname a {
  color: inherit;
  text-decoration: none;
}
.server-card__hostname a:hover { color: var(--accent); }
.server-card__serial {
  margin-block-start: 0.15rem;
  color: var(--text-2);
  font-size: var(--fs-2);
}
.server-card__leds {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: flex-end;
}
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.2em 0.65em;
  border-radius: var(--radius-pill);
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
  background: var(--surface-sunken);
  color: var(--text-2);
  border: var(--hair) solid var(--line);
}
.status-pill--ok {
  background: var(--ok-tint);
  color: var(--ok);
  border-color: transparent;
}
.status-pill--off {
  background: var(--surface-sunken);
  color: var(--text-2);
}
.server-card__facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4) var(--s-5);
  margin: 0;
}
.server-card__facts dt {
  margin: 0;
  font-size: var(--fs-2);
  color: var(--text-2);
  text-transform: var(--caps-eyebrow);
  letter-spacing: var(--track-eyebrow);
}
.server-card__facts dd {
  margin: 0.15rem 0 0;
  font-size: var(--fs-3);
  color: var(--ink);
  overflow-wrap: anywhere;
}
.server-card__notes { display: grid; gap: var(--s-2); }
.server-card__foot {
  margin-block-start: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block-start: var(--s-3);
  border-block-start: var(--hair) solid var(--line);
}
.server-board__pager { margin-block-start: var(--s-6); }

.mapping-note { display: grid; gap: 0.25rem; }
.mapping-note__summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
}
.mapping-note__summary::-webkit-details-marker { display: none; }
.mapping-note__body {
  font-size: var(--fs-2);
  color: var(--text-2);
  line-height: 1.45;
  max-inline-size: 42rem;
  padding: var(--s-3) var(--s-4);
  background: var(--warn-tint);
  border-radius: var(--radius-2);
  border: var(--hair) solid var(--line);
}
.mapping-note--info .mapping-note__body { background: var(--accent-tint); }

/* Server show: denser identity strip */
.server-hero {
  display: grid;
  gap: var(--s-5);
  padding: var(--s-6);
  margin-block-end: var(--s-6);
  background:
    linear-gradient(135deg, var(--accent-tint), transparent 55%),
    var(--surface-raised);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
}
.server-hero__top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-5);
}
.server-hero__title {
  margin: 0;
  font-size: clamp(1.4rem, 1.1rem + 1vw, 1.85rem);
  letter-spacing: var(--track-tight);
}
.server-hero__meta {
  margin: 0.35rem 0 0;
  color: var(--text-2);
  font-size: var(--fs-3);
}
.server-hero__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
  gap: var(--s-4);
  margin: 0;
}
.server-hero__facts dt {
  margin: 0;
  font-size: var(--fs-2);
  color: var(--text-2);
  text-transform: var(--caps-eyebrow);
  letter-spacing: var(--track-eyebrow);
}
.server-hero__facts dd {
  margin: 0.2rem 0 0;
  font-size: var(--fs-4);
  font-weight: var(--fw-medium);
}

/* ── Integration health on dashboard ─────────────────────────────────── */
.integration-grid {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: 1fr;
  margin-block-start: var(--s-5);
}
@media (min-width: 720px) {
  .integration-grid { grid-template-columns: 1fr 1fr; }
}
.integration-card {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-3);
  background: var(--surface-sunken);
}
.integration-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}

/* ── Site / port filter chips ────────────────────────────────────────── */
.site-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-pill);
  border: var(--hair) solid var(--line);
  background: var(--surface-raised);
  color: var(--ink);
  text-decoration: none;
  font-size: var(--fs-3);
  transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
}
.site-chip:hover { border-color: var(--accent-edge); color: var(--accent); }
/* The label on the chosen chip is --text-invert rather than white. The
   accent is a dark blue on the light theme and a light blue on the dark
   one, so a literal white held 8.2:1 in one scheme and 1.4:1 in the other
   — the site filter's current selection was unreadable on the dark theme
   and nothing else on the page said which site was in force. The token
   inverts with the scheme, which is the job it exists for. The count's
   plate is mixed from currentColor for the same reason. */
.site-chip.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-invert);
}
.site-chip.is-active .site-chip__count {
  background: color-mix(in srgb, currentColor 18%, transparent);
  color: inherit;
}
.site-chip__count {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-2);
  font-weight: var(--fw-bold);
  padding: 0.1em 0.45em;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-2);
}

.ports-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  inline-size: 100%;
}
.card__header--ports {
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s-5);
}
.card__header--ports .card__title { flex: 0 0 auto; }
.ports-filter__kinds {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
/* The search field and its button are one control and stay on one line:
   wrapping, the icon-only button dropped under the field it submits. The
   field gives up width instead, down to a few characters. */
.ports-filter__tools {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--s-3);
  min-inline-size: 0;
  margin-inline-start: auto;
}
.ports-filter__tools .toolbar__field { flex: 1 1 var(--w-sm); min-inline-size: var(--w-xs); max-inline-size: none; }
.ports-filter__locked {
  white-space: nowrap;
  font-size: var(--fs-3);
}

/* Hardware monitor: compact actions + silence until chooser */
.hw-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s-2);
  min-inline-size: 7.5rem;
}
.hw-actions .btn { inline-size: 100%; justify-content: center; }
.hw-probe { display: grid; gap: 2px; justify-items: start; }
.hw-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.hw-silence-choices {
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-2);
  padding: var(--s-4);
  margin: var(--s-5) 0;
  display: grid;
  gap: var(--s-3);
}
.hw-silence-choices legend { padding-inline: var(--s-2); }
.hw-silence-choice {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  padding: var(--s-3);
  border-radius: var(--radius-2);
  border: var(--hair) solid transparent;
  cursor: pointer;
}
.hw-silence-choice:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.hw-silence-choice input { margin-block-start: 0.2rem; }
.hw-silence-choice strong { display: block; }
.hw-silence-choice .meta { display: block; }
/* The GRE tunnel's own actions, under its figures. */
.gre-actions { margin-block-start: var(--s-4); }

/* ═══════════════════════════════════════════════════════════════════════
   Cycle history — used traffic per closed cycle
   ═══════════════════════════════════════════════════════════════════════

   The one chart in the product drawn on the server. The bars take the
   meter's fills, so a month over its allowance is the same red on this
   chart as on the bar that warned about it while it was open. Time runs
   left to right, oldest first, in both languages, as on the traffic chart:
   the plot is not mirrored on a right-to-left page. */
.cycle-chart { margin: 0 0 var(--s-5); }
.cycle-chart__plot { display: block; inline-size: 100%; block-size: 120px; overflow: visible; }
.cycle-chart__base { stroke: var(--line-strong); stroke-width: 1; }
.cycle-chart__bar { fill: var(--series-in); }
.cycle-chart__bar--over { fill: var(--danger-fill); }
.cycle-chart__limit { stroke: var(--text); stroke-width: 2; }
.cycle-chart__key .legend__swatch { display: inline-block; inline-size: 12px; block-size: 12px;
                                    border-radius: var(--radius-1); margin-inline-end: var(--s-3);
                                    background: currentColor; }
.cycle-chart__key--used { color: var(--series-in); }
.cycle-chart__key--over { color: var(--danger-fill); }
.cycle-chart__key--limit { color: var(--text); }

/* A reversed ledger row stays on the page and reads as struck through,
   because a grant that disappeared the moment it was undone would leave
   the next reader unable to tell it was ever given. */
.ledger-row--reversed td { color: var(--text-2); }
.ledger-row--reversed .ledger-amount { text-decoration: line-through; }
.ledger-reverse { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.ledger-reverse input { flex: 1 1 10rem; min-inline-size: 0; }

/* The customer's own figures on the portal service page. */
.portal-usage .meter { margin-block-start: var(--s-3); }
.portal-networks { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--s-3); }

/* The customer portal's bar: the mark and the portal's name at the start,
   three small links at the end, wrapping as a group under the brand on a
   phone. It was a .page-header, whose phone rules widen every control to a
   full row — right for a page's actions, wrong here: the mark became an
   accent-coloured bar across the screen and the three links stacked as
   three full-width rows above the page's own heading. */
.portal-bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-4) var(--s-5);
  margin-block-end: var(--s-7);
}
.portal-bar__brand { display: flex; align-items: center; gap: var(--s-4); min-inline-size: 0; }
.portal-bar__brand .brand__mark { flex: none; }
.portal-bar__text { min-inline-size: 0; line-height: var(--lh-snug); }
.portal-bar__title { font-size: var(--fs-4); font-weight: var(--fw-bold); }
.portal-bar__account { font-size: var(--fs-2); color: var(--text-2); overflow-wrap: anywhere; }
.portal-bar__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.portal-bar__actions > * { flex: none; }
.portal-bar__actions form { display: inline-flex; margin: 0; }
.portal-bar__actions .btn[aria-current="page"] { color: var(--text); background: var(--surface-hover); }

/* ═══════════ Touch targets declared after their components ═══════════ */

/* The coarse-pointer block near the forms comes before these components in
   the sheet, so their own minimum sizes outranked it and a phone got 24px
   to 38px targets: the account chip on every page, the estate and status
   chips, the import note toggles, the access-list submit and the list
   search. Declared last so the finger wins. */
@media (pointer: coarse) {
  .user-chip, .site-chip, .mac-lookup__recent, .mapping-note__summary,
  .acl-composer__go, .toolbar__field, .overview__more, .estate-site__head a,
  .faq__item > summary, .kv-row .num a,
  .estate-filter__search .toolbar__field,
  .estate-filter__search input[type="search"] { min-block-size: var(--tap); }
  .user-chip { min-inline-size: var(--tap); }
}

/* ═══ Page additions from the design and Persian pass (2.1.9) ═════════ */

/* administration */
/* ═══════════════════════════════════════════════════════════════════════
   Administration: settings sections, recovery codes, review buttons
   ═══════════════════════════════════════════════════════════════════════ */

/* The settings page names its sections in one row of links above the
   first card. It wraps like any toolbar, and keeps the cards' own rhythm
   below it. */
.settings-nav { margin-block-end: var(--s-6); }

/* A card reached by an in-page link lands below the sticky top bar rather
   than under it. */
.anchor-target { scroll-margin-block-start: calc(var(--topbar-h) + var(--s-4)); }

/* A heading inside a card body that splits one form into its parts
   (channels, then wording), one step below the card title. */
.card__subtitle { font-size: var(--fs-4); font-weight: var(--fw-medium); }

/* Recovery codes sit in the same row as their copy button, taking the
   room the code block used to take. */
.secret .recovery-codes { flex: 1 1 var(--w-lg); min-inline-size: 0; }

/* The review an operator already gave is the pressed one of the two
   buttons; the badge above says it in words as well. */
.review-actions .btn[aria-pressed="true"] {
  background: var(--accent-tint);
  border-color: var(--accent-edge);
  color: var(--accent);
  font-weight: var(--fw-medium);
}

/* An action in a page header that carries its explanation beneath it, so
   the sentence is seen on a touch screen rather than kept in a tooltip. It
   stays a column beside the title instead of taking a full measure. */
.page-header__aside { max-inline-size: var(--w-lg); }

/* bugs */
/* ═══════════════════════════════════════════════════════════════════════
   A header menu: one trigger and the actions behind it
   ═══════════════════════════════════════════════════════════════════════ */

/* Used where a page header had grown a row of equal buttons, so the one
   action the page is for stands alone and the rest sit behind one trigger.
   A native <details>: it opens with Enter or Space, needs no script, and
   its state is announced. The trigger is a .btn, so it keeps the button's
   focus ring and its 44px height under a coarse pointer. */
details.menu { position: relative; }
details.menu > summary { list-style: none; }
details.menu > summary::-webkit-details-marker { display: none; }
details.menu > summary .icon { transition: transform .15s; }
details.menu[open] > summary .icon { transform: rotate(180deg); }

.menu__panel {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + var(--s-2));
  z-index: 30;
  display: grid;
  gap: var(--s-1);
  min-inline-size: max(100%, var(--w-md));
  padding: var(--s-2);
  background: var(--surface);
  border: var(--hair) solid var(--line-strong);
  border-radius: var(--radius-2);
  box-shadow: var(--shadow-overlay);
}
.menu__panel > form { display: block; }
.menu__item { inline-size: 100%; justify-content: flex-start; text-align: start; }

@media (prefers-reduced-motion: reduce) {
  details.menu > summary .icon { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Chart figures in the reader's language
   ═══════════════════════════════════════════════════════════════════════ */

/* The chart's figures and its hover readout now carry a translated unit —
   «۱٫۲۳ گیگابیت بر ثانیه» — and the monospaced face they were set in has no
   Persian shaping, so the unit fell apart into isolated letters. A
   right-to-left page draws them in its own face; the figures stay tabular
   from the body. */
[dir="rtl"] .chart-figure__value,
[dir="rtl"] .chart__readout-value { font-family: inherit; }

/* infra-datacenters-racks-networks */
/* ------------------------------------------------------ delete card spacing

   A delete card sits under the form whose Save row it follows. .form-actions
   carries no block margin, so the card touched Save and Cancel and read as a
   third button in the same row. The gap is the one between two cards, which
   is what it is. Used by the datacenter, rack, network and switch forms. */
form + .form-card--danger { margin-block-start: var(--s-6); }

/* infra-switches-fex-ports */
/* ═══════════════════════════════════════════════════════════════════════
   Switches, FEX and ports
   ═══════════════════════════════════════════════════════════════════════ */

/* A form grid of at most three columns. The switch identity section and the
   FEX form hold nine fields each, and the plain .form-grid laid them out as
   two rows of four with the ninth alone under them. The column floor is the
   larger of --col and a third of the row, so the grid fits three where there
   is room and falls back to two and one exactly as .form-grid does. */
.form-grid--thirds {
  grid-template-columns: repeat(auto-fit, minmax(min(max(var(--col), calc((100% - 2 * var(--s-6)) / 3)), 100%), 1fr));
}

/* The port page's lock reason: a field of its own above the button row, at
   the width of a sentence rather than the width of the card. */
.port-actions__reason { max-inline-size: var(--w-lg); }

/* The cloud-host table's reasons, one to a line. */
.cloud-host-reasons {
  display: grid;
  gap: var(--s-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The FEX occupancy counts double as the key to the port grid: each chip
   carries a swatch drawn with the jack's own state class. */
.fex-legend {
  margin: 0;
  padding: 0;
  list-style: none;
}
.fex-swatch {
  display: inline-block;
  flex: 0 0 auto;
  inline-size: var(--s-5);
  block-size: var(--s-5);
  border-radius: var(--radius-1);
}
/* The jack's frame, at no specificity, so the state class on the same
   element (.fex-jack--used and the rest, declared earlier in the sheet)
   still draws its top edge and its fill. */
:where(.fex-swatch) {
  border: var(--hair) solid var(--line);
  background: var(--surface);
}


/* monitoring-hosts-hardware-proxies */
/* ═══════════════════════════════════════════════════════════════════════
   Hardware, hosts, monitoring sources and proxies
   ═══════════════════════════════════════════════════════════════════════ */

/* The hardware monitor's seven tiles are its view switcher now, so they sit
   in one row on a desktop: at the shared 180px floor a 1160px content area
   made six tracks and left "No controller" alone on a second row. The floor
   is the shared column less one step, so seven fit and a phone still gets
   two. */
.tiles.hw-views {
  grid-template-columns: repeat(auto-fit, minmax(min(calc(var(--col-narrow) - var(--s-9)), 100%), 1fr));
}

/* The view on display. The accent border alone is a colour change; the
   inset ring doubles its weight, so the chosen tile differs in shape too. */
.hw-views a.tile[aria-current="page"] {
  box-shadow: inset 0 0 0 var(--hair) var(--accent), var(--shadow-sm);
}

/* A credential this page cannot store: the field disabled, and the command
   that sets it as the worker written out beside it. */
.field > .secret-cli { margin-block: var(--s-3) 0; }
.secret-cli__body { min-inline-size: 0; flex: 1; }
.secret-cli__body > p { margin: 0 0 var(--s-3); }

/* A command is copied whole, so it scrolls rather than wraps: a wrapped
   line broke "dcmanage:host:credential" in the middle of the word. */
.secret-cli__command {
  margin: 0;
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--info-edge);
  border-radius: var(--radius-1);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-2);
  line-height: var(--lh-snug);
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start;
  white-space: pre;
  overflow-x: auto;
}
.secret-cli__command + .secret-cli__command { margin-block-start: var(--s-3); }
.secret-cli__command code { user-select: all; }

/* A monitoring account name on one line, cut short with the whole name on
   hover: broken mid-token it read as two names. */
.provider-user {
  max-inline-size: var(--w-lg);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The proxy setup guide, closed until asked for. The summary is the card's
   own header, so the guide reads as the same card whether open or shut. */
.proxy-setup > summary { cursor: pointer; list-style: none; }
.proxy-setup > summary::-webkit-details-marker { display: none; }
.proxy-setup:not([open]) > summary { border-block-end: 0; }
.proxy-setup > summary .icon { flex: 0 0 auto; transition: transform .15s; }
.proxy-setup[open] > summary .icon { transform: rotate(180deg); }
@media (pointer: coarse) {
  .proxy-setup > summary { min-block-size: var(--tap); }
}
@media (prefers-reduced-motion: reduce) {
  .proxy-setup > summary .icon { transition: none; }
}


/* operations */
/* ═══════════════════════════════════════════════════════════════════════
   Operations: the audit log, the operation log, the action queue and
   support reports
   ═══════════════════════════════════════════════════════════════════════ */

/* A moment in a table cell. Isolated, so a neighbouring word or figure
   cannot reorder it, but in the page's own direction rather than forced
   left to right: on a Persian page the date comes first, on the start side
   where the reader begins, and the audit, log and queue pages now read the
   same. The isolation stays on an inner span, so a stacked cell keeps its
   label on the start side. */
.when { unicode-bidi: isolate; }

/* The operation log's "needing attention" panel. A source and its counts
   are one line at a reading measure, rather than a name at one edge of a
   wide card and a number at the other. */
.log-attention { max-inline-size: var(--measure); }
.log-attention__counts { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s-3); }
@media (pointer: coarse) {
  .log-attention__counts a.badge { display: inline-flex; align-items: center; min-block-size: var(--tap); }
}

/* A log line's context, one key=value pair to a span: a long value breaks
   between pairs first and inside itself only when it must, instead of
   widening the Details column until the table scrolls. */
.log-context { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-4); }
.log-context > span { min-inline-size: 0; overflow-wrap: anywhere; }

/* The audit filter takes a dotted action key, typed left to right. Its
   placeholder is a Persian phrase on a Persian page, set right to left so
   it lines up with the toolbar it sits in. */
[dir="rtl"] .audit-action-filter::placeholder { direction: rtl; text-align: start; }

/* The support form's disclosure. The chevron is the marker, so the native
   one is hidden, and it turns when the panel is open; the admin pages do
   not load the public site's sheet, which is where these rules lived. */
.faq__item > summary { list-style: none; }
.faq__item > summary::-webkit-details-marker { display: none; }
.faq__item > summary .icon { flex: none; transition: transform .15s; }
.faq__item[open] > summary .icon { transform: rotate(180deg); }
@media (pointer: coarse) {
  .faq__item > summary { min-block-size: var(--tap); }
}

/* servers-services */
/* ═══════════════════════════════════════════════════════════════════════
   Servers and services
   ═══════════════════════════════════════════════════════════════════════ */

/* The server card's name is its one link now. The full-card overlay answers
   the pointer only, and the View button that repeated it is gone, so the
   machine icon that used to take a row of its own above the heading sits
   beside the hostname inside the link, and the link reaches the touch floor
   on a coarse pointer. The hostname itself is a <bdi dir="ltr">. */
.server-card__hostname a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  max-inline-size: 100%;
}
.server-card__hostname a > bdi { min-inline-size: 0; overflow-wrap: anywhere; }
.server-card__hostname .icon { color: var(--text-2); }
@media (pointer: coarse) {
  .server-card__hostname a { min-block-size: var(--tap); }
}

/* The labels under the cycle chart's bars run in the bars' order, left to
   right in both languages, one per bar and each the same share of the width
   as its bar, while each label's own words keep the page's direction. */
.cycle-chart__labels {
  display: flex;
  direction: ltr;
  margin-block: var(--s-2) 0;
  margin-inline: 0;
  padding: 0;
  list-style: none;
}
.cycle-chart__labels > li {
  flex: 1 1 0;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: var(--fs-1);
  line-height: var(--lh-snug);
  color: var(--text-2);
  text-align: center;
}
[dir="rtl"] .cycle-chart__labels > li { direction: rtl; }
.cycle-chart__labels > li > span {
  max-inline-size: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cycle-chart__used { color: var(--text); font-variant-numeric: tabular-nums; }
.cycle-chart__year { color: var(--text-3); }
.cycle-chart__unit { color: var(--text-3); }
/* A year of bars on a phone leaves each label a few characters: the month
   stays, and the figures, which the table under the chart carries in full,
   give way first. */
@container panel (max-width: 30rem) {
  .cycle-chart__used,
  .cycle-chart__unit { display: none; }
}

/* A file field in the page's language. The browser draws a file input's
   button and its "no file chosen" line in its own language, so the input is
   kept — focusable, named by the field's label — but drawn as nothing, and a
   button-styled label in the page's language and the chosen file's name are
   drawn in its place. Keyboard focus on the hidden input is shown on that
   button, with the same ring every other control uses. */
.filepick {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}
.filepick .filepick__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  min-block-size: 0;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.filepick .filepick__input:focus-visible + .filepick__button {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}
.filepick .filepick__input[aria-invalid="true"] + .filepick__button { border-color: var(--danger); }
.filepick__name {
  min-inline-size: 0;
  font-size: var(--fs-2);
  color: var(--text-2);
  overflow-wrap: anywhere;
}

/* shell-dashboard-public-portal */
/* The enforcement strip's own action: the pause or resume button sits at
   the end of the strip's line, with no frame of its own. It used
   .inline-form, whose border, sunken fill and top margin belong to a
   control row inside a card, so the button read as a second panel hung off
   the strip rather than the strip's button.
   Author: MAJID ISALOO */
.state-strip__action {
  margin-inline-start: auto;
  flex: none;
}

/* traffic-billing */
/* ── Traffic and billing pages (design and Persian pass) ────────────────────
   The evidence behind a reconciliation row, folded under its reason badges,
   and the customer sign-in address on the portal accounts page. Tokens and
   logical properties only. */

/* The ports behind a scope difference. Below the label of a stacked phone
   row rather than beside it, and set at the size of the meta lines around
   it: it is evidence for the badge above, not a heading of its own. */
.reconcile-evidence { clear: both; margin-block-start: var(--s-3); }
.reconcile-evidence > summary { font-size: var(--fs-2); color: var(--text-2); }
.reconcile-evidence > summary .icon { flex: 0 0 auto; }
.reconcile-evidence[open] > summary .icon { transform: rotate(180deg); }
.reconcile-evidence > p { margin-block: var(--s-3) 0; }
.reconcile-evidence__list {
  margin-block: var(--s-2) 0;
  padding-inline-start: var(--s-6);
  display: grid; gap: var(--s-2);
}

/* The sign-in address a customer is handed. It breaks after a slash or
   before a dot, where the markup offers a break, and only inside a segment
   when one segment alone is wider than the screen. */
.portal-url { margin-block-start: var(--s-4); }
.portal-url__value { overflow-wrap: anywhere; }

/* An error the platform could not put in the reader's words: one line in
   their language, and the message as it arrived one click away. The summary
   keeps the native full-width line, so beside a stacked row's floated label
   it wraps round the label rather than sliding under it. The message keeps
   its own direction and wraps inside the cell rather than widening it. */
.stored-error__original { margin-block-start: var(--s-2); }
.stored-error__original > summary { cursor: pointer; }
.stored-error__original > code {
  display: block;
  margin-block-start: var(--s-2);
  text-align: start;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
@media (pointer: coarse) {
  .stored-error__original > summary { min-block-size: var(--tap); align-content: center; }
}

/* A figure column whose cells hold a figure and a unit word: «۱۲۰٫۵۰ گیگابایت»,
   "120.50 GB". It takes the figure column's end alignment, as .num does,
   without .num's left-to-right direction, which reads a Persian unit before
   its figure. The unit is the same word on every row, so ending each line at
   the same edge lines the figures up on their last digit in either language.
   First used on the cycle history table (partials/cycle-history). */
table.data td.cell-figure,
table.data th.cell-figure { text-align: end; }


/* ═══════════════════════════════════════════════════════════════════════
   Frosted indigo — the platform's theme layer
   ═══════════════════════════════════════════════════════════════════════

   The WHMCS module and the platform are one product to the operator who
   uses both all day, and the module's Frosted indigo theme is the look the
   operator chose: an indigo primary, frosted glass over a pale mesh, one
   colour per section, a clearly larger type size (15px body, 30px page
   titles, nothing under 13px), the place you are lit indigo-to-violet, the
   one primary action in deep indigo ink and red kept for danger alone.

   The sidebar and the top bar are sticky, so they take no backdrop-filter:
   a blur on a sticky surface is the Chrome compositing bug recorded beside
   .topbar in this file. They use the dense --glass-bar fill instead, which
   reads as glass without filtering what scrolls under it.

   This block is appended to platform.css and changes no markup. It does two
   things, in order:

     F1–F4  re-points the platform's own tokens (layer two, the type ladder,
            space, radii, frame and density) and adds the theme's own, for
            the light scheme, the dark scheme and the fallbacks;
     F5–F11 reshapes the components with the same selectors the platform
            uses, so each rule wins by coming later rather than by weight;
            where a later sheet (site.css) or a heavier platform selector
            would win, the rule carries :root to be heard over it.

   Rules of the layer. Every colour is a token in F1 to F4; component rules
   read var() only. Sizes stay in rem with clamp() steps, as the platform's
   are. Direction comes from logical properties and from direction tokens
   (--to-inline-end, --lit-angle, --x-start, --x-end), so Persian mirrors
   without a second rule. Persian takes no tracking and no capitals. State
   keeps green, amber, red and cyan and every badge keeps its word; the
   section hues are decoration and never carry a state. backdrop-filter is
   used on the shell (sidebar, top bar, portal bar), cards, tiles, callouts,
   sheets and the sign-in card, never on table rows or a long list: a card
   that holds a table, and the server board, are dense sheets with no
   filter. Focus is an outline, which no shadow can cover. Fallbacks cover
   no backdrop-filter, reduced transparency, more contrast, reduced motion
   and forced colours.

   Author: MAJID ISALOO
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   F1. Tokens — the light scheme
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Brand: indigo, with violet for light and cyan at the far end ──── */
  --brand-50:  #eeefff;
  --brand-100: #e3e4ff;
  --brand-200: #c9cbfb;
  --brand-300: #a5b4fc;
  --brand-500: #5b53ee;
  --brand-600: #4f46e5;
  --brand-700: #4338ca;
  --brand-800: #3730a3;
  --brand-900: #2e2a7a;
  --brand-950: #231f5c;
  --white: #ffffff;
  --clear: rgba(255, 255, 255, 0);

  /* ── The canvas: a pale mesh and a rack-panel dot grid ─────────────── */
  --bd-1: #eef0fb;
  --bd-2: #f0f5fb;
  --bd-3: #f3f1fd;
  --blob-indigo: rgba(165, 180, 252, 0.50);
  --blob-teal:   rgba(153, 246, 228, 0.42);
  --blob-sky:    rgba(186, 230, 253, 0.55);
  --blob-violet: rgba(196, 181, 253, 0.40);
  --grid-dot:    rgba(67, 56, 202, 0.10);

  /* ── Glass: brighter at the top of a pane, clearer below it ─────────── */
  --glass-top:    rgba(255, 255, 255, 0.80);
  --glass-bottom: rgba(255, 255, 255, 0.62);
  --glass-strong: rgba(255, 255, 255, 0.88);
  --glass-soft:   rgba(255, 255, 255, 0.55);
  --glass-head:   rgba(255, 255, 255, 0.40);
  --glass-hover:  rgba(255, 255, 255, 0.84);
  --glass-dense:  rgba(255, 255, 255, 0.94);
  --glass-shell:  rgba(255, 255, 255, 0.70);
  --glass-shell-top: rgba(255, 255, 255, 0.86);
  /* A sticky bar has the page's own content scrolling under it, white
     screenshots included, so it is denser than the sidebar: its text must
     hold without the blur. */
  --glass-bar-top: rgba(255, 255, 255, 0.90);
  --glass-bar:     rgba(255, 255, 255, 0.82);
  --glass-edge:   rgba(255, 255, 255, 0.85);
  --glass-ring:   rgba(30, 27, 75, 0.08);
  --glass-blur:        blur(20px) saturate(165%);
  --glass-blur-strong: blur(26px) saturate(180%);
  --glass-fade: 18rem;

  /* Solid surfaces, for the fallbacks. */
  --solid-top:   #ffffff;
  --solid-pane:  #fbfbff;
  --solid-soft:  #f5f5fd;
  --solid-head:  #f7f7fe;
  --solid-base:  #f1f2fa;
  --solid-shell: #fbfbff;

  /* ── Controls ─────────────────────────────────────────────────────── */
  /* A button's face is one fill colour (the base) under one gradient. At
     night the base is clear, so the translucent gradient is laid once and
     not twice. */
  --btn-face-base:   #ffffff;
  --btn-face-top:    #ffffff;
  --btn-face-bottom: rgba(247, 247, 255, 0.92);
  --btn-face-hover:  #ffffff;
  --field:          #ffffff;
  --field-focus:     #ffffff;
  --track:           #e4e3f2;
  --tint-hover:  rgba(79, 70, 229, 0.07);
  --tint-active: rgba(79, 70, 229, 0.12);
  --tint-sunken: rgba(79, 70, 229, 0.045);
  --list-wash-a: rgba(238, 240, 255, 0.95);
  --list-wash-b: rgba(229, 247, 244, 0.85);
  --chip-ring:   rgba(255, 255, 255, 0.90);
  --neutral-tint: rgba(238, 240, 248, 0.92);

  /* The primary action: deep indigo ink. Its own tokens, because --ink is
     also read as a text colour (a server card's facts, a site chip). */
  --btn-ink-top:          #3730a3;
  --btn-ink-bottom:       #231f5c;
  --btn-ink-edge:         #231f5c;
  --btn-ink-hover-top:    #4338ca;
  --btn-ink-hover-bottom: #2e2a7a;
  --on-ink: #ffffff;

  /* Where you are: indigo to violet light. White on either end holds
     6.3:1 and 5.7:1. */
  --lit-a: #4f46e5;
  --lit-b: #7c3aed;
  --lit-c: #0891b2;
  --on-lit: #ffffff;
  --lit-chip: rgba(255, 255, 255, 0.22);
  --lit-veil: rgba(30, 27, 75, 0.30);

  /* ── Section hues (decoration, never a state) ─────────────────────────
     A glyph colour, the tint of its chip, and the two ends of a tile's top
     edge. One hue per section, the same on its navigation item and on any
     dashboard tile that opens it. Amber, green and red are not here. */
  --hue-indigo:  #4f46e5; --hue-indigo-tint:  #e7e6fd; --hue-indigo-a:  #4f46e5; --hue-indigo-b:  #0891b2;
  --hue-sky:     #0369a1; --hue-sky-tint:     #ddf0fd; --hue-sky-a:     #0284c7; --hue-sky-b:     #4f46e5;
  --hue-teal:    #0f766e; --hue-teal-tint:    #d5f5ee; --hue-teal-a:    #0d9488; --hue-teal-b:    #0284c7;
  --hue-violet:  #6d28d9; --hue-violet-tint:  #eee7fe; --hue-violet-a:  #7c3aed; --hue-violet-b:  #4f46e5;
  --hue-blue:    #1d4ed8; --hue-blue-tint:    #dfe8fe; --hue-blue-a:    #2563eb; --hue-blue-b:    #7c3aed;
  --hue-fuchsia: #a21caf; --hue-fuchsia-tint: #f8e6fc; --hue-fuchsia-a: #c026d3; --hue-fuchsia-b: #7c3aed;
  --hue-slate:   #475569; --hue-slate-tint:   #e5e9f0; --hue-slate-a:   #64748b; --hue-slate-b:   #4f46e5;
  --hue-cyan:    #0e7490; --hue-cyan-tint:    #d6f5fb; --hue-cyan-a:    #0891b2; --hue-cyan-b:    #4f46e5;

  /* State edges for a tile's top edge, from the fill to its brighter end. */
  --ok-edge-hi:     #22a35a;
  --warn-edge-hi:   #e08a12;
  --danger-edge-hi: #e5484d;
  --info-edge-hi:   #1592bd;

  /* A state tile's corner glow and chip fill, and the ring round a warning
     or danger tile. By day they are the badge tints and edges; at night
     they are bright hues at low alpha, so they glow like the section chips
     instead of laying an opaque brown or maroon square on the glass. */
  --ok-glow:     var(--ok-tint);
  --warn-glow:   var(--warn-tint);
  --danger-glow: var(--danger-tint);
  --info-glow:   var(--info-tint);
  --warn-ring:   var(--warn-edge);
  --danger-ring: var(--danger-edge);

  /* ── Depth ─────────────────────────────────────────────────────────── */
  --shadow-glass: 0 1px 2px rgba(30, 27, 75, 0.05), 0 8px 20px -6px rgba(30, 27, 75, 0.08), 0 26px 48px -22px rgba(67, 56, 202, 0.22);
  --shadow-lift:  0 2px 4px rgba(30, 27, 75, 0.06), 0 14px 30px -8px rgba(30, 27, 75, 0.14), 0 34px 60px -26px rgba(67, 56, 202, 0.32);
  --shadow-pill:  0 1px 2px rgba(30, 27, 75, 0.08), 0 4px 12px -4px rgba(67, 56, 202, 0.20);
  --shadow-lit:   0 10px 22px -8px rgba(99, 102, 241, 0.60);
  --shadow-ink:   0 1px 2px rgba(30, 27, 75, 0.20), 0 8px 18px -8px rgba(30, 27, 75, 0.50);
  --shadow-ink-hover: 0 1px 2px rgba(30, 27, 75, 0.20), 0 10px 22px -8px rgba(79, 70, 229, 0.55);
  --shadow-danger: 0 4px 12px -6px rgba(220, 38, 38, 0.35);
  --shadow-inset: inset 0 1px 2px rgba(30, 27, 75, 0.07);
  --shadow-shell: 0 1px 2px rgba(30, 27, 75, 0.04), 0 18px 40px -24px rgba(67, 56, 202, 0.30);
  --inner-shine:      inset 0 1px 0 rgba(255, 255, 255, 0.90);
  --inner-shine-dark: inset 0 1px 0 rgba(255, 255, 255, 0.22);

  /* ── Layer two, re-pointed ─────────────────────────────────────────────
     The surfaces stay solid. A sticky header cell, a frozen first column,
     a menu and a table sheet all lie over other content, and a translucent
     surface would show the rows beneath them. Glass is drawn by the
     component rules below with the --glass-* tokens instead. */
  --bg:             var(--bd-1);
  --surface:        #ffffff;
  --surface-raised: #fbfbff;
  --surface-sunken: #f4f5fe;
  --surface-hover:  #eef0fe;
  --surface-active: #e3e5fc;

  /* Lines are translucent: they never lie over one another, and a
     hairline that takes the hue of the glass under it reads as one family. */
  --line:         rgba(30, 27, 75, 0.10);
  --line-strong:  rgba(30, 27, 75, 0.16);
  --line-control: #787ca2;   /* 4.24:1 on white, 3.6:1 on the brightest glass */

  --text:          #1b1e3c;  /* 16.0:1 on white */
  --text-2:        #454a6c;  /*  8.6:1 on white, 7.9:1 on the hover tint */
  --text-3:        #454a6c;
  --text-disabled: #5d6386;  /*  6.0:1 */
  --text-invert:   #ffffff;

  --accent:        var(--brand-700);   /* 7.9:1 on white */
  --accent-strong: var(--brand-800);
  --accent-tint:   var(--brand-50);
  --accent-edge:   var(--brand-200);

  --danger:         #a8201a;  /* 7.4:1 */
  --danger-strong:  #861a15;
  --danger-tint:    #fdeaea;
  --danger-edge:    #f2bcb8;
  --danger-fill:    #b42828;
  --danger-control: #cf4444;  /* 4.6:1, the edge of a quiet destructive control */

  --ink:        var(--brand-900);
  --ink-hover:  var(--brand-800);
  --ink-invert: var(--white);

  --warn:      #8a4300;   /* 7.1:1 */
  --warn-tint: #fdf0d8;
  --warn-edge: #f1cd8e;
  --warn-fill: #a35f05;

  --ok:      #0b6b3a;     /* 6.9:1 */
  --ok-tint: #e3f5ea;
  --ok-edge: #a9dfbf;
  --ok-fill: #15803d;

  --info:      #0b6680;   /* 6.8:1 */
  --info-tint: #e0f3f9;
  --info-edge: #a3d9ea;
  --info-fill: #0e7fa8;

  --series-in:  var(--brand-600);
  --series-out: #c1760a;

  --meter-hatch: rgba(255, 255, 255, 0.5);

  --focus:      var(--brand-600);       /* 6.3:1 on white */
  --focus-halo: rgba(79, 70, 229, 0.20);

  --shadow-sm:      0 1px 2px rgba(30, 27, 75, 0.06);
  --shadow-card:    var(--shadow-glass);
  --shadow:         var(--shadow-lift);
  --shadow-lg:      0 4px 12px rgba(30, 27, 75, 0.08), 0 28px 56px -20px rgba(30, 27, 75, 0.30);
  --shadow-sticky:  0 1px 0 var(--line), 0 10px 24px -18px rgba(30, 27, 75, 0.40);
  --shadow-overlay: 0 24px 56px -16px rgba(30, 27, 75, 0.36), 0 2px 6px rgba(30, 27, 75, 0.08);
  --scrim:          rgba(30, 27, 75, 0.08);

  /* The platform's equipment families take the section hues, so a tile, a
     card strip and a navigation chip that mean the same thing agree. */
  --kind-site:    var(--hue-cyan);    --kind-site-tint:    var(--hue-cyan-tint);
  --kind-rack:    var(--hue-violet);  --kind-rack-tint:    var(--hue-violet-tint);
  --kind-switch:  var(--hue-sky);     --kind-switch-tint:  var(--hue-sky-tint);
  --kind-server:  var(--hue-blue);    --kind-server-tint:  var(--hue-blue-tint);
  --kind-host:    var(--hue-teal);    --kind-host-tint:    var(--hue-teal-tint);
  --kind-service: var(--hue-fuchsia); --kind-service-tint: var(--hue-fuchsia-tint);

  /* High-contrast members, pointed at by the prefers-contrast block. */
  --hc-line:         rgba(30, 27, 75, 0.24);
  --hc-line-strong:  rgba(30, 27, 75, 0.34);
  --hc-edge:         rgba(30, 27, 75, 0.26);
  --hc-text-2:       #343956;
  --hc-line-control: #5c6088;

  /* Forced colours: system colours only. */
  --fc-edge:   CanvasText;
  --fc-active: Highlight;
  --fc-active-text: HighlightText;
  --fc-canvas: Canvas;

  /* ── Type: larger and calmer ───────────────────────────────────────────
     15px body and controls, 17px card titles, a 26–30px page title, 28–34px
     figures, and nothing on screen below 13px. The steps keep their jobs. */
  --fs-1: 0.8125rem;    /* 13px   — eyebrow, column header, sidebar section */
  --fs-2: 0.875rem;     /* 14px   — meta line, badge, hint */
  --fs-3: 0.9375rem;    /* 15px   — body, table cell */
  --fs-4: 1.0625rem;    /* 17px   — card title */
  --fs-5: 1.25rem;      /* 20px   — a metric whose value is a word */
  --fs-6: clamp(1.625rem, 1.3rem + 0.9vw, 1.875rem);    /* 26–30px — page title */
  --fs-7: clamp(1.75rem, 1.4rem + 1vw, 2.125rem);       /* 28–34px — a figure */
  --fs-8: clamp(2rem, 1.3rem + 2.6vw, 2.75rem);         /* 32–44px — the public hero */
  --fs-control: 0.90625rem;   /* 14.5px — buttons, fields, navigation */
  --fs-cell: 0.90625rem;      /* 14.5px — a table cell, so a wide list still fits */
  --fs-lead: 1rem;            /* 16px   — the sentence under a page title */
  --lh-tight: 1.2;
  --lh-snug: 1.35;
  --lh-body: 1.6;
  --lh-figure: 1.1;           /* a tile's figure */
  --track-tight: -0.015em;
  /* No capitals and no tracking on labels, in either language. */
  --track-eyebrow: 0;
  --caps-eyebrow: none;

  /* ── Space, radius, frame ─────────────────────────────────────────── */
  --s-6: 1.25rem;   /* 20px */
  --s-7: 1.5rem;    /* 24px */
  --s-8: 2rem;      /* 32px */
  --s-9: 3rem;      /* 48px */
  --radius-1: 8px;
  --radius-2: 12px;
  --radius-3: 20px;
  --radius-4: 28px;
  --radius-chip: 10px;
  --radius-slip: 16px;

  --cell-pad-y: var(--s-5);
  --cell-pad-x: 1rem;        /* 16px: a wide list still fits its columns */
  --card-pad:   var(--s-6);
  --rail-pad-y: var(--s-5);
  --control-h:  2.5rem;      /* 40px */
  --control-sm: 2.25rem;     /* 36px */
  --chip:       1.875rem;    /* 30px — a navigation chip */
  --chip-lg:    2.625rem;    /* 42px — a tile chip */
  --sidebar-w:  16.5rem;     /* 264px */
  --topbar-h:   3.75rem;     /* 60px */
  --shell-inset: var(--s-5);

  /* ── Direction: gradients, glows, the mesh ───────────────────────────── */
  --lit-angle: 135deg;
  --mesh-angle: 165deg;
  --pane-angle: 170deg;      /* a tile's glass: bright at the start corner */
  --x-start: 0%;
  --x-end: 100%;
  --x-s1: 8%;
  --x-e1: 78%;
  --x-e2: 96%;
}

/* Persian: glyphs read smaller at one size and need more leading; rows keep
   a tighter leading than prose; the mesh, the lit gradient and every glow
   mirror. */
:root[dir="rtl"] {
  --fs-1: 0.84375rem;   /* 13.5px */
  --fs-2: 0.875rem;     /* 14px */
  --fs-3: 0.9375rem;    /* 15px */
  --fs-6: clamp(1.5rem, 1.2rem + 0.9vw, 1.75rem);       /* 24–28px */
  --fs-7: clamp(1.625rem, 1.3rem + 1vw, 2rem);          /* 26–32px */
  --fs-control: 0.9375rem;
  --fs-cell: 0.9375rem;
  --lh-tight: 1.35;
  --lh-snug: 1.5;
  --lh-body: 1.8;
  /* A figure can hold a word (۶۷۸٫۶۴ گیگابایت); if it ever wraps, the dots
     and marks of one line must clear the next. */
  --lh-figure: 1.3;
  --track-tight: 0;
  --track-eyebrow: 0;
  --caps-eyebrow: none;
  --lit-angle: 225deg;
  --mesh-angle: 195deg;
  --pane-angle: 190deg;
  --x-start: 100%;
  --x-end: 0%;
  --x-s1: 92%;
  --x-e1: 22%;
  --x-e2: 4%;
}

/* The comfortable density loosens the frame one notch further and steps
   the workhorse size up to 16px. */
:root[data-density="comfortable"] {
  --cell-pad-y: var(--s-6);
  --cell-pad-x: var(--s-6);
  --card-pad:   var(--s-7);
  --rail-pad-y: var(--s-6);
  --control-h:  2.75rem;
  --fs-3: 1rem;
  --fs-control: 0.96875rem;
  --fs-cell: 0.96875rem;
}
:root[dir="rtl"][data-density="comfortable"] { --fs-3: 1rem; --fs-control: 1rem; --fs-cell: 1rem; }

/* ═══════════════════════════════════════════════════════════════════════
   F2. Tokens — the dark scheme
   ═══════════════════════════════════════════════════════════════════════

   Not an inversion: the same idea at night. A deep indigo-navy mesh with
   the blobs turned to light, frosted panes of indigo glass over it, light
   ink, and hues that promote to their pale members. The two blocks are one
   body, as the platform's dark blocks are. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bd-1: #0e1130;
    --bd-2: #0c1430;
    --bd-3: #150f34;
    --blob-indigo: rgba(79, 70, 229, 0.40);
    --blob-teal:   rgba(13, 148, 136, 0.22);
    --blob-sky:    rgba(2, 132, 199, 0.20);
    --blob-violet: rgba(124, 58, 237, 0.32);
    --grid-dot:    rgba(165, 180, 252, 0.07);

    --glass-top:    rgba(38, 41, 94, 0.62);
    --glass-bottom: rgba(24, 27, 66, 0.56);
    --glass-strong: rgba(36, 39, 90, 0.80);
    --glass-soft:   rgba(255, 255, 255, 0.05);
    --glass-head:   rgba(255, 255, 255, 0.035);
    --glass-hover:  rgba(52, 56, 122, 0.70);
    --glass-dense:  rgba(22, 25, 58, 0.95);
    --glass-shell:  rgba(17, 19, 48, 0.62);
    --glass-shell-top: rgba(26, 29, 70, 0.72);
    --glass-bar-top: rgba(26, 29, 70, 0.92);
    --glass-bar:     rgba(17, 19, 48, 0.86);
    --glass-edge:   rgba(165, 180, 252, 0.16);
    --glass-ring:   rgba(0, 0, 0, 0.30);

    --solid-top:   #1c2048;
    --solid-pane:  #181c40;
    --solid-soft:  #1d2149;
    --solid-head:  #1a1e44;
    --solid-base:  #0f1230;
    --solid-shell: #151939;

    --btn-face-base:   var(--clear);
    --btn-face-top:    rgba(165, 180, 252, 0.14);
    --btn-face-bottom: rgba(165, 180, 252, 0.07);
    --btn-face-hover:  rgba(165, 180, 252, 0.20);
    --field:           #12152f;
    --field-focus:     #151937;
    --track:           #2a2f62;
    --tint-hover:  rgba(165, 180, 252, 0.09);
    --tint-active: rgba(165, 180, 252, 0.15);
    --tint-sunken: rgba(165, 180, 252, 0.05);
    --list-wash-a: rgba(99, 102, 241, 0.22);
    --list-wash-b: rgba(45, 212, 191, 0.10);
    --chip-ring:   rgba(255, 255, 255, 0.07);
    --neutral-tint: rgba(255, 255, 255, 0.07);

    /* Night ink: the primary turns to a pale indigo face with deep ink
       text, so it never reads as one more lit item beside the current page
       and the chosen chips. #1e1b4b on #c7d2fe holds 10.8:1. */
    --btn-ink-top:          #e0e3ff;
    --btn-ink-bottom:       #c7d2fe;
    --btn-ink-edge:         #eef0ff;
    --btn-ink-hover-top:    #ffffff;
    --btn-ink-hover-bottom: #dfe3ff;
    --on-ink: #1e1b4b;

    --lit-chip: rgba(255, 255, 255, 0.20);
    --lit-veil: rgba(10, 8, 40, 0.40);

    --hue-indigo:  #a5b4fc; --hue-indigo-tint:  rgba(99, 102, 241, 0.22);  --hue-indigo-a:  #6366f1; --hue-indigo-b:  #22d3ee;
    --hue-sky:     #7dd3fc; --hue-sky-tint:     rgba(14, 165, 233, 0.20);  --hue-sky-a:     #0ea5e9; --hue-sky-b:     #6366f1;
    --hue-teal:    #5eead4; --hue-teal-tint:    rgba(20, 184, 166, 0.20);  --hue-teal-a:    #14b8a6; --hue-teal-b:    #0ea5e9;
    --hue-violet:  #c4b5fd; --hue-violet-tint:  rgba(139, 92, 246, 0.24);  --hue-violet-a:  #8b5cf6; --hue-violet-b:  #6366f1;
    --hue-blue:    #93c5fd; --hue-blue-tint:    rgba(59, 130, 246, 0.22);  --hue-blue-a:    #3b82f6; --hue-blue-b:    #8b5cf6;
    --hue-fuchsia: #f0abfc; --hue-fuchsia-tint: rgba(217, 70, 239, 0.20);  --hue-fuchsia-a: #d946ef; --hue-fuchsia-b: #8b5cf6;
    --hue-slate:   #cbd5e1; --hue-slate-tint:   rgba(148, 163, 184, 0.20); --hue-slate-a:   #94a3b8; --hue-slate-b:   #6366f1;
    --hue-cyan:    #67e8f9; --hue-cyan-tint:    rgba(6, 182, 212, 0.20);   --hue-cyan-a:    #06b6d4; --hue-cyan-b:    #6366f1;

    --ok-edge-hi:     #4ade80;
    --warn-edge-hi:   #fbbf24;
    --danger-edge-hi: #f87171;
    --info-edge-hi:   #22d3ee;

    --ok-glow:     rgba(74, 222, 128, 0.14);
    --warn-glow:   rgba(251, 191, 36, 0.15);
    --danger-glow: rgba(248, 113, 113, 0.16);
    --info-glow:   rgba(34, 211, 238, 0.14);
    --warn-ring:   rgba(251, 191, 36, 0.32);
    --danger-ring: rgba(248, 113, 113, 0.34);

    --shadow-glass: 0 1px 2px rgba(0, 0, 0, 0.30), 0 18px 40px -18px rgba(0, 0, 0, 0.60), 0 26px 52px -26px rgba(79, 70, 229, 0.40);
    --shadow-lift:  0 2px 4px rgba(0, 0, 0, 0.35), 0 22px 44px -16px rgba(0, 0, 0, 0.65), 0 34px 60px -26px rgba(99, 102, 241, 0.50);
    --shadow-pill:  0 1px 2px rgba(0, 0, 0, 0.40), 0 6px 14px -6px rgba(99, 102, 241, 0.45);
    --shadow-lit:   0 10px 24px -8px rgba(124, 58, 237, 0.70);
    --shadow-ink:   0 1px 2px rgba(0, 0, 0, 0.40), 0 10px 22px -10px rgba(165, 180, 252, 0.45);
    --shadow-ink-hover: 0 1px 2px rgba(0, 0, 0, 0.40), 0 10px 22px -10px rgba(165, 180, 252, 0.55);
    --shadow-danger: 0 6px 16px -8px rgba(248, 113, 113, 0.45);
    --shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-shell: 0 1px 2px rgba(0, 0, 0, 0.30), 0 24px 48px -24px rgba(0, 0, 0, 0.70);
    --inner-shine:      inset 0 1px 0 rgba(255, 255, 255, 0.07);
    --inner-shine-dark: inset 0 1px 0 rgba(255, 255, 255, 0.18);

    --bg:             var(--bd-1);
    --surface:        #161a3a;
    --surface-raised: #1a1e42;
    --surface-sunken: #12152f;
    --surface-hover:  #232857;
    --surface-active: #2b3068;

    --line:         rgba(165, 180, 252, 0.13);
    --line-strong:  rgba(165, 180, 252, 0.21);
    --line-control: #7a80b5;   /* 4.3:1 on the surface */

    --text:          #eceefe;  /* 15.6:1 on the surface */
    --text-2:        #b4b8dc;  /*  9.0:1, 7.0:1 hovered, 6.2:1 active */
    --text-3:        #b4b8dc;
    --text-disabled: #8a8fbd;  /*  5.3:1 */
    --text-invert:   #0b0d22;

    --accent:        #a5b4fc;  /* 8.6:1 */
    --accent-strong: #c7d2fe;
    --accent-tint:   #232a63;
    --accent-edge:   #424a99;

    --danger:         #fca5a5;  /* 9.0:1 */
    --danger-strong:  #fecaca;
    --danger-tint:    #3a1626;
    --danger-edge:    #7a2a3c;
    --danger-fill:    #e5484d;
    --danger-control: #f87171;  /* 6.1:1 */

    --ink:        #e0e3ff;
    --ink-hover:  #ffffff;
    --ink-invert: #0b0d22;

    --warn:      #f6c76b;  /* 10.5:1 */
    --warn-tint: #33260f;
    --warn-edge: #6b4b17;
    --warn-fill: #e29a1f;

    --ok:      #74dca0;    /* 10.1:1 */
    --ok-tint: #0f2d22;
    --ok-edge: #1f5a3d;
    --ok-fill: #2fa261;

    --info:      #7dd3fc;  /* 10.0:1 */
    --info-tint: #0c2a3a;
    --info-edge: #1d4f66;
    --info-fill: #2b9cb6;

    --series-in:  #a5b4fc;
    --series-out: #e29a1f;

    --meter-hatch: rgba(255, 255, 255, 0.35);

    --focus:      #a5b4fc;
    --focus-halo: rgba(165, 180, 252, 0.30);

    --shadow-sm:      0 1px 2px rgba(0, 0, 0, 0.40);
    --shadow-lg:      0 4px 12px rgba(0, 0, 0, 0.50), 0 28px 56px -20px rgba(0, 0, 0, 0.75);
    --shadow-sticky:  0 1px 0 var(--line), 0 10px 24px -14px rgba(0, 0, 0, 0.80);
    --shadow-overlay: 0 24px 56px -16px rgba(0, 0, 0, 0.75), 0 2px 6px rgba(0, 0, 0, 0.40);
    --scrim:          rgba(0, 0, 0, 0.50);

    --state-observing-tint: var(--surface-sunken);
    --state-observing-edge: var(--line-strong);

    --kind-site:    var(--hue-cyan);    --kind-site-tint:    var(--hue-cyan-tint);
    --kind-rack:    var(--hue-violet);  --kind-rack-tint:    var(--hue-violet-tint);
    --kind-switch:  var(--hue-sky);     --kind-switch-tint:  var(--hue-sky-tint);
    --kind-server:  var(--hue-blue);    --kind-server-tint:  var(--hue-blue-tint);
    --kind-host:    var(--hue-teal);    --kind-host-tint:    var(--hue-teal-tint);
    --kind-service: var(--hue-fuchsia); --kind-service-tint: var(--hue-fuchsia-tint);

    --hc-line:         rgba(165, 180, 252, 0.30);
    --hc-line-strong:  rgba(165, 180, 252, 0.42);
    --hc-edge:         rgba(199, 210, 254, 0.40);
    --hc-text-2:       #d0d3f0;
    --hc-line-control: #9ca1d0;
  }
}

/* The identical body, for an explicit choice of dark. */
:root[data-theme="dark"] {
  --bd-1: #0e1130;
  --bd-2: #0c1430;
  --bd-3: #150f34;
  --blob-indigo: rgba(79, 70, 229, 0.40);
  --blob-teal:   rgba(13, 148, 136, 0.22);
  --blob-sky:    rgba(2, 132, 199, 0.20);
  --blob-violet: rgba(124, 58, 237, 0.32);
  --grid-dot:    rgba(165, 180, 252, 0.07);

  --glass-top:    rgba(38, 41, 94, 0.62);
  --glass-bottom: rgba(24, 27, 66, 0.56);
  --glass-strong: rgba(36, 39, 90, 0.80);
  --glass-soft:   rgba(255, 255, 255, 0.05);
  --glass-head:   rgba(255, 255, 255, 0.035);
  --glass-hover:  rgba(52, 56, 122, 0.70);
  --glass-dense:  rgba(22, 25, 58, 0.95);
  --glass-shell:  rgba(17, 19, 48, 0.62);
  --glass-shell-top: rgba(26, 29, 70, 0.72);
  --glass-bar-top: rgba(26, 29, 70, 0.92);
  --glass-bar:     rgba(17, 19, 48, 0.86);
  --glass-edge:   rgba(165, 180, 252, 0.16);
  --glass-ring:   rgba(0, 0, 0, 0.30);

  --solid-top:   #1c2048;
  --solid-pane:  #181c40;
  --solid-soft:  #1d2149;
  --solid-head:  #1a1e44;
  --solid-base:  #0f1230;
  --solid-shell: #151939;

  --btn-face-base:   var(--clear);
  --btn-face-top:    rgba(165, 180, 252, 0.14);
  --btn-face-bottom: rgba(165, 180, 252, 0.07);
  --btn-face-hover:  rgba(165, 180, 252, 0.20);
  --field:           #12152f;
  --field-focus:     #151937;
  --track:           #2a2f62;
  --tint-hover:  rgba(165, 180, 252, 0.09);
  --tint-active: rgba(165, 180, 252, 0.15);
  --tint-sunken: rgba(165, 180, 252, 0.05);
  --list-wash-a: rgba(99, 102, 241, 0.22);
  --list-wash-b: rgba(45, 212, 191, 0.10);
  --chip-ring:   rgba(255, 255, 255, 0.07);
  --neutral-tint: rgba(255, 255, 255, 0.07);

  --btn-ink-top:          #e0e3ff;
  --btn-ink-bottom:       #c7d2fe;
  --btn-ink-edge:         #eef0ff;
  --btn-ink-hover-top:    #ffffff;
  --btn-ink-hover-bottom: #dfe3ff;
  --on-ink: #1e1b4b;

  --lit-chip: rgba(255, 255, 255, 0.20);
  --lit-veil: rgba(10, 8, 40, 0.40);

  --hue-indigo:  #a5b4fc; --hue-indigo-tint:  rgba(99, 102, 241, 0.22);  --hue-indigo-a:  #6366f1; --hue-indigo-b:  #22d3ee;
  --hue-sky:     #7dd3fc; --hue-sky-tint:     rgba(14, 165, 233, 0.20);  --hue-sky-a:     #0ea5e9; --hue-sky-b:     #6366f1;
  --hue-teal:    #5eead4; --hue-teal-tint:    rgba(20, 184, 166, 0.20);  --hue-teal-a:    #14b8a6; --hue-teal-b:    #0ea5e9;
  --hue-violet:  #c4b5fd; --hue-violet-tint:  rgba(139, 92, 246, 0.24);  --hue-violet-a:  #8b5cf6; --hue-violet-b:  #6366f1;
  --hue-blue:    #93c5fd; --hue-blue-tint:    rgba(59, 130, 246, 0.22);  --hue-blue-a:    #3b82f6; --hue-blue-b:    #8b5cf6;
  --hue-fuchsia: #f0abfc; --hue-fuchsia-tint: rgba(217, 70, 239, 0.20);  --hue-fuchsia-a: #d946ef; --hue-fuchsia-b: #8b5cf6;
  --hue-slate:   #cbd5e1; --hue-slate-tint:   rgba(148, 163, 184, 0.20); --hue-slate-a:   #94a3b8; --hue-slate-b:   #6366f1;
  --hue-cyan:    #67e8f9; --hue-cyan-tint:    rgba(6, 182, 212, 0.20);   --hue-cyan-a:    #06b6d4; --hue-cyan-b:    #6366f1;

  --ok-edge-hi:     #4ade80;
  --warn-edge-hi:   #fbbf24;
  --danger-edge-hi: #f87171;
  --info-edge-hi:   #22d3ee;

  --ok-glow:     rgba(74, 222, 128, 0.14);
  --warn-glow:   rgba(251, 191, 36, 0.15);
  --danger-glow: rgba(248, 113, 113, 0.16);
  --info-glow:   rgba(34, 211, 238, 0.14);
  --warn-ring:   rgba(251, 191, 36, 0.32);
  --danger-ring: rgba(248, 113, 113, 0.34);

  --shadow-glass: 0 1px 2px rgba(0, 0, 0, 0.30), 0 18px 40px -18px rgba(0, 0, 0, 0.60), 0 26px 52px -26px rgba(79, 70, 229, 0.40);
  --shadow-lift:  0 2px 4px rgba(0, 0, 0, 0.35), 0 22px 44px -16px rgba(0, 0, 0, 0.65), 0 34px 60px -26px rgba(99, 102, 241, 0.50);
  --shadow-pill:  0 1px 2px rgba(0, 0, 0, 0.40), 0 6px 14px -6px rgba(99, 102, 241, 0.45);
  --shadow-lit:   0 10px 24px -8px rgba(124, 58, 237, 0.70);
  --shadow-ink:   0 1px 2px rgba(0, 0, 0, 0.40), 0 10px 22px -10px rgba(165, 180, 252, 0.45);
  --shadow-ink-hover: 0 1px 2px rgba(0, 0, 0, 0.40), 0 10px 22px -10px rgba(165, 180, 252, 0.55);
  --shadow-danger: 0 6px 16px -8px rgba(248, 113, 113, 0.45);
  --shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-shell: 0 1px 2px rgba(0, 0, 0, 0.30), 0 24px 48px -24px rgba(0, 0, 0, 0.70);
  --inner-shine:      inset 0 1px 0 rgba(255, 255, 255, 0.07);
  --inner-shine-dark: inset 0 1px 0 rgba(255, 255, 255, 0.18);

  --bg:             var(--bd-1);
  --surface:        #161a3a;
  --surface-raised: #1a1e42;
  --surface-sunken: #12152f;
  --surface-hover:  #232857;
  --surface-active: #2b3068;

  --line:         rgba(165, 180, 252, 0.13);
  --line-strong:  rgba(165, 180, 252, 0.21);
  --line-control: #7a80b5;

  --text:          #eceefe;
  --text-2:        #b4b8dc;
  --text-3:        #b4b8dc;
  --text-disabled: #8a8fbd;
  --text-invert:   #0b0d22;

  --accent:        #a5b4fc;
  --accent-strong: #c7d2fe;
  --accent-tint:   #232a63;
  --accent-edge:   #424a99;

  --danger:         #fca5a5;
  --danger-strong:  #fecaca;
  --danger-tint:    #3a1626;
  --danger-edge:    #7a2a3c;
  --danger-fill:    #e5484d;
  --danger-control: #f87171;

  --ink:        #e0e3ff;
  --ink-hover:  #ffffff;
  --ink-invert: #0b0d22;

  --warn:      #f6c76b;
  --warn-tint: #33260f;
  --warn-edge: #6b4b17;
  --warn-fill: #e29a1f;

  --ok:      #74dca0;
  --ok-tint: #0f2d22;
  --ok-edge: #1f5a3d;
  --ok-fill: #2fa261;

  --info:      #7dd3fc;
  --info-tint: #0c2a3a;
  --info-edge: #1d4f66;
  --info-fill: #2b9cb6;

  --series-in:  #a5b4fc;
  --series-out: #e29a1f;

  --meter-hatch: rgba(255, 255, 255, 0.35);

  --focus:      #a5b4fc;
  --focus-halo: rgba(165, 180, 252, 0.30);

  --shadow-sm:      0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow-lg:      0 4px 12px rgba(0, 0, 0, 0.50), 0 28px 56px -20px rgba(0, 0, 0, 0.75);
  --shadow-sticky:  0 1px 0 var(--line), 0 10px 24px -14px rgba(0, 0, 0, 0.80);
  --shadow-overlay: 0 24px 56px -16px rgba(0, 0, 0, 0.75), 0 2px 6px rgba(0, 0, 0, 0.40);
  --scrim:          rgba(0, 0, 0, 0.50);

  --state-observing-tint: var(--surface-sunken);
  --state-observing-edge: var(--line-strong);

  --kind-site:    var(--hue-cyan);    --kind-site-tint:    var(--hue-cyan-tint);
  --kind-rack:    var(--hue-violet);  --kind-rack-tint:    var(--hue-violet-tint);
  --kind-switch:  var(--hue-sky);     --kind-switch-tint:  var(--hue-sky-tint);
  --kind-server:  var(--hue-blue);    --kind-server-tint:  var(--hue-blue-tint);
  --kind-host:    var(--hue-teal);    --kind-host-tint:    var(--hue-teal-tint);
  --kind-service: var(--hue-fuchsia); --kind-service-tint: var(--hue-fuchsia-tint);

  --hc-line:         rgba(165, 180, 252, 0.30);
  --hc-line-strong:  rgba(165, 180, 252, 0.42);
  --hc-edge:         rgba(199, 210, 254, 0.40);
  --hc-text-2:       #d0d3f0;
  --hc-line-control: #9ca1d0;
}

/* ═══════════════════════════════════════════════════════════════════════
   F3. Tokens by width and pointer
   ═══════════════════════════════════════════════════════════════════════

   The platform re-points these at the same widths; the theme's values are
   restated after its own so the phone and tablet steps still apply. */

@media (max-width: 1100px) { :root { --sidebar-w: 15rem; } }
@media (max-width: 900px)  { :root { --topbar-h: 3.5rem; --shell-inset: var(--s-4); } }
@media (max-width: 620px)  {
  :root { --cell-pad-x: var(--s-5); --card-pad: 1rem; --fs-6: 1.625rem; --fs-7: 1.75rem; }
  :root[dir="rtl"] { --fs-6: 1.5rem; --fs-7: 1.625rem; }
}

/* ═══════════════════════════════════════════════════════════════════════
   F4. Token fallbacks
   ═══════════════════════════════════════════════════════════════════════

   Each block is written for both schemes at once: the selector list
   matches the root in every theme state with at least the weight of the
   scheme blocks above, and the values point at each scheme's own solid
   members, so one rule serves light and dark. */

/* No backdrop-filter: nothing frosts, so every pane becomes solid and the
   dot grid never shows through text. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  :root, :root:not([data-theme="light"]) {
    --glass-top: var(--solid-top);
    --glass-bottom: var(--solid-pane);
    --glass-strong: var(--solid-top);
    --glass-soft: var(--solid-soft);
    --glass-head: var(--solid-head);
    --glass-hover: var(--solid-top);
    --glass-shell: var(--solid-shell);
    --glass-shell-top: var(--solid-shell);
    --glass-bar: var(--solid-shell);
    --glass-bar-top: var(--solid-shell);
  }
}

/* Asked for less transparency: solid panes, no blur, a flat ground. */
@media (prefers-reduced-transparency: reduce) {
  :root, :root:not([data-theme="light"]) {
    --glass-top: var(--solid-top);
    --glass-bottom: var(--solid-pane);
    --glass-strong: var(--solid-top);
    --glass-soft: var(--solid-soft);
    --glass-head: var(--solid-head);
    --glass-hover: var(--solid-top);
    --glass-dense: var(--solid-top);
    --glass-shell: var(--solid-shell);
    --glass-shell-top: var(--solid-shell);
    --glass-bar: var(--solid-shell);
    --glass-bar-top: var(--solid-shell);
    --list-wash-a: var(--solid-head);
    --list-wash-b: var(--solid-head);
    --neutral-tint: var(--solid-soft);
    --glass-blur: none;
    --glass-blur-strong: none;
    --blob-indigo: var(--clear);
    --blob-teal: var(--clear);
    --blob-sky: var(--clear);
    --blob-violet: var(--clear);
    --grid-dot: var(--clear);
    --bd-1: var(--solid-base);
    --bd-2: var(--solid-base);
    --bd-3: var(--solid-base);
  }
}

/* Asked for more contrast: firmer hairlines and pane edges, darker (or, at
   night, lighter) secondary text, and solid panes with no blur over a
   ground with no blobs or dot grid, so no text depends on what lies
   behind the glass. */
@media (prefers-contrast: more) {
  :root, :root:not([data-theme="light"]) {
    --line: var(--hc-line);
    --line-strong: var(--hc-line-strong);
    --glass-edge: var(--hc-edge);
    --text-2: var(--hc-text-2);
    --text-3: var(--hc-text-2);
    --line-control: var(--hc-line-control);
    --glass-top: var(--solid-top);
    --glass-bottom: var(--solid-pane);
    --glass-strong: var(--solid-top);
    --glass-soft: var(--solid-soft);
    --glass-head: var(--solid-head);
    --glass-hover: var(--solid-top);
    --glass-dense: var(--solid-top);
    --glass-shell: var(--solid-shell);
    --glass-shell-top: var(--solid-shell);
    --glass-bar: var(--solid-shell);
    --glass-bar-top: var(--solid-shell);
    --glass-blur: none;
    --glass-blur-strong: none;
    --blob-indigo: var(--clear);
    --blob-teal: var(--clear);
    --blob-sky: var(--clear);
    --blob-violet: var(--clear);
    --grid-dot: var(--clear);
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   F5. The canvas
   ═══════════════════════════════════════════════════════════════════════

   The mesh is a fixed layer under the page rather than a fixed body
   background: a composited layer scrolls without repainting, and the glass
   always has the same colour field to frost. It mirrors in Persian through
   the direction tokens. The public site's own ground steps aside so it
   shows there too. */

body,
body.operator {
  background: var(--bg);
  font-size: var(--fs-3);
  line-height: var(--lh-body);
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--bd-1);
  background-image:
    radial-gradient(circle at 1px 1px, var(--grid-dot) 1px, var(--clear) 1.6px),
    radial-gradient(48rem 32rem at var(--x-start) 0%, var(--blob-indigo), var(--clear) 72%),
    radial-gradient(42rem 32rem at var(--x-end) 0%, var(--blob-teal), var(--clear) 72%),
    radial-gradient(40rem 34rem at var(--x-e1) 48%, var(--blob-sky), var(--clear) 72%),
    radial-gradient(44rem 36rem at var(--x-s1) 82%, var(--blob-violet), var(--clear) 72%),
    radial-gradient(40rem 32rem at var(--x-e2) 100%, var(--blob-teal), var(--clear) 72%),
    linear-gradient(var(--mesh-angle), var(--bd-1) 0%, var(--bd-2) 48%, var(--bd-3) 100%);
  background-size: 22px 22px, auto, auto, auto, auto, auto, auto;
  background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
}

:root .site { background: var(--clear); }

/* The scheme's own accent: indigo-700 by day, indigo-300 at night, where
   the light step would be a dim tick on a navy pane. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }

/* A control reached by keyboard is never left under the sticky top bar. */
html { scroll-padding-block-start: calc(var(--topbar-h) + var(--s-6)); }

/* ═══════════════════════════════════════════════════════════════════════
   F6. Shell: the glass sidebar, the coloured navigation, the top bar
   ═══════════════════════════════════════════════════════════════════════ */

.sidebar {
  background: linear-gradient(180deg, var(--glass-shell-top), var(--glass-shell));
  border-inline-end: var(--hair) solid var(--glass-edge);
  box-shadow: var(--shadow-shell), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}

/* Above the drawer width the sidebar floats on the mesh as a pane of its
   own, inset from the window, so the glass has an edge to be seen by. */
@media (min-width: 901px) {
  .sidebar {
    margin: var(--shell-inset);
    margin-inline-end: 0;
    inset-block-start: var(--shell-inset);
    block-size: calc(100vh - 2 * var(--shell-inset));
    block-size: calc(100dvh - 2 * var(--shell-inset));
    border: var(--hair) solid var(--glass-edge);
    border-radius: var(--radius-3);
  }
}

.brand {
  gap: var(--s-5);
  padding-block: var(--s-6) var(--s-5);
  border-block-end-color: var(--line);
}
.brand__mark,
.signin__brand .brand__mark {
  inline-size: 2.5rem; block-size: 2.5rem;
  border-radius: var(--radius-2);
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a) 0%, var(--lit-b) 58%, var(--lit-c) 120%);
  color: var(--on-lit);
  box-shadow: var(--shadow-lit), var(--inner-shine-dark);
}
.brand__mark svg { inline-size: 1.3125rem; block-size: 1.3125rem; }
.brand__name { font-size: var(--fs-4); letter-spacing: var(--track-tight); }
.brand__sub { font-size: var(--fs-1); }

.sidebar__nav { gap: var(--s-1); padding-block: var(--s-5) var(--s-6); }
.sidebar__section {
  padding: var(--s-6) var(--s-3) var(--s-3);
  font-size: var(--fs-1);
  font-weight: var(--fw-bold);
  color: var(--text-2);
}

/* Each section has its own colour chip; the page you are on is lit. The
   chip is the link's own icon, padded into a tile of its hue. */
.sidebar__link {
  --hue: var(--hue-indigo); --hue-tint: var(--hue-indigo-tint);
  gap: var(--s-4);
  min-block-size: 2.625rem;
  margin-inline: 0;
  padding-block: var(--s-2);
  padding-inline: var(--s-2) var(--s-5);
  border-radius: 14px;
  color: var(--text-2);
  font-size: var(--fs-control);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  transition: background-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
}
.sidebar__link > svg.icon {
  inline-size: var(--chip); block-size: var(--chip);
  padding: 0.40625rem;
  border-radius: var(--radius-chip);
  background: var(--hue-tint);
  color: var(--hue);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
  transition: background-color 160ms ease, color 160ms ease;
}
.sidebar__link:hover {
  background: var(--hue-tint);
  color: var(--text);
  text-decoration: none;
}
.sidebar__link:hover > svg.icon { background: var(--btn-face-hover); }
.sidebar__link:active { background: var(--hue-tint); }

:root .sidebar__link.is-active,
:root .sidebar__link[aria-current="page"],
:root .sidebar__link[aria-current="page"]:hover {
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a), var(--lit-b));
  color: var(--on-lit);
  font-weight: var(--fw-bold);
  box-shadow: var(--shadow-lit), inset 0 0 0 var(--hair) var(--lit-chip), var(--inner-shine-dark);
}
:root .sidebar__link.is-active > svg.icon,
:root .sidebar__link[aria-current="page"] > svg.icon {
  background: var(--lit-chip);
  color: var(--on-lit);
  box-shadow: inset 0 0 0 var(--hair) var(--lit-chip);
}

.sidebar__footer {
  padding-block: var(--s-5);
  border-block-start-color: var(--line);
  font-size: var(--fs-1);
}

/* The drawer's summary, under the drawer width. */
.nav-drawer > summary { border-radius: var(--radius-2); font-size: var(--fs-control); }

/* The top bar: a frosted strip that floats level with the sidebar while
   the page is at rest and rides the top edge once it scrolls. */
.topbar {
  margin-block-start: var(--shell-inset);
  margin-inline: var(--shell-inset);
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-bar-top), var(--glass-bar));
  box-shadow: var(--shadow-shell), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
.topbar__inner { padding-inline: var(--s-5); }
.crumbs { font-size: var(--fs-2); }
.crumbs > :last-child { font-weight: var(--fw-bold); }

/* The theme and density toggles are one-button tracks: drawn as square
   buttons of the bar's height and edge, so the row is one family with the
   language button and the user chip. A pressed toggle takes the accent
   edge. */
.topbar .segmented:has(> :only-child) {
  padding: 0;
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  box-shadow: var(--shadow-sm);
}
.topbar .segmented:has(> [aria-pressed="true"]:only-child) { border-color: var(--accent); }
.topbar .segmented > :only-child {
  min-block-size: calc(var(--control-h) - 2px);
  min-inline-size: calc(var(--control-h) - 2px);
  padding-inline: 0;
  border-radius: inherit;
}
.topbar .segmented > :only-child svg { inline-size: 1.0625rem; block-size: 1.0625rem; }
/* The bar's buttons (language, sign out) stand at the bar's control
   height, level with the toggles and the user chip. */
.topbar__actions .btn { min-block-size: var(--control-h); }
.topbar__actions .btn--icon { min-inline-size: var(--control-h); }

.user-chip {
  min-block-size: var(--control-h);
  padding-inline: var(--s-2) var(--s-5);
  border-color: var(--line-control);
  background: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-sm);
}
a.user-chip:hover { border-color: var(--brand-600); box-shadow: var(--shadow-pill); }
.avatar {
  inline-size: 1.875rem; block-size: 1.875rem;
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a), var(--lit-b));
  color: var(--on-lit);
  font-size: var(--fs-1);
  box-shadow: var(--inner-shine-dark);
}

/* The page head: a 26–30px title in solid ink, a 16px lead. */
.page-header { gap: var(--s-5) var(--s-6); margin-block-end: var(--s-7); }
.page-header h1 {
  font-size: var(--fs-6);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  background: none;
  color: var(--text);
  -webkit-text-fill-color: currentColor;
}
.page-header p { font-size: var(--fs-lead); line-height: var(--lh-body); }
.page-header .eyebrow,
.page-header .section__eyebrow { color: var(--accent); font-size: var(--fs-2); font-weight: var(--fw-bold); }
.content { padding-block: var(--s-7) var(--s-8); }

/* The customer portal's bar is the portal's shell: the same glass strip. */
.portal-bar {
  padding: var(--s-4) var(--s-5);
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-shell-top), var(--glass-shell));
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  box-shadow: var(--shadow-shell), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
.portal-bar__title { font-size: var(--fs-4); }
.portal-bar__actions .btn[aria-current="page"] { background: var(--tint-active); color: var(--accent); }

/* ═══════════════════════════════════════════════════════════════════════
   F7. Section hues
   ═══════════════════════════════════════════════════════════════════════

   One hue per section, set on the navigation item and on the dashboard
   tile, card strip or overview card that leads to the same place. State
   hues come after and outrank these (F8).

   An overview card is matched by its "View all" link where it has one, and
   otherwise by its heading icon, which is the same icon partial its
   section's navigation link draws (the globe for Services, the route for
   Proxies). Should an icon change, the card falls back to indigo. */

.tile, .overview__card, .card--kind {
  --hue: var(--hue-indigo); --hue-tint: var(--hue-indigo-tint);
  --edge-a: var(--hue-indigo-a); --edge-b: var(--hue-indigo-b);
}

.sidebar__link:is([href$="/admin/infrastructure/datacenters"], [href$="/admin/infrastructure/monitoring"], [href$="/admin/support"]),
.tile--site, .card--site {
  --hue: var(--hue-cyan); --hue-tint: var(--hue-cyan-tint);
  --edge-a: var(--hue-cyan-a); --edge-b: var(--hue-cyan-b);
}

.sidebar__link:is([href$="/admin/infrastructure/switches"]),
.tile--switch, .card--switch {
  --hue: var(--hue-sky); --hue-tint: var(--hue-sky-tint);
  --edge-a: var(--hue-sky-a); --edge-b: var(--hue-sky-b);
}

.sidebar__link:is([href$="/admin/infrastructure/hardware"], [href$="/admin/plans"], [href$="/admin/ai"]),
.overview__card:has(.overview__more[href$="/admin/infrastructure/hardware"]),
.tile--rack, .card--rack {
  --hue: var(--hue-violet); --hue-tint: var(--hue-violet-tint);
  --edge-a: var(--hue-violet-a); --edge-b: var(--hue-violet-b);
}

.sidebar__link:is([href$="/admin/network/proxies"], [href$="/admin/servers"], [href$="/admin/settings/schedule"], [href$="/admin/queue"], [href$="/admin/logs"]),
.overview__card:has(.overview__more[href$="/admin/queue"]),
.overview__card:has(> .overview__head > svg path[d^="M8.5 6H14"]),
.tile--server, .card--server {
  --hue: var(--hue-blue); --hue-tint: var(--hue-blue-tint);
  --edge-a: var(--hue-blue-a); --edge-b: var(--hue-blue-b);
}

.sidebar__link:is([href$="/admin/infrastructure/hosts"], [href$="/admin/allowances"], [href$="/admin/traffic/reconcile"], [href$="/admin/traffic/packages"]),
.tile--host, .card--host {
  --hue: var(--hue-teal); --hue-tint: var(--hue-teal-tint);
  --edge-a: var(--hue-teal-a); --edge-b: var(--hue-teal-b);
}

.sidebar__link:is([href$="/admin/services"], [href$="/admin/administrators"], [href$="/admin/roles"], [href$="/admin/portal-accounts"]),
.overview__card:has(> .overview__head > svg path[d="M12 2v20M2 12h20"]),
.tile--service, .card--service {
  --hue: var(--hue-fuchsia); --hue-tint: var(--hue-fuchsia-tint);
  --edge-a: var(--hue-fuchsia-a); --edge-b: var(--hue-fuchsia-b);
}

.sidebar__link:is([href$="/admin/infrastructure/iso"], [href$="/admin/api-keys"], [href$="/admin/security"], [href$="/admin/settings"], [href$="/admin/audit"]) {
  --hue: var(--hue-slate); --hue-tint: var(--hue-slate-tint);
  --edge-a: var(--hue-slate-a); --edge-b: var(--hue-slate-b);
}

/* ═══════════════════════════════════════════════════════════════════════
   F8. Surfaces: glass cards, tiles, the dashboard
   ═══════════════════════════════════════════════════════════════════════ */

.card {
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-top), var(--glass-bottom) var(--glass-fade));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-glass), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}

/* A card that holds a table or a long list (a role's permission groups,
   an ACL's ports, a MAC path), and a settings section, which runs to two
   screens of form, is read row by row: a dense, steady sheet with no
   filter over thousands of pixels. */
.card:has(table.data, .perm-group, .acl-port, .mac-path__row),
.card.anchor-target {
  background: var(--glass-dense);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.card__header {
  min-block-size: 4rem;
  gap: var(--s-4) var(--s-5);
  padding: var(--rail-pad-y) var(--card-pad);
  background: var(--glass-head);
  border-block-end: var(--hair) solid var(--line);
  font-size: var(--fs-4);
  font-weight: var(--fw-bold);
}
/* The header above a table carries the indigo-to-teal wash where the
   list's title and filters sit. */
.card:has(table.data) > .card__header {
  background-image: linear-gradient(var(--to-inline-end), var(--list-wash-a), var(--list-wash-b));
}
.card__header > h2, .card__title, .card__header > span:first-child {
  gap: var(--s-4);
  letter-spacing: var(--track-tight);
}
/* An icon inside a control in the header takes the control's colour. */
.card__header :is(.btn, a) svg { color: currentColor; }
.card__header > h2 > svg.icon,
.card__title > svg.icon,
.form-card__icon {
  inline-size: 2.125rem; block-size: 2.125rem;
  padding: 0.5rem;
  border-radius: var(--radius-chip);
  background: var(--hue-indigo-tint);
  color: var(--hue-indigo);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
}
.card--kind .card__header > h2 > svg.icon,
.card--kind .card__title > svg.icon { background: var(--hue-tint); color: var(--hue); }
.form-card__icon { padding: 0; }
.form-card__icon svg { inline-size: 1.125rem; block-size: 1.125rem; }
.form-card__header {
  min-block-size: 4rem;
  padding: var(--rail-pad-y) var(--card-pad);
  background: var(--glass-head);
  border-block-end-color: var(--line);
  font-size: var(--fs-4);
}
.card__header .btn, .card__header input, .card__header select { font-size: var(--fs-control); }
.card__header .badge, .card__header .count { font-size: var(--fs-2); }
/* On a washed header the count's own tint would match the wash it sits
   on, so there it is a solid pill with an accent edge. */
.card__header .count { background: var(--surface); box-shadow: inset 0 0 0 var(--hair) var(--accent-edge); }
.card__body { padding: var(--card-pad); }
.card__body > p { line-height: var(--lh-body); }
.card__subtitle { font-size: var(--fs-4); font-weight: var(--fw-bold); }

/* The coloured strip of a card that names a kind of equipment, in the
   card's own radius and running start to end. */
.card--kind::before {
  block-size: 3px;
  opacity: 1;
  background: linear-gradient(var(--to-inline-end), var(--edge-a), var(--edge-b));
}

/* ── Statistic tiles: glass with a 3px gradient top edge and a glow in the
   far corner, coloured by the section the tile opens or by its state. ── */
.tiles { gap: var(--s-5); }
.tile {
  --tile-pane: var(--glass-bottom);
  position: relative;
  gap: var(--s-5);
  padding: var(--s-6);
  border: var(--hair) solid var(--glass-edge);
  border-block-start-color: var(--clear);
  border-radius: var(--radius-3);
  background:
    linear-gradient(var(--to-inline-end), var(--edge-a), var(--edge-b)) top / 100% 3px no-repeat,
    radial-gradient(16rem 10rem at var(--x-end) 0%, var(--hue-tint), var(--clear) 70%),
    linear-gradient(var(--pane-angle), var(--glass-strong), var(--tile-pane));
  background-origin: border-box;
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--glass-ring);
  transition: transform 180ms ease, box-shadow 180ms ease;
}
a.tile:hover {
  --tile-pane: var(--glass-hover);
  transform: translateY(-2px);
  border-color: var(--glass-edge);
  border-block-start-color: var(--clear);
  box-shadow: var(--shadow-lift), 0 0 0 var(--hair) var(--glass-ring);
}
a.tile:active { transform: translateY(0); }
a.tile[aria-current="page"],
.hw-views a.tile[aria-current="page"] {
  border-color: var(--glass-edge);
  box-shadow: var(--shadow-glass), 0 0 0 2px var(--brand-600);
}

.tile__icon {
  inline-size: var(--chip-lg); block-size: var(--chip-lg);
  border-radius: var(--radius-slip);
  background: var(--hue-tint);
  color: var(--hue);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
}
.tile__icon svg { inline-size: 1.3125rem; block-size: 1.3125rem; }
.tile__label {
  font-size: var(--fs-control);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--text-2);
}
.tile__value {
  margin-block-start: var(--s-2);
  font-size: var(--fs-7);
  line-height: var(--lh-figure);
  letter-spacing: var(--track-tight);
  color: var(--text);
}
.tile__value--word { font-size: var(--fs-5); line-height: var(--lh-snug); }
/* The quieter half of a figure ("176 of 294") steps down to the word size,
   so the figure keeps one line in a four-up tile. */
.tile__value .faint { font-size: var(--fs-5); font-weight: var(--fw-medium); letter-spacing: normal; }
.tile__hint, .tile__stat { font-size: var(--fs-2); }

/* The dashboard's count tiles stay a step quieter than the live figures,
   and keep their glass and their colour. */
.tiles--counts .tile { padding: var(--s-5) var(--s-6); box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--glass-ring); }
.tiles--counts .tile__value { font-size: var(--fs-6); font-weight: var(--fw-bold); }
.tiles--counts a.tile:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift), 0 0 0 var(--hair) var(--glass-ring); }

/* State beats section: these outrank the hue rules in F7. The glow and
   the chip take the state's glow token, so at night they light the glass
   as the section chips do. */
.tile.tile--ok, .tile.tile--success, .tile__icon.tile__icon--ok, .tile__icon.tile__icon--success {
  --hue: var(--ok); --hue-tint: var(--ok-glow); --edge-a: var(--ok-fill); --edge-b: var(--ok-edge-hi);
}
.tile.tile--warning, .tile__icon.tile__icon--warning {
  --hue: var(--warn); --hue-tint: var(--warn-glow); --edge-a: var(--warn-fill); --edge-b: var(--warn-edge-hi);
}
.tile.tile--danger, .tile__icon.tile__icon--danger {
  --hue: var(--danger); --hue-tint: var(--danger-glow); --edge-a: var(--danger-fill); --edge-b: var(--danger-edge-hi);
}
.tile.tile--info, .tile__icon.tile__icon--info {
  --hue: var(--info); --hue-tint: var(--info-glow); --edge-a: var(--info-fill); --edge-b: var(--info-edge-hi);
}
.tile__icon.tile__icon--accent { --hue: var(--hue-indigo); --hue-tint: var(--hue-indigo-tint); }
.tile__icon:is(.tile__icon--ok, .tile__icon--success, .tile__icon--warning, .tile__icon--danger, .tile__icon--info, .tile__icon--accent,
               .tile__icon--site, .tile__icon--rack, .tile__icon--switch, .tile__icon--server, .tile__icon--host, .tile__icon--service) {
  background: var(--hue-tint);
  color: var(--hue);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
}
.tile.tile--danger { box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--danger-ring); }
.tile.tile--warning { box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--warn-ring); }

/* ── The dashboard's three tiers ─────────────────────────────────────── */
.overview { gap: var(--s-5); }
.overview__card {
  padding: var(--s-6) var(--s-6) var(--s-6);
  border: var(--hair) solid var(--glass-edge);
  border-block-start-color: var(--clear);
  border-radius: var(--radius-3);
  background:
    linear-gradient(var(--to-inline-end), var(--edge-a), var(--edge-b)) top / 100% 3px no-repeat,
    radial-gradient(18rem 10rem at var(--x-end) 0%, var(--hue-tint), var(--clear) 70%),
    linear-gradient(var(--pane-angle), var(--glass-strong), var(--glass-bottom));
  background-origin: border-box;
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--glass-ring);
}
.overview__head { gap: var(--s-4); border-block-end-color: var(--line); }
.overview__head h2 { font-size: var(--fs-4); letter-spacing: var(--track-tight); }
.overview__head svg {
  inline-size: 2.125rem; block-size: 2.125rem;
  padding: 0.5rem;
  border-radius: var(--radius-chip);
  background: var(--hue-tint);
  color: var(--hue);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
}
.overview__more { font-size: var(--fs-2); font-weight: var(--fw-medium); }
.figure__value { font-size: var(--fs-7); }
.figure__label { font-size: var(--fs-2); }
.overview__rows { gap: var(--s-3); }

/* What is wrong right now: a callout with a red rail. */
:root .alarms {
  border: var(--hair) solid var(--danger-edge);
  border-inline-start: 4px solid var(--danger-fill);
  border-radius: var(--radius-slip);
  background: linear-gradient(var(--to-inline-end), var(--danger-tint), var(--glass-strong));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-sm);
}
.alarms__head strong { font-size: var(--fs-4); }
.alarms.alarms--quiet {
  border-color: var(--ok-edge);
  border-inline-start: 4px solid var(--ok-fill);
  background: linear-gradient(var(--to-inline-end), var(--ok-tint), var(--glass-strong));
  font-size: var(--fs-3);
}

/* Smaller panels inside a card: soft glass with a hairline ring. */
.integration-card,
.device-panel,
.ilo-action,
.net-card,
.acl-port,
.mac-path__row,
.shelf__facts,
.inline-form {
  border-color: var(--line);
  background: var(--glass-soft);
  box-shadow: var(--inner-shine);
}
.integration-card, .device-panel, .ilo-action, .net-card, .acl-port { border-radius: var(--radius-slip); }

/* The server board: up to a hundred cards, so dense panes with no filter. */
.server-card {
  gap: var(--s-4);
  padding: var(--s-6);
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-dense), var(--glass-top));
  box-shadow: var(--shadow-glass), 0 0 0 var(--hair) var(--glass-ring);
}
.server-card:hover {
  border-color: var(--glass-edge);
  box-shadow: var(--shadow-lift), 0 0 0 var(--hair) var(--accent-edge);
}
.server-card__hostname { font-size: var(--fs-5); }
.server-card__hostname .icon {
  inline-size: 2.125rem; block-size: 2.125rem;
  padding: 0.5rem;
  border-radius: var(--radius-chip);
  background: var(--kind-server-tint);
  color: var(--kind-server);
}
.server-card__facts dt { font-size: var(--fs-1); font-weight: var(--fw-medium); }
.server-card__facts dd { font-size: var(--fs-3); }
.server-card__foot { border-block-start-color: var(--line); }

/* One panel above a board or a page: frosted, like a card. */
.estate-filter,
.command-bar,
.server-hero,
.form-actions--bar,
.pager {
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-top), var(--glass-bottom) var(--glass-fade));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-glass), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
.server-hero {
  background:
    radial-gradient(28rem 14rem at var(--x-start) 0%, var(--hue-indigo-tint), var(--clear) 70%),
    linear-gradient(180deg, var(--glass-top), var(--glass-bottom) var(--glass-fade));
  background-origin: border-box;
}
.server-hero__title { font-size: var(--fs-6); }
.server-hero__facts dt { font-size: var(--fs-1); font-weight: var(--fw-medium); }
.server-hero__facts dd { font-size: var(--fs-4); }
.estate-toolbar__num { font-size: var(--fs-7); }
.card .pager {
  border: 0;
  border-block-start: var(--hair) solid var(--line);
  border-radius: 0;
  background: var(--glass-head);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}

.kv-row { padding-block: var(--s-4); border-block-end-color: var(--line); }

/* ═══════════════════════════════════════════════════════════════════════
   F9. Controls, data, messages
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Buttons ─────────────────────────────────────────────────────────── */
.btn {
  gap: var(--s-3);
  min-block-size: var(--control-h);
  padding: 0 1.125rem;
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background-color: var(--btn-face-base);
  background-image: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  color: var(--text);
  font-size: var(--fs-control);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-sm);
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, box-shadow 150ms ease, transform 150ms ease;
}
.btn svg.icon { inline-size: 1.0625rem; block-size: 1.0625rem; }
.btn:hover {
  background-color: var(--btn-face-hover);
  background-image: none;
  border-color: var(--brand-600);
  color: var(--accent);
  box-shadow: var(--shadow-pill);
}
.btn:not(.btn--primary, .btn--danger, .btn--danger-quiet):active { background-color: var(--tint-active); background-image: none; }
.btn:active { transform: translateY(1px); }

/* The one primary action is deep indigo ink, not a bright colour. */
.btn--primary,
.btn--primary:hover {
  border-color: var(--btn-ink-edge);
  color: var(--on-ink);
}
.btn--primary {
  background-color: var(--btn-ink-bottom);
  background-image: linear-gradient(180deg, var(--btn-ink-top), var(--btn-ink-bottom));
  box-shadow: var(--shadow-ink), var(--inner-shine-dark);
}
.btn--primary:hover {
  background-color: var(--btn-ink-hover-bottom);
  background-image: linear-gradient(180deg, var(--btn-ink-hover-top), var(--btn-ink-hover-bottom));
  box-shadow: var(--shadow-ink-hover), var(--inner-shine-dark);
}

/* Red is for danger only: the solid control that answers the question,
   and the quiet one that asks it. The solid one is restated in full, since
   the theme's .btn above has its weight and comes after the platform's.
   White on #a8201a holds 7.4:1; at night #0b0d22 on #fca5a5 holds 9.6:1. */
.btn--danger {
  background-color: var(--danger);
  background-image: none;
  border-color: var(--danger);
  color: var(--text-invert);
  box-shadow: var(--shadow-danger);
}
.btn--danger:hover {
  background-color: var(--danger-strong);
  background-image: none;
  border-color: var(--danger-strong);
  color: var(--text-invert);
  box-shadow: var(--shadow-danger);
}
.btn--danger-quiet {
  background-color: var(--btn-face-base);
  background-image: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  border-color: var(--danger-control);
  color: var(--danger);
}
.btn--danger-quiet:hover,
.btn--danger-quiet:focus-visible {
  background-color: var(--danger);
  background-image: none;
  border-color: var(--danger);
  color: var(--text-invert);
  box-shadow: var(--shadow-danger);
}
.btn--danger-quiet:active { background-color: var(--danger-strong); border-color: var(--danger-strong); color: var(--text-invert); }

.btn--selected,
.btn--selected:hover,
.review-actions .btn[aria-pressed="true"] {
  background-color: var(--accent-tint);
  background-image: none;
  border-color: var(--brand-600);
  color: var(--accent);
}
.btn--quiet,
.btn--ghost {
  border-color: var(--clear);
  background-color: var(--clear);
  background-image: none;
  box-shadow: none;
  color: var(--text-2);
}
.btn--quiet:hover,
.btn--ghost:hover { border-color: var(--clear); background-color: var(--tint-hover); box-shadow: none; color: var(--accent); }

.btn--sm {
  min-block-size: var(--control-sm);
  padding-inline: 0.875rem;
  border-radius: var(--radius-chip);
  font-size: var(--fs-2);
}
.btn--icon { padding-inline: var(--s-4); min-inline-size: var(--control-sm); }

.btn[disabled], .btn[aria-disabled="true"],
.btn--danger-quiet[disabled], .btn--danger-quiet[aria-disabled="true"] {
  background-color: var(--tint-sunken);
  background-image: none;
  border-color: var(--line-strong);
  color: var(--text-disabled);
  box-shadow: none;
}

.power-btn, .device-task__open {
  border-radius: var(--radius-2);
  background-color: var(--btn-face-base);
  background-image: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  box-shadow: var(--shadow-sm);
}
.power-btn:hover { background-color: var(--btn-face-hover); background-image: none; border-color: var(--brand-600); color: var(--accent); }
.power-btn--danger:hover:not([disabled]),
.power-btn--danger:focus-visible:not([disabled]) { background-image: none; }
/* The power state keeps its green: the base rule above has the same
   weight as the platform's variant and comes later. */
.power-btn--on {
  background-color: var(--ok-tint);
  background-image: none;
  border-color: var(--ok-edge);
  color: var(--ok);
}
.power-btn--on:hover { background-color: var(--ok-tint); border-color: var(--ok-fill); color: var(--ok); }

/* ── Badges, counts, chips ───────────────────────────────────────────── */
.badge {
  gap: 0.375rem;
  padding: 0.125rem var(--s-5);
  border-color: var(--line-strong);
  background: var(--neutral-tint);
  box-shadow: none;
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
  line-height: 1.5;
}
/* A state badge leads with a dot of its colour; the word still says it. */
.badge:is(.badge--ok, .badge--success, .badge--warning, .badge--danger, .badge--info):not(:has(.dot))::before {
  content: "";
  inline-size: 7px; block-size: 7px;
  flex: none;
  border-radius: 50%;
  background: currentColor;
}
.badge--accent  { background: var(--accent-tint); border-color: var(--accent-edge); color: var(--accent); }
.badge--ok,
.badge--success { background: var(--ok-tint);     border-color: var(--ok-edge);     color: var(--ok); }
.badge--warning { background: var(--warn-tint);   border-color: var(--warn-edge);   color: var(--warn); }
.badge--danger  { background: var(--danger-tint); border-color: var(--danger-edge); color: var(--danger); }
.badge--info    { background: var(--info-tint);   border-color: var(--info-edge);   color: var(--info); }

.status-pill { padding: 0.125rem var(--s-5); font-size: var(--fs-2); border-color: var(--line-strong); background: var(--neutral-tint); }
.status-pill--ok { background: var(--ok-tint); border-color: var(--ok-edge); color: var(--ok); }

.count {
  min-inline-size: 1.75rem;
  border-radius: var(--radius-pill);
  border-color: var(--clear);
  background: var(--accent-tint);
  color: var(--accent);
  font-size: var(--fs-1);
  font-weight: var(--fw-bold);
}
.tag { border-radius: var(--radius-1); }

.state-chip { padding: 0.125rem var(--s-5); font-size: var(--fs-1); }

/* A filter chip, and the one chosen: the same light as the current page. */
.site-chip,
.ilo-filter__chip,
.estate-chip {
  min-block-size: var(--control-sm);
  padding: 0 var(--s-5);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  color: var(--text);
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
  box-shadow: var(--shadow-sm);
}
.site-chip:hover, .ilo-filter__chip:hover, .estate-chip--link:hover {
  border-color: var(--brand-600);
  background: var(--btn-face-hover);
  color: var(--accent);
  box-shadow: var(--shadow-pill);
}
.site-chip__count {
  min-inline-size: 1.625rem;
  text-align: center;
  background: var(--accent-tint);
  color: var(--accent);
  font-size: var(--fs-1);
}
.site-chip.is-active,
.ilo-filter__chip.is-active,
.site-chip.is-active:hover {
  border-color: var(--brand-700);
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a), var(--lit-b));
  color: var(--on-lit);
  box-shadow: var(--shadow-lit), var(--inner-shine-dark);
}
.site-chip.is-active .site-chip__count { background: var(--lit-veil); color: var(--on-lit); }

/* ── Segmented control: a frosted track, the chosen cell raised out of it ── */
.segmented {
  gap: var(--s-1);
  padding: var(--s-1);
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-slip);
  background: var(--glass-soft);
  box-shadow: 0 0 0 var(--hair) var(--glass-ring), var(--shadow-inset);
}
.segmented > * {
  min-block-size: var(--control-sm);
  padding-inline: var(--s-5);
  border: 0;
  border-radius: var(--radius-2);
  color: var(--text-2);
  font-size: var(--fs-2);
  font-weight: var(--fw-medium);
}
.segmented > :first-child { border: 0; }
.segmented > :hover { background: var(--glass-hover); color: var(--accent); }
.segmented > :active { background: var(--tint-active); }
/* The chosen cell is raised and ringed in the accent, so it is told from
   the track by its edge (about 7:1) and not by colour or weight alone. */
.segmented > [aria-current],
.segmented > [aria-pressed="true"],
.segmented > .is-current {
  background: var(--btn-face-hover);
  color: var(--accent-strong);
  font-weight: var(--fw-bold);
  box-shadow: 0 0 0 var(--hair) var(--accent), var(--shadow-pill);
}
.segmented > [aria-current]:focus-visible,
.segmented > [aria-pressed="true"]:focus-visible,
.segmented > .is-current:focus-visible { box-shadow: 0 0 0 var(--hair) var(--accent), var(--shadow-pill); }

/* ── Pagination ──────────────────────────────────────────────────────── */
.pager__summary { font-size: var(--fs-2); }
.pager__controls {
  gap: var(--s-1);
  padding: var(--s-1);
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-slip);
  background: var(--glass-soft);
  box-shadow: 0 0 0 var(--hair) var(--glass-ring), var(--shadow-inset);
}
.pager__page, .pager__step, .pager__gap, .pager__controls .btn {
  min-inline-size: var(--control-sm);
  min-block-size: var(--control-sm);
  border: 0;
  border-radius: var(--radius-2);
  font-size: var(--fs-2);
}
.pager__controls > :first-child { border: 0; }
.pager__page:hover, .pager__step:hover, .pager__controls .btn:hover { background: var(--glass-hover); color: var(--accent); }
.pager__page.is-current, .pager__controls .btn--primary {
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a), var(--lit-b));
  color: var(--on-lit);
  font-weight: var(--fw-bold);
  box-shadow: var(--shadow-lit);
}
/* The ring turns inward in this track (F11); on the lit cell it is drawn
   in the lit cell's own text colour so it shows on indigo and violet. */
.pager__page.is-current:focus-visible { box-shadow: var(--shadow-lit); outline-color: var(--on-lit); }
.pager [aria-disabled="true"] { background: var(--clear); color: var(--text-disabled); }

/* ── Forms ───────────────────────────────────────────────────────────── */
.field { margin-block-end: var(--s-6); }
.field > label,
fieldset.field > legend { font-size: var(--fs-control); font-weight: var(--fw-medium); }
.hint, .field-error { font-size: var(--fs-2); }

input:not([type]), input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="search"], input[type="url"],
input[type="tel"], select, textarea {
  min-block-size: var(--control-h);
  padding: var(--s-3) var(--s-5);
  border: var(--hair) solid var(--line-control);
  border-radius: var(--radius-2);
  background-color: var(--field);
  color: var(--text);
  font-size: var(--fs-control);
  box-shadow: var(--shadow-inset);
  transition: border-color 150ms ease, box-shadow 150ms ease, background-color 150ms ease;
}
input:hover, select:hover, textarea:hover { border-color: var(--brand-600); }
input:where(:not([type="checkbox"], [type="radio"])):focus, select:focus, textarea:focus {
  border-color: var(--brand-600);
  background-color: var(--field-focus);
  box-shadow: 0 0 0 4px var(--focus-halo);
}
input[disabled], select[disabled], textarea[disabled] {
  background-color: var(--surface-sunken);
  box-shadow: none;
}
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--danger);
  background-color: var(--danger-tint);
}
::placeholder { color: var(--text-disabled); }

.choice__option {
  padding: var(--s-5) var(--s-6);
  border-color: var(--line);
  border-radius: var(--radius-slip);
  background: var(--glass-soft);
  box-shadow: var(--inner-shine);
}
.choice__option:hover { border-color: var(--brand-600); background: var(--glass-hover); }
.choice__option:has(input:checked) { border-color: var(--brand-600); background: var(--accent-tint); box-shadow: 0 0 0 1px var(--brand-600); }
.choice__option--armed:has(input:checked) { border-color: var(--danger); background: var(--danger-tint); box-shadow: 0 0 0 1px var(--danger); }
.choice__desc { font-size: var(--fs-2); }

.perm-group { border-color: var(--line); border-radius: var(--radius-slip); background: var(--glass-soft); }
.perm-group__header { background: var(--glass-head); border-block-end-color: var(--line); }
.perm-group__title { font-size: var(--fs-4); }
.perm-row { padding: var(--s-4) var(--s-5); border-block-end-color: var(--line); }
.perm-row:hover { background: var(--tint-hover); }
.perm-row__key { font-size: var(--fs-1); }

/* ── Tables: dense solid sheets with hairline rows ─────────────────────── */
.table-wrap {
  background:
    linear-gradient(to right, var(--surface) 40%, var(--clear)) 0 0 / 28px 100% no-repeat local,
    linear-gradient(to left,  var(--surface) 40%, var(--clear)) 100% 0 / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,    var(--scrim), var(--clear)) 0 0 / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--scrim), var(--clear)) 100% 0 / 12px 100% no-repeat scroll;
  background-color: var(--surface);
}
table.data th {
  padding: var(--s-4) var(--cell-pad-x);
  border-block-end: var(--hair) solid var(--line-strong);
  font-size: var(--fs-1);
  font-weight: var(--fw-bold);
  color: var(--text-2);
}
table.data td {
  padding: var(--cell-pad-y) var(--cell-pad-x);
  border-block-end: var(--hair) solid var(--line);
  font-size: var(--fs-cell);
  line-height: var(--lh-snug);
}
table.data td strong { font-weight: var(--fw-bold); }

.empty-state__icon {
  inline-size: 3.25rem; block-size: 3.25rem;
  border: 0;
  border-radius: var(--radius-slip);
  background: var(--accent-tint);
  color: var(--accent);
}
.empty-state__icon svg { inline-size: 1.5rem; block-size: 1.5rem; }
.empty-state__title { font-size: var(--fs-4); font-weight: var(--fw-bold); }

/* ── Messages: frosted callouts with a state rail ───────────────────────── */
.alert,
.state-strip {
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6);
  border: var(--hair) solid var(--line);
  border-inline-start: 4px solid var(--text-2);
  border-radius: var(--radius-slip);
  background: var(--glass-strong);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-3);
}
.alert svg, .state-strip svg { inline-size: 1.125rem; block-size: 1.125rem; }
.alert--success { border-color: var(--ok-edge);     border-inline-start-color: var(--ok-fill);     background: linear-gradient(var(--to-inline-end), var(--ok-tint), var(--glass-strong)); }
.alert--danger  { border-color: var(--danger-edge); border-inline-start-color: var(--danger-fill); background: linear-gradient(var(--to-inline-end), var(--danger-tint), var(--glass-strong)); }
.alert--warning { border-color: var(--warn-edge);   border-inline-start-color: var(--warn-fill);   background: linear-gradient(var(--to-inline-end), var(--warn-tint), var(--glass-strong)); }
.alert--info    { border-color: var(--info-edge);   border-inline-start-color: var(--info-fill);   background: linear-gradient(var(--to-inline-end), var(--info-tint), var(--glass-strong)); }
.state-strip--armed { border-color: var(--ok-edge);   border-inline-start-color: var(--ok-fill);   background: linear-gradient(var(--to-inline-end), var(--ok-tint), var(--glass-strong)); }
.state-strip--off   { border-color: var(--warn-edge); border-inline-start-color: var(--warn-fill); background: linear-gradient(var(--to-inline-end), var(--warn-tint), var(--glass-strong)); }

.mapping-note__body { border-radius: var(--radius-2); font-size: var(--fs-2); }
.reason { font-size: var(--fs-3); }

/* ── Meter: an 8px bar on a tinted track ─────────────────────────────── */
.meter {
  block-size: 8px;
  border: 0;
  background: var(--track);
}

/* ── Code and secrets ───────────────────────────────────────────────── */
.code-block { border-radius: var(--radius-2); font-size: var(--fs-2); }

/* ── Sheets and menus ───────────────────────────────────────────────── */
.sheet {
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-strong), var(--glass-top));
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  box-shadow: var(--shadow-overlay), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
.sheet__head { border-block-end-color: var(--line); background: var(--glass-head); }
.sheet__foot { border-block-start-color: var(--line); background: var(--glass-head); }
.sheet__close { border-radius: var(--radius-2); }
.sheet__close:hover { background: var(--tint-hover); }
.sheet-section__title { font-size: var(--fs-control); }

/* A menu lies over content, so it is a solid sheet. */
.menu__panel {
  padding: var(--s-3);
  border-color: var(--line-strong);
  border-radius: var(--radius-slip);
  background: var(--surface);
  box-shadow: var(--shadow-overlay);
}

/* ── Sign-in and the error pages ─────────────────────────────────────── */
.signin__card {
  max-inline-size: 27rem;
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-4);
  background: linear-gradient(180deg, var(--glass-strong), var(--glass-bottom));
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  box-shadow: var(--shadow-lift), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
.signin__brand .brand__mark { inline-size: 3.25rem; block-size: 3.25rem; border-radius: var(--radius-slip); }
.signin__brand .brand__mark svg { inline-size: 1.75rem; block-size: 1.75rem; }
.signin__title { font-size: var(--fs-6); }
.signin__sub { font-size: var(--fs-3); }
.signin__locale {
  min-block-size: var(--control-sm);
  border-color: var(--line-control);
  background: linear-gradient(180deg, var(--btn-face-top), var(--btn-face-bottom));
  font-size: var(--fs-2);
}
.signin__locale:hover { border-color: var(--brand-600); color: var(--accent); background: var(--btn-face-hover); }

/* ── The public site ──────────────────────────────────────────────────
   site.css is loaded after this layer and draws its own cards on the
   surface token, so these rules carry the root in their selector to be
   heard over it. Its header and footer are the site's shell; its feature
   and plan cards are cards. */
:root .site-header {
  background: linear-gradient(180deg, var(--glass-bar-top), var(--glass-bar));
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  border-block-end-color: var(--glass-edge);
  box-shadow: var(--shadow-shell);
}
:root .site-footer {
  background: linear-gradient(180deg, var(--glass-shell-top), var(--glass-shell));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-block-start-color: var(--glass-edge);
}
:root .section--sunken { background: var(--tint-sunken); }
:root .feature,
:root .plan {
  border: var(--hair) solid var(--glass-edge);
  border-radius: var(--radius-3);
  background: linear-gradient(180deg, var(--glass-top), var(--glass-bottom) var(--glass-fade));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-glass), var(--inner-shine), 0 0 0 var(--hair) var(--glass-ring);
}
:root .plan--featured { box-shadow: var(--shadow-lift), 0 0 0 2px var(--brand-600); }
:root .feature__icon {
  inline-size: var(--chip-lg); block-size: var(--chip-lg);
  border-radius: var(--radius-slip);
  background: var(--hue-indigo-tint);
  color: var(--hue-indigo);
  box-shadow: inset 0 0 0 var(--hair) var(--chip-ring);
}
:root .step__number {
  background-color: var(--lit-a);
  background-image: linear-gradient(var(--lit-angle), var(--lit-a), var(--lit-b));
  color: var(--on-lit);
}
:root .shot { border-color: var(--glass-edge); border-radius: var(--radius-3); box-shadow: var(--shadow-lift), 0 0 0 var(--hair) var(--glass-ring); }
/* site.css sets Latin leading on its headlines (1.12 and 1.2); a Persian
   headline that wraps needs the room for its marks, or the tanween of one
   line lands on the alef of the line above. */
:root[dir="rtl"] .hero__title,
:root[dir="rtl"] .section__title { line-height: var(--lh-tight); }

/* ── Records drawn on the page ──────────────────────────────────────── */
.rack { border-radius: var(--radius-slip); box-shadow: var(--shadow-lift); }
.rack__device { border-radius: var(--radius-1); }
.power-led, .port-chip { border-color: var(--line); background: var(--glass-soft); }
.port-chip { border-radius: var(--radius-2); }
/* The link state rail survives the hairline above. */
.port-chip--up { border-inline-start-color: var(--ok-fill); }
.port-chip--down { border-inline-start-color: var(--text-disabled); }
/* A machine that is out of service or disabled stays sunken on the board,
   and an inline device panel stays part of its card. */
.server-card:is(.server-card--out, .server-card--disabled) { background: var(--surface-sunken); }
.device-panel--inline { background: var(--clear); box-shadow: none; }
.fex-jack { border-radius: var(--radius-1); }

/* ═══════════════════════════════════════════════════════════════════════
   F10. Widths and pointers
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  /* The sidebar becomes a frosted card at the top of the page. */
  .sidebar {
    margin: var(--shell-inset) var(--shell-inset) 0;
    inline-size: auto;
    border: var(--hair) solid var(--glass-edge);
    border-radius: var(--radius-3);
  }
  .sidebar > .brand { padding-block: var(--s-4); }
  .card__header, .card__body { padding-inline: var(--card-pad); }
  table.data th, table.data td { padding-inline: var(--cell-pad-x); }
  /* Stacked rows are small white cards on a soft indigo ground. */
  table.data[data-stack] tbody { display: grid; gap: var(--s-4); padding: var(--s-4); background: var(--tint-sunken); }
  table.data[data-stack] tbody tr {
    padding: var(--s-4) var(--s-5);
    border: 0;
    border-radius: var(--radius-slip);
    background: var(--surface);
    box-shadow: 0 0 0 var(--hair) var(--line), var(--shadow-sm);
  }
  table.data[data-stack] tbody td[data-label]::before { font-size: var(--fs-1); font-weight: var(--fw-bold); }
  /* Rows stacked inside a padded card body need no second inset. */
  .card__body:not(.card__body--flush) table.data[data-stack] tbody { padding: 0; background: var(--clear); }
}

@media (max-width: 620px) {
  .content { padding-block: var(--s-6) var(--s-7); }
  .page-header { margin-block-end: var(--s-6); }
  .tiles { gap: var(--s-4); }
  .tile { padding: var(--s-5); gap: var(--s-4); }
  .tile__icon { inline-size: 2.25rem; block-size: 2.25rem; border-radius: var(--radius-2); }
  .tile__icon svg { inline-size: 1.125rem; block-size: 1.125rem; }
  .overview__card { padding: var(--s-5); }
  .network-hub__tabs.segmented { gap: var(--s-1); background: var(--glass-soft); }
  .network-hub__tabs.segmented > * { background: var(--clear); }
  .network-hub__tabs.segmented > :is([aria-current], [aria-pressed="true"], .is-current) {
    background: var(--btn-face-hover);
    box-shadow: 0 0 0 var(--hair) var(--accent), var(--shadow-pill);
  }
  .portal-bar { padding: var(--s-4); }
  /* The pager track scrolls; centred, its start would overflow where no
     scroll reaches it (in Persian, the previous arrow and the current page).
     safe center centres it only while it fits. */
  .pager__controls { justify-content: flex-start; justify-content: safe center; }
  /* The top bar at phone width: the avatar-only user chip takes even
     insets, so the disc sits in its middle, and the section crumb never
     shortens into another word; only the current page's crumb, which the
     title repeats, may. */
  .user-chip { padding-inline: var(--s-2); }
  .crumbs > :not([aria-current]) { flex-shrink: 0; }
  /* A state line and its button: the button takes a line of its own at
     the end rather than squeezing the sentence into a column. */
  .state-strip { flex-wrap: wrap; }
  .state-strip > div { flex: 1 1 14rem; }
}

/* Where the platform hides the language button's label, the top bar's
   icon buttons take slim, even insets so the crumbs keep their room. */
@media (max-width: 430px) {
  .topbar__actions .btn--sm { padding-inline: var(--s-3); }
}

/* A finger: the theme's controls keep the platform's 44px floor, and a
   field reads at 16px so a phone does not zoom into it. */
@media (pointer: coarse) {
  .btn, .btn--sm, .segmented > *, .pager__page, .pager__step, .pager__controls .btn,
  .sidebar__link, .site-chip, .ilo-filter__chip, .estate-chip, .signin__locale, .user-chip {
    min-block-size: var(--tap);
  }
  .segmented > *, .pager__page, .pager__step { min-inline-size: var(--tap); }
  .topbar .segmented > :only-child { min-block-size: calc(var(--tap) - 2px); min-inline-size: calc(var(--tap) - 2px); }
  .topbar__actions .btn { min-block-size: var(--tap); }
  .topbar__actions .btn--icon { min-inline-size: var(--tap); }
  input:not([type]), input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="date"], input[type="search"], input[type="url"],
  input[type="tel"], select, textarea { min-block-size: var(--tap); font-size: 1rem; }
}

/* ═══════════════════════════════════════════════════════════════════════
   F11. Focus, motion and forced colours
   ═══════════════════════════════════════════════════════════════════════ */

/* An outline, so no glass shadow can cover it. The platform's one focus
   rule stays the rule; this only restates the ring where a component above
   draws its own shadow on the same element. */
:is(.btn, .sidebar__link, .site-chip, .tile, .segmented > *, .user-chip, .signin__locale):focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}
/* Inside a track that clips (the segmented control, the pager) the ring
   turns inward, as the platform draws it. */
.segmented > :focus-visible,
.pager__controls > :focus-visible { outline-offset: calc(var(--focus-offset) * -1.5); }

@media (prefers-reduced-motion: reduce) {
  a.tile:hover, .tiles--counts a.tile:hover, .btn:active { transform: none; }
  .sidebar__link, .sidebar__link > svg.icon, .tile, .btn, .site-chip,
  input, select, textarea { transition: none; }
}

@media (forced-colors: active) {
  body::before { display: none; }
  .sidebar, .topbar, .portal-bar, .card, .tile, .overview__card, .alarms, .alert, .state-strip,
  .sheet, .signin__card, .server-card, .estate-filter, .command-bar, .server-hero, .pager {
    border: 1px solid var(--fc-edge);
  }
  /* The current or chosen item is a system highlight fill, which leaves the
     outline free to show keyboard focus on it as on anything else. */
  :root .sidebar__link:is(.is-active, [aria-current="page"]),
  :root .sidebar__link:is(.is-active, [aria-current="page"]):hover,
  .segmented > :is([aria-current], [aria-pressed="true"], .is-current),
  .pager__page.is-current,
  .site-chip.is-active,
  .site-chip.is-active:hover,
  .ilo-filter__chip.is-active {
    forced-color-adjust: none;
    background: var(--fc-active);
    color: var(--fc-active-text);
    box-shadow: none;
  }
  :root .sidebar__link:is(.is-active, [aria-current="page"]) > svg.icon {
    forced-color-adjust: none;
    background: var(--fc-active);
    color: var(--fc-active-text);
    box-shadow: none;
  }
  :root .sidebar__link:is(.is-active, [aria-current="page"]):focus-visible,
  :root .site-chip.is-active:focus-visible,
  :root .ilo-filter__chip.is-active:focus-visible {
    outline: 2px solid var(--fc-edge);
    outline-offset: 2px;
    box-shadow: none;
  }
  /* In a clipping track the ring stays inward, in the canvas colour on
     the highlight fill. */
  :root .segmented > :is([aria-current], [aria-pressed="true"], .is-current):focus-visible,
  :root .pager__page.is-current:focus-visible {
    outline: 2px solid var(--fc-canvas);
    outline-offset: -4px;
    box-shadow: none;
  }
  .meter { forced-color-adjust: none; background: var(--fc-canvas); border: 1px solid var(--fc-edge); }
}

/* ═══════════════════════════════════════════════════════════════════════
   The public demonstration (App\Support\Demo)

   Drawn only when the demonstration is switched on. Tokens only, so the
   same rules hold in light, dark and forced colours.
   ═══════════════════════════════════════════════════════════════════════ */

/* The line at the top of every signed-in page. Informational rather than a
   warning: nothing is wrong, the data is simply not real. */
.demo-banner {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-4) var(--s-5);
  margin-block-end: var(--s-6);
  border: var(--hair) solid var(--accent-edge);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-2);
  background: var(--accent-tint);
  color: var(--text);
  font-size: var(--fs-3);
}
.demo-banner > svg { flex: 0 0 auto; margin-block-start: 2px; color: var(--accent); }
.demo-banner__text { margin: 0; line-height: var(--lh-snug); }
.demo-banner__text strong { color: var(--accent); font-weight: var(--fw-bold); margin-inline-end: var(--s-2); }

/* The sign-ins on a sign-in page, under the form. */
.demo-accounts {
  margin-block-start: var(--s-7);
  padding-block-start: var(--s-6);
  border-block-start: var(--hair) solid var(--line);
}
.demo-accounts__title {
  display: flex; align-items: center; gap: var(--s-3);
  margin: 0 0 var(--s-3);
  font-size: var(--fs-4); font-weight: var(--fw-bold);
  color: var(--text);
}
.demo-accounts__title > svg { color: var(--accent); }
.demo-accounts__hint { margin: 0 0 var(--s-5); color: var(--text-2); font-size: var(--fs-2); line-height: var(--lh-body); }
.demo-accounts__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-4); }
.demo-account {
  display: grid; gap: var(--s-3);
  padding: var(--s-5);
  border: var(--hair) solid var(--accent-edge);
  border-radius: var(--radius-2);
  background: var(--accent-tint);
}
.demo-account__role { font-weight: var(--fw-bold); color: var(--accent); font-size: var(--fs-3); }
.demo-account__creds { margin: 0; display: grid; gap: var(--s-2); font-size: var(--fs-2); }
.demo-account__creds > div { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4); align-items: baseline; }
.demo-account__creds dt { color: var(--text-2); min-inline-size: 5.5rem; }
.demo-account__creds dd { margin: 0; color: var(--text); overflow-wrap: anywhere; user-select: all; }
.demo-account__use { justify-self: start; }

/* The call to action on the public home page. */
.demo-cta {
  display: grid; gap: var(--s-5);
  margin-block: var(--s-7);
  padding: var(--s-6);
  border: var(--hair) solid var(--accent-edge);
  border-radius: var(--radius-3);
  background: var(--accent-tint);
}
.demo-cta__note { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); color: var(--text); }
.demo-cta__actions { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.site-nav__demo { border-color: var(--accent-edge); color: var(--accent); }

/* A secret-bearing settings form, held read-only. The fieldset only carries
   the disabled state; it draws nothing, so the form keeps its own layout. */
.demo-lock { display: contents; }
