/* ============================================================================
   LogRite Management — stylesheet
   ----------------------------------------------------------------------------
   Ported onto the real app.logrite.ai design system. Rules this file obeys:

   1. NO external resources. No @font-face, no @import, no url(http...). The
      product loads zero web fonts and runs on the system stack; so do we. The
      only url() values here are inline `data:image/svg+xml` checkbox ticks.
   2. BOTH THEMES ARE AUTHORED, neither is an inversion of the other. LIGHT is
      the base on `:root`; `@media (prefers-color-scheme: dark)` supplies dark;
      and `:root[data-theme="dark"]` / `:root[data-theme="light"]` override the
      media query in BOTH directions so the in-page toggle always wins. `.dark`
      is kept as a synonym of `[data-theme="dark"]` because ThemeContext.jsx and
      login.html both spell it that way. This console still SHIPS dark (index.html
      carries data-theme="dark"), so its default is unchanged. Identical to the
      mechanism downtime.css uses — one design system, two sites.
   3. #E64100 is a FILL, never text. Orange text is --accent-orange-text:
      #BF3D00 on light, #FF8B5B on dark. (index.html:75-78 of the product.)
   4. Cards are OUTLINED, not raised: card background == page background, with a
      1px border and a near-invisible 1px lift.
   5. Deliberate deviations from the product, all in the accessibility
      direction: a real :focus-visible ring everywhere, a focus trap in
      dialogs, and Escape-to-close. The product has none of these.
   ========================================================================== */

/* ============================================================
   1. TOKENS
   ------------------------------------------------------------
   The palette is CSS custom properties on :root, and every component
   below styles through a token rather than a literal.

   LIGHT is the base. `@media (prefers-color-scheme: dark)` supplies
   dark, and `:root[data-theme="dark"]` / `:root[data-theme="light"]`
   override the media query in BOTH directions so an explicit choice
   always wins. This is character-for-character the mechanism
   downtime.css uses, which is the point: the two operations sites are
   now one design system, not two that merely look alike.

   `:root.dark` is kept as a synonym of `[data-theme="dark"]`. The
   product's ThemeContext.jsx toggles a `dark` class on <html> and so
   does login.html, so both spellings have to mean dark or a stored
   preference would silently stop working. index.html ships
   data-theme="dark", so dark remains this console's default and the
   media query only decides for a document that carries neither.

   themes.js:4-47 + index.html:56-94 remain the source of the values.
   ============================================================ */
:root {
  color-scheme: light;

  /* Surfaces */
  --bg-primary:   #FFFFFF;
  --bg-secondary: #F8F9FA;
  --bg-tertiary:  #F3F4F6;
  --bg-hover:     #E5E7EB;
  --bg-active:    #D1D5DB;

  /* Borders */
  --border-primary:   #E5E7EB;
  --border-secondary: #D1D5DB;
  --border-tertiary:  #E5E7EB;
  --border-hover:     #E64100;

  /* Text */
  --text-primary:   #111827;
  --text-secondary: #374151;
  --text-tertiary:  #6B7280;
  --text-muted:     #9CA3AF;

  /* Brand */
  --accent-orange:        #E64100;
  --accent-orange-light:  #FF8B5B;
  --accent-orange-text:   #BF3D00;   /* #E64100 as text on white is 4.10:1 — FAILS */
  --accent-orange-hover:  #B33300;   /* primary-button hover (FOOTGUN #3) */
  --accent-orange-bg:     #FFF1EA;
  --accent-orange-border: #F5B79A;

  /* Semantic — identical in both themes (themes.js:70) */
  --accent-blue:   #2E90FA;
  --accent-green:  #10B981;
  --accent-red:    #EF4444;
  --accent-yellow: #F79009;
  --status-success: #17B26A;
  --status-warning: #F79009;
  --status-error:   #F04438;
  --status-info:    #2E90FA;

  /* Elevation */
  --shadow-sm:    0 1px 2px 0 rgba(10,13,18,.05);
  --shadow-md:    0 4px 6px -1px rgba(0,0,0,.1);
  --shadow-lg:    0 10px 15px -3px rgba(0,0,0,.1);
  --shadow-modal: 0 25px 50px -12px rgba(0,0,0,.18);
  --shadow-menu:  0 4px 12px rgba(0,0,0,.12);

  /* Overlay */
  --scrim: rgba(20,18,15,.55);
  --sheen: rgba(0,0,0,.02);

  /* Radius (--radius: .5rem, src/index.css:1065) */
  --radius-sm:  4px;
  --radius-md:  6px;
  --radius-lg:  8px;    /* THE LogRite radius */
  --radius-xl:  12px;   /* cards */
  --radius-2xl: 16px;   /* modals */
  --radius-full: 9999px;

  /* Type — system stack only, no webfont anywhere */
  --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
               "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --fs-micro: 10px;
  --fs-nano:  11px;
  --fs-xs:    .75rem;
  --fs-sm:    .875rem;   /* DEFAULT BODY */
  --fs-base:  1rem;
  --fs-lg:    1.125rem;
  --fs-xl:    1.25rem;
  --fs-2xl:   1.5rem;
  --fs-3xl:   1.875rem;

  /* Spacing scale. New and rewritten layout uses `gap` off this scale
     rather than per-element margins, so the rhythm is one decision in
     one place instead of a margin argument at every call site. */
  --sp-1: 4px;
  --sp-2: 6px;
  --sp-3: 10px;
  --sp-4: 14px;
  --sp-5: 16px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 48px;

  /* Motion — the house transition is colors/300ms/ease-in-out */
  --dur-fast: 150ms;
  --dur-base: 200ms;
  --dur-slow: 300ms;
  --ease:        cubic-bezier(.4,0,.2,1);
  --ease-out:    cubic-bezier(0,0,.2,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);

  /* Log-severity ramp (rowLogLevel.js:81-90) — quietest to loudest.
     `unset` is deliberately NOT a sixth hue: it is a dashed outline, so
     "nobody recorded a value" is told apart by SHAPE, not only colour. */
  --sev-error-fg: #B91C1C; --sev-error-bd: #FECACA; --sev-error-bg: #FEF2F2;
  --sev-warn-fg:  #B45309; --sev-warn-bd:  #FDE68A; --sev-warn-bg:  #FFFBEB;
  --sev-info-fg:  #15803D; --sev-info-bd:  #BBF7D0; --sev-info-bg:  #F0FDF4;
  --sev-debug-fg: #1D4ED8; --sev-debug-bd: #BFDBFE; --sev-debug-bg: #EFF6FF;
  --sev-trace-fg: #0E7490; --sev-trace-bd: #A5F3FC; --sev-trace-bg: #ECFEFF;
  --sev-none-fg:  #6B7280; --sev-none-bd:  #D1D5DB; --sev-none-bg:  transparent;
  --sev-violet-fg:#6D28D9; --sev-violet-bd:#DDD6FE; --sev-violet-bg:#F5F3FF;

  /* Charts — the Analytics module is the authority here, not --accent-*.
     palette.js:37 `bars`. */
  --chart-1: #E64100;
  --chart-2: #17B26A;
  --chart-3: #2563EB;
  --chart-4: #F79009;
  --chart-5: #8B5CF6;
  --chart-grid:  #EDF0F3;
  --chart-ink:   #6B7280;
  --chart-track: #E5E7EB;
  --chart-none:  #E5E7EB;
  --chart-mem:   #E64100;
  --chart-load:  #2563EB;
  --chart-area:  rgba(230,65,0,.12);

  /* ---- DENSITY + BREAKPOINTS -------------------------------------------
     The spacing scale above says what the steps ARE; these say which step a
     given kind of seam gets, so "how much air does a card get" is one
     decision rather than a px argument at every call site. They are also the
     only values that change with viewport: the @media blocks below restate
     THESE, never per-component padding, which is what stopped the console
     being responsive in more than three places.

     ⚠ The breakpoints are Logrite-UI's own (src/responsive.css), which are
     Tailwind's: 640 / 768 / 1024 / 1280. This console used to mix in 460,
     720, 980, 1100 and 1279 — five extra tiers nothing else in the product
     shares, so a layout that reflowed correctly in the app reflowed at a
     different width here. CSS cannot read a custom property inside a media
     QUERY, so the scale cannot be enforced by var() — `test_design_parity.js`
     greps every @media in both stylesheets and fails on a width off-scale. */
  --gap-page:    var(--sp-6);   /* between top-level cards on a page */
  --gap-card:    var(--sp-5);   /* inside a card, between its blocks */
  --gap-control: var(--sp-3);   /* between controls on one toolbar row */
  --gap-inline:  var(--sp-2);   /* icon-to-label, chip-to-chip */
  --pad-card:    var(--sp-5);
  --pad-page:    var(--sp-6);

  /* Grid tracks. A card grid states its MINIMUM column, never a count —
     a count has to be restated at every breakpoint and drifts from the
     container it actually sits in. */
  --col-svc:  20.5rem;
  --col-stat: 15rem;

  /* Shell geometry (useSidebar.js:69-70) */
  --sidebar-width: 280px;
  --sidebar-collapsed: 68px;
  --mobile-header: 64px;
}

/* ============================================================
   1b. TOKENS — DARK.
   The media query is the default signal for a document that has made
   no explicit choice; the [data-theme]/.dark rules below override it.
   ============================================================ */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg-primary:   #0C0E12;
    --bg-secondary: #13161B;
    --bg-tertiary:  #1A1D24;
    --bg-hover:     #22262F;
    --bg-active:    #2A2E37;

    --border-primary:   #2A2A2A;
    --border-secondary: #373A41;
    --border-tertiary:  #22262F;

    --text-primary:   #FFFFFF;
    --text-secondary: #CECFD2;
    --text-tertiary:  #94979C;
    --text-muted:     #6B7280;

    --accent-orange-text:   #FF8B5B;   /* orange AS TEXT, dark theme */
    --accent-orange-bg:     #2A150C;
    --accent-orange-border: #7A3113;

    --shadow-md:    0 4px 6px -1px rgba(0,0,0,.3);
    --shadow-lg:    0 10px 15px -3px rgba(0,0,0,.3);
    --shadow-modal: 0 25px 50px -12px rgba(0,0,0,.6);
    --shadow-menu:  0 4px 12px rgba(0,0,0,.3);

    --scrim: rgba(0,0,0,.6);
    --sheen: rgba(255,255,255,.03);

    --sev-error-fg: #FDA29B; --sev-error-bd: #912018; --sev-error-bg: #55160C;
    --sev-warn-fg:  #FEC84B; --sev-warn-bd:  #93370D; --sev-warn-bg:  #4E1D09;
    --sev-info-fg:  #75E0A7; --sev-info-bd:  #085D3A; --sev-info-bg:  #053321;
    --sev-debug-fg: #84CAFF; --sev-debug-bd: #1849A9; --sev-debug-bg: #102A56;
    --sev-trace-fg: #67E3F9; --sev-trace-bd: #155B75; --sev-trace-bg: #0D2D3A;
    --sev-none-fg:  #94979C; --sev-none-bd:  #373A41; --sev-none-bg:  transparent;
    --sev-violet-fg:#C3B5FD; --sev-violet-bd:#5B21B6; --sev-violet-bg:#2E1065;

    --chart-grid:  #22262F;
    --chart-ink:   #94979C;
    --chart-track: #22262F;
    --chart-none:  #22262F;
    --chart-area:  rgba(230,65,0,.16);
  }
}

/* Explicit dark — wins over a light OS preference. `.dark` is the
   product's own spelling and means exactly the same thing. */
:root[data-theme="dark"],
:root.dark {
  color-scheme: dark;

  --bg-primary:   #0C0E12;
  --bg-secondary: #13161B;
  --bg-tertiary:  #1A1D24;
  --bg-hover:     #22262F;
  --bg-active:    #2A2E37;

  --border-primary:   #2A2A2A;
  --border-secondary: #373A41;
  --border-tertiary:  #22262F;

  --text-primary:   #FFFFFF;
  --text-secondary: #CECFD2;
  --text-tertiary:  #94979C;
  --text-muted:     #6B7280;

  --accent-orange-text:   #FF8B5B;
  --accent-orange-bg:     #2A150C;
  --accent-orange-border: #7A3113;

  --shadow-md:    0 4px 6px -1px rgba(0,0,0,.3);
  --shadow-lg:    0 10px 15px -3px rgba(0,0,0,.3);
  --shadow-modal: 0 25px 50px -12px rgba(0,0,0,.6);
  --shadow-menu:  0 4px 12px rgba(0,0,0,.3);

  --scrim: rgba(0,0,0,.6);
  --sheen: rgba(255,255,255,.03);

  --sev-error-fg: #FDA29B; --sev-error-bd: #912018; --sev-error-bg: #55160C;
  --sev-warn-fg:  #FEC84B; --sev-warn-bd:  #93370D; --sev-warn-bg:  #4E1D09;
  --sev-info-fg:  #75E0A7; --sev-info-bd:  #085D3A; --sev-info-bg:  #053321;
  --sev-debug-fg: #84CAFF; --sev-debug-bd: #1849A9; --sev-debug-bg: #102A56;
  --sev-trace-fg: #67E3F9; --sev-trace-bd: #155B75; --sev-trace-bg: #0D2D3A;
  --sev-none-fg:  #94979C; --sev-none-bd:  #373A41; --sev-none-bg:  transparent;
  --sev-violet-fg:#C3B5FD; --sev-violet-bd:#5B21B6; --sev-violet-bg:#2E1065;

  --chart-grid:  #22262F;
  --chart-ink:   #94979C;
  --chart-track: #22262F;
  --chart-none:  #22262F;
  --chart-area:  rgba(230,65,0,.16);
}

/* Explicit light — wins over a dark OS preference, and over a stale
   `dark` class. Values repeat the base block because `:root` alone
   would lose to the media query above. Last, so it wins. */
:root[data-theme="light"] {
  color-scheme: light;

  --bg-primary:   #FFFFFF;
  --bg-secondary: #F8F9FA;
  --bg-tertiary:  #F3F4F6;
  --bg-hover:     #E5E7EB;
  --bg-active:    #D1D5DB;

  --border-primary:   #E5E7EB;
  --border-secondary: #D1D5DB;
  --border-tertiary:  #E5E7EB;

  --text-primary:   #111827;
  --text-secondary: #374151;
  --text-tertiary:  #6B7280;
  --text-muted:     #9CA3AF;

  --accent-orange-text:   #BF3D00;
  --accent-orange-bg:     #FFF1EA;
  --accent-orange-border: #F5B79A;

  --shadow-md:    0 4px 6px -1px rgba(0,0,0,.1);
  --shadow-lg:    0 10px 15px -3px rgba(0,0,0,.1);
  --shadow-modal: 0 25px 50px -12px rgba(0,0,0,.18);
  --shadow-menu:  0 4px 12px rgba(0,0,0,.12);

  --scrim: rgba(20,18,15,.55);
  --sheen: rgba(0,0,0,.02);

  --sev-error-fg: #B91C1C; --sev-error-bd: #FECACA; --sev-error-bg: #FEF2F2;
  --sev-warn-fg:  #B45309; --sev-warn-bd:  #FDE68A; --sev-warn-bg:  #FFFBEB;
  --sev-info-fg:  #15803D; --sev-info-bd:  #BBF7D0; --sev-info-bg:  #F0FDF4;
  --sev-debug-fg: #1D4ED8; --sev-debug-bd: #BFDBFE; --sev-debug-bg: #EFF6FF;
  --sev-trace-fg: #0E7490; --sev-trace-bd: #A5F3FC; --sev-trace-bg: #ECFEFF;
  --sev-none-fg:  #6B7280; --sev-none-bd:  #D1D5DB; --sev-none-bg:  transparent;
  --sev-violet-fg:#6D28D9; --sev-violet-bd:#DDD6FE; --sev-violet-bg:#F5F3FF;

  --chart-grid:  #EDF0F3;
  --chart-ink:   #6B7280;
  --chart-track: #E5E7EB;
  --chart-none:  #E5E7EB;
  --chart-area:  rgba(230,65,0,.12);
}

/* ============================================================
   2. RESET + BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html {
  scrollbar-width: thin;
  scrollbar-color: var(--border-secondary) transparent;
  scrollbar-gutter: stable;
}
html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
}
body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.25rem;
  -webkit-font-smoothing: antialiased;
  /* the page body must never scroll sideways — wide content scrolls in its
     own container instead */
  overflow-x: hidden;
}
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 600; color: var(--text-primary); }
p  { margin: 0; }
dl, dd, dt { margin: 0; }
a { color: var(--accent-orange-text); text-decoration: none; }
a:hover { text-decoration: underline; }
button, input, select, textarea { font: inherit; color: inherit; }
code, pre, .lr-mono { font-family: var(--font-mono); }
img, svg { max-width: 100%; }

/* The product has NO focus-visible system at all. That is its single biggest
   accessibility gap (Appendix A). We do not reproduce it. */
:focus-visible {
  outline: 2px solid var(--accent-orange);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: rgba(230,65,0,.32); }

/* 6px scrollbar, thumb turns brand orange on hover (src/index.css:60-88) */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; border-radius: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-secondary); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent-orange); }

/* Autofill suppression (src/index.css:113-130) */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text-primary) !important;
  caret-color: var(--text-primary) !important;
  -webkit-box-shadow: 0 0 0 1000px var(--bg-secondary) inset !important;
  transition: background-color 600000s ease-in-out 0s, color 600000s ease-in-out 0s;
}

/* Honoured globally, exactly as src/responsive.css:270-279 does it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

.lr-skip {
  position: absolute; left: -9999px; top: 0; z-index: 10050;
  background: var(--bg-secondary); color: var(--text-primary);
  border: 1px solid var(--accent-orange);
  padding: 10px 16px; border-radius: 0 0 var(--radius-lg) 0;
  font-weight: 600;
}
.lr-skip:focus { left: 0; text-decoration: none; }

.lr-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================================================
   3. ICONS — 16px default, 20px nav, flex-shrink:0 everywhere
   ============================================================ */
.lr-icon      { width: 16px; height: 16px; flex-shrink: 0; display: block; }
.lr-icon--lg  { width: 20px; height: 20px; }
.lr-icon--sm  { width: 14px; height: 14px; }
.lr-icon--xs  { width: 12px; height: 12px; }
.lr-icon--xl  { width: 24px; height: 24px; }
.lr-icon--32  { width: 32px; height: 32px; }

/* ============================================================
   4. TYPE HELPERS
   ============================================================ */
.lr-label {
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-tertiary);
}
.lr-title-page { font-size: var(--fs-2xl); line-height: 2rem;    font-weight: 600; }
.lr-title-card { font-size: var(--fs-lg);  line-height: 1.75rem; font-weight: 600; }
.lr-hint  { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.1rem; }
.lr-muted { color: var(--text-tertiary); }
.lr-num   { font-variant-numeric: tabular-nums; }
.lr-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ============================================================
   5. SHELL — fixed 280/68 rail, content follows via --sidebar-width
   ============================================================ */
.lr-shell { min-height: 100vh; min-height: 100dvh; }

.lr-sidebar {
  position: fixed; left: 0; top: 0; z-index: 50;
  width: var(--sidebar-width);
  height: 100vh; height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg-primary);
  border-right: 1px solid var(--border-primary);
  padding: 16px 12px 12px;
  gap: 4px;
  overflow: visible;
  transition: width var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.lr-sidebar__scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; gap: 4px;
  margin: 0 -4px; padding: 0 4px;
}

.lr-main {
  margin-left: var(--sidebar-width);
  min-width: 0;
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column;
  transition: margin-left var(--dur-slow) var(--ease);
}

/* Brand block */
.lr-brand { display: flex; align-items: center; gap: 10px; padding: 2px 6px 12px; min-width: 0; }
.lr-brand__mark { width: 32px; height: 32px; flex-shrink: 0; display: block; }
.lr-brand__text { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.lr-brand__text b { font-size: var(--fs-sm); font-weight: 600; letter-spacing: .01em; }
.lr-brand__text span {
  font-size: var(--fs-micro); color: var(--text-tertiary);
  text-transform: uppercase; letter-spacing: .09em; font-weight: 500;
}

/* Nav */
.lr-nav { display: flex; flex-direction: column; gap: 2px; }
.lr-nav-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 8px 12px; border-radius: var(--radius-lg);
  background: none; border: 0; cursor: pointer; text-align: left;
  color: var(--text-secondary); font-size: var(--fs-sm); font-weight: 600;
  text-decoration: none;
  transition: background-color var(--dur-slow) var(--ease);
}
.lr-nav-item:hover { background: var(--bg-hover); text-decoration: none; }
.lr-nav-item[aria-current="page"] { background: var(--bg-hover); color: var(--text-primary); }
.lr-nav-item:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: -2px; }
.lr-nav-item__icon {
  width: 20px; height: 20px; flex-shrink: 0; color: var(--text-tertiary);
  transition: color var(--dur-slow) var(--ease), filter var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease);
}
/* The active state is signalled by a GLOWING COLOURED ICON, not an indicator
   bar. That drop-shadow is a genuine LogRite signature. */
.lr-nav-item[aria-current="page"] .lr-nav-item__icon {
  color: var(--accent-orange);
  filter: drop-shadow(0 0 8px rgba(230,65,0,.5));
}
.lr-nav-item:hover .lr-nav-item__icon { color: var(--accent-orange); transform: scale(1.08); }
.lr-nav-item__label { transition: transform var(--dur-slow) var(--ease); min-width: 0; }
.lr-nav-item:hover .lr-nav-item__label { transform: translateX(4px); }
.lr-nav-item__count {
  margin-left: auto; flex-shrink: 0;
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 600;
  color: var(--text-tertiary);
  background: var(--bg-secondary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-full); padding: 1px 7px;
}
.lr-nav-item__count.is-bad { color: var(--sev-error-fg); border-color: var(--sev-error-bd); }

/* Collapsed rail */
.lr-sidebar.is-collapsed { width: var(--sidebar-collapsed); }
.lr-sidebar.is-collapsed .lr-nav-item {
  width: 40px; height: 40px; justify-content: center; padding: 0; margin: 0 auto 1px;
  gap: 0;
}
.lr-sidebar.is-collapsed .lr-nav-item__label,
.lr-sidebar.is-collapsed .lr-nav-item__count,
.lr-sidebar.is-collapsed .lr-brand__text,
.lr-sidebar.is-collapsed .lr-side-label,
.lr-sidebar.is-collapsed .lr-hostchips,
.lr-sidebar.is-collapsed .lr-account__meta,
.lr-sidebar.is-collapsed .lr-account__caret,
.lr-sidebar.is-collapsed .lr-segmented { display: none; }
.lr-sidebar.is-collapsed .lr-brand { justify-content: center; padding-left: 0; padding-right: 0; }
.lr-sidebar.is-collapsed .lr-account { padding: 4px; background: none; border-color: transparent; justify-content: center; }
.lr-sidebar.is-collapsed .lr-sidefoot { align-items: center; }

.lr-side-label {
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: .09em; color: var(--text-tertiary);
  padding: 14px 8px 6px;
}

/* Segmented host switcher */
.lr-segmented {
  display: flex; gap: 2px; padding: 3px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-lg);
}
.lr-segmented button {
  flex: 1 1 0; min-width: 0;
  padding: 5px 8px; border: 0; border-radius: var(--radius-md);
  background: none; color: var(--text-tertiary);
  font-size: var(--fs-xs); font-weight: 600; cursor: pointer;
  transition: background-color var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease);
}
.lr-segmented button:hover { background: var(--bg-hover); color: var(--text-secondary); }
.lr-segmented button[aria-pressed="true"] { background: var(--accent-orange); color: #fff; }
.lr-segmented button:focus-visible {
  outline: 2px solid var(--accent-orange); outline-offset: 1px;
}

/* ICON-ONLY segment. The base rule is `flex: 1 1 0` so text segments share
   the width evenly; with the label gone that stretches a 16px glyph across
   half the control and the pressed state paints a wide orange slab. These
   segments size to their icon and stay square. */
.lr-segmented--icon button {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 24px; padding: 0;
}
.lr-segmented--sm.lr-segmented--icon button { width: 26px; height: 22px; }
.lr-segmented--icon .lr-icon { margin: 0; }

/* Sidebar host chips */
.lr-hostchips { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
/* A BUTTON now — it was a <div> that looked like a control and ignored every
   click. Width, text-align and font are reset because a button inherits none
   of them from the sidebar the way a div did. */
.lr-hostchip-row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left; font: inherit;
  padding: 6px 10px; border-radius: var(--radius-lg);
  background: var(--bg-secondary); border: 1px solid var(--border-tertiary);
  font-size: var(--fs-xs); min-width: 0;
  cursor: pointer;
  transition: background-color var(--dur-slow) var(--ease),
              border-color var(--dur-slow) var(--ease);
}
.lr-hostchip-row:hover { background: var(--bg-hover); border-color: var(--border-secondary); }
.lr-hostchip-row.is-active {
  border-color: var(--accent-orange);
  background: color-mix(in srgb, var(--accent-orange) 10%, var(--bg-secondary));
}
.lr-hostchip-row.is-active b { color: var(--text-primary); }
.lr-hostchip-row b { font-weight: 600; color: var(--text-secondary); }
.lr-hostchip-row span:last-child {
  margin-left: auto; color: var(--text-tertiary);
  font-family: var(--font-mono); font-size: var(--fs-micro);
  white-space: nowrap;
}

/* Sidebar footer + account chip */
.lr-sidefoot { margin-top: auto; padding-top: 10px; display: flex; flex-direction: column; gap: 8px; position: relative; }
.lr-flag {
  font-size: var(--fs-micro); letter-spacing: .04em; text-transform: uppercase;
  font-weight: 600; color: var(--text-muted); padding: 0 4px;
}
.lr-flag.is-on { color: var(--sev-warn-fg); }

.lr-account {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px; border-radius: var(--radius-xl);
  background: var(--bg-secondary); border: 1px solid var(--border-tertiary);
  cursor: pointer; text-align: left;
  transition: background-color var(--dur-slow) var(--ease), border-color var(--dur-slow) var(--ease);
}
.lr-account:hover { background: var(--bg-hover); }
.lr-avatar {
  width: 36px; height: 36px; flex-shrink: 0; border-radius: var(--radius-full);
  background: var(--accent-orange); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: var(--fs-sm); box-shadow: var(--shadow-sm);
}
.lr-account__meta { flex: 1 1 auto; min-width: 0; }
.lr-account__name { font-size: var(--fs-sm); font-weight: 500; color: var(--text-primary); }
/* the product hardcodes text-gray-400 here in BOTH themes, a light-mode
   contrast bug — we use the token instead */
.lr-account__mail { font-size: var(--fs-xs); color: var(--text-tertiary); }
.lr-account__caret { color: var(--text-tertiary); flex-shrink: 0; }

/* .menubar — flies out to the RIGHT of the rail (slider.css:1-15) */
.lr-menubar {
  position: absolute; left: calc(100% + 10px); bottom: 12px;
  width: 265px; max-width: calc(100vw - 20px);
  z-index: 60;
  padding: 10px 12px; border-radius: var(--radius-xl);
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
  color: var(--text-secondary);
  box-shadow: var(--shadow-menu);
  animation: lr-fade-in .2s var(--ease-out);
}
.lr-menubar__head {
  padding: 4px 8px 8px; border-bottom: 1px solid var(--border-tertiary); margin-bottom: 6px;
}
.lr-menubar ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.lr-menu-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; padding: 8px 10px; border-radius: var(--radius-md);
  background: none; border: 0; cursor: pointer; text-align: left;
  font-weight: 600; color: var(--text-secondary);
  transition: background-color var(--dur-slow) var(--ease);
}
.lr-menu-item:hover { background: var(--bg-hover); }
.lr-menu-item__left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.lr-menu-item__label { font-size: var(--fs-sm); font-weight: 500; }
.lr-menu-item__kbd {
  display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0;
  padding: 1px 5px; border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-sm); color: var(--text-tertiary);
  font-size: var(--fs-micro); font-family: var(--font-mono);
}

/* ============================================================
   6. TOPBAR / PAGE HEAD
   ============================================================ */
.lr-topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 16px 24px;
  border-bottom: 1px solid var(--border-tertiary);
  background: var(--bg-primary);
}
@supports (backdrop-filter: blur(8px)) {
  .lr-topbar { background: color-mix(in srgb, var(--bg-primary) 92%, transparent); backdrop-filter: blur(8px); }
}
.lr-topbar__right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.lr-freshness {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 10px; border-radius: var(--radius-full);
  border: 1px solid var(--border-tertiary); background: var(--bg-secondary);
  font-size: var(--fs-xs); color: var(--text-tertiary); white-space: nowrap;
}

/* status dot — SHAPE carries the state as well as colour, so red/green
   colourblind readers can still tell them apart:
   live = circle, stale = diamond, dead = square. */
.lr-dot {
  width: 8px; height: 8px; flex-shrink: 0; border-radius: var(--radius-full);
  background: var(--text-muted); display: inline-block;
}
.lr-dot.is-live { background: var(--status-success); }
.lr-dot.is-stale { background: var(--status-warning); border-radius: 1px; transform: rotate(45deg); }
.lr-dot.is-dead { background: var(--status-error); border-radius: 1px; }
.lr-dot.is-idle { background: none; border: 1.5px dashed var(--sev-none-bd); }

/* ============================================================
   7. CONTROLS — buttons
   ============================================================ */
.lr-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  flex-shrink: 0;
  padding: 8px 16px; border-radius: var(--radius-lg);
  font-size: var(--fs-sm); font-weight: 600; line-height: 1.25rem;
  border: 1px solid transparent; cursor: pointer;
  background: none; color: var(--text-secondary);
  white-space: nowrap;
  transition: background-color var(--dur-slow) var(--ease),
              border-color var(--dur-slow) var(--ease),
              color var(--dur-slow) var(--ease);
}
.lr-btn:disabled { opacity: .5; cursor: not-allowed; }
.lr-btn:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 2px; }

/* the 2px #FFFFFF1F inner bevel is what makes the LogRite primary read as lit */
.lr-btn--primary {
  background: var(--accent-orange); color: #fff;
  border: 2px solid rgba(255,255,255,.12);
  box-shadow: var(--shadow-sm);
}
.lr-btn--primary:hover:not(:disabled) { background: var(--accent-orange-hover); }

.lr-btn--secondary {
  background: var(--bg-secondary); color: var(--text-secondary);
  border: 1px solid var(--border-secondary); box-shadow: var(--shadow-sm);
}
.lr-btn--secondary:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--accent-orange); }

.lr-btn--ghost { padding: 8px 12px; font-weight: 500; color: var(--text-secondary); }
.lr-btn--ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); }

/* FOOTGUN #4: two destructive reds exist. #D92D20 is the better-contrasting. */
.lr-btn--danger { background: #D92D20; color: #fff; border-color: rgba(255,255,255,.12); }
.lr-btn--danger:hover:not(:disabled) { background: #B42318; }

.lr-btn--sm { padding: 5px 10px; font-size: var(--fs-xs); }

/* A button carrying a warning about the SERVER-side channel, not about
   itself: the browser toggle still works, but a deploy finishing while this
   tab is closed will announce itself nowhere. Both tokens are theme-aware and
   defined for light and dark, and the border carries the signal as well as
   the text so it survives greyscale. */
.lr-btn.is-warn { color: var(--sev-warn-fg); border-color: var(--sev-warn-bd); }
.lr-btn.is-warn:hover:not(:disabled) { background: var(--sev-warn-bg); color: var(--sev-warn-fg); }
.lr-btn--block { width: 100%; }
.lr-btn-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.lr-iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; padding: 6px; border-radius: var(--radius-md);
  background: none; border: 1px solid transparent; cursor: pointer;
  color: var(--text-tertiary);
  transition: color var(--dur-slow) var(--ease), background-color var(--dur-slow) var(--ease);
}
.lr-iconbtn:hover:not(:disabled) { color: var(--accent-orange); background: var(--bg-hover); }
.lr-iconbtn:disabled { opacity: .5; cursor: not-allowed; }
.lr-iconbtn--danger:hover:not(:disabled) { color: var(--status-error); }
.lr-iconbtn.is-copied { color: var(--status-success); }

/* The house close button: RiCloseFill + group-hover rotate-90.
   If you copy one micro-interaction from LogRite, copy this one. */
.lr-close {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px; border-radius: var(--radius-lg);
  background: none; border: 0; cursor: pointer; color: #85888E;
  transition: color var(--dur-slow) var(--ease), background-color var(--dur-slow) var(--ease);
}
.lr-close:hover { color: var(--text-primary); background: var(--bg-hover); }
.lr-close .lr-icon { width: 20px; height: 20px; transition: transform var(--dur-slow) var(--ease); }
.lr-close:hover .lr-icon { transform: rotate(90deg); }

/* ============================================================
   8. CONTROLS — inputs, search, select, checkbox, tabs
   ============================================================ */
.lr-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.lr-field > label, .lr-field-label {
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-tertiary);
}

/* 2px border AT REST so the control does not jump 1px on focus — the product
   goes 1px -> 2px and shifts. Fixed here, not copied. */
.lr-input, .lr-select {
  width: 100%; min-width: 0;
  padding: 8px 12px; border-radius: var(--radius-lg);
  font-size: var(--fs-sm); line-height: 1.25rem;
  background: var(--bg-secondary); color: var(--text-primary);
  border: 2px solid var(--border-secondary);
  transition: border-color var(--dur-slow) var(--ease);
}
.lr-input::placeholder { color: var(--text-muted); }
.lr-input:hover:not(:disabled), .lr-select:hover:not(:disabled) { border-color: var(--accent-orange); }
.lr-input:focus-visible, .lr-select:focus-visible {
  border-color: var(--accent-orange); outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px;
}
.lr-input:disabled, .lr-select:disabled { opacity: .55; cursor: not-allowed; }
.lr-select { cursor: pointer; padding-right: 30px; appearance: none;
  background-repeat: no-repeat; background-position: right 8px center; background-size: 14px 14px; }
:root .lr-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394979C' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
.lr-select option { background: var(--bg-secondary); color: var(--text-primary); }

/* focus-within on the WRAPPER with a bare borderless input inside
   (ListSearchField.jsx:22-42) */
.lr-search {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 7px 10px; border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  border: 1px solid var(--border-secondary);
  transition: border-color var(--dur-slow) var(--ease);
}
.lr-search:hover, .lr-search:focus-within { border-color: var(--accent-orange); }
.lr-search:focus-within { outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px; }
.lr-search > .lr-icon { color: var(--text-muted); }
.lr-search input {
  flex: 1 1 auto; min-width: 0;
  background: none; border: 0; outline: none;
  font-size: var(--fs-sm); color: var(--text-secondary);
}
.lr-search input::placeholder { color: var(--text-muted); }
.lr-search input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* .anomaly-checkbox — the login "Remember for 30 days" box, and the better
   default for a dense ops surface (src/index.css:233-265). Plus the
   :focus-visible ring the product forgot. */
.lr-check {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: var(--fs-sm); color: var(--text-secondary); user-select: none;
  padding: 2px 0; white-space: nowrap;
}
.lr-check input[type="checkbox"] {
  width: 15px; height: 15px; flex-shrink: 0;
  border-radius: .25em; appearance: none; -webkit-appearance: none;
  cursor: pointer; margin: 0;
  border: 2px solid var(--border-secondary);
  background-color: var(--bg-secondary);
  transition: all .2s ease-in-out;
}
.lr-check input[type="checkbox"]:checked {
  background-color: var(--accent-orange);
  border-color: var(--accent-orange);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3E%3C/svg%3E");
  background-size: 100% 100%; background-position: center; background-repeat: no-repeat;
}
.lr-check input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--accent-orange); outline-offset: 2px;
}

/* Filled pill tabs — the newer of the product's two tab systems */
.lr-tabs {
  display: flex; align-items: center; gap: 4px; flex-wrap: nowrap;
  overflow-x: auto; padding: 4px; margin: 0;
  background: var(--bg-secondary); border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-xl);
  scrollbar-width: none;
}
.lr-tabs::-webkit-scrollbar { display: none; }
.lr-tab {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  white-space: nowrap; padding: 6px 14px; border-radius: var(--radius-lg);
  border: 0; background: none; cursor: pointer;
  font-size: var(--fs-sm); font-weight: 500; color: var(--text-secondary);
  transition: background-color var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease);
}
.lr-tab:hover { background: var(--bg-hover); }
.lr-tab[aria-selected="true"] { background: var(--accent-orange); color: #fff; }
.lr-tab[aria-selected="true"] .lr-tab__count { background: rgba(255,255,255,.22); color: #fff; }
.lr-tab__count {
  font-size: var(--fs-micro); font-family: var(--font-mono);
  padding: 0 5px; border-radius: var(--radius-full);
  background: var(--bg-hover); color: var(--text-tertiary);
}

/* soft affordance pill */
.lr-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; font-size: var(--fs-xs); border-radius: var(--radius-full);
  border: 1px solid var(--border-secondary);
  background: var(--bg-hover); color: var(--text-tertiary);
  transition: border-color var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease);
  text-decoration: none;
}
a.lr-chip:hover, button.lr-chip:hover { border-color: var(--accent-orange); color: var(--text-secondary); text-decoration: none; }

/* host chip: dev vs prod told apart by shape too — prod is filled, dev outlined */
.lr-host-tag {
  display: inline-flex; align-items: center; flex-shrink: 0;
  padding: 1px 7px; border-radius: var(--radius-full);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase;
  border: 1px solid var(--border-secondary); color: var(--text-tertiary);
  background: none;
}
.lr-host-tag.is-prod { background: var(--accent-orange); border-color: var(--accent-orange); color: #fff; }
.lr-host-tag.is-dev  { background: none; border-style: dashed; color: var(--text-tertiary); }

/* ============================================================
   9. BADGES — severity ramp, with the dashed `unset`
   ============================================================ */
.lr-badge {
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  width: fit-content; padding: 2px 6px;
  border-radius: var(--radius-md);
  font-size: var(--fs-xs); font-weight: 600;
  border: 1px solid var(--sev-none-bd); color: var(--sev-none-fg);
  background: var(--bg-secondary);
}
.lr-badge--error { color: var(--sev-error-fg); border-color: var(--sev-error-bd); background: var(--sev-error-bg); }
.lr-badge--warn  { color: var(--sev-warn-fg);  border-color: var(--sev-warn-bd);  background: var(--sev-warn-bg);  }
.lr-badge--ok    { color: var(--sev-info-fg);  border-color: var(--sev-info-bd);  background: var(--sev-info-bg);  }
.lr-badge--info  { color: var(--sev-debug-fg); border-color: var(--sev-debug-bd); background: var(--sev-debug-bg); }
.lr-badge--trace { color: var(--sev-trace-fg); border-color: var(--sev-trace-bd); background: var(--sev-trace-bg); }
/* "Nobody recorded a value here" — NOT a severity. Told apart by SHAPE. */
.lr-badge--unset { color: var(--sev-none-fg); border-color: var(--sev-none-bd);
                   background: transparent; border-style: dashed; }
.lr-badge--brand { color: var(--accent-orange-text); border-color: var(--accent-orange-border);
                   background: var(--accent-orange-bg); }

/* state pill: colour AND glyph shape */
.lr-state {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  padding: 2px 8px 2px 6px; border-radius: var(--radius-full);
  font-size: var(--fs-xs); font-weight: 600;
  border: 1px solid var(--sev-none-bd); color: var(--sev-none-fg); background: transparent;
}
.lr-state--up    { color: var(--sev-info-fg);  border-color: var(--sev-info-bd);  background: var(--sev-info-bg); }
.lr-state--warn  { color: var(--sev-warn-fg);  border-color: var(--sev-warn-bd);  background: var(--sev-warn-bg); }
.lr-state--down  { color: var(--sev-error-fg); border-color: var(--sev-error-bd); background: var(--sev-error-bg); }
.lr-state--idle  { border-style: dashed; }
/* Stopped ON PURPOSE and kept: a pre-adoption backup. Deliberately NOT the
   error palette — the whole defect was that "stopped" and "broken" looked
   identical here. Neutral fill, dashed edge, and a dot with its own shape so
   the distinction survives without colour. */
.lr-state--backup {
  color: var(--text-tertiary); border-color: var(--border-secondary);
  background: var(--bg-secondary); border-style: dashed;
}
.lr-state--backup .lr-dot {
  background: none; border: 1.5px solid currentColor; border-radius: 2px;
}
.lr-state .lr-dot { width: 7px; height: 7px; background: currentColor; }
.lr-state--warn .lr-dot { border-radius: 1px; transform: rotate(45deg); }   /* diamond */
.lr-state--down .lr-dot { border-radius: 1px; }                              /* square  */
.lr-state--idle .lr-dot { background: none; border: 1.5px dashed currentColor; }

/* ============================================================
   10. CARDS + PANELS — OUTLINED, not raised
   ============================================================ */
.lr-card {
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  min-width: 0;
}
.lr-card__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--gap-control); flex-wrap: wrap;
  padding: var(--sp-4) var(--pad-card); border-bottom: 1px solid var(--border-tertiary);
}
@media (max-width: 767px) {
  .lr-card__head { padding: var(--sp-3) var(--sp-4); }
  .lr-card__body { padding: var(--sp-4); }
  /* The actions cluster drops to its own line rather than squeezing the
     title to two characters — a head that wraps is fine, a truncated card
     title is not. */
  .lr-card__actions { flex: 1 1 100%; }
}
/* The right-hand side of a card head when it carries more than one control.
   Aligned to the baseline of the title block rather than stretched, so a
   button and a checkbox sitting together do not end up different heights. */
.lr-card__actions {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.lr-card__body { padding: var(--pad-card); min-width: 0; }
.lr-card__body--flush { padding: 0; }
.lr-card__icon {
  width: 40px; height: 40px; flex-shrink: 0; border-radius: var(--radius-lg);
  border: 1px solid var(--border-secondary);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-secondary); color: var(--accent-orange);
}
/* The dialog size. Six call sites wrote `style="width:36px;height:36px"` —
   one size, six copies, and nothing to stop the seventh being 34. */
.lr-card__icon--sm { width: 36px; height: 36px; }
/* A destructive dialog's icon. This was spelled out inline as a colour plus a
   border colour, so the one place that says "this dialog is dangerous" was an
   attribute rather than a name. */
.lr-card__icon--danger {
  color: var(--status-error); border-color: var(--sev-error-bd);
  background: var(--sev-error-bg);
}

/* Page rhythm, off the scale. The page gap and the card gap are the same
   decision made once, so a page does not need to argue about it per section. */
.lr-page {
  padding: var(--pad-page) var(--pad-page) var(--sp-8);
  display: flex; flex-direction: column; gap: var(--gap-page); min-width: 0;
}
.lr-page:focus { outline: none; }
.lr-grid { display: grid; gap: var(--gap-card); }
/* `auto-fit` + minmax, never a fixed count. 380px was wide enough that two
   cards needed 760px of CONTENT — which the 280px rail does not leave until
   about 1080px, so these silently sat single-column on a 1024px laptop. */
.lr-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.lr-grid--kpi { grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--col-stat)), 1fr)); }

/* `min(100%, …)` above is what stops a grid track being wider than its own
   container: a bare minmax(22rem, 1fr) on a 320px phone lays out a 352px
   column and the PAGE scrolls sideways, which is the single most common way
   a responsive grid breaks. */

@media (max-width: 767px) {
  .lr-page { padding: var(--sp-5) var(--sp-4) var(--sp-7); gap: var(--sp-5); }
  .lr-grid { gap: var(--sp-4); }
}
@media (max-width: 479px) {
  .lr-page { padding: var(--sp-4) var(--sp-3) var(--sp-7); }
}

/* KPI tile — summary before detail */
.lr-kpi {
  position: relative; overflow: hidden;
  background: var(--bg-secondary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-xl);
  padding: 14px 16px 14px 18px;
  box-shadow: var(--shadow-sm);
  min-width: 0;
}
/* severity STRIPE, so the tile's state survives a greyscale print */
.lr-kpi::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--border-tertiary);
}
.lr-kpi.is-ok::before   { background: var(--status-success); }
.lr-kpi.is-warn::before { background: var(--status-warning); }
.lr-kpi.is-bad::before  { background: var(--status-error); }
.lr-kpi__top {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-tertiary);
}
.lr-kpi__top .lr-icon { width: 13px; height: 13px; }
.lr-kpi__val {
  font-size: var(--fs-2xl); line-height: 2rem; font-weight: 600;
  color: var(--text-primary); margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.lr-kpi.is-warn .lr-kpi__val { color: var(--sev-warn-fg); }
.lr-kpi.is-bad  .lr-kpi__val { color: var(--sev-error-fg); }
.lr-kpi__sub { font-size: var(--fs-xs); color: var(--text-tertiary); margin-top: 2px; }

/* Host card */
.lr-hostcard { position: relative; overflow: hidden; }
.lr-hostcard::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--status-success);
}
.lr-hostcard.is-warn::before { background: var(--status-warning); }
.lr-hostcard.is-down::before { background: var(--status-error); }
.lr-hostcard__head { display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  border-bottom: 1px solid var(--border-tertiary); flex-wrap: wrap; }
.lr-hostcard__name { font-size: var(--fs-base); font-weight: 600; }
.lr-hostcard__role { font-size: var(--fs-xs); color: var(--text-tertiary);
  text-transform: uppercase; letter-spacing: .06em; }
.lr-hostcard__split { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 16px; align-items: center; }
.lr-hostcard__foot {
  padding: 10px 16px; border-top: 1px solid var(--border-tertiary);
  font-size: var(--fs-xs); color: var(--text-tertiary); font-family: var(--font-mono);
}

/* Meters */
.lr-meters { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.lr-meter__label {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: var(--fs-xs); color: var(--text-tertiary); margin-bottom: 4px;
}
.lr-meter__label b { color: var(--text-secondary); font-weight: 600;
  font-family: var(--font-mono); font-size: var(--fs-nano); }
.lr-bar { height: 6px; border-radius: var(--radius-full); background: var(--chart-track); overflow: hidden; }
.lr-bar > span { display: block; height: 100%; background: var(--status-success);
  border-radius: var(--radius-full); transition: width var(--dur-slow) var(--ease); }
.lr-bar > span.is-warn { background: var(--status-warning); }
.lr-bar > span.is-bad  { background: var(--status-error); }

/* Charts */
.lr-chart { width: 100%; min-width: 0; }
.lr-chart svg { display: block; width: 100%; }
.lr-chart-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; margin-bottom: 8px;
}
.lr-legend { display: flex; gap: 12px; flex-wrap: wrap; font-size: var(--fs-micro); color: var(--text-tertiary); }
.lr-legend > span { display: inline-flex; align-items: center; gap: 5px; }
.lr-legend i { width: 9px; height: 3px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
.lr-chart-empty {
  font-size: var(--fs-xs); color: var(--text-muted);
  border: 1px dashed var(--border-secondary); border-radius: var(--radius-lg);
  padding: 18px; text-align: center;
}
.lr-donut { display: flex; flex-direction: column; align-items: center; gap: 6px; flex-shrink: 0; }
.lr-donut svg { width: 110px; height: 110px; }
.lr-donut__cap { font-size: var(--fs-micro); color: var(--text-tertiary); text-align: center; }

/* ============================================================
   11. TABLES — the rounded-xl shell, header band, zebra, row hover
   ============================================================ */
.lr-table-scroll {
  width: 100%; max-width: 100%;
  overflow-x: auto; overflow-y: visible;
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
}
.lr-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.lr-table thead th {
  position: sticky; top: 0; z-index: 10;
  padding: 10px 16px; text-align: left; white-space: nowrap;
  font-size: var(--fs-nano); font-weight: 600;
  text-transform: uppercase; letter-spacing: .025em;
  color: var(--text-tertiary);
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-tertiary);
}
.lr-table th.is-num, .lr-table td.is-num { text-align: right; }
.lr-table td {
  padding: 9px 16px; color: var(--text-secondary);
  border-bottom: 1px solid var(--border-tertiary);
  vertical-align: middle;
}
.lr-table tbody tr { transition: background-color var(--dur-base) var(--ease); }
.lr-table tbody tr:nth-child(odd)  { background: var(--bg-secondary); }
.lr-table tbody tr:nth-child(even) { background: var(--bg-primary); }
/* ⚠ Hover feedback is NOT an affordance. This rule used to carry
   `cursor: pointer` for every <tr> in the console, so the disk drill-down's
   image rows, the dev→prod table and the Resources registry all told the
   operator they opened something and then did nothing when clicked — a
   promise made by a stylesheet that no click handler had ever agreed to.
   The pointer now belongs to `.lr-row-link`, which is put on a row by the
   code that also wires the handler, so the two cannot drift apart. */
.lr-table tbody tr:hover { background: var(--bg-hover); }
.lr-table tbody tr:last-child td { border-bottom: 0; }

/* Tables scroll INSIDE their card, and say so.
   `.lr-table-scroll` already sets overflow-x, but a scroll region with no
   visible edge is one people do not know is there: the last column simply
   looks cut off, and on the Deployments table that column is the status.
   `overscroll-behavior-x: contain` stops a horizontal flick inside the table
   turning into a browser back-navigation on trackpads and touch — losing the
   page you were reading is a far worse outcome than not scrolling. */
.lr-table-scroll {
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

@media (max-width: 767px) {
  /* Tighter gutters buy about 14px per column, which is the difference
     between five columns fitting and the table scrolling on a phone. */
  .lr-table thead th { padding: var(--sp-2) var(--sp-3); }
  .lr-table td { padding: var(--sp-2) var(--sp-3); }
  /* A sticky header inside a horizontally scrolling region repaints badly on
     mobile Safari and pins a row that is only half on screen. */
  .lr-table thead th { position: static; }
}
/* the row's own severity stripe */
.lr-table tbody tr td:first-child { position: relative; padding-left: 19px; }
.lr-table tbody tr td:first-child::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent;
}
.lr-table tbody tr.is-sick td:first-child::before { background: var(--status-warning); }
.lr-table tbody tr.is-dead td:first-child::before { background: var(--status-error); }

/* The services table gets its OWN vertical scroll container, which is what
   makes `position:sticky` on the <th> actually work. Without it the page is
   the only scroller, the header sticks to the viewport top, and the sticky
   topbar (z-index 30) covers it — a header that is present in the DOM and
   invisible on screen. This is also what the product's own `.table-container`
   does (a fixed-height shell with overflow-y:auto).
   The disk table is 4-5 rows, so it never scrolls and does not want sticky. */
#sec-services .lr-table-scroll {
  max-height: calc(100vh - 260px);
  min-height: 260px;
  overflow-y: auto;
}
/* Keyed to the SAME tier the mobile header appears at — this reserves room
   for that header, so at any other width it reserves space for a bar that is
   not there. */
@media (max-width: 1023px) {
  #sec-services .lr-table-scroll { max-height: calc(100vh - 320px); }
}
#sec-disk .lr-table thead th { position: static; }

/* Sortable column headers. The button fills the cell rather than wrapping
   only the label, so the whole header is the target — a 9px glyph is not a
   click target, and a header that sorts only when you hit the arrow reads as
   broken. */
.lr-th-sort {
  display: inline-flex; align-items: center; gap: 5px;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit;
  transition: color var(--dur-base) var(--ease);
}
.lr-table th.is-num .lr-th-sort, .lr-table th.lr-num .lr-th-sort { flex-direction: row-reverse; }
.lr-th-sort:hover { color: var(--accent-orange-text); }
.lr-th-sort:focus-visible {
  outline: 2px solid var(--accent-orange-text); outline-offset: 2px; border-radius: 3px;
}
.lr-th-sort.is-active { color: var(--accent-orange-text); }
.lr-table th[aria-sort="ascending"] .lr-th-sort,
.lr-table th[aria-sort="descending"] .lr-th-sort { color: var(--accent-orange-text); }

/* The idle glyph is present but faint on every sortable column, so which
   columns sort is legible WITHOUT hovering each one to find out. It is not
   hidden-until-hover: a control you have to discover by accident is one most
   operators never find. */
.lr-th-sort__mark { width: 12px; height: 12px; opacity: .35; flex: none; }
.lr-th-sort:hover .lr-th-sort__mark { opacity: .75; }
.lr-th-sort.is-active .lr-th-sort__mark { opacity: 1; }

/* ============================ services: list + grid =======================
   One card per SERVICE. The table this replaced had one row per CONTAINER,
   so the same service appeared once per host and once more for each stopped
   `--preadopt-` rollback copy — six of those exist on dev right now.

   The two views share every inner element and differ only in how the card
   lays out, which is what keeps them from drifting into two designs.
   ========================================================================= */

/* Spacing comes off the scale, and the GRID gets a wider gutter than the
   list. They are not the same question: stacked rows read as one object and
   a big gap between them just looks like a broken list, whereas tiles read
   as separate objects and a tight gutter makes them collide into a wall.
   The old file used 10px and 12px — close enough to look accidental at both
   jobs, which is what made the grid feel cramped. */
.lr-svc-view { display: grid; gap: var(--sp-4); padding: var(--sp-1) 0 var(--sp-2); }
.lr-svc-view--list { grid-template-columns: 1fr; }
.lr-svc-view--grid {
  /* `auto-fill` + a MINIMUM track, never a column count: the sidebar
     collapses and this container changes width without the viewport
     changing at all, so a count keyed to a breakpoint would be wrong in
     exactly the state an operator uses most. */
  grid-template-columns: repeat(auto-fill, minmax(var(--col-svc), 1fr));
  gap: var(--sp-5);
}

.lr-svc {
  display: flex; min-width: 0;
  border: 1px solid var(--border-primary); border-radius: var(--radius-xl);
  background: var(--bg-primary);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.lr-svc:hover {
  border-color: var(--border-hover); box-shadow: var(--shadow-md);
}
/* Lift only in GRID. In a list the neighbours are 1px away and a translated
   row shears against them; as tiles there is a real gutter to move inside. */
.lr-svc-view--grid .lr-svc:hover { transform: translateY(-1px); }
.lr-svc.is-attention { border-left: 3px solid var(--status-error); }
.lr-svc.is-orphaned  { border-left: 3px solid var(--status-warning); }

.lr-svc__main {
  display: flex; align-items: center; gap: var(--gap-control); min-width: 0;
  background: none; border: 0; cursor: pointer; color: inherit;
  text-align: left; padding: var(--sp-4) var(--sp-5); flex: 0 1 auto;
}
.lr-svc__main:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: -2px; }
.lr-svc__text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.lr-svc__name {
  font-weight: 600; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lr-svc__metaline { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.lr-svc__meta {
  font-size: var(--fs-micro); color: var(--text-muted); font-family: var(--font-mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lr-svc__hosts { display: flex; flex-direction: column; justify-content: center;
  min-width: 0; gap: 2px; }
.lr-svc__inst {
  display: flex; align-items: center; gap: var(--gap-control); min-width: 0;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.lr-svc__inst:hover { background: var(--bg-secondary); }
.lr-svc__num { font-variant-numeric: tabular-nums; font-size: var(--fs-xs);
  color: var(--text-tertiary); min-width: 4.5rem; text-align: right; }
.lr-svc__ports { font-family: var(--font-mono); font-size: var(--fs-micro);
  color: var(--text-tertiary); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.lr-svc__open { flex-shrink: 0; }
.lr-svc__foot {
  font-size: var(--fs-micro); color: var(--text-muted);
  padding: 0 var(--sp-5) var(--sp-3); margin: 0;
}

/* LIST: name on the left, instances filling the rest of the row. */
.lr-svc-view--list .lr-svc { flex-wrap: wrap; align-items: center; }
.lr-svc-view--list .lr-svc__main { flex: 1 1 14rem; max-width: 22rem; }
.lr-svc-view--list .lr-svc__hosts { flex: 2 1 22rem; }
.lr-svc-view--list .lr-svc__inst { padding-right: 12px; }
.lr-svc-view--list .lr-svc__ports { flex: 1 1 auto; }
.lr-svc-view--list .lr-svc__foot { flex: 1 0 100%; }

/* GRID: a tile — name on top, one line per host underneath. */
.lr-svc-view--grid .lr-svc { flex-direction: column; align-items: stretch; }
.lr-svc-view--grid .lr-svc__main { padding-bottom: var(--sp-3); }
.lr-svc-view--grid .lr-svc__hosts {
  padding: 0 var(--sp-4) var(--sp-4); gap: var(--sp-2);
}
.lr-svc-view--grid .lr-svc__inst {
  border: 1px solid var(--border-tertiary); background: var(--bg-secondary);
  flex-wrap: wrap; row-gap: var(--sp-1);
  padding: var(--sp-3);
}
.lr-svc-view--grid .lr-svc__num { min-width: 3.5rem; }
.lr-svc-view--grid .lr-row-actions { margin-left: auto; }

.lr-segmented--sm button { padding: 3px 9px; font-size: var(--fs-micro); gap: 5px; }
.lr-select--sm { padding: 3px 8px; font-size: var(--fs-xs); }

/* Refresh, while it is actually refreshing. A control that looks identical
   before and after the click is indistinguishable from one that does nothing,
   which is exactly how this button read. */
.lr-btn.is-busy { opacity: .75; cursor: progress; }
.lr-btn.is-busy .lr-icon { animation: lr-spin 1s linear infinite; }

.lr-subhead {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--text-tertiary);
  margin: 20px 0 8px;
}
.lr-probe { border: 1px solid var(--border-tertiary); border-radius: var(--radius-sm);
  padding: 8px 10px; margin-bottom: 8px; background: var(--bg-secondary); }
.lr-probe__head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.lr-probe__out { font-family: var(--font-mono); font-size: var(--fs-micro);
  white-space: pre-wrap; word-break: break-word; margin: 0;
  color: var(--text-secondary); max-height: 8rem; overflow: auto; }

/* ============================ deployment steps ===========================
   Coolify shows a deploy as a list of steps. The useful part is not the
   animation — it is knowing WHICH part is slow when a deploy takes four
   minutes. These steps are derived from the job's own log, so they cannot
   disagree with it.
   ========================================================================= */

.lr-steps { list-style: none; margin: 0 0 4px; padding: 0;
  display: grid; gap: 2px; }
.lr-step {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 8px; border-radius: var(--radius-sm);
  font-size: var(--fs-xs); color: var(--text-tertiary);
}
.lr-step__mark { width: 1rem; height: 1rem; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; }
.lr-step__label { flex: 1 1 auto; min-width: 0; }
.lr-step__time { font-variant-numeric: tabular-nums; font-size: var(--fs-micro); }
.lr-step.is-done { color: var(--text-secondary); }
.lr-step.is-done .lr-step__mark { color: var(--status-success); }
.lr-step.is-running {
  color: var(--text-primary); font-weight: 600; background: var(--bg-secondary);
}
.lr-step.is-failed { color: var(--status-error); background: var(--accent-red-bg, transparent); }
.lr-step.is-failed .lr-step__mark { color: var(--status-error); }
/* "Never ran" must not look like "still to come". A pending step on a job
   that already finished would otherwise read as a deploy stuck forever. */
.lr-step.is-skipped { opacity: .45; }
.lr-step.is-skipped .lr-step__time { font-style: italic; }
.lr-step.is-pending .lr-step__mark::before {
  content: ""; width: .5rem; height: .5rem; border-radius: 50%;
  border: 1.5px solid var(--border-secondary);
}

.lr-jobprog { padding: 12px 20px 4px; border-bottom: 1px solid var(--border-primary); }
.lr-jobprog__head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.lr-jobprog__pct { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary);
  font-variant-numeric: tabular-nums; }
.lr-jobprog__eta { font-size: var(--fs-xs); color: var(--text-tertiary); }
.lr-jobprog__basis { margin: 6px 0 0; }
.lr-bar__fill { display: block; height: 100%; background: var(--status-info);
  transition: width .4s ease; }
/* A bar whose width came from counting steps rather than from timing is
   hatched, so it never passes for a measurement. */
.lr-bar__fill.is-estimated {
  background-image: repeating-linear-gradient(45deg,
    rgba(255,255,255,.28) 0 6px, transparent 6px 12px);
}
.lr-bar--sm { height: 4px; min-width: 4rem; }
.lr-miniprog { display: inline-flex; align-items: center; gap: 8px; min-width: 9rem; }
.lr-miniprog .lr-bar { flex: 1 1 4rem; }
.lr-miniprog__txt { font-size: var(--fs-micro); color: var(--text-tertiary);
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.lr-err-text { color: var(--status-error); }
.lr-link { color: var(--accent-orange-text); text-decoration: none; }
.lr-link:hover { text-decoration: underline; }
.lr-link + .lr-link { margin-left: 8px; }
.lr-img { color: var(--text-tertiary); font-size: var(--fs-xs); font-family: var(--font-mono);
  display: inline-block; max-width: 24ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom; }
.lr-commit {
  font-family: var(--font-mono); font-size: var(--fs-nano);
  padding: 1px 6px; border-radius: var(--radius-sm);
  background: var(--bg-hover); color: var(--text-secondary);
  border: 1px solid var(--border-tertiary);
}
.lr-ports { font-family: var(--font-mono); font-size: var(--fs-nano); color: var(--text-tertiary);
  white-space: nowrap; }
.lr-row-actions { display: inline-flex; gap: 2px; justify-content: flex-end; }

.lr-empty-row { text-align: center; color: var(--text-muted); padding: 32px 16px !important; }

/* Toolbar above a table */
.lr-toolbar {
  display: flex; align-items: center; gap: var(--gap-control); flex-wrap: wrap;
  padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border-tertiary);
  min-width: 0;
}
.lr-toolbar .lr-search { flex: 1 1 260px; }

/* THE INLINE label+control pairing, for toolbars. Before this, a filter was a
   `.lr-check` — the CHECKBOX label — with `style="gap:6px"` repeated at each
   call site, so the gap was a local decision and the row sat at a rhythm no
   other toolbar shared. One class, one gap, off the scale.

   ⚠ NOT `.lr-field`, which is taken: section 8 defines that as the VERTICAL
   form group (label above control) used by the login form, the deploy branch
   picker and the disk search. Both are one class (0,1,0), so a second
   `.lr-field` rule declared later in the file does not merge with the first —
   it silently wins, and every stacked form field in the console lays out
   horizontally instead. Two different layouts need two different names. */
.lr-inline-field {
  display: inline-flex; align-items: center; gap: var(--gap-inline);
  min-width: 0; margin: 0;
}
.lr-inline-field > .lr-label { flex: none; }
/* Pushes a control to the trailing edge without `style="margin-left:auto"`
   at the call site. On a wrapped row auto margins strand it on a line of its
   own, so it is dropped below the phone breakpoint. */
.lr-inline-field--end { margin-left: auto; }

/* A vertical FLOW. Blocks stacked in a pane used to push each other down with
   `style="margin-top:18px"` repeated per block — three copies of one decision,
   at a value (18px) that is not on the spacing scale at all, so the pane sat
   between --sp-5 and --sp-6 and matched nothing else on the page.
   One container, one gap: adding a block needs no margin argument. */
.lr-flow { display: flex; flex-direction: column; gap: var(--gap-card); min-width: 0; }
.lr-flow--tight { gap: var(--sp-3); }
/* A block in a flow contributes no margin of its own — belt and braces, so a
   component that carries a default margin elsewhere does not double the gap
   only in here. */
.lr-flow > * { margin-top: 0; margin-bottom: 0; }
/* Prose and short forms get a measure; a 28rem select in a 1200px pane is a
   usability problem, not a styling one. */
.lr-measure { max-width: 28rem; }

/* A card head reused as a plain SECTION head inside a dialog: no card border
   to sit against, so no padding, and it supplies its own rhythm. Was
   `style="padding:0;margin:18px 0 8px"` at the call site. */
.lr-card__head--bare {
  padding: 0; border-bottom: 0;
  margin: var(--sp-5) 0 var(--sp-3);
}
.lr-card__head--bare:first-child { margin-top: 0; }
/* A section head INSIDE a dialog ranks below the dialog's own title, so it
   steps down one size. The call site said `style="font-size:.9375rem"` (15px,
   not on the type scale); --fs-base is the nearest step and is. Dropping the
   inline without this would have made the sub-heading LARGER than it was. */
.lr-card__head--bare .lr-title-card { font-size: var(--fs-base); line-height: 1.5rem; }

/* A filter row is the one toolbar whose controls should stay together when
   the page narrows rather than each wrapping alone. */
.lr-toolbar--filter { row-gap: var(--sp-3); }

@media (max-width: 767px) {
  .lr-toolbar { padding: var(--sp-3) var(--sp-4); }
  .lr-inline-field--end { margin-left: 0; }
}
@media (max-width: 639px) {
  /* Full-width fields: at 360px a From box and a To box side by side leave
     each about 90px, which truncates the date it is showing. */
  .lr-toolbar--filter .lr-inline-field { flex: 1 1 100%; }
  .lr-toolbar--filter .lr-inline-field .lr-input,
  .lr-toolbar--filter .lr-inline-field .lr-select,
  /* .lr-dd is the enhanced dropdown. It has to be listed here too or the
     one control on this row that is NOT a native element stays at its
     content width while its neighbours go full-bleed — the rest of the
     filter row reflows and the per-page picker does not. */
  .lr-toolbar--filter .lr-inline-field .lr-dd,
  .lr-toolbar--filter .lr-inline-field .lr-dp { flex: 1 1 auto; width: 100%; min-width: 0; }
  .lr-toolbar--filter .lr-segmented { width: 100%; }
}
.lr-count-note { font-size: var(--fs-xs); color: var(--text-tertiary); white-space: nowrap;
  font-variant-numeric: tabular-nums; }

/* Compact date inputs for a table's filter bar. The native picker is kept —
   a hand-rolled calendar is one more thing to get wrong about locales, and
   the browser's already knows the operator's. */
.lr-input--sm { padding: 3px 8px; font-size: var(--fs-xs); }
.lr-input[type="date"] { font-variant-numeric: tabular-nums; min-width: 9.5rem; }

/* Pager under a table. Centred rather than pinned right: the position count
   is the part worth reading, and it belongs between its two controls. */
.lr-pager {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--border-tertiary);
  flex-wrap: wrap;
}
.lr-pager .lr-btn[disabled] { opacity: .4; cursor: not-allowed; }

/* ============================================================
   12. ATTENTION LIST / EMPTY STATES
   ============================================================ */
.lr-attention { display: flex; flex-direction: column; }
.lr-att-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 16px; border: 0; border-bottom: 1px solid var(--border-tertiary);
  background: none; color: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--dur-base) var(--ease);
  position: relative; padding-left: 19px;
}
.lr-att-row::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--status-warning);
}
.lr-att-row.is-bad::before { background: var(--status-error); }
.lr-att-row:last-child { border-bottom: 0; }
.lr-att-row:hover { background: var(--bg-hover); }
.lr-att-row__body { flex: 1 1 auto; min-width: 0; }
.lr-att-row__name { font-weight: 600; color: var(--text-primary); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.lr-att-row__why { font-size: var(--fs-xs); color: var(--text-tertiary); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.lr-att-row__right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.lr-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 48px 16px; text-align: center; gap: 4px;
}
.lr-empty__icon {
  width: 64px; height: 64px; border-radius: var(--radius-full);
  background: var(--bg-hover); color: var(--text-muted);
  display: flex; align-items: center; justify-content: center; margin-bottom: 12px;
}
.lr-empty__title { font-size: var(--fs-lg); font-weight: 600; color: var(--text-primary); }
.lr-empty__desc { font-size: var(--fs-sm); color: var(--text-tertiary); max-width: 28rem; }

.lr-note {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--sev-none-bd); color: var(--text-secondary);
  background: var(--bg-secondary); font-size: var(--fs-sm);
}
.lr-note--warn  { border-color: var(--sev-warn-bd);  background: var(--sev-warn-bg);  color: var(--sev-warn-fg); }
.lr-note--error { border-color: var(--sev-error-bd); background: var(--sev-error-bg); color: var(--sev-error-fg); }
.lr-note--info  { border-color: var(--sev-debug-bd); background: var(--sev-debug-bg); color: var(--sev-debug-fg); }
/* flex-shrink:0 — a note is a flex row, and without it the icon is the
   thing that gives way: on a narrow card the glyph squashes to an ellipse
   while the text it belongs to keeps its width. The tracker already had
   this; the console did not, which is exactly the kind of one-sided fix
   the parity test now catches. */
.lr-note .lr-icon { margin-top: 2px; flex-shrink: 0; }

/* ============================================================
   13. KEY/VALUE + ENV LISTS
   ============================================================ */
.lr-kv { display: grid; grid-template-columns: minmax(120px, 190px) minmax(0, 1fr); gap: 0; }
.lr-kv dt {
  padding: 8px 12px 8px 0; font-size: var(--fs-xs); color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-tertiary); font-weight: 500;
}
.lr-kv dd {
  padding: 8px 0; border-bottom: 1px solid var(--border-tertiary);
  color: var(--text-secondary); min-width: 0;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  word-break: break-word;
}

/* Deployment timeline: queued -> started -> finished, each with the gap that
   follows it. Four columns so the times align down the page; the relative and
   absolute forms are separate cells rather than one glued string, because a
   reader scans one column or the other, never both. */
/* "not measured" must not read as a number. It is the answer docker gives for
   a stopped container, and rendering it as 0 on a page whose entire job is
   saying where the memory went is the one wrong answer available. */
.lr-unmeasured { color: var(--text-muted); font-style: italic; font-size: var(--fs-nano); }

/* A host card opens its own resource breakdown. Focus is visible because the
   card is reachable by keyboard, not only by mouse. */
.lr-hostcard.is-clickable { cursor: pointer; }
.lr-hostcard.is-clickable:hover { border-color: var(--border-secondary); }
.lr-hostcard.is-clickable:focus-visible {
  outline: 2px solid var(--accent-orange); outline-offset: 2px;
}

.lr-timeline { margin: 4px 0 14px; }
.lr-tl {
  display: grid;
  grid-template-columns: minmax(80px, 110px) minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid var(--border-tertiary);
  font-size: var(--fs-xs);
}
.lr-tl:last-child { border-bottom: 0; }
.lr-tl__label { color: var(--text-tertiary); font-weight: 600; }
.lr-tl__time { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.lr-tl__rel, .lr-tl__gap { color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* A step that has not happened must not look like one that happened at an
   unknown time. */
.lr-tl.is-pending .lr-tl__time { color: var(--text-muted); font-style: italic; }

@media (max-width: 767px) {
  .lr-tl { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .lr-tl__label { grid-column: 1 / -1; }
}

.lr-envlist {
  border: 1px solid var(--border-tertiary); border-radius: var(--radius-lg);
  overflow: hidden; max-height: 52vh; overflow-y: auto;
}
.lr-env-row {
  display: grid; grid-template-columns: auto minmax(120px, 260px) minmax(0, 1fr);
  gap: 10px; align-items: baseline;
  padding: 6px 10px; border-bottom: 1px solid var(--border-tertiary);
  font-family: var(--font-mono); font-size: var(--fs-nano);
}
.lr-env-row:last-child { border-bottom: 0; }
.lr-env-row:nth-child(odd) { background: var(--bg-secondary); }
.lr-env-key { color: var(--accent-orange-text); word-break: break-all; font-weight: 600; }
.lr-env-val { color: var(--text-secondary); word-break: break-all; }
.lr-env-val.is-masked { color: var(--text-muted); font-style: italic; }
/* A revealed value has to be distinguishable from one that was never secret,
   or the table stops telling you which rows are sensitive the moment one is
   open. Normal weight, accent colour, not italic. */
.lr-env-val.is-revealed { color: var(--accent-orange-text); font-style: normal; }
.lr-env-empty { padding: 20px; text-align: center; color: var(--text-muted); font-size: var(--fs-sm); }

/* ======================================================================
   THE WALLET
   ----------------------------------------------------------------------
   One CARD per retired value, not a row in a table. The old shape reused
   `.lr-env-edit-row`, whose four fixed columns were built for an editor —
   so two timestamps, an author and four buttons were crushed into a cell
   sized for a short input, which is what wrapped every date onto three
   lines in the screenshot that prompted this.

   A card can also carry the thing a row could not: the two dates as a
   described list, so "in use from" and "replaced" are labelled instead of
   being two bare timestamps the reader has to tell apart by position.
   ====================================================================== */
.lr-wallet__head {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: 0 0 var(--sp-2);
  border-bottom: 1px solid var(--border-tertiary);
}
.lr-wallet__for {
  font-family: var(--font-mono); font-weight: 600;
  color: var(--accent-orange-text); word-break: break-all;
}
.lr-wallet__entry {
  display: grid; gap: var(--sp-2) var(--sp-4);
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border-tertiary);
  font-size: var(--fs-xs);
}
.lr-wallet__entry:last-child { border-bottom: 0; }
.lr-wallet__entry > .lr-env-key { grid-column: 1 / -1; }

/* Label above value, so a long timestamp never has to share a line with the
   word describing it. */
.lr-wallet__when { margin: 0; display: grid; gap: 1px var(--sp-3);
                   grid-template-columns: auto minmax(0, 1fr); }
.lr-wallet__when dt { color: var(--text-muted); font-size: var(--fs-nano); }
.lr-wallet__when dd { margin: 0; font-family: var(--font-mono);
                      color: var(--text-secondary); word-break: break-all; }

/* Same two states as a live row, and deliberately the same colours: a past
   secret on screen must look exactly as conspicuous as a current one. */
.lr-wallet__val {
  grid-column: 1 / -1; margin: 0;
  font-family: var(--font-mono); word-break: break-all; line-height: 1.5;
}
.lr-wallet__val.is-masked { color: var(--text-muted); font-style: italic; }
.lr-wallet__val.is-revealed { color: var(--accent-orange-text); }
.lr-wallet__entry .lr-env-actions { justify-self: end; }

@media (max-width: 639px) {
  .lr-wallet__entry { grid-template-columns: minmax(0, 1fr); }
  .lr-wallet__entry .lr-env-actions { justify-self: start; }
}
/* The header of a narrowed editor: which variable is being changed, and the
   way back to all of them. Its own row rather than part of the note above,
   because the note is prose and this is a control. */
.lr-env-focus {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border-tertiary);
}

/* The EDITOR's rows, wider than the read-only list above because each carries
   an input and its controls. Four columns rather than three: name, what it is
   now, what it will become, and what can be done to it. */
.lr-env-edit-row {
  display: grid;
  grid-template-columns: minmax(140px, 22%) minmax(0, 1fr) minmax(160px, 26%) auto;
  gap: 10px; align-items: center;
  padding: 10px; border-bottom: 1px solid var(--border-tertiary);
  font-family: var(--font-mono); font-size: var(--fs-nano);
}
.lr-env-edit-row:last-child { border-bottom: 0; }
.lr-env-edit-row:nth-child(odd) { background: var(--bg-secondary); }
/* A row with an unsaved change is marked on its leading EDGE, not by a fill:
   the rows already alternate, so a background would collide with the striping
   and read as "every other row is pending". */
.lr-env-edit-row.is-pending { box-shadow: inset 3px 0 0 var(--accent-orange-text); }
.lr-env-edit-row.is-new { box-shadow: inset 3px 0 0 var(--text-muted); }
.lr-env-meta {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  color: var(--text-secondary); word-break: break-all;
}
.lr-env-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }

/* ============================================================
   14. LOG OUTPUT
   ============================================================ */
.lr-logs {
  margin: 0; padding: 14px 16px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-lg);
  font-family: var(--font-mono); font-size: var(--fs-nano); line-height: 1.55;
  color: var(--text-secondary);
  white-space: pre-wrap; word-break: break-word;
  max-height: 62vh; overflow: auto; min-height: 220px;
  tab-size: 4;
}
.lr-logs:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: -2px; }

/* The loader sits INSIDE the <pre> while a tail is being read, so the log
   pane's monospace / nano / pre-wrap treatment — which is for log text — has
   to stop applying to a sentence about waiting for it. */
.lr-logs > .lr-loading {
  font-family: var(--font-sans); font-size: var(--fs-sm);
  white-space: normal; word-break: normal; min-height: 100%;
}

/* ============================================================
   15. MODAL SYSTEM — one implementation, both dialogs
   ============================================================ */
/* The reveal step-up. z-index above .lr-modal-container (10001), because it is
   always raised FROM a dialog — the env editor or a card's Environment tab —
   and a password prompt rendered behind the thing that asked for it is a
   prompt nobody can answer. */
.lr-stepup__veil {
  position: fixed; inset: 0; z-index: 10100;
  display: flex; align-items: center; justify-content: center;
  padding: 16px; background: var(--scrim);
}
@supports (backdrop-filter: blur(4px)) {
  .lr-stepup__veil { backdrop-filter: blur(4px); }
}
.lr-stepup {
  width: 100%; max-width: 26rem;
  display: flex; flex-direction: column; gap: 10px;
  padding: 20px;
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-2xl);
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45);
}
.lr-stepup__title { font-size: var(--fs-md); font-weight: 600; color: var(--text-primary); }
.lr-stepup__note { font-size: var(--fs-xs); color: var(--text-secondary); line-height: 1.5; }
.lr-stepup__actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
.lr-dialog-error {
  font-size: var(--fs-xs); color: var(--status-error, #F04438);
  border-left: 2px solid var(--status-error, #F04438); padding-left: 8px;
}

.lr-modal-backdrop {
  position: fixed; inset: 0; z-index: 10000;
  background: var(--scrim);
}
@supports (backdrop-filter: blur(4px)) {
  .lr-modal-backdrop { backdrop-filter: blur(4px); }
}
.lr-modal-container {
  position: fixed; inset: 0; z-index: 10001;
  display: flex; align-items: center; justify-content: center;
  padding: 16px; overflow-y: auto;
}
.lr-modal {
  display: flex; flex-direction: column;
  width: 100%; max-width: 32rem; max-height: 90vh;
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-2xl);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
  animation: lr-fade-in .3s var(--ease-out);
}

/* Modal widths are a small named set, not a per-dialog measurement. `--sm` is
   a confirm, the default is a form, `--lg` is one that carries prose or a
   table. These were inline `max-width` values, which is how a fourth width
   appears without anyone deciding it should.
   ⚠ Declared AFTER `.lr-modal` deliberately: a modifier class has the SAME
   specificity (0,1,0) as the base class, so source order is the only thing
   that makes it win. Above the base rule these parse fine, apply to the right
   elements, and do nothing. */
.lr-modal--sm { max-width: 28rem; }
.lr-modal--lg { max-width: 34rem; }
.lr-modal--wide { max-width: 64rem; }
.lr-modal__header {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 24px; border-bottom: 1px solid var(--border-tertiary);
  /* the near-invisible sheen that makes the header read as a distinct band */
  background: linear-gradient(to right, rgba(255,255,255,.03), transparent, rgba(255,255,255,.03));
  flex-shrink: 0;
}
.lr-modal__title { font-size: var(--fs-xl); font-weight: 600; min-width: 0; }
.lr-modal__sub { font-size: var(--fs-xs); color: var(--text-tertiary); font-family: var(--font-mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lr-modal__head-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.lr-modal__head-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.lr-modal__body { flex: 1 1 0%; overflow-y: auto; padding: 16px 24px; min-height: 0; }
.lr-modal__body p + p { margin-top: 10px; }
.lr-modal__footer {
  display: flex; align-items: center; justify-content: flex-end; gap: 12px;
  padding: 16px 24px; border-top: 1px solid var(--border-tertiary); flex-shrink: 0;
}
.lr-modal__tabs { padding: 10px 24px 0; flex-shrink: 0; }
.lr-modal__banner {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 24px 0; padding: 12px 14px;
  border: 1px solid var(--sev-error-bd); background: var(--sev-error-bg);
  color: var(--sev-error-fg); border-radius: var(--radius-lg);
  font-size: var(--fs-sm);
}
.lr-modal__banner + .lr-modal__tabs { padding-top: 12px; }

/* The stack that replaced the single fixed banner. */
.lr-modal__banners { display: flex; flex-direction: column; gap: 8px; margin: 0 24px; }
.lr-modal__banners + .lr-modal__tabs { padding-top: 12px; }
.lr-mbanner {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--border-secondary); background: var(--bg-secondary);
  color: var(--text-secondary); font-size: var(--fs-sm);
}
.lr-mbanner.is-warn {
  border-color: var(--sev-error-bd); background: var(--sev-error-bg);
  color: var(--sev-error-fg);
}
.lr-mbanner.is-info {
  border-color: var(--sev-info-bd); background: var(--sev-info-bg);
  color: var(--sev-info-fg);
}
.lr-mbanner .lr-icon { flex: 0 0 auto; margin-top: 2px; }
.lr-mbanner__body { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.lr-mbanner__body b { font-weight: 600; }
.lr-mbanner__body p { margin: 0; }
.lr-mbanner__body .lr-count-note { color: inherit; opacity: .85; }
.lr-mbanner__acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
/* A disabled remediation button still has to be READ — it carries the reason
   in its title, and an invisible control teaches nothing. */
.lr-mbanner__acts .lr-btn[disabled] { opacity: .55; cursor: not-allowed; }
.lr-pane-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }

/* A dialog wrapper is boxless: its children are position:fixed, so it needs no
   box of its own. It exists purely so a single `hidden` attribute can show or
   hide a whole dialog — backdrop, centring box and panel — in one move.
   `[hidden] { display:none !important }` still wins over this. */
.lr-dialog { display: contents; }

/* A second dialog (sign-out confirm, action confirm, production confirm) may
   open on top of the service-detail dialog, so it gets its own rung. */
.lr-dialog--top .lr-modal-backdrop  { z-index: 10010; }
.lr-dialog--top .lr-modal-container { z-index: 10011; }

@keyframes lr-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ============================================================
   16. TOASTS
   ============================================================ */
.lr-toasts {
  position: fixed; right: 16px; top: 16px; z-index: 10020;
  display: flex; flex-direction: column; gap: 8px;
  width: min(420px, calc(100vw - 32px));
}
.lr-toast {
  padding: 12px 14px; border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  border: 1px solid var(--border-secondary);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm); color: var(--text-secondary);
  animation: lr-fade-in .3s var(--ease-out);
  position: relative; padding-left: 16px; overflow: hidden;
}
.lr-toast::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--border-secondary); }
.lr-toast.is-ok::before  { background: var(--status-success); }
.lr-toast.is-bad::before { background: var(--status-error); }
.lr-toast b { color: var(--text-primary); font-weight: 600; }
.lr-toast pre {
  margin: 8px 0 0; padding: 8px; max-height: 180px; overflow: auto;
  background: var(--bg-primary); border-radius: var(--radius-md);
  font-family: var(--font-mono); font-size: var(--fs-micro); color: var(--text-tertiary);
  white-space: pre-wrap; word-break: break-word;
}

/* ============================================================
   17. UPTIME PAGE
   ============================================================ */
.lr-tracker {
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  padding: 20px; border-radius: var(--radius-xl);
  border: 1px solid var(--border-tertiary); background: var(--bg-secondary);
}
.lr-tracker__body { flex: 1 1 280px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.lr-tracker__host {
  font-family: var(--font-mono); font-size: var(--fs-base); font-weight: 600;
  color: var(--text-primary); word-break: break-all;
}
.lr-factgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
.lr-fact {
  padding: 12px 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--border-tertiary); background: var(--bg-secondary);
}
.lr-fact__k { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-tertiary); font-weight: 600; }
.lr-fact__v { font-size: var(--fs-lg); font-weight: 600; margin-top: 4px;
  font-variant-numeric: tabular-nums; color: var(--text-primary); }
.lr-fact__n { font-size: var(--fs-xs); color: var(--text-tertiary); margin-top: 2px; }

/* ============================================================
   18. MOBILE HEADER + DRAWER
   ============================================================ */
.lr-mobile-header {
  display: none;
  position: fixed; top: 0; left: 0; width: 100%; height: var(--mobile-header);
  z-index: 45;
  align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding: 0 var(--sp-5);
  background: var(--bg-primary); border-bottom: 1px solid var(--border-primary);
}
.lr-scrim {
  display: none; position: fixed; inset: 0; z-index: 44;
  background: rgba(0,0,0,.5); border: 0;
}

@media (max-width: 1023px) {
  .lr-mobile-header { display: flex; }
  /* the drawer is always full width — --sidebar-width may be the collapsed
     68px from the desktop rail, which would give a 68px drawer */
  .lr-sidebar { transform: translateX(-100%); width: 280px; }
  .lr-sidebar.is-open { transform: translateX(0); }
  .lr-sidebar.is-collapsed { width: 280px; }
  .lr-sidebar.is-collapsed .lr-nav-item {
    width: 100%; height: auto; justify-content: flex-start; padding: 8px 12px; gap: 12px; margin: 0;
  }
  .lr-sidebar.is-collapsed .lr-nav-item__label,
  .lr-sidebar.is-collapsed .lr-nav-item__count,
  .lr-sidebar.is-collapsed .lr-brand__text,
  .lr-sidebar.is-collapsed .lr-side-label,
  .lr-sidebar.is-collapsed .lr-hostchips,
  .lr-sidebar.is-collapsed .lr-account__meta,
  .lr-sidebar.is-collapsed .lr-account__caret,
  .lr-sidebar.is-collapsed .lr-segmented { display: revert; }
  .lr-sidebar.is-collapsed .lr-brand { justify-content: flex-start; }
  .lr-sidebar.is-collapsed .lr-account { padding: 10px; background: var(--bg-secondary);
    border-color: var(--border-tertiary); justify-content: flex-start; }
  .lr-scrim.is-open { display: block; }
  .lr-main { margin-left: 0; padding-top: var(--mobile-header); }
  .lr-topbar { top: var(--mobile-header); }
  .lr-collapse-btn { display: none; }
  .lr-menubar { left: 10px; right: 10px; bottom: 80px; width: auto; }
}

@media (max-width: 767px) {
  .lr-page { padding: 16px 12px 40px; }
  .lr-topbar { padding: 12px 12px; }
  .lr-hostcard__split { grid-template-columns: minmax(0,1fr); }
  .lr-donut { align-self: center; }
  .lr-kv { grid-template-columns: minmax(0,1fr); }
  .lr-kv dt { border-bottom: 0; padding-bottom: 0; }
  .lr-env-row { grid-template-columns: auto minmax(0,1fr); }
  .lr-env-val { grid-column: 1 / -1; }
  /* One column on a phone: four columns at this width give every value a
     one-character-per-line wrap, which is the defect that made the deployment
     dialog unreadable. */
  .lr-env-edit-row { grid-template-columns: minmax(0, 1fr); }
  .lr-env-actions { justify-content: flex-start; }
  .lr-modal__header, .lr-modal__body, .lr-modal__footer { padding-left: 16px; padding-right: 16px; }
  .lr-modal__tabs, .lr-modal__banner { margin-left: 0; padding-left: 16px; padding-right: 16px; }
  .lr-modal__banner { margin-left: 16px; margin-right: 16px; padding-left: 14px; padding-right: 14px; }
  /* iOS zoom prevention (responsive.css:222-228) */
  input, select, textarea { font-size: 16px !important; }
}

.lr-collapse-btn {
  padding: 6px; border-radius: var(--radius-lg);
  background: rgba(230,65,0,.15); color: var(--accent-orange);
  border: 0; cursor: pointer; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color var(--dur-slow) var(--ease);
}
.lr-collapse-btn:hover { background: rgba(230,65,0,.25); }

/* ============================================================
   19. LOGIN PAGE
   ============================================================ */
.lr-login {
  min-height: 100vh; min-height: 100dvh;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.lr-login__col {
  width: 100%; max-width: 28rem;             /* max-w-md = 448px */
  display: flex; flex-direction: column; align-items: center;
  position: relative;
}
.lr-login__card {
  width: 100%;
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-2xl);
  box-shadow: var(--shadow-sm);
  padding: 32px 28px;
}
.lr-login__logo { width: 60px; height: 60px; display: block; margin: 0 auto 16px; }
.lr-login__title { font-size: var(--fs-2xl); line-height: 2rem; font-weight: 600; text-align: center; }
.lr-login__sub { font-size: var(--fs-sm); color: var(--text-tertiary); text-align: center;
  margin-top: 8px; margin-bottom: 28px; }
.lr-login__row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 20px 0 28px; flex-wrap: wrap;
}
.lr-login__err {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  width: 100%; margin-bottom: 16px;
  padding: 12px 14px; border-radius: var(--radius-lg);
  border: 1px solid var(--sev-error-bd); background: var(--sev-error-bg);
  color: var(--sev-error-fg); font-size: var(--fs-sm);
  animation: lr-fade-in .3s var(--ease-out);
}
.lr-login__err .lr-close { color: inherit; }
.lr-login__err .lr-close:hover { color: var(--text-primary); }
.lr-login__note {
  margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border-tertiary);
  font-size: var(--fs-xs); color: var(--text-muted); line-height: 1.6;
}
.lr-field-err {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); color: var(--status-error);
}
.lr-pw-wrap { position: relative; }
.lr-pw-wrap .lr-input { padding-right: 42px; }
.lr-pw-toggle {
  position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
  padding: 6px; border: 0; background: none; cursor: pointer;
  color: var(--text-tertiary); border-radius: var(--radius-md);
  display: inline-flex; align-items: center; justify-content: center;
}
.lr-pw-toggle:hover:not(:disabled) { color: var(--accent-orange); }
.lr-pw-toggle:disabled { opacity: .5; cursor: not-allowed; }

/* The border-spinner that used to live here is GONE. Every wait in this
   console — the login button, the deploy pane, the log views, the disk and
   reclaim panels, the first paint — is now `.lr-loading` / `.lr-loader`,
   defined at the foot of this file. Two wait affordances means a wait looks
   like the product in some places and like a generic web page in others. */

/* The theme switch on the login page sits top-right of the viewport. */
.lr-login__theme { position: fixed; top: 16px; right: 16px; z-index: 5; }

/* ============================================================
   20. DISK DRILL-DOWN · SEARCH FIELD · CLICKABLE ROWS
   ============================================================ */

/* Visually hidden but still read aloud. Used for labels on controls whose
   purpose is obvious to a sighted user from an adjacent icon. */
.lr-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Search field: a leading icon and a trailing clear button, both INSIDE the
   input's box. The input gets the padding so the caret never runs under
   either one. */
.lr-field--search { position: relative; }
.lr-field--search .lr-field__icon {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  display: flex; color: var(--text-muted); pointer-events: none;
}
.lr-input--search { padding-left: 34px; padding-right: 34px; }
/* Safari renders its own inset clear button on type=search and it lands on
   top of ours. */
.lr-input--search::-webkit-search-decoration,
.lr-input--search::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.lr-field__clear {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-tertiary); cursor: pointer;
  transition: color var(--dur-slow) var(--ease), background var(--dur-slow) var(--ease);
}
.lr-field__clear:hover { color: var(--text-primary); background: var(--bg-tertiary); }
.lr-field__clear:focus-visible {
  outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px; color: var(--text-primary);
}

/* Summary figures above a detail table. */
.lr-stat-row {
  display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 16px;
}
.lr-stat {
  flex: 1 1 8rem; min-width: 0;
  padding: 10px 12px; border-radius: var(--radius-lg);
  background: var(--bg-secondary); border: 1px solid var(--border-tertiary);
}
.lr-stat__label {
  font-size: var(--fs-nano); font-weight: 600; text-transform: uppercase;
  letter-spacing: .025em; color: var(--text-tertiary);
}
.lr-stat__value {
  margin-top: 2px; font-size: var(--fs-base); font-weight: 600;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.lr-stat--warn .lr-stat__value { color: var(--status-warning); }

/* A table row that opens something. Cursor and hover alone are not enough —
   the row is also a real <button>-like target for the keyboard, which is why
   the markup carries role/tabindex and this only styles the affordance. */
.lr-row-link { cursor: pointer; transition: background var(--dur-slow) var(--ease); }
.lr-row-link:hover { background: var(--bg-tertiary); }
.lr-row-link:focus-visible {
  outline: 2px solid var(--accent-orange); outline-offset: -2px; background: var(--bg-tertiary);
}
.lr-row-link .lr-row-chev { color: var(--text-muted); transition: color var(--dur-slow) var(--ease); }
.lr-row-link:hover .lr-row-chev { color: var(--accent-orange); }

/* "permission denied" / "this is a floor, not a total" markers in the
   filesystem breakdown. A row we could not measure must not read as a zero. */
.lr-unreadable .lr-stat__value, td.lr-unreadable { color: var(--status-warning); }
.lr-muted-cell { color: var(--text-muted); }

@media (max-width: 639px) {
  .lr-stat { flex: 1 1 100%; }
}

/* Two-line table cell: the identity on top, provenance underneath. Used by the
   disk drill-down, where "which volume" is the name and "which service owns
   it" is the line that makes the name mean something. */
.lr-cell-main { color: var(--text-primary); overflow-wrap: anywhere; }
.lr-cell-sub {
  margin-top: 2px; font-size: var(--fs-xs); color: var(--text-tertiary);
  overflow-wrap: anywhere;
}

/* ============================================================
   21. SEARCHABLE COMBOBOX  (mirrors Logrite-UI's index selector)
   ============================================================ */
.lr-combo { position: relative; min-width: 0; }
.lr-combo__input { padding-right: 56px; cursor: pointer; font-weight: 500; }
.lr-combo__tools {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; gap: 2px;
}
.lr-combo__clear, .lr-combo__toggle {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-tertiary); cursor: pointer;
  transition: color var(--dur-slow) var(--ease), background var(--dur-slow) var(--ease);
}
.lr-combo__clear:hover, .lr-combo__toggle:hover {
  color: var(--text-primary); background: var(--bg-tertiary);
}
.lr-combo__clear:focus-visible, .lr-combo__toggle:focus-visible {
  outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px;
}
.lr-combo__toggle .lr-icon { transition: transform var(--dur-slow) var(--ease); }
.lr-combo.is-open .lr-combo__toggle .lr-icon { transform: rotate(180deg); }

/* Anchored to the field rather than portalled to <body>: this console has no
   overflow-hidden ancestor between the toolbar and the page, so a portal would
   buy nothing and cost the scroll/resize repositioning the product needs. */
.lr-combo__list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 60;
  margin: 0; padding: 4px; list-style: none;
  max-height: 15rem; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg-secondary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(0,0,0,.35));
}
.lr-combo__list--up { top: auto; bottom: calc(100% + 4px); }
.lr-combo__opt {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: var(--radius-sm);
  font-size: var(--fs-sm); color: var(--text-primary); cursor: pointer;
}
.lr-combo__opt:hover, .lr-combo__opt.is-active { background: var(--bg-tertiary); }
.lr-combo__opt[aria-selected="true"] { color: var(--accent-orange-text); font-weight: 600; }
.lr-combo__opt-host {
  margin-left: auto; font-size: var(--fs-nano); text-transform: uppercase;
  letter-spacing: .025em; color: var(--text-tertiary); white-space: nowrap;
}
.lr-combo__opt-stopped { color: var(--text-muted); font-size: var(--fs-xs); }
/* Why a service in this list cannot simply be deployed. Pushed to the
   right so it reads as an annotation on the name rather than part of it,
   and amber because it is a precondition, not a failure. */
.lr-combo__note {
  margin-left: auto; font-size: var(--fs-nano); white-space: nowrap;
  color: var(--sev-warn-fg);
}
.lr-combo__group {
  padding: 6px 10px 4px; font-size: var(--fs-nano); font-weight: 600;
  text-transform: uppercase; letter-spacing: .025em; color: var(--text-tertiary);
}
.lr-combo__empty { padding: 10px; font-size: var(--fs-sm); color: var(--text-muted); }

/* ============================================================
   22. Deployments + Resources
   ============================================================ */

/* A sub-line under a stat's value. The stat block already carries a label and
   a value; this is the denominator — "4.2 GB" means nothing without "of
   48 GB" next to it. */
.lr-stat__sub {
  display: block; margin-top: 2px;
  font-size: var(--fs-nano); color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}

/* An inline proportion bar for a table cell. Deliberately NOT the existing
   .lr-meters block, which is a labelled stack for the host cards; this one
   has to sit inside a <td> without changing the row height. */
.lr-meter {
  display: block; width: 100%; min-width: 48px; height: 6px;
  border-radius: 999px; background: var(--bg-tertiary);
  border: 1px solid var(--border-tertiary); overflow: hidden;
}
.lr-meter__fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--text-muted);
  transition: width var(--dur-slow) var(--ease);
}
/* Colour carries severity, but it is never the ONLY carrier — the numeric
   value sits in the cell immediately to the left of every meter. */
.lr-meter.is-ok   .lr-meter__fill { background: var(--status-success, var(--sev-info-fg)); }
.lr-meter.is-warn .lr-meter__fill { background: var(--status-warning, var(--sev-warn-fg)); }
.lr-meter.is-crit .lr-meter__fill { background: var(--sev-error-fg); }

/* The live job log. Tall enough to read a build in, capped so the history
   table above it stays on screen. */
#deploy-log { max-height: 60vh; overflow: auto; }

/* The row for the job currently open in the log pane below. */
#deploy-rows tr.is-active { background: var(--bg-tertiary); }
#deploy-rows tr.is-active td:first-child { box-shadow: inset 2px 0 0 var(--accent-orange); }

@media (max-width: 767px) {
  /* On a phone the meter column is the first thing worth dropping: the
     percentage is already in the cell beside it. */
  #sec-resources .lr-table td:nth-child(4),
  #sec-resources .lr-table th:nth-child(4) { display: none; }
}

/* A KPI tile that opens something. The affordance has to be visible — a
   number nobody knows is clickable is the same as a number that is not. */
.lr-kpi.is-clickable { cursor: pointer; transition: border-color var(--dur-slow) var(--ease),
                                                    background var(--dur-slow) var(--ease); }
.lr-kpi.is-clickable:hover { border-color: var(--accent-orange); background: var(--bg-tertiary); }
.lr-kpi.is-clickable:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 2px; }

/* A command an operator is meant to run by hand, next to a copy button.
   Shown even for the prunes this console will NOT execute — deciding not to
   press a button for someone is different from hiding how it is done. */
.lr-cmd {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin: 10px 0; padding: 8px 10px;
  background: var(--bg-tertiary); border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-lg);
}
.lr-cmd code {
  flex: 1 1 18ch; min-width: 0; overflow-x: auto; white-space: pre;
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text-secondary);
}

/* ============================================================
   LOGRITE LOADER — the product's own wait mark
   ------------------------------------------------------------
   A faithful vanilla port of the React component at
   frontend/src/components/common/LogriteLoader.jsx, size='xl'.

   The component is six small squares, each rotate(45deg), laid out to
   draw a checkmark above a two-square base, all driven by ONE shared
   2s ease-in-out infinite keyframe with a 0.15s stagger. The six
   positions, the five keyframe percentages and both colour ramps are
   copied verbatim; only the Tailwind class names become real CSS,
   because there is no Tailwind on these pages.

     xl  =  container w-20 h-20 (5rem)  ·  square w-4 h-4 (1rem)

   Size is one custom property. The square is size/5 and every position
   is a percentage of the container, so the mark scales exactly: the
   default IS xl, and --lr-loader-size is only ever reduced where 5rem
   physically cannot fit (inside a submit button, inside a table cell).

   Colour comes from tokens, never from literals in the component rules,
   so the ramp swaps with the theme like everything else.
   ============================================================ */

:root {
  /* LIGHT ramp. The dark-theme maroon reads muddy on a white surface, so
     light uses a brighter ramp whose lightest shade still holds contrast. */
  --lr-loader-1: #B33300;
  --lr-loader-2: #B33300;
  --lr-loader-3: #B33300;
  --lr-loader-4: #E64100;
  --lr-loader-5: #FF7F4D;
  --lr-loader-6: #FFA07A;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --lr-loader-1: #802400;
    --lr-loader-2: #802400;
    --lr-loader-3: #802400;
    --lr-loader-4: #B33300;
    --lr-loader-5: #FF7F4D;
    --lr-loader-6: #FFBFA6;
  }
}

:root[data-theme="dark"],
:root.dark {
  --lr-loader-1: #802400;
  --lr-loader-2: #802400;
  --lr-loader-3: #802400;
  --lr-loader-4: #B33300;
  --lr-loader-5: #FF7F4D;
  --lr-loader-6: #FFBFA6;
}

:root[data-theme="light"] {
  --lr-loader-1: #B33300;
  --lr-loader-2: #B33300;
  --lr-loader-3: #B33300;
  --lr-loader-4: #E64100;
  --lr-loader-5: #FF7F4D;
  --lr-loader-6: #FFA07A;
}

.lr-loader {
  --lr-loader-size: 5rem;          /* xl: w-20 */
  position: relative;
  /* inline-block, and built from <span>/<i>, so the mark is phrasing content
     and is legal inside a <button> as well as on its own in a panel. */
  display: inline-block;
  width: var(--lr-loader-size);
  height: var(--lr-loader-size);
  flex: 0 0 auto;
  vertical-align: middle;
}

.lr-loader > i {
  position: absolute;
  display: block;
  width: calc(var(--lr-loader-size) / 5);    /* xl: w-4 = 1rem */
  height: calc(var(--lr-loader-size) / 5);
  opacity: 0;
  transform: rotate(45deg);
  /* LONGHANDS, deliberately — NOT the `animation:` shorthand.
     `.lr-loader > i` is (0,1,1) and `.lr-loader__s1` is (0,1,0), so this
     rule WINS on specificity for every property it sets. The shorthand
     sets all eight longhands, including `animation-delay: 0s`, which beat
     the per-square delays below and made all six squares pulse in unison:
     the mark blinked as one logo instead of building block by block. The
     React component has no such collision because its delay is an inline
     style. Never reintroduce the shorthand here. */
  animation-name: logrite-sequence;
  animation-duration: 2s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* The one keyframe the component defines, percentage for percentage. The
   1.3 overshoot at 20% is what closes the gaps between the squares; 1.15
   is where they settle and stay connected. */
@keyframes logrite-sequence {
  0%   { opacity: 0; transform: rotate(45deg) scale(0.9); }
  20%  { opacity: 1; transform: rotate(45deg) scale(1.3); }
  30%  {             transform: rotate(45deg) scale(1.15); }
  80%  { opacity: 1; transform: rotate(45deg) scale(1.15); }
  100% { opacity: 0; transform: rotate(45deg) scale(1.15); }
}

/* Sequence order: bottom pair, then the checkmark left-to-right.
   Explicit index classes rather than :nth-child, so the mark survives a
   caller appending anything to the container. */
.lr-loader__s1 { top: 85%; left: 23%; background: var(--lr-loader-1); animation-delay: 0s;    }
.lr-loader__s2 { top: 85%; left: 50%; background: var(--lr-loader-2); animation-delay: .15s;  }
.lr-loader__s3 { top: 28%; left: 22%; background: var(--lr-loader-3); animation-delay: .30s;  }
.lr-loader__s4 { top: 42%; left: 36%; background: var(--lr-loader-4); animation-delay: .45s;  }
.lr-loader__s5 { top: 28%; left: 50%; background: var(--lr-loader-5); animation-delay: .60s;  }
.lr-loader__s6 { top: 14%; left: 64%; background: var(--lr-loader-6); animation-delay: .75s;  }

/* Smaller instances. NOT a design choice — these are the only places a 5rem
   mark physically cannot go: beside a line of text in a note row, inside a
   control, and inside a 20px status strip. Same six squares, same keyframe,
   same ramp, same percentages; only --lr-loader-size differs. Everything
   else on both sites is xl. */
/* ============================================================
   THE ROUND SPINNER — for buttons and inline waits
   ------------------------------------------------------------
   The LogriteLoader used to be shrunk to 1.25rem to fit inside a button.
   At that size each of its six squares is FOUR PIXELS, so the sequence that
   draws a checkmark at 5rem reads as a handful of dots blinking on and off
   at random. It was the product's mark in name only.

   The product does not do that either. Logrite-UI has a separate,
   centralised round loading effect for exactly this job
   (`src/index.css` → `.loading-effect` / `.animate-spin`), and the inline
   form used in buttons is
       w-4 h-4 rounded-full border-2 border-current border-t-transparent
       animate-spin
   which is what this is: same 1s linear infinite rotation, same
   transparent-top ring, and `currentColor` so it inherits the button's
   own text colour on every variant without a second palette.

   The LogriteLoader keeps the jobs it is good at — a page, a panel, a card
   — at the one size it was designed for.
   ============================================================ */
.lr-spin {
  display: inline-block;
  flex: 0 0 auto;
  width: 1rem;                       /* w-4 */
  height: 1rem;
  border: 2px solid currentColor;    /* border-2 border-current */
  border-top-color: transparent;     /* border-t-transparent */
  border-radius: 9999px;             /* rounded-full */
  animation: lr-spin 1s linear infinite;
  vertical-align: middle;
}
.lr-spin--sm { width: .875rem; height: .875rem; border-width: 2px; }
.lr-spin--lg { width: 1.25rem; height: 1.25rem; }

/* The product's keyframe, verbatim: a `to` frame and nothing else, so the
   element starts from whatever rotation it already has. */
@keyframes lr-spin {
  to { transform: rotate(360deg); }
}

/* A rotation is the one motion a reduced-motion reader still needs here —
   it is the only thing saying "working". The global clamp would freeze it
   at a single frame, which is indistinguishable from a broken icon, so the
   rotation is kept and simply slowed. */
@media (prefers-reduced-motion: reduce) {
  .lr-spin {
    animation: lr-spin 2s linear infinite !important;
    animation-duration: 2s !important;
    animation-iteration-count: infinite !important;
  }
}

/* The wait block: mark above its sentence, centred, laid out with gap. */
.lr-loading {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-4);
  min-height: 10rem;
  color: var(--text-tertiary);
  font-size: var(--fs-sm);
  text-align: center;
}
.lr-loading__text { max-width: 34ch; line-height: 1.5; }

/* The whole-page first paint. The console IS a wait until its first payload
   lands, so the mark gets real vertical room rather than sitting in a corner
   of an otherwise empty page. */
.lr-loading--page { min-height: min(60vh, 28rem); }

/* ============================================================
   FIRST PAINT — the loader is the ONLY legible thing
   ------------------------------------------------------------
   Before /api/overview answers there is nothing to show, and the console
   used to prove it by showing the loader NEXT TO an empty section: card
   chrome, headings and "—" placeholders with no numbers behind them. A wait
   and an answer on screen together reads as half-broken.

   So: the shell (sidebar, header, the routed section) is blurred and made
   inert, and this overlay sits above it, unblurred, as a sibling. `filter`
   on an ancestor would blur the overlay too — which is exactly why
   #first-load is not inside .lr-shell any more.

   The blur is deliberately light. Heavier reads as a modal dialog the user
   has to dismiss; this is a page that is still arriving.
   ============================================================ */
.lr-boot {
  position: fixed;
  background: var(--bg-primary);   /* fallback; the color-mix below wins */
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Not fully opaque: the blurred shell behind should stay readable AS a
     shape, so the page reads as "loading" and not as "navigated away". */
  /* --bg-primary is what <body> paints, and it is defined for all four
     theme blocks. (--bg-base does not exist in this stylesheet; using it
     would have fallen back to transparent and shown no veil at all.) */
  background: color-mix(in srgb, var(--bg-primary) 62%, transparent);
}
.lr-boot[hidden] { display: none; }

body.is-booting .lr-shell {
  filter: blur(5px);
  /* A blurred control is still clickable and still focusable, which is how
     someone tabs into a button they cannot read and fires an action on a
     host that has not loaded. Both are closed here, not just the visual. */
  pointer-events: none;
  user-select: none;
}
body.is-booting .lr-shell * { pointer-events: none !important; }

/* The blur is a transition, not a state to be endured: a reduced-motion
   reader gets the dimming without the focus pull of a blur. */
@media (prefers-reduced-motion: reduce) {
  body.is-booting .lr-shell { filter: none; opacity: .5; }
}

/* Backdrop-filter is the nicer effect where it exists (it frosts what is
   actually behind the overlay rather than pre-blurring the shell), but it is
   applied IN ADDITION, never INSTEAD: on a browser without it the shell blur
   above is what does the work. */
@supports (backdrop-filter: blur(2px)) {
  .lr-boot { backdrop-filter: blur(2px); }
}

/* Inline variant: mark beside the sentence, for a note row or a toolbar. */
.lr-loading--inline {
  flex-direction: row; gap: var(--sp-3);
  padding: 0; min-height: 0; text-align: left;
}
.lr-loading--inline .lr-loading__text { max-width: none; }

/* A wait inside a table body, so the columns do not collapse behind it. */
.lr-loading-cell { padding: var(--sp-6) var(--sp-4) !important; text-align: center; }
.lr-loading-cell .lr-loading { padding: 0; min-height: 0; }

/* REDUCED MOTION.
   The global reduce block above sets animation-iteration-count:1 and
   animation-duration:.01ms with !important, which would land every square
   on the 100% frame — opacity 0 — and leave an INVISIBLE loader. So the
   mark is made static and fully visible here, and the only movement left
   is a slow breath on the container. Later in the file and !important, so
   it beats the global rule. */
@media (prefers-reduced-motion: reduce) {
  /* The global reduce block at the top of this file clamps EVERY animation to
     .01ms and one iteration with !important. On this mark that lands all six
     squares on the 100% frame — opacity 0 — so the loader would be invisible.
     It has to be answered here; the only question is with what.

     It used to be answered with a different animation: static squares plus a
     slow breath on the container. That removed the invisible-loader bug and
     introduced a worse one — the console became the only surface in the
     product showing a loader nobody else has. LogriteLoader.jsx carries no
     reduced-motion branch, so the product's mark sequences for everyone.

     So: exempt this ONE component from the clamp and let it run the identical
     sequence. The animation is a 2s opacity/scale cycle on six 1rem squares —
     no travel, no parallax, no flashing (it never crosses 3Hz). That is the
     class of motion the reduce preference is not aimed at, and matching the
     product everywhere is worth more here than a blanket rule applied to a
     mark it was never about. */
  .lr-loader > i {
    animation-name: logrite-sequence !important;
    animation-duration: 2s !important;
    animation-timing-function: ease-in-out !important;
    animation-iteration-count: infinite !important;
    animation-duration: 2s !important;
    animation-iteration-count: infinite !important;
  }
  .lr-loader__s1 { animation-delay: 0s    !important; }
  .lr-loader__s2 { animation-delay: .15s  !important; }
  .lr-loader__s3 { animation-delay: .30s  !important; }
  .lr-loader__s4 { animation-delay: .45s  !important; }
  .lr-loader__s5 { animation-delay: .60s  !important; }
  .lr-loader__s6 { animation-delay: .75s  !important; }
}

/* ============================================================
   THE DISK PAGE
   ------------------------------------------------------------
   Reading order is summary → composition → detail. Everything here is
   flex/grid with `gap` off the --sp-* scale; no per-element margins.
   ============================================================ */

/* The card carries a severity STRIPE, the same 3px treatment .lr-kpi uses,
   so a column of host cards can be scanned for the bad one without reading a
   single number. */
.lr-disk { position: relative; overflow: hidden; }
.lr-disk::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--border-tertiary);
}
.lr-disk.is-ok::before   { background: var(--status-success); }
.lr-disk.is-warn::before { background: var(--status-warning); }
.lr-disk.is-bad::before  { background: var(--status-error); }

.lr-disk__ident  { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.lr-disk__title  { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }

.lr-disk__body {
  display: flex; flex-direction: column; gap: var(--sp-6);
}

/* Summary: the donut sits beside the figures on a wide card and above them on
   a narrow one. minmax(0,1fr) on the figures column is what stops a long byte
   string forcing the card wider than its grid cell. */
.lr-disk__summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: center;
}
.lr-disk__figures { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
.lr-disk__fillwrap { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }

.lr-disk__detail { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
.lr-disk__block  { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }

/* The composition bar. One track = the whole filesystem. State is carried by
   the severity colour AND, for the reclaimable and reserved parts, by a
   TEXTURE — so the bar still parses in greyscale and with a red-green colour
   deficiency, which a three-colour bar would not. */
.lr-fill {
  display: flex; width: 100%; height: 14px; min-width: 0;
  border-radius: var(--radius-full); overflow: hidden;
  background: var(--chart-track);
  border: 1px solid var(--border-tertiary);
}
.lr-fill__seg {
  display: block; height: 100%; min-width: 0;
  transition: width var(--dur-slow) var(--ease), background-color var(--dur-slow) var(--ease);
}
.lr-fill__seg.is-ok   { background: var(--status-success); }
.lr-fill__seg.is-warn { background: var(--status-warning); }
.lr-fill__seg.is-bad  { background: var(--status-error); }
/* hatched = "in use, but docker would give it back" */
.lr-fill__seg.is-reclaim {
  background: repeating-linear-gradient(45deg,
    var(--accent-orange) 0 3px, var(--accent-orange-light) 3px 6px);
}
/* pinstriped = "counted in capacity, reserved for root, never yours" */
.lr-fill__seg.is-reserved {
  background: repeating-linear-gradient(90deg,
    var(--border-secondary) 0 2px, transparent 2px 4px);
}
.lr-fill__cap {
  font-size: var(--fs-xs); color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}

/* A third line on a summary figure: the number, then what it is a share of. */
.lr-stat__sub {
  margin-top: 2px; font-size: var(--fs-nano); color: var(--text-tertiary);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.lr-stat__sub:empty { display: none; }
/* The stat row inside a card body supplies its own gap, so it must not also
   carry the bottom margin the modal version needs. */
.lr-stat-row--flush { margin-bottom: 0; gap: var(--sp-3); }
/* "there is a meaningful amount here" — brand, not severity: reclaimable
   space is an opportunity, not a fault. */
.lr-stat--brand { border-color: var(--accent-orange-border); background: var(--accent-orange-bg); }
.lr-stat--brand .lr-stat__value { color: var(--accent-orange-text); }

/* Figures are compared down a column, so every numeric cell is tabular. */
.lr-table td.is-num, .lr-table th.is-num,
.lr-table td.lr-num, .lr-table th.lr-num { font-variant-numeric: tabular-nums; }

@media (max-width: 767px) {
  .lr-disk__summary { grid-template-columns: minmax(0, 1fr); justify-items: stretch; }
  .lr-disk__summary > .lr-donut { justify-self: center; }
}

/* ============================================================
   NARROW PHONES — 360px is the floor this console is built to.
   Nothing here hides data; it re-flows it, and the one column that
   goes is a bar whose number is already in the cell beside it.
   ============================================================ */
@media (max-width: 479px) {
  .lr-page { padding: 12px 10px 40px; gap: 14px; }
  .lr-card__head { padding: 12px 12px; gap: var(--sp-3); }
  .lr-card__body { padding: 12px; }
  .lr-grid--kpi { grid-template-columns: minmax(0, 1fr); }
  .lr-grid--2   { grid-template-columns: minmax(0, 1fr); }
  .lr-stat-row  { gap: var(--sp-2); }
  .lr-toolbar   { gap: var(--sp-2); }
  /* A row of buttons must wrap rather than push the card sideways. */
  .lr-btn-row   { flex-wrap: wrap; }
  .lr-btn-row > .lr-btn { flex: 1 1 auto; justify-content: center; }
  /* The donut is the headline; give it the width it needs and no more. */
  .lr-donut svg { width: 96px; height: 96px; }
  .lr-loading   { padding: var(--sp-5) var(--sp-3); }
  /* The mark itself does NOT shrink here. LogriteLoader at size='xl' is
     w-20 (5rem) at every viewport width, and a console-only breakpoint that
     quietly made it 4rem is exactly the kind of drift that makes the same
     loader look like two different loaders. Only the padding around it
     responds. */
  .lr-cmd       { flex-direction: column; align-items: stretch; }
  .lr-modal__header, .lr-modal__body, .lr-modal__footer {
    padding-left: 12px; padding-right: 12px;
  }
}

/* The last line of defence against a sideways page. Every wide thing on both
   sites already scrolls inside its own container; this makes sure a long
   unbroken token — an image digest, a mount path, a 60-char container name —
   cannot be the one that gets through. */
.lr-page, .lr-card, .lr-card__body, .lr-modal__body { min-width: 0; }
.lr-cell-main, .lr-cell-sub, .lr-hint, .lr-count-note,
.lr-stat__value, .lr-stat__sub, .lr-loading__text { overflow-wrap: anywhere; }

/* ============================================================
   LOGRITE DATE PICKER — a dependency-free port of Logrite-UI's
   src/components/common/LogriteDatePicker.jsx.

   The app gets this look from react-datepicker plus ~120 lines of override.
   These consoles have no build step and no React, so the calendar is built
   by hand — but every value below is lifted from that component rather than
   re-chosen, because "looks close" is how two surfaces of one product drift
   apart. Selected day #E64100 / white, today bold orange, hover
   .375rem-radius, header on --bg-secondary, popover on --shadow-lg.

   ⚠ It replaces `<input type="date">`, whose popup is UA chrome: it cannot
   be themed, and Safari, Chrome and Firefox each render a different one.
   That is why this exists rather than a restyled native field — the native
   control looked right until it was opened.
   ============================================================ */

.lr-dp { position: relative; display: inline-flex; min-width: 0; }
.lr-dp__field {
  display: inline-flex; align-items: center; gap: var(--gap-inline);
  min-width: 0;
  padding: 7px var(--sp-3) 7px var(--sp-3);
  border: 1px solid var(--border-secondary); border-radius: var(--radius-lg);
  background: var(--bg-primary); color: var(--text-primary);
  font: inherit; font-size: var(--fs-sm); text-align: left; cursor: pointer;
  transition: border-color var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease);
}
/* Filling the container is the WRAPPER's decision, not the field's. As a
   blanket `width:100%` this stretched every standalone field to the width of
   whatever flex row it was dropped into. */
.lr-dp > .lr-dp__field { width: 100%; }
.lr-dp__field:hover { border-color: var(--border-secondary); }
.lr-dp__field:focus-visible,
.lr-dp.is-open .lr-dp__field {
  outline: none;
  border-color: var(--accent-orange);
  box-shadow: 0 0 0 1px var(--accent-orange);
}
.lr-dp__icon { color: var(--text-tertiary); flex: none; }
/* The VALUE and the PLACEHOLDER are different colours — an empty bound and a
   set one must not look alike, because "no lower bound" and "the 1st" return
   very different row counts and the table gives no other clue which it used. */
.lr-dp__value { flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.lr-dp__value.is-empty { color: var(--text-muted); }
/* Clear sits INSIDE the field: a bound you cannot unset without retyping is
   how a filter ends up permanently narrowed. */
.lr-dp__clear {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; padding: 0; margin-right: -2px;
  border: 0; border-radius: var(--radius-full);
  background: none; color: var(--text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.lr-dp__clear:hover { background: var(--bg-hover); color: var(--text-primary); }
.lr-dp__clear:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 1px; }

.lr-dp--sm .lr-dp__field { padding: 5px var(--sp-2); font-size: var(--fs-xs); }
/* The same size, applied to the FIELD itself. `.lr-dp--sm` is a modifier on
   the wrapper and reaches the field by descent, so a call site that renders a
   bare field with no wrapper — the tracker's date-range triggers do — gets
   the class and none of its effect. */
.lr-dp__field--sm { padding: 5px var(--sp-2); font-size: var(--fs-xs); }

/* ---- the popover ---- */
.lr-dp__pop {
  position: absolute; z-index: 60; top: calc(100% + 6px); left: 0;
  min-width: 17.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3);
  animation: lr-dp-in var(--dur-fast) var(--ease-out);
}
/* Right-aligned when the field sits near the viewport edge — set in JS, not
   guessed here, because the edge depends on the sidebar's collapsed state. */
.lr-dp__pop.is-right { left: auto; right: 0; }
.lr-dp__pop[hidden] { display: none; }
@keyframes lr-dp-in { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .lr-dp__pop { animation: none; } }

.lr-dp__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-inline); padding-bottom: var(--sp-3);
}
.lr-dp__month {
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-primary);
}
.lr-dp__nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1px solid transparent; border-radius: var(--radius-md);
  background: none; color: var(--text-tertiary); cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.lr-dp__nav:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); }
.lr-dp__nav:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 1px; }
.lr-dp__nav:disabled { opacity: .35; cursor: not-allowed; }

.lr-dp__grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
.lr-dp__dow {
  font-size: var(--fs-micro); font-weight: 600; color: var(--text-tertiary);
  text-align: center; padding: var(--sp-1) 0;
}
.lr-dp__day {
  display: inline-flex; align-items: center; justify-content: center;
  height: 30px; padding: 0; border: 0; border-radius: .375rem;
  background: none; color: var(--text-secondary);
  font: inherit; font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.lr-dp__day:hover:not(:disabled):not([aria-selected="true"]) {
  background: var(--bg-hover); color: var(--text-primary);
}
.lr-dp__day:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: -2px; }
.lr-dp__day.is-outside { color: var(--text-muted); opacity: .55; }
.lr-dp__day.is-today { font-weight: 700; color: var(--accent-orange-text); }
.lr-dp__day[aria-selected="true"] {
  background: var(--accent-orange); color: #fff; font-weight: 600;
}
/* A day outside the allowed window is disabled, never hidden — a calendar
   that silently omits dates reads as a rendering fault. */
.lr-dp__day:disabled { color: var(--text-muted); opacity: .4; cursor: not-allowed; }
.lr-dp__day.is-today[aria-selected="true"] { color: #fff; }

.lr-dp__foot {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--border-tertiary);
}
.lr-dp__preset {
  flex: 1 1 auto; padding: 5px var(--sp-2);
  border: 1px solid var(--border-tertiary); border-radius: var(--radius-md);
  background: var(--bg-secondary); color: var(--text-secondary);
  font: inherit; font-size: var(--fs-micro); font-weight: 600; cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.lr-dp__preset:hover {
  background: var(--accent-orange-bg); color: var(--accent-orange-text);
  border-color: var(--accent-orange-border);
}
.lr-dp__preset:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 1px; }

/* On a phone the popover is a centred sheet rather than an anchored card:
   17.5rem anchored to a field near the right edge overflows a 360px screen,
   and `overflow-x` on the page would let the whole console slide sideways. */
@media (max-width: 639px) {
  .lr-dp { display: flex; width: 100%; }
  .lr-dp__pop {
    position: fixed; top: 50%; left: 50%; right: auto;
    transform: translate(-50%, -50%);
    width: min(20rem, calc(100vw - 2rem));
    box-shadow: var(--shadow-modal);
  }
  .lr-dp__pop.is-right { left: 50%; right: auto; }
  .lr-dp__day { height: 34px; }
}

/* ============================================================
   CUSTOM DROPDOWN (.lr-dd)
   ------------------------------------------------------------
   The native <select> is the one control on this console the browser
   still draws itself. `appearance: none` restyles the CLOSED box and
   nothing else: the open list is an OS menu, so it ignores the theme,
   the radius, the focus ring and the type scale, and on macOS it paints
   a light popup over a dark console. Every other picker here — the
   combobox, the date picker — is ours; this was the odd one out.

   Built the same way as the date picker, and for the same reason: the
   <select> STAYS in the DOM as the value, and app.js enhances it into
   this listbox. `$("svc-sort").value`, `.addEventListener("change")`
   and the URL route are untouched readers, and a script that fails
   leaves a working native control behind rather than a dead button.
   ============================================================ */
.lr-dd { position: relative; display: inline-flex; min-width: 0; }
.lr-dd--block { display: flex; width: 100%; }

/* The trigger mirrors .lr-input exactly — same 2px resting border, so a
   dropdown and a text box on one toolbar row are the same height to the
   pixel. They were not before: the select's own arrow box added 2px. */
.lr-dd__btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  width: 100%; min-width: 0;
  padding: 8px 12px; border-radius: var(--radius-lg);
  font: inherit; font-size: var(--fs-sm); line-height: 1.25rem; font-weight: 500;
  text-align: left;
  background: var(--bg-secondary); color: var(--text-primary);
  border: 2px solid var(--border-secondary); cursor: pointer;
  transition: border-color var(--dur-slow) var(--ease),
              background-color var(--dur-slow) var(--ease);
}
.lr-dd__btn:hover:not(:disabled) { border-color: var(--accent-orange); }
.lr-dd__btn:focus-visible {
  border-color: var(--accent-orange);
  outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px;
}
.lr-dd__btn:disabled { opacity: .55; cursor: not-allowed; }
.lr-dd.is-open .lr-dd__btn { border-color: var(--accent-orange); }
.lr-dd__value { flex: 1 1 auto; min-width: 0; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.lr-dd__value.is-placeholder { color: var(--text-muted); font-weight: 400; }
.lr-dd__caret {
  flex: 0 0 auto; color: var(--text-muted);
  transition: transform var(--dur-base) var(--ease), color var(--dur-base) var(--ease);
}
.lr-dd.is-open .lr-dd__caret { transform: rotate(180deg); color: var(--accent-orange); }

.lr-dd--sm .lr-dd__btn { padding: 3px 8px; font-size: var(--fs-xs); gap: var(--sp-1); }

/* The popup. Fixed-positioned and placed by script, for the same reason
   the combobox list is: a toolbar with `overflow: hidden` anywhere in its
   ancestry clips an absolutely-positioned menu, and the clip is invisible
   until the one row you need is the one that got cut. */
.lr-dd__pop {
  position: fixed; z-index: 90; min-width: 9rem;
  max-height: 17rem; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-1); margin: 0;
  list-style: none;
  background: var(--bg-primary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-menu);
  animation: lr-dd-in var(--dur-fast) var(--ease-out);
  scrollbar-width: thin;
}
@keyframes lr-dd-in { from { opacity: 0; transform: translateY(-4px); } }
.lr-dd__opt {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 7px var(--sp-3); border-radius: var(--radius-md);
  font-size: var(--fs-sm); color: var(--text-secondary);
  cursor: pointer; white-space: nowrap;
}
.lr-dd__opt:hover, .lr-dd__opt.is-active {
  background: var(--bg-tertiary); color: var(--text-primary);
}
.lr-dd__opt[aria-selected="true"] {
  color: var(--accent-orange-text); font-weight: 600;
  background: var(--accent-orange-bg);
}
.lr-dd__opt[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
/* The tick reserves its width whether or not it is drawn, so choosing a
   different option does not shift every label 20px sideways. */
.lr-dd__tick { flex: 0 0 auto; width: 14px; height: 14px; opacity: 0; }
.lr-dd__opt[aria-selected="true"] .lr-dd__tick { opacity: 1; }
.lr-dd__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lr-dd__empty {
  padding: var(--sp-3); font-size: var(--fs-xs); color: var(--text-muted);
  text-align: center;
}
/* The <select> it was built from. `hidden` would take it out of the a11y
   tree AND out of form submission; this keeps it as the value-bearing
   element while the listbox above carries the semantics. */
.lr-dd__native {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
@media (prefers-reduced-motion: reduce) {
  .lr-dd__pop { animation: none; }
  .lr-dd__caret { transition: none; }
}

/* A muted notification toggle. It is still an ACTIVE control — the operator
   can turn it back on — so it is not styled as disabled; it is styled as
   deliberately quiet, which is a different claim. */
.lr-btn.is-muted { color: var(--text-muted); }
.lr-btn.is-muted .lr-icon { color: var(--text-muted); }

/* ============================================================
   THE ROW-DETAIL DIALOG (#info-dialog)
   ------------------------------------------------------------
   Sections stacked off the spacing scale, no per-element margins.
   ⚠ The wrapper is deliberately NOT a .lr-kv: kvList() returns its
   own <dl class="lr-kv">, and a .lr-kv inside a .lr-kv lands in the
   outer grid's 190px label column, where every value wraps one
   character per line.
   ============================================================ */
.lr-info__block { display: flex; flex-direction: column; gap: var(--sp-2); }
.lr-info__block + .lr-info__block {
  margin-top: var(--sp-5); padding-top: var(--sp-5);
  border-top: 1px solid var(--border-tertiary);
}
.lr-info__head {
  font-size: var(--fs-nano); font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-tertiary); margin: 0;
}
.lr-info__text {
  margin: 0; font-size: var(--fs-sm); line-height: 1.55;
  color: var(--text-secondary);
}
.lr-info__block .lr-hint { margin: 0; }

/* A list of things you can go INTO, inside the row-detail dialog. Real
   <button>s, so the keyboard reaches them and the disabled state is a real
   disabled state rather than a class that only looks inert. */
.lr-info__row {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; padding: var(--sp-3);
  border: 1px solid var(--border-tertiary); border-radius: var(--radius-lg);
  background: var(--bg-secondary); color: inherit; font: inherit;
  text-align: left; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.lr-info__row + .lr-info__row { margin-top: var(--sp-2); }
.lr-info__row:hover:not(:disabled) {
  border-color: var(--accent-orange); background: var(--bg-tertiary);
}
.lr-info__row:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 1px; }
/* Inert because there is genuinely nothing behind it — the title says which,
   and it must not read as "temporarily unavailable". */
.lr-info__row:disabled { cursor: default; opacity: .6; }
.lr-info__row-main { flex: 1 1 auto; min-width: 0; display: flex;
                     flex-direction: column; gap: 1px; }
.lr-info__row-name { font-size: var(--fs-sm); font-weight: 600; color: var(--text-primary); }
.lr-info__row-sub { font-size: var(--fs-xs); color: var(--text-tertiary); }
.lr-info__row-val {
  flex: 0 0 auto; font-size: var(--fs-sm); font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--text-secondary);
}
.lr-info__row:hover:not(:disabled) .lr-row-chev { color: var(--accent-orange); }

/* A summary figure that is also a way in. Same box as .lr-stat — it must
   not look like a different KIND of thing, only like one you can press. */
.lr-stat--action {
  display: block; width: 100%; font: inherit; text-align: left;
  cursor: pointer; color: inherit;
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.lr-stat--action:hover { border-color: var(--accent-orange); background: var(--bg-tertiary); }
.lr-stat--action:focus-visible { outline: 2px solid var(--accent-orange); outline-offset: 1px; }
.lr-stat--action .lr-stat__label {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-1);
}
.lr-stat--action .lr-row-chev {
  color: var(--text-muted); transition: color var(--dur-fast) var(--ease),
                                        transform var(--dur-fast) var(--ease);
}
.lr-stat--action:hover .lr-row-chev { color: var(--accent-orange); transform: translateX(2px); }

/* ⚠ A table cell whose contents are a label and a trailing chevron. The
   chevron used to be positioned with `vertical-align: -2px` on an inline
   SVG, which has nothing to align against in a cell with no shared baseline:
   the glyph sat high and moved with the font size. Flex centres it, and
   `justify-content: space-between` puts it at the edge the row opens
   towards instead of hanging 6px off the end of the word. */
.lr-cell-link {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); min-width: 0;
}
.lr-cell-link > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ============================================================
   SEGMENTED CONTROL — the auto-width variant
   ------------------------------------------------------------
   The base rule is `flex: 1 1 0`, which is right for the host switcher
   (two segments that should split the rail evenly) and wrong for a set
   of quick ranges: it made "All" exactly as wide as "30 days" and then
   painted the pressed state across a quarter of the control, which is
   how four small filters ended up reading as a colour block.
   ============================================================ */
.lr-segmented--auto button { flex: 0 1 auto; padding: 5px var(--sp-3); }
/* --sm drops to 10px, which is below this console's floor for anything a
   finger has to hit. The range group is a primary filter, not a caption. */
.lr-segmented--sm.lr-segmented--auto button {
  font-size: var(--fs-xs); padding: 4px var(--sp-3); letter-spacing: 0;
}
.lr-segmented--auto button[aria-pressed="true"] { box-shadow: var(--shadow-sm); }

/* The two "start something" cards on Deployments, side by side. Equal
   heights so the two toolbars line up rather than one card's footer
   floating halfway up the other. */
.lr-deploy-top { align-items: stretch; }
.lr-deploy-top > .lr-card { display: flex; flex-direction: column; }
.lr-deploy-top > .lr-card > .lr-toolbar:last-child { margin-top: auto; }
/* EXCEPT the promote card. Its only content is one row, so stretching it to
   the deploy card's height (which grows further whenever the data-safety
   verdict appears under that toolbar) left a tall empty box with the Image
   field stranded at its foot, reading as a section that failed to load. It
   sits at its own height, with the toolbar directly under its heading. */
.lr-deploy-top > .lr-card--promote { align-self: start; }
.lr-deploy-top > .lr-card--promote > .lr-toolbar:last-child { margin-top: 0; }

/* ============================================================
   DENSITY — the complaint was "too data heavy … less spacing
   between them", and it was true of the Services grid in
   particular: --sp-4 (14px) between cards that each carry a
   border, a left status rule and up to four rows of their own.
   Cards need to read as separate objects before they read as a
   list, and 14px at 1440px wide does not do that.

   Raised on the TOKENS the grids already consume, not per grid,
   so one decision moves the whole console. The @media blocks
   below restate the same tokens — which is why the narrow
   layouts did not need touching.
   ============================================================ */
/* ⚠ The padding is NOT touched here. It lives on .lr-svc__main /
   __hosts / __foot, and a rule on .lr-svc itself would double it — the
   card would grow and the gutter between cards would not, which is the
   opposite of the complaint. What is wrong is the GAP: a card carrying
   a border, a 3px status rule and up to four rows of its own was
   separated from the next one by 14px, so the grid read as one long
   ruled table rather than as a set of objects.

   List and grid deliberately differ. In a list the cards are full-width
   bands and 16px is enough to break them apart; as tiles they have a
   gutter on all four sides and want the next step up, or the vertical
   seam reads tighter than the horizontal one at the same value. */
.lr-svc-view { gap: var(--sp-5); padding: var(--sp-2) 0 var(--sp-3); }
.lr-svc-view--grid { gap: var(--sp-6); }
/* The card's own rows should sit closer together than two cards do, or
   the boundary between two services is no stronger than the boundary
   between two rows of one service. */
.lr-svc-view--grid .lr-svc__hosts { gap: var(--sp-2); }

@media (max-width: 767px) {
  /* Tighter, but still a step above the row rhythm inside a card. */
  .lr-svc-view { gap: var(--sp-4); }
  .lr-svc-view--grid { gap: var(--sp-5); }
}

/* A stacked view: the page rhythm, off the scale, in one class. Seven call
   sites across the two consoles wrote `style="display:flex;flex-direction:
   column;gap:20px"` by hand — seven decisions where there is one, and 20px
   is not on the scale. downtime.css carries the identical rule. */
.lr-view { display: flex; flex-direction: column; gap: var(--gap-page); min-width: 0; }
@media (max-width: 767px) { .lr-view { gap: var(--sp-5); } }

/* The tracker block is a CARD now, so it needs the card's padding rather
   than the 20px it carried as a bare panel. Everything else about it — the
   icon, the body column, the wrap — is unchanged. */
.lr-card.lr-tracker { padding: var(--pad-card); background: var(--bg-primary); }
.lr-card.lr-tracker .lr-hint { margin: 0; }

/* Small utilities, so a spacing decision has a name instead of being
   re-argued at each call site. All three replaced literals that appeared
   in more than one file. */
.lr-badge-after { margin-left: var(--sp-2); }
.lr-stack-gap { margin-top: var(--sp-4); }
.lr-stack-gap--sm { margin-top: var(--sp-2); margin-bottom: 0; }
.lr-stack-gap-b { margin-bottom: var(--sp-5); }
.lr-prewrap { white-space: pre-wrap; }
.lr-row-inline { display: flex; align-items: center; gap: var(--gap-inline); flex-wrap: wrap; }

/* A labelled field that should take the rest of its toolbar row. The
   promote picker did this with `style="flex:1 1 320px"` on the label and
   `style="flex:1"` on the control — two inline declarations describing one
   intention, which is how the two got out of step on narrow screens. */
.lr-inline-field--grow { flex: 1 1 20rem; min-width: 0; }
.lr-inline-field--grow > .lr-dd, .lr-select--grow + .lr-dd { flex: 1 1 auto; min-width: 0; }
/* The promote Image combobox fills the field the way the enhanced dropdown
   before it did, so the row keeps its shape. */
.lr-inline-field--grow > .lr-combo--grow { flex: 1 1 auto; min-width: 12rem; }

/* Dev -> prod bulk promote: the checkbox column stays narrow, and the bulk
   bar sits flush above the table it acts on. */
.lr-table th.lr-col-check, .lr-table td.lr-col-check { width: 1%; padding-right: 0; }
.lr-col-check .lr-check { padding: 0; }
.lr-col-check input[type="checkbox"]:disabled { opacity: .35; cursor: not-allowed; }
.lr-drift-bulk { justify-content: flex-end; flex-wrap: wrap; padding: 0 0 var(--sp-3); }
.lr-drift-bulk .lr-count-note { margin-right: auto; }

/* Failed-job diagnosis: why it happened + how to fix it, in the job dialog.
   An error-tinted card rather than red text, so it reads as guidance. */
.lr-diag {
  margin: var(--sp-4) 0; padding: var(--sp-4);
  border: 1px solid var(--status-error); border-left-width: 4px;
  border-radius: var(--radius-md); background: var(--bg-tertiary);
}
.lr-diag__head { display: flex; align-items: center; gap: var(--sp-2); color: var(--status-error); }
.lr-diag__title { margin: 0; font-size: var(--fs-base); color: var(--text-primary); }
.lr-diag__step { margin: var(--sp-1) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
.lr-diag__label {
  margin: var(--sp-3) 0 var(--sp-1); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .04em; color: var(--text-secondary);
}
.lr-diag__why { margin: 0; font-size: var(--fs-sm); color: var(--text-primary); line-height: 1.5; }
.lr-diag__fix { margin: 0; padding-left: 1.25rem; font-size: var(--fs-sm); line-height: 1.5; }
.lr-diag__fix li + li { margin-top: var(--sp-1); }
.lr-diag__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.lr-diag__evidence { margin-top: var(--sp-3); font-size: var(--fs-xs); }
.lr-diag__evidence summary { cursor: pointer; color: var(--text-secondary); }
.lr-logs--short { max-height: 14rem; margin-top: var(--sp-2); white-space: pre-wrap; word-break: break-word; }
.lr-diag__head .lr-diag__copy { margin-left: auto; }
.lr-diag__actions a.lr-btn { text-decoration: none; }
.lr-diag__copy-ev { margin-top: var(--sp-2); }

/* ── One-click clear (×) on free-text boxes — see enhanceClearables() in app.js.
   The same treatment the Logrite app gives its search fields
   (ListSearchField.jsx): text-gray-500 at rest, accent orange and a quarter
   turn on hover. Hidden while the box is empty via :placeholder-shown, so a
   value written by script needs no listener to show or hide it. */
.lr-clearable { position: relative; display: inline-flex; min-width: 0; width: 100%; }
.lr-inline-field .lr-clearable, .lr-toolbar .lr-clearable { width: auto; }
.lr-clearable > .lr-input { flex: 1 1 auto; padding-right: 34px; }
.lr-clear {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-muted); cursor: pointer;
  transition: color var(--dur-slow) var(--ease);
}
.lr-clearable > .lr-clear { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }
.lr-clear .lr-icon { transition: transform 300ms var(--ease); }
.lr-clear:hover { color: var(--accent-orange); }
.lr-clear:hover .lr-icon { transform: rotate(90deg); }
.lr-clear:focus-visible { outline: 2px solid rgba(230,65,0,.35); outline-offset: 1px; color: var(--accent-orange); }
input:placeholder-shown ~ .lr-clear, input:disabled ~ .lr-clear, input[readonly] ~ .lr-clear { display: none; }
.lr-search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

/* ── Environment tab: per-row Edit / Delete (envRowActions in app.js). A fourth
   column only when actions are enabled, so a read-only console's list is
   unchanged. Delete is disabled, with the reason in its title, for a variable
   the overlay does not declare: removing it there would change nothing. */
/* FOUR COLUMNS, NOT FIVE. There used to be a `has-reveal` variant adding a
   column for the eye, because only rows the server masked had one. Every row
   is masked now and every row has an eye, so the eye joined edit and delete in
   the actions group and the fifth column — and the cascade problem of spelling
   out `.has-reveal.has-actions` so it would not wrap — went with it. */
.lr-env-row.has-actions { grid-template-columns: auto minmax(120px, 260px) minmax(0, 1fr) auto; }
.lr-env-row__actions { display: flex; gap: 2px; align-self: center; }
.lr-iconbtn--danger:hover:not(:disabled) { color: var(--status-error, #F04438); }
@media (max-width: 767px) {
  .lr-env-row.has-actions { grid-template-columns: auto minmax(0,1fr) auto; }
  .lr-env-row.has-actions .lr-env-val { grid-column: 1 / -1; grid-row: 2; }
}

/* ── Terminal (dev only) — terminal.py / app.js term* ─────────────────── */
.lr-term__out {
  margin: 12px 0 0; min-height: 18rem; max-height: 58vh; overflow: auto;
  padding: 12px 14px; border-radius: var(--radius-lg);
  background: #0c0e12; color: #CECFD2; border: 1px solid var(--border-secondary);
  font-family: var(--font-mono); font-size: var(--fs-xs); line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
}
.lr-term__out .is-cmd { color: #FF7F4D; font-weight: 600; }
.lr-term__out .is-meta { color: #6B6F76; }
.lr-term__out .is-err { color: #FDA29B; }
.lr-term__line { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.lr-term__prompt { font-family: var(--font-mono); font-size: var(--fs-xs); color: #FF7F4D; white-space: nowrap; }
.lr-term__input {
  flex: 1 1 auto; min-width: 0; padding: 8px 10px; border-radius: var(--radius-lg);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  background: var(--bg-secondary); color: var(--text-primary);
  border: 2px solid var(--border-secondary); outline: none;
}
.lr-term__input:focus-visible { border-color: var(--accent-orange); }
.lr-term__input:disabled { opacity: .55; cursor: not-allowed; }
.lr-term__unlock { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
@media (max-width: 767px) {
  .lr-term__line { flex-wrap: wrap; }
  .lr-term__prompt { width: 100%; }
}

/* ============================================================
   FOLD — a section whose header is the whole click target
   ------------------------------------------------------------
   Two places needed the same thing and neither had it. The
   reclaim dialog rendered four cards with every item table
   expanded, so "27 GB reclaimable" opened as several screens of
   image digests and the four VERDICTS — the part that decides
   anything — were scattered down the scroll. The Archive has the
   same shape with more rows.

   So: shut by default, and the header carries what you need to
   decide whether to open it. The command lives UP THERE, beside
   the title, because an operator who has decided to run the
   prune by hand should not have to expand a table of things they
   are not going to click in order to reach it.

   The head is a div[role=button], not a <button>: it holds a
   Copy control and a nested <button> is invalid HTML. app.js
   gives it Enter/Space and leaves clicks on anything
   interactive inside it alone.
   ============================================================ */
.lr-fold {
  border: 1px solid var(--border-tertiary); border-radius: var(--radius-lg);
  background: var(--bg-secondary); overflow: hidden;
}
.lr-fold + .lr-fold { margin-top: var(--sp-3); }
.lr-fold__head {
  /* ⚠ flex-wrap IS THE FIX, not a nicety. Without it the aside below cannot
     take its own line however it is sized, so at 375px a long artifact URL
     held the row and the title was squeezed to one character per line —
     thirty lines of vertical text where the heading should be. */
  display: flex; align-items: flex-start; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4); cursor: pointer;
  transition: background-color .15s ease;
}
.lr-fold__head:hover { background: var(--bg-tertiary); }
.lr-fold__head:focus-visible {
  outline: 2px solid var(--accent-orange); outline-offset: -2px;
}
/* The chevron is the only thing on the row that says "this opens",
   so it points at the state rather than merely spinning. */
.lr-fold__chev {
  flex: 0 0 auto; margin-top: 2px; color: var(--text-muted);
  transition: transform .18s ease;
}
.lr-fold.is-open > .lr-fold__head .lr-fold__chev { transform: rotate(180deg); }
/* A floor in CHARACTERS, not a percentage: the title is the only thing on
   this row that must stay readable, so it claims a width the command cannot
   bid against and the command wraps underneath instead. */
.lr-fold__main { flex: 1 1 24ch; min-width: 0; }
.lr-fold__title {
  margin: 0; font-size: var(--fs-base); font-weight: 600; color: var(--text-primary);
  line-height: 1.4; overflow-wrap: anywhere;
}
/* TWO LINES on the header row. These descriptions are a document's own
   opening words and run to a full paragraph; unclamped, one artifact was
   200px tall and six rows filled the screen, which is the same "too heavy to
   scan" this fold exists to fix. The whole description is in the body. */
.lr-fold__sub {
  margin: 2px 0 0; font-size: var(--fs-sm); color: var(--text-secondary);
  line-height: 1.5; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.lr-fold__meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px;
}
/* The command cluster. `flex-wrap` and the 100% basis below 768px are what
   stop a long `docker buildx prune` from pushing the chevron off-screen on a
   phone — the row it sits in is the one thing on this page that must stay
   reachable at every width. */
.lr-fold__aside {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: 0 1 auto; min-width: 0; justify-content: flex-end;
}
.lr-fold__cmd {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 4px 6px 4px 8px;
  background: var(--bg-tertiary); border: 1px solid var(--border-tertiary);
  border-radius: var(--radius-md);
}
.lr-fold__cmd code {
  flex: 1 1 auto; min-width: 0; max-width: 46ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-secondary);
}
.lr-fold__body { padding: var(--sp-4); border-top: 1px solid var(--border-tertiary); }
.lr-fold__body > :first-child { margin-top: 0; }
/* 1023px, not 767px. At 768 the URL took more than half the row and the
   title was crammed into 24ch over two lines — the command is the least
   important thing on this row and it was winning. Below a desktop width it
   takes its own line and the heading gets the whole of the first one. */
@media (max-width: 1023px) {
  .lr-fold__aside { flex: 1 1 100%; justify-content: flex-start; }
  .lr-fold__cmd { flex: 1 1 auto; }
  .lr-fold__cmd code { max-width: none; }
}
@media (max-width: 639px) {
  .lr-fold__head { padding: var(--sp-3); gap: var(--sp-2); }
  /* ONE line of description on a phone. Two plus a wrapped title plus the
     badges plus the command made a shut row 223px tall, which is most of the
     viewport for a row nobody has opened. */
  .lr-fold__sub { -webkit-line-clamp: 1; }
  .lr-fold__body { padding: var(--sp-3); }
}
/* The full description inside an opened fold — the header row clamps it. */
.lr-fold__full {
  margin: 0 0 var(--sp-3); font-size: var(--fs-sm); line-height: 1.6;
  color: var(--text-secondary); overflow-wrap: anywhere;
}

/* ======================================================================
   Security posture badge (topbar)
   ----------------------------------------------------------------------
   Opened by :hover and :focus-within ONLY. No JS toggles it, deliberately:
   every other panel in this console is JS-driven and has to defend itself
   against the five-second tick re-rendering underneath it. A CSS panel
   cannot be ripped open or shut by a refresh, and a real <button> trigger
   means it works from the keyboard without a focus trap.

   Colours come from the same --sev-* tokens as .lr-badge, so this badge and
   every severity pill on the page stay one vocabulary. The states are the
   ones ops/security/posture.py emits, and nothing here invents a sixth.

   ⚠ "NOT VERIFIED" IS TOLD APART BY SHAPE, not only by colour, following
   .lr-badge--unset above. absent/unknown/out-of-scope get a DASHED border:
   they are the absence of a verdict rather than a mild one, and the whole
   failure this badge exists to prevent is an absence being read as an "ok".
   Colour alone cannot carry that distinction for a colour-blind operator.
   ====================================================================== */
.lr-sec { position: relative; display: inline-flex; }

.lr-sec__chip {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  font: inherit; font-size: var(--fs-xs); font-weight: 600;
  line-height: 1.4;
  border: 1px solid var(--sev-none-bd); border-radius: var(--radius-full);
  background: var(--bg-secondary); color: var(--sev-none-fg);
  /* `help` was right while hover was the only way in. It is a real toggle now
     and a help cursor on a button reads as "this does nothing when clicked". */
  cursor: pointer; white-space: nowrap;
}
.lr-sec__chip:focus-visible {
  outline: 2px solid var(--accent-orange-border); outline-offset: 2px;
}

.lr-sec--pass    .lr-sec__chip { color: var(--sev-info-fg);  border-color: var(--sev-info-bd);  background: var(--sev-info-bg); }
.lr-sec--fail    .lr-sec__chip { color: var(--sev-error-fg); border-color: var(--sev-error-bd); background: var(--sev-error-bg); }
.lr-sec--blocked .lr-sec__chip,
.lr-sec--stale   .lr-sec__chip,
.lr-sec--other-commit .lr-sec__chip { color: var(--sev-warn-fg); border-color: var(--sev-warn-bd); background: var(--sev-warn-bg); }
.lr-sec--unknown .lr-sec__chip,
.lr-sec--absent  .lr-sec__chip,
.lr-sec--out-of-scope .lr-sec__chip {
  color: var(--sev-none-fg); border-color: var(--sev-none-bd);
  background: transparent; border-style: dashed;
}

/* ⚠ THE BOX AND ITS SCROLLING BODY ARE TWO ELEMENTS, NOT ONE.
   `.lr-sec__panel` is the positioned, bordered box; `.lr-sec__pop` is the part
   that scrolls. They were one element while hover was the only way to open it,
   and they cannot stay one: a grab bar has to sit OUTSIDE the scroll area or it
   scrolls away from under the pointer mid-drag, and it has to sit outside
   `#sec-pop` in particular because renderSecurity() clears that node every
   tick. Hence flex-column + `min-height: 0` on the body, which is what lets a
   max-height on the parent turn into a scrollbar on the child. */
.lr-sec__panel {
  position: absolute; top: calc(100% + var(--sp-2)); right: 0; z-index: 60;
  display: none; flex-direction: column;
  width: min(32rem, calc(100vw - 2rem));
  max-height: min(32rem, calc(100vh - 8rem)); overflow: hidden;
  border: 1px solid var(--border-primary); border-radius: var(--radius-lg);
  background: var(--bg-secondary); color: var(--text-primary);
  box-shadow: var(--shadow-menu);
  text-align: left; white-space: normal;
}
.lr-sec__pop {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--sp-4);
}

/* The free peek, unchanged and still listener-free. It is scoped to a panel
   still inside `.lr-sec`: pinning MOVES the panel to <body>, so a pinned panel
   is no longer a descendant and these two rules stop applying to it. That is
   what keeps "hovering the chip" from fighting "the panel is pinned". */
.lr-sec:hover .lr-sec__panel,
.lr-sec:focus-within .lr-sec__panel { display: flex; }

/* ⚠ PINNED MEANS REPARENTED TO <body>, AND THE REASON IS A STACKING CONTEXT.
   `.lr-topbar` is `position: sticky; z-index: 30`, which makes it a stacking
   context — so the panel's z-index is scoped INSIDE it and no value, however
   large, lifts the panel above a dialog. Dragged to the bottom of the page it
   would slide behind the page content. Out at <body> it can be layered
   honestly: above the modals (10000-10011), so the posture can be read beside
   the Deploy tab it is about, and deliberately BELOW the toasts (10020) and
   the step-up password veil (10100) — covering a password prompt with a
   read-only panel would be a prompt nobody can answer. */
.lr-sec__panel.is-pinned {
  display: flex; position: fixed; top: 0; left: 0; right: auto; z-index: 10015;
  max-height: min(34rem, calc(100vh - 2rem));
}
.lr-sec__panel.is-dragging { user-select: none; cursor: grabbing; }

/* Only a pinned panel carries a grab bar. On a hover peek it would be a row of
   controls nobody can reach — the pointer has to cross the gap to get there,
   and crossing the gap is what closes it. */
.lr-sec__grab { display: none; }
.lr-sec__panel.is-pinned .lr-sec__grab {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-primary);
  background: var(--bg-tertiary); color: var(--text-muted);
  cursor: grab; touch-action: none;
}
.lr-sec__panel.is-dragging .lr-sec__grab { cursor: grabbing; }
.lr-sec__grabhint { flex: 1 1 auto; font-size: var(--fs-nano); }
.lr-sec__close {
  flex: 0 0 auto; display: inline-flex; padding: 2px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-muted); cursor: pointer;
}
.lr-sec__close:hover { color: var(--text-primary); background: var(--bg-secondary); }
.lr-sec__close:focus-visible {
  outline: 2px solid var(--accent-orange-border); outline-offset: 1px;
}

.lr-sec__title { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 700; }
.lr-sec__sub   { margin: 0 0 var(--sp-3); font-size: var(--fs-xs); color: var(--text-muted); }
.lr-sec__head  { display: flex; align-items: baseline; justify-content: space-between;
                 gap: var(--sp-2);
                 margin: var(--sp-4) 0 var(--sp-1); font-size: var(--fs-xs);
                 font-weight: 700; color: var(--text-secondary);
                 text-transform: uppercase; letter-spacing: .04em; }
.lr-sec__count { font-weight: 600; color: var(--text-muted);
                 text-transform: none; letter-spacing: 0; }

/* ⚠ TWO LINES PER ROW, NOT ONE, AND THIS WAS A REAL DEFECT.
   The first cut laid the name and the reason side by side in one flex row with
   the reason at `flex: 0 0 auto` — so a reason like "the gates passed on
   dac786d, but this container does not report which commit it is running"
   refused to shrink, crushed the service name to a few characters and ran the
   whole row off the panel. Reasons here are SENTENCES; a right-aligned column
   was the wrong shape for them from the start.
   So: a header line carrying the name and a state pill, and the reason beneath
   it with the full width to wrap into. */
.lr-sec__row {
  display: block; padding: var(--sp-2) 0;
  border-top: 1px solid var(--border-secondary);
  font-size: var(--fs-xs);
}
.lr-sec__rowhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-2);
}
.lr-sec__name { min-width: 0; overflow-wrap: anywhere; font-weight: 600;
                color: var(--text-primary); }
.lr-sec__why  { margin: 2px 0 0; color: var(--text-muted);
                overflow-wrap: anywhere; line-height: 1.45; }

/* The per-row state, as a pill rather than a bare word. Same shape rule as the
   chip above: absence of a verdict is DASHED, so it is not read as a mild
   verdict by anyone who cannot separate the two ambers. */
.lr-sec__state {
  flex: 0 0 auto; padding: 1px var(--sp-2); border-radius: var(--radius-full);
  border: 1px solid var(--sev-none-bd); background: var(--bg-tertiary);
  color: var(--sev-none-fg); font-weight: 600; font-size: var(--fs-nano);
  white-space: nowrap; text-transform: lowercase;
}
.lr-sec__state.is-pass { color: var(--sev-info-fg); border-color: var(--sev-info-bd);
                         background: var(--sev-info-bg); }
.lr-sec__state.is-fail { color: var(--sev-error-fg); border-color: var(--sev-error-bd);
                         background: var(--sev-error-bg); }
.lr-sec__state.is-warn { color: var(--sev-warn-fg); border-color: var(--sev-warn-bd);
                         background: var(--sev-warn-bg); }
.lr-sec__state.is-none { background: transparent; border-style: dashed; }

/* A group that is entirely one state says so once, instead of repeating the
   same pill down fifteen rows. */
.lr-sec__groupnote { margin: 0 0 var(--sp-1); color: var(--text-muted); }

.lr-sec__foot {
  margin-top: var(--sp-3); padding-top: var(--sp-2);
  border-top: 1px solid var(--border-primary);
  font-size: var(--fs-xs); color: var(--text-muted);
}

/* The panel is anchored to the right edge of a topbar that becomes very narrow
   on a phone; pinned to that edge it would hang off-screen with no way to
   scroll to it. 639px, not 640: a max-width breakpoint is the COMPLEMENT of
   the scale's 640 min-width tier, and test_design_parity.js enforces exactly
   that — it rejected 640px here. */
@media (max-width: 639px) {
  .lr-sec__panel:not(.is-pinned) {
    position: fixed; left: var(--sp-3); right: var(--sp-3); width: auto;
  }
}
