/* Light by default, dark on request — one token set, two palettes.
   `color-scheme` matters as much as the colours: without it the browser paints
   its own scrollbars, selects and checkboxes for the WRONG theme, and a light
   page ends up with black form controls that nothing in this file can reach. */
:root {
  color-scheme: light;
  --bg: #F4F6F8; --panel: #FFFFFF; --panel2: #EDF0F4; --line: #E2E6EC; --line2: #C9D0DA;
  --ink: #14171C; --muted: #5B6472; --dim: #8A93A1;
  /* Colour is a signal, never decoration: green = running, amber = wants you,
     red = about to destroy something, blue = you can click it. Every value below
     clears 4.5:1 on its own ground, checked. */
  --green: #067647; --red: #D92D20; --blue: #3B5BDB; --amber: #B45309;
  --cyan: #0E7490; --violet: #6D28D9;
  --on-accent: #FFFFFF;
  /* The one action a screen wants you to take. It is `blue` because that is what
     blue MEANS here -- the primary button used to be `--ink`, i.e. pure
     luminance, which made the loudest thing on the page the one thing carrying no
     signal at all. `-fill` is a step deeper than `--blue` so white text sits on
     it; `-soft` and `-ring` are the same hue at low alpha, for tinted grounds and
     focus. */
  --accent: var(--blue); --accent-fill: #3B5BDB; --accent-strong: #2C46B4;
  --accent-ink: #FFFFFF; --accent-soft: rgba(59,91,219,.10);
  --accent-ring: rgba(59,91,219,.42);
  /* The page's dot grid and the bar's translucent ground: both were literals
     that assumed a dark page and survived the token swap unnoticed. */
  --dot: #DDE2E9; --bar: rgba(237,240,244,.92);
  /* Elevation. On a light page a shadow does the lifting; on a dark one it cannot
     be seen at all, so `--edge` adds the hairline top highlight that real
     materials have and shadows there stay tight and dark. */
  --shadow-1: 0 1px 2px rgba(16,24,40,.05), 0 1px 3px rgba(16,24,40,.06);
  --shadow-2: 0 2px 8px rgba(16,24,40,.07), 0 24px 52px rgba(16,24,40,.16);
  --shadow-accent: 0 1px 2px rgba(16,24,40,.10), 0 6px 16px rgba(59,91,219,.28);
  --edge: inset 0 1px 0 rgba(255,255,255,.65);

  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;

  /* One radius scale, so a chip and the dialog behind it look related. The file
     had 5, 6, 7, 9, 12, 14 and 16px scattered through it, which is what makes a
     careful layout still read as unfinished. */
  --r-xs: 6px; --r-sm: 8px; --r-md: 11px; --r-lg: 14px; --r-xl: 18px; --r-pill: 999px;

  /* One motion system. `--t` is the everyday hover/press; `--t-slow` is for
     something that travels (a bar filling, a panel arriving). The curve leaves
     fast and settles, which is what makes a press feel answered rather than
     animated. Every duration here is overridden to ~0 under reduced-motion. */
  --ease: cubic-bezier(.2, .7, .3, 1);
  --t: 140ms; --t-slow: 320ms;

  /* The dark ramp, defined once and only assigned below -- it is applied from two
     places (the attribute and the OS preference) and a third copy is how the two
     drift apart. */
  --d-bg: #0B0E13; --d-panel: #12161F; --d-panel2: #1A2029;
  --d-line: #232B37; --d-line2: #343E4D;
  --d-ink: #E7EAF0; --d-muted: #8B94A4; --d-dim: #5D6675;
  --d-green: #4ADE80; --d-red: #FF6B6B; --d-blue: #7FA8FF; --d-amber: #F0B429;
  --d-cyan: #56D4DD; --d-violet: #A371F7;
  --d-accent-fill: #3567DD; --d-accent-strong: #5B89F2;
  --d-accent-soft: rgba(127,168,255,.13); --d-accent-ring: rgba(127,168,255,.45);
  --d-dot: #161D2B; --d-bar: rgba(26,32,41,.88);
}
/* The two places dark is turned on. Assignments only -- every value lives above.
   app.js stamps `data-theme` on its first line, so the OS-preference block below
   only ever reaches a page with no script to do it: the SIGN-IN page, which is
   deliberately server-rendered with none. It arrived white on a dark desktop,
   being the one screen in the product that could not ask. `:not([data-theme])`
   is what keeps it from ever fighting an explicit choice, in either direction. */
[data-theme="dark"] {
  color-scheme: dark;
  --bg: var(--d-bg); --panel: var(--d-panel); --panel2: var(--d-panel2);
  --line: var(--d-line); --line2: var(--d-line2);
  --ink: var(--d-ink); --muted: var(--d-muted); --dim: var(--d-dim);
  --green: var(--d-green); --red: var(--d-red); --blue: var(--d-blue);
  --amber: var(--d-amber); --cyan: var(--d-cyan); --violet: var(--d-violet);
  --on-accent: #0A0E14;
  --accent-fill: var(--d-accent-fill); --accent-strong: var(--d-accent-strong);
  --accent-ink: #FFFFFF; --accent-soft: var(--d-accent-soft);
  --accent-ring: var(--d-accent-ring);
  --dot: var(--d-dot); --bar: var(--d-bar);
  --shadow-1: 0 1px 2px rgba(0,0,0,.40);
  --shadow-2: 0 2px 8px rgba(0,0,0,.45), 0 28px 64px rgba(0,0,0,.60);
  --shadow-accent: 0 1px 2px rgba(0,0,0,.40), 0 6px 18px rgba(53,103,221,.42);
  --edge: inset 0 1px 0 rgba(255,255,255,.045);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    color-scheme: dark;
    --bg: var(--d-bg); --panel: var(--d-panel); --panel2: var(--d-panel2);
    --line: var(--d-line); --line2: var(--d-line2);
    --ink: var(--d-ink); --muted: var(--d-muted); --dim: var(--d-dim);
    --green: var(--d-green); --red: var(--d-red); --blue: var(--d-blue);
    --amber: var(--d-amber); --cyan: var(--d-cyan); --violet: var(--d-violet);
    --on-accent: #0A0E14;
    --accent-fill: var(--d-accent-fill); --accent-strong: var(--d-accent-strong);
    --accent-ink: #FFFFFF; --accent-soft: var(--d-accent-soft);
    --accent-ring: var(--d-accent-ring);
    --dot: var(--d-dot); --bar: var(--d-bar);
    --shadow-1: 0 1px 2px rgba(0,0,0,.40);
    --shadow-2: 0 2px 8px rgba(0,0,0,.45), 0 28px 64px rgba(0,0,0,.60);
    --shadow-accent: 0 1px 2px rgba(0,0,0,.40), 0 6px 18px rgba(53,103,221,.42);
    --edge: inset 0 1px 0 rgba(255,255,255,.045);
  }
}
* { box-sizing: border-box; }
/* The UA's `[hidden] { display: none }` loses to ANY author rule that sets
   `display` on the same element -- and `form label`/`.checks label` below both
   do. Without this, every `el.hidden = true` on a label is silently a no-op
   (the seed dialog showed "Profile" and "Scale spec" at the same time in both
   modes). Needs !important: it has to outrank those class rules. */
[hidden] { display: none !important; }
html, body { height: 100%; }
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--sans);
  display: flex; flex-direction: column; min-height: 100vh;
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0); background-size: 26px 26px; }

/* ---- top bar ---- */
.topbar { display: flex; align-items: center; gap: .9rem; padding: .8rem 1.4rem;
  border-bottom: 1px solid var(--line); background: var(--bar); backdrop-filter: blur(6px);
  position: sticky; top: 0; z-index: 10; }
/* `--bar` is `--panel2` at 92%: the greyish step, not white and not the ground.
   White made the header a slab floating over a grey well; the ground made it read
   as a hole in dark, where the ground is nearly black. One step up from the page
   is the reading that works in both, and the rule below it does the dividing.
   The status bar takes the same token, so the two edges of the frame agree. */
.brand { font-family: var(--mono); font-weight: 700; font-size: 1.05rem; letter-spacing: -.01em; }
.brand .dollar { color: var(--green); }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.toplinks { display: flex; align-items: center; gap: .5rem; }
/* The hamburger exists only where the links would wrap onto a second row, and the
   disclosure that reveals the action pane only where the pane is not a column. Both
   qualified by the class that sets their `display`, because `.chip.iconbtn` and
   `.btn.small` are two-class selectors and a bare `.navtoggle` loses to them --
   which is how a hamburger came to sit on a 1440px bar next to the links it
   replaces. */
.chip.navtoggle, .btn.panetoggle { display: none; }

/* ---- how everything you can touch behaves ----
   One rule, so a chip, a tab, a workspace row and a button all answer a pointer
   at the same speed and in the same direction. Before this only `.btn` had a
   transition, and the rest of the interface snapped between states while the
   buttons eased -- which reads as unfinished long before anyone can say why. */
.btn, .chip, .toplink, .linkbtn, .wrow, .tab, .linkrow, .aplist button,
.memenu button, .cp, .urlbox .copy, .wsrow-main, .att, .hcard, .card,
input, select, textarea, dialog {
  transition: background-color var(--t) var(--ease), border-color var(--t) var(--ease),
    color var(--t) var(--ease), box-shadow var(--t) var(--ease),
    opacity var(--t) var(--ease), transform var(--t) var(--ease);
}
/* A press has to be felt, not just registered. 1px is the whole effect. */
.btn:active:not(:disabled), .chip:active, .cp:active, .aplist button:active:not(:disabled) {
  transform: translateY(1px);
}
/* One focus ring, on the keyboard path only. There was exactly one
   :focus-visible rule in the file -- on the sign-in inputs -- so tabbing through
   the dashboard showed whatever the browser felt like drawing, or nothing. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
.wrow:focus-visible { outline-offset: -2px; }
/* A field being typed into is the one place a soft ring beats a hard outline:
   it says "this one" without drawing a second border beside the one already
   there. Pointer and keyboard both, because focus here is always deliberate. */
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
/* Reduced motion is a floor, not a feature: keep every state change, remove the
   travel. Durations rather than `none`, so transitionend still fires. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .btn:active:not(:disabled), .chip:active, .cp:active { transform: none; }
}

/* ---- buttons / chips ---- */
.btn { font-size: .82rem; padding: .42rem .8rem; border-radius: var(--r-sm);
  border: 1px solid var(--line2); background: var(--panel2); color: var(--ink);
  cursor: pointer; white-space: nowrap; }
.btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.btn.primary { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.btn.primary:hover { background: var(--accent-fill); border-color: var(--accent-fill);
  color: var(--accent-ink); box-shadow: var(--shadow-accent); }
/* Red means ABOUT TO destroy something, and a control sitting in a footer is not
   about to do anything. Prune lived permanently red at the bottom of the rail,
   which is the loudest colour in the palette spent on the quietest moment; it
   arrives when you reach for it instead. */
.btn.danger { color: var(--muted); border-color: transparent; background: transparent; }
.btn.danger:hover { color: var(--red); border-color: var(--red);
  background: rgba(217,45,32,.10); }
.btn.danger:focus-visible { color: var(--red); }
.btn.small { padding: .3rem .6rem; font-size: .78rem; }
/* `docker compose stop|start|restart|down` run synchronously for seconds with no
   progress to stream, so the button itself carries the feedback: a spinner and
   the verb ("Stopping…"), with the repro's other actions greyed out until it
   returns. Without it the click looked ignored and users clicked again, firing
   concurrent compose commands at one project. */
.btn:disabled { cursor: default; opacity: .5; }
.btn:disabled:hover { border-color: var(--line2); color: var(--ink); }
.btn.working:disabled { opacity: 1; color: var(--blue); border-color: var(--blue);
  background: rgba(88,166,255,.1); display: inline-flex; align-items: center; gap: .45rem; }
.spin { width: .72em; height: .72em; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: rc-spin .7s linear infinite; }
@keyframes rc-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spin { animation: none; }              /* the verb still says what is happening */
  .pill.working { animation: none; }
}
.chip { font-size: .74rem; color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: .18rem .6rem; display: inline-flex; align-items: center; gap: .35rem;
  background: none; cursor: pointer; }
.chip:hover { border-color: var(--blue); color: var(--ink); }
/* One doctor finding. Colour the mark, not the text: the message is the content. */
.dcheck { display: flex; gap: .5rem; align-items: baseline; padding: .22rem 0; font-size: .84rem; }
.dcheck .dmark { font-family: var(--mono); flex: 0 0 1em; }
.dcheck.ok .dmark { color: var(--green); }
.dcheck.warn .dmark { color: var(--amber); }
.dcheck.fail .dmark { color: var(--red); }
.banner.ok { border-color: rgba(63,185,80,.45); background: rgba(63,185,80,.08); }
.banner.warn { border-color: rgba(210,153,34,.45); background: rgba(210,153,34,.08); }
.chip::before { content: "●"; font-size: .6rem; color: var(--dim); }
/* A chip that is only a mark: square, no status dot, and the mark takes the
   button's own ink so it follows the theme it switches. */
.chip.iconbtn { padding: .3rem; width: 1.85rem; height: 1.85rem; justify-content: center; }
.chip.iconbtn::before { content: none; }
.chip.iconbtn svg { width: 15px; height: 15px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round; }
.chip.iconbtn:hover { color: var(--accent); border-color: var(--accent); }
.chip.up::before { color: var(--green); }
.chip.down::before { color: var(--red); }

/* ---- who is signed in, and who owns what ----
   On a shared box the first question at a glance is "which of these are mine".
   Owned-by-you is the accent; everyone else is quiet, because muting the majority
   is what makes the few stand out. */
.whoami { font-size: .74rem; color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: .18rem .6rem;
  background: none; cursor: pointer; }
.whoami:hover { border-color: var(--blue); color: var(--ink); }
.whoami::before { content: "@"; color: var(--dim); }
.whoami::after { content: " ▾"; color: var(--dim); }

/* ---- sign-in page (server-rendered; see web/signin.py) ----
   Its own document rather than a state inside the SPA, so these rules are the
   only ones it uses. app.js never loads there. */
.signin-body { display: grid; place-items: center; padding: 2rem 1rem; }
.signin { width: min(24rem, 100%); }
.signin .brand { font-family: var(--mono); font-weight: 700; font-size: 1.3rem;
  text-align: center; margin-bottom: 1.6rem; }
.signin-form { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.4rem 1.5rem; }
.signin-form label { display: block; margin: 0 0 .3rem; font-size: .8rem;
  color: var(--muted); }
.signin-form input { width: 100%; margin-bottom: 1rem; padding: .55rem .7rem;
  border: 1px solid var(--line2); border-radius: var(--r-sm); background: var(--bg);
  color: var(--ink); font: inherit; }
.signin-form input:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.signin-form button { width: 100%; padding: .55rem; margin-top: .2rem; }
/* Banners carry the ?e= state and the transport warning. Colour is the only
   difference between them, so each one names its own severity. */
.signin-banner { font-size: .84rem; border-radius: var(--r-sm); padding: .6rem .75rem;
  margin: 0 0 1rem; border: 1px solid var(--line2); background: var(--panel2); }
.signin-banner.bad { border-color: rgba(248,81,73,.45); background: rgba(248,81,73,.09);
  color: #ffb4b0; }
.signin-banner.warn { border-color: rgba(210,153,34,.45); background: rgba(210,153,34,.09);
  color: #e8c76a; }
.signin-banner.ok { border-color: rgba(63,185,80,.45); background: rgba(63,185,80,.09);
  color: #86e29b; }
.signin-foot { color: var(--dim); font-size: .76rem; text-align: center;
  margin: 1rem 0 0; line-height: 1.6; }
.signin-foot code { font-family: var(--mono); color: var(--muted); }
.owner { font-size: .7rem; color: var(--muted);
  border: 1px solid var(--line2); border-radius: 999px; padding: .05rem .45rem; }
.owner::before { content: "@"; color: var(--dim); }
.owner.me { color: var(--cyan); border-color: rgba(86,212,221,.4); background: rgba(86,212,221,.07); }
.owner.me::before { color: var(--cyan); }

/* ---- main / split layout ---- */
/* Status pills: still used by the rail header count, the detail badge and
   the job list. They were collateral in the layout replacement. */
.pill { font-size: .74rem; border-radius: 999px; padding: .2rem .6rem;
  display: inline-flex; align-items: center; gap: .35rem; border: 1px solid transparent; }
.pill::before { content: "●"; font-size: .6rem; }
.pill.running { color: var(--green); border-color: rgba(63,185,80,.4); background: rgba(63,185,80,.1); }
.pill.stopped { color: var(--amber); border-color: rgba(210,153,34,.4); background: rgba(210,153,34,.1); }
.pill.down { color: var(--muted); border-color: var(--line); }
/* state "?" (docker unreachable) -- mapped to .unknown by stateClass() */
.pill.unknown { color: var(--muted); border-color: var(--line); border-style: dashed; }
/* A restarting Rocket.Chat is almost always a crash loop, so it pulses like the
   in-flight pill rather than sitting quietly next to "stopped". */
.pill.restarting { color: var(--red); border-color: rgba(248,81,73,.4); background: rgba(248,81,73,.1);
  animation: rc-pulse 1.1s ease-in-out infinite; }
.pill.dead { color: var(--red); border-color: rgba(248,81,73,.4); background: rgba(248,81,73,.1); }
.pill.paused, .pill.created { color: var(--amber); border-color: rgba(210,153,34,.4);
  background: rgba(210,153,34,.1); }
/* A client-side transitional state (we issued the action and it has not returned),
   deliberately styled apart from the server-reported ones above. */
.pill.working { color: var(--blue); border-color: rgba(88,166,255,.4); background: rgba(88,166,255,.1);
  animation: rc-pulse 1.1s ease-in-out infinite; }
@keyframes rc-pulse { 50% { opacity: .45; } }
.pill.default { color: var(--blue); border-color: rgba(88,166,255,.4); background: rgba(88,166,255,.1); }
.pill.default::before { content: none; }

/* ---- cards (left list) ---- */

/* ---- three panes: scan · work · act ---- */
.panes { flex: 1; display: grid; grid-template-columns: 288px minmax(0, 1fr) 244px;
  min-height: 0; }
.panes.no-detail { grid-template-columns: 288px minmax(0, 1fr); }
.panes.no-detail .actpane { display: none; }

.rail { background: var(--panel); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0; }
.rail-head { display: flex; align-items: center; justify-content: space-between;
  padding: .8rem .85rem .5rem; }
.rail-title { font-size: .68rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); }
.rail-controls { padding: 0 .85rem .75rem; display: grid; gap: .45rem;
  border-bottom: 1px solid var(--line); }
.rail-two { display: flex; flex-wrap: wrap; gap: .45rem; }
.rail-two .input { flex: 1 1 7.5rem; min-width: 0; }
.rail-two .input { min-width: 0; }
.btn.block { width: 100%; justify-content: center; }
/* Each workspace is a card, so it needs its own ground and its own edge. It had
   neither: an unselected row was text on the rail with a 2px state bar beside it,
   which is why nothing looked like a card until you happened to select one. */
/* `minmax(0, 1fr)`, not the implicit `auto` track. An auto track is at least as wide
   as its widest item's MIN-content, and a row whose name is `white-space: nowrap` has
   a min-content of the whole name -- so one long workspace name made every row 484px
   wide inside a 287px rail, which the rail then scrolled sideways: the state tags and
   the group counts were clipped off the right edge. */
.rows { flex: 1; overflow: auto; padding: .55rem; display: grid;
  grid-template-columns: minmax(0, 1fr); gap: .4rem; align-content: start; }
.rail-foot { padding: .6rem .9rem .8rem; border-top: 1px solid var(--line); }

/* A GROUP HEADING, saying what the rows under it want from you. Rendered only when
   more than one group has rows -- over a list that is entirely one state it would be
   a label for the thing it is inside, and the pill in the rail head already says it. */
.rgrp { display: flex; align-items: center; gap: .45rem; padding: .55rem .35rem .15rem; }
.rgrp:first-child { padding-top: .1rem; }
.rgrp-t { font-size: .6rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); }
.rgrp-n { margin-left: auto; font-family: var(--mono); font-size: .64rem; color: var(--dim); }
.rdot { width: 6px; height: 6px; border-radius: 50%; background: var(--dim); }
.rdot.ok { background: var(--green); }
.rdot.warn { background: var(--amber); }
.rdot.off { background: transparent; box-shadow: inset 0 0 0 1px var(--dim); }

/* One ROW per workspace: the whole name (it is the thing you scan for), the version
   right-aligned, what it is and whose, and the state as a left gutter that encodes
   itself by SHAPE as well as hue -- so it survives greyscale, which is how these end
   up pasted into a ticket. No buttons: six repeated per card was 36 on a screen.

   One line was tried and reverted. It fits more rows and pays for them with the port,
   the owner and the scenario, which are what people search a rail BY on a shared box.
   The grouping above is what "which one is unhappy" needed. */
.wrow { position: relative; display: grid; gap: 3px; width: 100%; text-align: left;
  padding: .55rem .7rem .55rem .9rem; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--bg); color: var(--ink);
  cursor: pointer; font: inherit; }
.wrow::before { content: ""; position: absolute; left: .28rem; top: .7rem; bottom: .7rem;
  width: 2px; border-radius: 2px; background: var(--line2); }
.wrow[data-state="running"]::before { background: var(--green); }
.wrow[data-state="stopped"]::before,
.wrow[data-state="paused"]::before, .wrow[data-state="created"]::before { background: var(--amber); }
.wrow[data-state="restarting"]::before, .wrow[data-state="dead"]::before { background: var(--red); }
.wrow[data-state="down"]::before, .wrow[data-state="unknown"]::before {
  background: transparent; box-shadow: inset 0 0 0 1px var(--dim); }
.wrow[data-state="working"]::before { background: var(--blue); }
.wrow:hover { background: var(--panel2); border-color: var(--line2); }
/* Selected is the accent, not just a heavier grey. It was `--panel2` with a grey
   inset ring -- the same treatment hover already used, so the row you were ON and
   the row you were merely over looked alike. */
.wrow[aria-current="true"] { background: var(--accent-soft); border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent); }
.wrow .r1 { display: flex; align-items: baseline; gap: .45rem; min-width: 0; }
.wrow .nm { flex: 1; min-width: 0; font-weight: 600; font-size: .84rem;
  overflow-wrap: break-word; }
.wrow .ver { flex: 0 0 auto; font-family: var(--mono); font-size: .76rem; font-weight: 600;
  color: var(--muted); }
.wrow[aria-current="true"] .ver { color: var(--ink); }
.wrow .star { flex: 0 0 auto; font-size: .62rem; color: var(--dim); }
.wrow .meta { font-family: var(--mono); font-size: .7rem; color: var(--dim);
  overflow-wrap: break-word; }
.wrow .r3 { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.wstate { display: inline-flex; align-items: center; gap: .3rem; font-size: .6rem;
  font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.wstate::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.wstate.running { color: var(--green); }
.wstate.stopped, .wstate.paused, .wstate.created { color: var(--amber); }
.wstate.down, .wstate.unknown { color: var(--dim); }
.wstate.down::before, .wstate.unknown::before { background: transparent; box-shadow: inset 0 0 0 1px currentColor; }
/* `failed` joins the red group rather than getting a colour of its own: a wedged
   Helm release or a pod in ImagePullBackOff is the same signal as `dead` -- this is
   not coming back on its own and it wants you. Without a rule here the word would
   render at the base colour, which reads as "fine". */
.wstate.restarting, .wstate.dead, .wstate.failed { color: var(--red); }
.wstate.working { color: var(--blue); }
.wage { font-family: var(--mono); font-size: .64rem; color: var(--dim); }

.stage { overflow: auto; min-height: 0; }

/* ---- the action pane ---- */
/* Persistent, not a dropdown, and it LEADS with the one or two things this
   workspace's state actually admits, each with the reason it was chosen. Four titled
   groups over eleven equally-weighted items is a taxonomy, and a taxonomy is what you
   need when you do not know what you are looking for -- the question people arrive
   with is "what do I do with this one now", and a heading called "Keep or move it"
   does not answer it. Equal 30px rows and single-line labels are most of what makes
   the rest look composed; the destructive action is pinned to the bottom rather than
   trailing the last item, because it is not the eleventh thing on a menu. */
.actpane { background: var(--panel); border-left: 1px solid var(--line);
  overflow: auto; display: flex; flex-direction: column; padding-bottom: 1.1rem; }
.apgroup { display: flex; align-items: center; gap: .6rem; padding: 1.05rem .95rem .35rem;
  font-size: .62rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); }
.apgroup::after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* The suggestions: the heading says these are suggestions, and each item says what it
   does. Nothing else -- a reason written under each one was a sentence to read before
   you could act. Blue: the rule at the top of this file gives blue to "you can click
   it", and these are the things it is suggesting you click. */
.apnext { display: grid; gap: .35rem; padding: 0 .8rem; }
.apn { display: block; text-align: left; padding: .5rem .7rem;
  border: 1px solid var(--line2); border-radius: var(--r-sm); background: var(--bg);
  font: inherit; color: var(--ink); cursor: pointer; }
.apn:first-child { border-color: var(--accent); background: var(--accent-soft); }
.apn:hover { border-color: var(--accent); }
.apn b { font-size: .8rem; }
.apn:first-child b { color: var(--blue); }

.aplist { display: grid; }
.aplist button { display: flex; align-items: center; height: 30px; padding: 0 .95rem;
  font: inherit; font-size: .8rem; color: var(--ink); background: none; border: 0;
  text-align: left; cursor: pointer; white-space: nowrap; }
.aplist button::before { content: ""; width: 3px; height: 3px; border-radius: 50%;
  background: var(--line2); margin-right: .6rem; flex: 0 0 auto; }
.aplist button:hover { background: var(--panel2); color: var(--blue); }
.aplist button:hover::before { background: var(--blue); }
.aplist button:disabled { color: var(--dim); cursor: default; }
.aplist button:disabled:hover { background: none; color: var(--dim); }
.aplist button:disabled:hover::before { background: var(--line2); }
.apnote { padding: .6rem .95rem 0; font-size: .7rem; line-height: 1.5; color: var(--dim); }
.apdanger { margin-top: auto; padding: .8rem .95rem 0; border-top: 1px solid var(--line); }
.apdanger button { display: flex; align-items: center; justify-content: center; width: 100%;
  height: 30px; border: 1px solid var(--line2); border-radius: var(--r-sm); background: none;
  font: inherit; font-size: .74rem; font-weight: 600; color: var(--red); cursor: pointer; }
.apdanger button:hover { background: rgba(217,45,32,.08); border-color: var(--red); }
.apdanger p { margin: .5rem 0 0; font-size: .66rem; line-height: 1.5; color: var(--dim); }

/* The stage is GROUND, not a surface.
   It was `--panel`, and so is every card standing on it — which made a card an
   outline drawn on a field of its own colour, and made the 242px below the last
   one read as a hole in the panel rather than as page. The rail and the action
   pane keep `--panel`; between them is now a well, and the body's dot grid (which
   nothing had ever been able to see) shows through it. */
.detail, .stage { background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 1.1rem 1.3rem; position: sticky; top: 5rem; min-height: 60vh; }
.detail .placeholder { color: var(--dim); text-align: center; padding: 4rem 1rem; font-family: var(--mono); }
.d-head { display: flex; align-items: center; gap: .6rem; }
.d-head .dot { color: var(--dim); font-size: .7rem; } .d-head .dot.running { color: var(--green); } .d-head .dot.stopped { color: var(--amber); }
.d-head .name { font-weight: 700; font-size: 1.35rem; letter-spacing: -.02em; }
.d-head .close { margin-left: auto; background: none; border: none; color: var(--muted); font-size: 1.3rem; cursor: pointer; line-height: 1; }
.d-head .close:hover { color: var(--ink); }
/* Five uppercase tabs need 402px and a phone gives 380. The shell's
   `overflow: hidden` used to swallow the difference, which meant BACKUPS was off
   the edge and unreachable with nothing to say so. It scrolls in its own strip
   now -- the rule this file already applies to any wide content. */
.tabs { display: flex; gap: 1.2rem; border-bottom: 1px solid var(--line); margin: 1rem 0;
  overflow-x: auto; scrollbar-width: thin; }
.tab { flex: 0 0 auto; }
.tab { font-size: .7rem; font-weight: 600; letter-spacing: .09em;
  text-transform: uppercase; color: var(--dim); background: none; border: none;
  padding: .5rem 0; cursor: pointer; border-bottom: 2px solid transparent; }
.tab:hover { color: var(--ink); }
.tab.active { color: var(--blue); border-bottom-color: var(--blue); }
.d-actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1.1rem; }
/* THE TRIAGE BLOCK -- what is wrong and what to do about it, above the tabs.
   Amber on purpose: the rule at the top of this file gives red to "about to destroy
   something", and a crash-looping workspace wants you rather than threatening you.
   Spending red here would leave nothing louder for the Down button. */
.triage { display: grid; gap: .5rem; margin: .9rem 0 0; }
.tri { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .1rem .7rem;
  align-items: start; padding: .85rem 1rem .9rem;
  background: rgba(180,83,9,.07); border: 1px solid rgba(180,83,9,.4);
  border-radius: var(--r-md); }
[data-theme="dark"] .tri { background: rgba(240,180,41,.08); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tri { background: rgba(240,180,41,.08); }
}
.tri-i { color: var(--amber); font-size: .78rem; line-height: 1.5; }
.tri-t { font-size: .84rem; line-height: 1.6; }
.tri-t b { display: block; font-size: .9rem; }
.tri-t span { color: var(--muted); }
.tri-a { grid-column: 2; display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }

/* Three columns, and exactly six facts fill them. Cells carry their own right/bottom
   rule and the third in each row drops the right one, so a rare seventh fact adds a
   row instead of a hole.

   Rows were tried here and reverted: a fact reads as a READING, and a reading wants
   size and a box. Stacked into label/value lines the same six became a form, and
   health and uptime -- the two that change -- stopped being findable without looking
   for them. */
.kv-grid { display: grid; grid-template-columns: repeat(3, 1fr);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
  overflow: hidden; margin-bottom: 1.3rem; }
.kv { padding: .85rem 1.05rem .95rem; border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line); }
.kv:nth-child(3n) { border-right: 0; }
.kv:nth-last-child(-n+3) { border-bottom: 0; }
.kv .k { font-size: .64rem; font-weight: 600; color: var(--dim);
  text-transform: uppercase; letter-spacing: .1em; }
.kv .v { font-family: var(--mono); font-size: 1.15rem; font-weight: 600;
  letter-spacing: -.02em; margin-top: .28rem; overflow-wrap: anywhere; }
.kv .v.green { color: var(--green); }
.kv .v.bad { color: var(--red); }
.kv .v.warn { color: var(--amber); }

/* The identity line: the pairing this product exists to get right, then what
   kind of workspace it is and whose. */
.d-sub { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem;
  font-family: var(--mono); font-size: .76rem; color: var(--muted);
  margin: .45rem 0 0; }
.d-sub b { font-size: .92rem; font-weight: 700; color: var(--ink); letter-spacing: -.02em; }
.d-sub i { font-style: normal; color: var(--line2); }
.d-sub .bad { color: var(--red); }
.section-label { font-size: .64rem; font-weight: 600; color: var(--dim);
  text-transform: uppercase; letter-spacing: .1em; margin: 1.4rem 0 .55rem; }
.chart-box { border: 1px solid var(--line); border-radius: var(--r-md); padding: .8rem; background: var(--bg); }
/* A surface, like everything else Overview puts a fact in. It was `--bg` to stand
   out from a `--panel` stage; the stage is ground now, so `--bg` made it vanish
   into the page it sits on. */
.urlbox { border: 1px solid var(--line); border-radius: var(--r-md); padding: .8rem .9rem;
  background: var(--panel); box-shadow: var(--shadow-1), var(--edge);
  display: flex; align-items: center; gap: .6rem; margin-top: 1.2rem; }
/* The value shrinks; the copy button does not. Without this the workspace path -- the
   longest string on the panel -- ran UNDER the button on a narrow screen, which is
   both unreadable and a click that copies while looking like it hit the text. */
.urlbox > div:first-child { min-width: 0; }
.urlbox .copy { flex: 0 0 auto; }
.urlbox .k { font-family: var(--mono); font-size: .72rem; color: var(--muted); }
.urlbox a { font-family: var(--mono); color: var(--blue); text-decoration: none; display: block; margin-top: .2rem; }
/* A urlbox value that is text, not a link (admin login, workspace path). */
.urlbox .flat { font-family: var(--mono); color: var(--ink); margin-top: .2rem; word-break: break-all; }
.urlbox + .urlbox { margin-top: .5rem; }
.banner { font-size: .84rem; border-radius: var(--r-sm); padding: .6rem .7rem; margin: .8rem 0 0;
  border: 1px solid var(--line2); background: var(--panel2); color: var(--ink); }
.banner.bad { border-color: rgba(248,81,73,.45); background: rgba(248,81,73,.08); }
.urlbox .copy { margin-left: auto; background: none; border: 1px solid var(--line2); color: var(--muted); border-radius: var(--r-sm); padding: .3rem .5rem; cursor: pointer; }
.urlbox .copy:hover { color: var(--blue); border-color: var(--blue); }
/* Env-vars tab: a Rocket.Chat SETTING and a plain env var are not interchangeable,
   so the row says which. `.pill` carries a ● bullet, which reads as a status here —
   suppress it for this inline label. */
.pill.small { font-size: .64rem; padding: .05rem .4rem; margin-left: .45rem;
  color: var(--blue); border-color: rgba(88,166,255,.35); background: rgba(88,166,255,.1); }
.pill.small::before { content: none; }
.yours { color: var(--amber); margin-left: .35rem; }

/* Containers, Env vars and Backups are all a .dtable, and it was a bare table on
   whatever happened to be behind it — a panel, until the stage became ground, and
   then nothing at all. One rule gives all three tabs the surface Overview has.
   `separate` rather than `collapse`, because a collapsed table ignores the radius
   and the corners spill past the frame. */
.dtable { width: 100%; border-collapse: separate; border-spacing: 0;
  font-family: var(--mono); font-size: .8rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--shadow-1), var(--edge); overflow: hidden; }
.dtable th { text-align: left; color: var(--muted); font-weight: 500; padding: .5rem .7rem; border-bottom: 1px solid var(--line); }
.dtable td { padding: .5rem .7rem; border-bottom: 1px solid var(--line); word-break: break-all; }
.dtable tr:last-child td { border-bottom: 0; }
.dtable tbody tr:hover td { background: var(--panel2); }
.dtable td.v { color: var(--muted); }
/* ---- the log viewer ----
   Toolbar and stream are one object, so they share one frame: the controls sit in
   a header strip and the stream hangs off the bottom of it, rather than floating
   above an unrelated black box. */
.logbox { position: relative; border: 1px solid var(--line); border-radius: var(--r-md);
  overflow: hidden; background: var(--panel); box-shadow: var(--shadow-1), var(--edge); }
/* Over the stream rather than beside it: it belongs to the scroll position, and
   it is only there while that position is not the bottom. */
.logjump { position: absolute; right: 1rem; bottom: .9rem; z-index: 2;
  font-family: var(--sans); font-size: .74rem; font-weight: 600;
  padding: .38rem .75rem; border-radius: var(--r-pill); cursor: pointer;
  background: var(--accent-fill); color: var(--accent-ink); border: 0;
  box-shadow: var(--shadow-accent);
  transition: background-color var(--t) var(--ease), transform var(--t) var(--ease); }
.logjump:hover { background: var(--accent-strong); }
.logjump:active { transform: translateY(1px); }
.logctl { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap;
  padding: .55rem .65rem; border-bottom: 1px solid var(--line); background: var(--panel); }
.logctl .input { height: 1.85rem; padding: 0 .55rem; font-size: .76rem;
  border: 1px solid var(--line2); border-radius: var(--r-xs);
  background-color: var(--bg); color: var(--ink); }
.logctl select.input { -webkit-appearance: none; appearance: none; cursor: pointer;
  padding-right: 1.7rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238B94A4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .55rem center; }
.logctl .input:hover { border-color: var(--muted); }
.logctl .input:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring); }
/* Search takes the room the two fixed-width selects do not want. */
.logctl input.input { flex: 1 1 9rem; min-width: 6rem; }
/* What the stream is doing, in the strip that owns it. Quiet while it is working
   and loud only when it is not, because the answer is usually "fine". */
.logstat { display: inline-flex; align-items: center; gap: .4rem; flex: 0 0 auto;
  font-size: .72rem; color: var(--dim); white-space: nowrap; }
.logstat::before { content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: 0 0 auto; }
.logstat.live { color: var(--green); }
.logstat.wait { color: var(--muted); }
.logstat.idle { color: var(--muted); }
.logstat.idle::before { background: transparent; box-shadow: inset 0 0 0 1px currentColor; }
.logstat.bad { color: var(--red); font-weight: 600; }
.logretry { font: inherit; font-size: .72rem; font-weight: 600; color: var(--accent);
  background: none; border: 0; padding: 0 0 0 .2rem; cursor: pointer;
  text-decoration: underline; }
.logfollow { display: inline-flex; align-items: center; gap: .35rem; font-family: var(--mono);
  font-size: .76rem; color: var(--muted); cursor: pointer; user-select: none; }
.logfollow input { accent-color: var(--accent-fill); cursor: pointer; }
.logview { background: var(--bg); border: 0; border-radius: 0; padding: .5rem .65rem;
  max-height: 56vh; overflow: auto; overscroll-behavior: contain;
  font-family: var(--mono); font-size: .78rem; line-height: 1.55; }
[data-theme="dark"] .logview { background: #05070b; }
/* Off-screen rows cost nothing to lay out or paint. A 3000-row buffer is the
   normal case here, and this is the difference between scrolling it and fighting
   it. `contain-intrinsic-size` keeps the scrollbar honest while they are skipped. */
.logrow { display: flex; gap: .6rem; padding: .05rem 0; white-space: pre-wrap;
  word-break: break-word; content-visibility: auto; contain-intrinsic-size: auto 1.2rem; }
/* Every column a fixed width, so the four line up down the whole view. `.lt` was
   `auto` and a line without a timestamp -- which is every line a service prints
   without JSON -- collapsed it to nothing, so the level and the service of that row
   sat one column to the left of the rows around it. Columns that do not line up are
   most of what "the logs are not readable" means. */
.logrow .lt { flex: 0 0 5rem; color: var(--dim); }
.logrow .ll { flex: 0 0 46px; font-weight: 700; }
/* Wide enough for `authorization`, the longest service the chart names: at 92px it
   wrapped onto a second line and pushed its own message down with it. */
.logrow .ls { flex: 0 0 7rem; color: var(--muted); }
.logrow .lm { flex: 1; color: var(--ink); }
[data-theme="dark"] .logrow .lm { color: #c9d1d9; }
.ll.lv-error, .ll.lv-fatal { color: var(--red); }
/* An error's message takes the red of the theme it is in. It was a literal `#ffb4b0`
   in both -- a pale pink chosen for a near-black ground, which on the light theme's
   white put the one line anybody opened the logs to read at the lowest contrast on the
   screen. Tokens, for exactly this reason. */
.logrow.lv-error .lm, .logrow.lv-fatal .lm { color: var(--red); }
[data-theme="dark"] .logrow.lv-error .lm,
[data-theme="dark"] .logrow.lv-fatal .lm { color: #ffb4b0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logrow.lv-error .lm,
  :root:not([data-theme="light"]) .logrow.lv-fatal .lm { color: #ffb4b0; }
}
.ll.lv-warn { color: var(--amber); }
.ll.lv-info { color: var(--green); }
.ll.lv-debug, .ll.lv-trace { color: var(--dim); }

/* new-repro placeholder */
.card.new { align-items: center; justify-content: center; text-align: center; border: 1.5px dashed var(--line2);
  border-left: 1.5px dashed var(--line2); background: transparent; cursor: pointer; min-height: 150px; gap: .3rem; }
.card.new:hover { border-color: var(--blue); background: rgba(88,166,255,.04); }
.card.new .plus { font-size: 1.8rem; color: var(--muted); border: 1px solid var(--line2); border-radius: var(--r-md);
  width: 46px; height: 46px; display: grid; place-items: center; margin-bottom: .3rem; }
.card.new .t { font-family: var(--mono); font-weight: 700; }
.card.new .s { font-size: .8rem; color: var(--muted); }
.empty { color: var(--muted); text-align: center; margin-top: 3rem; grid-column: 1/-1; }

/* ---- footer status bar ---- */
.statusbar { display: flex; gap: 1.4rem; align-items: center; padding: .55rem 1.4rem;
  border-top: 1px solid var(--line); background: var(--bar); font-family: var(--mono);
  font-size: .74rem; color: var(--muted);
  z-index: 20; }
/* Scoped to the app shell: app.css is served to the sign-in page too, and that
   one is a normal document that must be free to scroll. */
body.shell { height: 100vh; min-height: 0; overflow: hidden; }
body.shell .statusbar { flex: 0 0 auto; }
.statusbar .sb-right { margin-left: auto; }

/* ---- dialogs ---- */
/* A card that floats, not a box that appears. The shadow does the lifting the
   border was being asked to do, and a softer backdrop keeps the page behind
   legible as context rather than blacking it out. */
dialog { border: 1px solid var(--line); border-radius: var(--r-xl); background: var(--panel);
  color: var(--ink); width: min(620px, 94vw); padding: 1.45rem 1.55rem 1.25rem;
  box-shadow: var(--shadow-2), var(--edge); }
dialog::backdrop { background: rgba(20,23,28,.32); backdrop-filter: blur(3px); }
[data-theme="dark"] dialog::backdrop { background: rgba(4,6,9,.6); }
dialog menu { display: flex; justify-content: flex-end; gap: .5rem; margin: 1.35rem 0 0;
  padding: .95rem 0 0; border-top: 1px solid var(--line); }
dialog h2 { margin: 0 0 .35rem; font-size: 1.02rem; font-weight: 700; letter-spacing: -.01em; }
form label { display: block; margin: .6rem 0; font-size: .88rem; color: var(--muted); }
form input, form select, form textarea { width: 100%; margin-top: .28rem; padding: .5rem .65rem;
  border: 1px solid var(--line2);
  border-radius: var(--r-sm); background: var(--bg); color: var(--ink); font: inherit; }
/* A JSON request body is code: proportional text made braces and indentation
   unreadable, and `font: inherit` above would otherwise hand it the sans stack. */
form textarea { font-family: var(--mono); font-size: .8rem; resize: vertical; }
.checks label { display: flex; align-items: center; gap: .5rem; width: auto; color: var(--ink); }
.checks input { width: auto; margin: 0; }
.row2 { display: flex; gap: .6rem; } .row2 label { flex: 1; }
/* An HTTP method needs ~7rem, not half the row, so the path gets the rest. */
.row2 label.narrow { flex: 0 0 7.5rem; }
/* A named group of fields inside a section, for when a disclosure of its own
   would just make the form look like it has two Advanced areas. */
.fgroup { margin-top: 1rem; padding-top: .85rem; border-top: 1px solid var(--line); }
.fgroup-t { display: block; font-size: .66rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); margin-bottom: .2rem; }
.adv { margin-top: .8rem; border-top: 1px solid var(--line); padding-top: .6rem; }
.adv summary { font-size: .8rem; font-weight: 500; color: var(--muted);
  cursor: pointer; padding: .1rem 0; }
.adv summary:hover { color: var(--ink); }
.adv summary:hover { color: var(--ink); }
form menu { display: flex; justify-content: flex-end; gap: .5rem; padding: 0; margin: 1.1rem 0 0; }
/* ---- the create dialog's three sections ----
   Moved from .docs/design/prototype/create.html as-is: every value is a token this
   system already has, and the prototype introduced no colour, radius or shadow of
   its own. The NUMBER is not decoration -- choosing a runtime re-shapes the form,
   and the number says which part changed rather than leaving fields to appear and
   vanish in a scroll. */
.sec { border-top: 1px solid var(--line); padding-top: .9rem; margin-top: 1rem; }
.sec:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.sec-h { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .7rem; }
.sec-n { font: 600 .7rem/1.5 var(--mono); color: var(--dim);
  min-width: 1.15rem; height: 1.15rem; text-align: center;
  border: 1px solid var(--line2); border-radius: var(--r-pill); }
.sec-t { font-weight: 600; font-size: .9rem; color: var(--ink); }
.sec-sub { font-size: .78rem; color: var(--muted); margin-left: auto; }

/* The runtime fork. Two cards rather than a select, because the two facts that
   decide it -- what it costs and what it cannot do -- have nowhere to live inside
   an <option>. Blue is "you can click it", which is what these are. */
.forks { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
.fork { text-align: left; padding: .7rem .8rem; border: 1px solid var(--line2);
  border-radius: var(--r-md); background: var(--panel); cursor: pointer;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease); }
.fork:hover { border-color: var(--accent); }
.fork[aria-pressed="true"] { border-color: var(--accent);
  background: var(--accent-soft); box-shadow: 0 0 0 3px var(--accent-ring); }
.fork-n { display: flex; align-items: center; gap: .4rem;
  font-weight: 600; font-size: .88rem; color: var(--ink); }
.fork-dot { width: .5rem; height: .5rem; border-radius: var(--r-pill);
  border: 1px solid var(--line2); }
.fork[aria-pressed="true"] .fork-dot { background: var(--accent); border-color: var(--accent); }
.fork-cost { font: .72rem/1.4 var(--mono); color: var(--muted); margin: .3rem 0 .35rem; }
.fork-can { font-size: .74rem; color: var(--muted); }

/* Arrangement is DEPENDENT on the runtime, so it is a segmented control inside the
   same section rather than a second peer dropdown beside it. */
.seg { display: inline-flex; border: 1px solid var(--line2);
  border-radius: var(--r-sm); overflow: hidden; margin-top: .85rem; }
.seg button { border: 0; background: var(--panel); color: var(--muted);
  padding: .38rem .8rem; font: inherit; font-size: .82rem; cursor: pointer;
  border-right: 1px solid var(--line2); }
.seg button:last-child { border-right: 0; }
.seg button[aria-pressed="true"] { background: var(--accent-soft); color: var(--ink);
  font-weight: 600; }
.seg-note { font-size: .76rem; color: var(--muted); margin: .45rem 0 0; }
.adv-gone { margin: 0 0 .7rem; font-size: .76rem; color: var(--muted);
  padding-left: .6rem; border-left: 2px solid var(--line2); }
.hint { color: var(--muted); font-size: .82rem; margin: .1rem 0 .4rem; }
/* A server message quoted verbatim, where the newlines in it carry meaning: the
   metrics-server install is two commands, the helm upgrade is one long one. Without
   this they collapse into a paragraph and the command cannot be copied out of it. */
.hint.pre, .empty.pre { white-space: pre-wrap; text-align: left; font-size: .8rem; }
.log { background: #05070b; color: #c9d1d9; font-family: var(--mono); font-size: .8rem; line-height: 1.45;
  padding: .8rem; border-radius: var(--r-sm); max-height: 52vh; overflow: auto; white-space: pre-wrap; word-break: break-word;
  border: 1px solid var(--line); }
.log .warn { color: var(--amber); } .log .error { color: var(--red); } .log .done { color: var(--green); }
.plan { margin: .5rem 0; padding: .6rem .7rem; border: 1px solid var(--line2); border-radius: var(--r-sm); background: var(--bg);
  font-size: .82rem; max-height: 34vh; overflow: auto; }
/* Activity rows: a button so the whole row is clickable and keyboard-reachable. */
/* ---- People ----
   Six cells, so it cannot borrow Activity's four. Name takes the room, the two
   facts sit at fixed widths, and the controls stay together at the end -- which
   is what makes a column of people scannable rather than a stack of forms. */
.people { display: grid; border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--panel); box-shadow: var(--shadow-1), var(--edge); overflow: hidden; }
.prow { display: grid; grid-template-columns: minmax(0, 1fr) auto 8rem auto;
  gap: .7rem; align-items: center; padding: .5rem .8rem;
  border-top: 1px solid var(--line); }
.prow:first-child { border-top: 0; }
.prow:hover { background: var(--panel2); }
.p-name { font-family: var(--mono); font-size: .84rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.p-sess { font-size: .72rem; color: var(--dim); white-space: nowrap; }
.prow .input { height: 1.9rem; padding: 0 .5rem; font-size: .76rem;
  border: 1px solid var(--line2); border-radius: var(--r-xs);
  background-color: var(--bg); color: var(--ink); }
.prow .input:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring); }
/* `remove` sits in a row of controls, so it keeps their outline -- quiet in
   colour, not shapeless. Borderless is right for the standalone Prune link and
   wrong beside a bordered `reset`, where it reads as an unfinished button. */
.p-act { display: inline-flex; gap: .4rem; }
.p-act .btn.danger { border-color: var(--line2); }
.p-act .btn.danger:hover { border-color: var(--red); }
.people .empty { margin: 1.6rem 0; }
#people-add { margin-top: .8rem; align-items: center; }

/* ---- Activity: one grid, so the columns line up down the page ----
   The list wore `.plan` -- the config-import dialog's box -- and the rows were a
   flex line whose second cell was the variable-width one, so nothing under it
   agreed with anything above it. Four cells, always: what happened, to what, who,
   when. The origin pill rides inside the `who` cell rather than being a
   conditional fifth column that knocked those rows out of step. */
.jobs { display: grid; }
/* What this list is, and how to narrow it — one strip, ruled off from the rows so
   the first row does not read as another control. */
.jobs-head { padding: .8rem .95rem; border-bottom: 1px solid var(--line);
  display: grid; gap: .55rem; }
.jobs-head .hint { margin: 0; }
.jobs-head .row2 { margin: 0; align-items: center; }
.jobs-head .input { flex: 1 1 auto; height: 2rem; padding: 0 .6rem; font-size: .78rem;
  border: 1px solid var(--line2); border-radius: var(--r-sm);
  background-color: var(--bg); color: var(--ink); }
.jobs-head .input:hover { border-color: var(--muted); }
.jobs-head .input:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring); }
/* A toggle, styled as one, rather than the log viewer's follow-checkbox borrowed
   whole and left to look like a stray control beside the field. */
.jobs-head .toggle { flex: 0 0 auto; display: inline-flex; align-items: center; gap: .4rem;
  height: 2rem; padding: 0 .7rem; font-size: .76rem; color: var(--muted);
  border: 1px solid var(--line2); border-radius: var(--r-sm);
  background: var(--panel); cursor: pointer; user-select: none; white-space: nowrap;
  transition: background-color var(--t) var(--ease), border-color var(--t) var(--ease),
    color var(--t) var(--ease); }
.jobs-head .toggle:hover { border-color: var(--muted); color: var(--ink); }
.jobs-head .toggle:has(input:checked) { color: var(--red); border-color: var(--red);
  background: rgba(217,45,32,.08); }
.jobs-head .toggle input { accent-color: var(--red); cursor: pointer; margin: 0; }
.jobs .empty { margin: 2rem 0; }
.jobrow { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr) auto auto;
  gap: .8rem; align-items: center; width: 100%; text-align: left;
  padding: .5rem .95rem; border: 0; border-top: 1px solid var(--line);
  background: none; font: inherit; color: var(--ink); }
.jobs .jobrow:first-child { border-top: 0; }
button.jobrow { cursor: pointer; }
button.jobrow:hover { background: var(--panel2); }
.jobrow.denied { background: rgba(217,45,32,.06); }
.jkind { font-family: var(--mono); font-size: .82rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.jmeta { display: inline-flex; align-items: center; gap: .4rem; justify-self: end; }
.jage { color: var(--dim); font-family: var(--mono); font-size: .72rem; white-space: nowrap;
  justify-self: end; }
.jstatus { font-family: var(--mono); font-size: .66rem; text-transform: uppercase;
  letter-spacing: .05em; font-weight: 700; text-align: center;
  border: 1px solid var(--line2); border-radius: var(--r-pill); padding: .1rem .35rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jstatus.running { color: var(--blue); border-color: rgba(88,166,255,.45); }
.jstatus.queued { color: var(--muted); border-color: rgba(139,148,158,.45); }
.jstatus.done { color: var(--green); border-color: rgba(63,185,80,.45); }
.jstatus.error { color: var(--red); border-color: rgba(248,81,73,.45); }
.plan hr, .result hr { border: none; border-top: 1px solid var(--line); margin: .5rem 0; }
.plan .kv { font-family: var(--mono); padding: .1rem 0; word-break: break-all; }
.plan .kv code { color: var(--blue); } .plan .warn { color: var(--amber); }
.result { color: #c9d1d9; font-size: .82rem; line-height: 1.5; margin-top: .4rem; }
.result .ok { color: var(--green); font-weight: 700; } .result .bad { color: var(--red); font-weight: 700; }
.grafana-embed { width: 100%; height: 320px; border: 1px solid var(--line); border-radius: var(--r-sm); margin-top: .5rem; background: #fff; }
/* Toast stack: one fixed container, newest at the bottom. Individually-positioned
   toasts used to overlay each other exactly when a failure repeated. */
.toasts { position: fixed; bottom: 3rem; left: 50%; transform: translateX(-50%);
          display: flex; flex-direction: column; gap: .4rem; align-items: center;
          z-index: 60; pointer-events: none; max-width: min(90vw, 40rem); }
.toast { background: var(--red); color: var(--on-accent); padding: .55rem 1rem; border-radius: var(--r-sm);
         font-size: .85rem; box-shadow: 0 6px 20px rgba(0,0,0,.45); text-align: center; }
.toast.ok { background: var(--green); color: #06210d; }
.toast.info { background: var(--blue); color: #04162e; }

/* ---- responsive ----
   The three panes are a 288px rail, the stage, and a 244px action pane: 532px of
   fixed chrome. There was nothing responsive about them at all -- the @media rules
   here still named `.layout`, the class the workbench redesign replaced with
   `.panes`, so they had matched nothing since. Measured, at 900px height:

     width   stage   result
      768    236px   unusable
      640    108px   unusable
      480     42px   AND the page scrolled sideways (532 > 480)

   Two steps, each removing one fixed track rather than hiding a control. Nothing
   becomes unreachable at any width: the actions move under the panel, and the rail
   moves above it. A control you cannot reach on a laptop is worse than one you have
   to scroll to. */
@media (max-width: 1100px) { .kv-grid { grid-template-columns: repeat(2, 1fr); } }

/* Step one: the action pane stops being a column and becomes a strip under the
   stage, full width. It keeps its own scroll and its danger zone stays at the end. */
@media (max-width: 1080px) {
  .panes { grid-template-columns: 288px minmax(0, 1fr); }
  .actpane { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--line);
    max-height: 40vh; }
  .aplist { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: .25rem; }
}

/* Step two: one column. The rail is a capped, scrollable strip above the stage --
   it is how you choose a workspace, so it cannot simply go. */
@media (max-width: 760px) {
  /* Normal flow, not a one-column grid. A grid still sizes its rows from the track
     it was given, so the stage's row stayed as short as the shell allowed and the
     cards simply PAINTED over the action pane and the status bar below them. In
     block flow each pane is as tall as its own content and they follow each other. */
  .panes, .panes.no-detail { display: block; }
  .rail { border-right: 0; border-bottom: 1px solid var(--line); max-height: 38vh; }
  /* The stage is `position: sticky` so it stays put while the rail scrolls beside
     it. In ONE column there is nothing beside it, and a sticky box in a grid row
     rides up over the row above -- the rail ended up underneath the panel, and
     the workspace rows could not even be clicked. The reset for this lived in the
     old `.layout` rule, which stopped matching anything when the panes were
     renamed and took this with it. */
  .detail, .stage { position: static; top: auto; min-height: 0; }
  /* And the shell stops being viewport-locked. On a desktop each pane scrolls
     inside a 100vh frame, which is right when they sit side by side. Stacked into
     one column that same frame gives each of the three a third of the screen and
     its own scrollbar: the panel came out as a ~200px window showing four of six
     cells with every notes card below the cut, reachable only by scrolling a box
     most readers would not know was a box. THIS is what "everything collapses on
     resize" was. In one column the PAGE scrolls and the panel is as tall as its
     content; the rail stays capped on purpose, because it is a picker.

     `body.shell` is where the lock actually is -- 100vh plus `overflow: hidden`,
     which also pinned the status bar to the viewport, so it came out drawn across
     the middle of the panel rather than under it. */
  body.shell { height: auto; min-height: 100vh; overflow: visible; }
  .stage { overflow: visible; }
  .actpane { max-height: none; }
  .forks { grid-template-columns: 1fr; }
  .statusbar { flex-wrap: wrap; gap: .5rem 1rem; }
  .grafana-embed { height: 240px; }

  /* ---- D: designed for this width, rather than merely surviving it ---- */

  /* The four top links become a popover off one button. Four links, a theme toggle
     and an account name wrapped onto a second row of chrome here, which on a 430px
     screen is a quarter of the height spent before anything is said about a
     workspace. Same four buttons, moved -- not a second copy. */
  .chip.navtoggle { display: inline-flex; }
  .toplinks { position: absolute; top: calc(100% + .3rem); right: .6rem; z-index: 40;
    display: none; flex-direction: column; align-items: stretch; gap: 0;
    min-width: 11rem; padding: .3rem; background: var(--panel);
    border: 1px solid var(--line2); border-radius: var(--r-md);
    box-shadow: var(--shadow-2); }
  body.nav-open .toplinks { display: flex; }
  .toplinks .toplink { padding: .5rem .6rem; text-align: left; border-radius: var(--r-sm); }
  .toplinks .toplink:hover { background: var(--panel2); }
  /* The current-page marker is a 2px rule under a horizontal nav; stacked it would
     be a line across the middle of the item below it. */
  .toplinks .toplink::after { content: none; }
  .topbar { position: relative; }

  /* The rail becomes a horizontal picker, one row high. It is how you choose a
     workspace so it cannot go, and a capped vertical strip spent 38vh of a phone on
     a list of four names. */
  .rail { max-height: none; }
  .rail-controls { display: flex; flex-direction: row; align-items: center;
    padding: .5rem .6rem .55rem; overflow-x: auto; scrollbar-width: thin; }
  .rail-controls > * { flex: 0 0 auto; }
  .rail-controls .input { width: 11rem; }
  /* `.block` is width:100% -- in a row it took the whole strip and pushed the filter
     and the three selects off the right edge of a scroller nothing said was one. */
  .rail-controls .btn.block { width: auto; }
  .rail-two { display: flex; gap: .4rem; }
  .rows { display: flex; flex-direction: row; overflow-x: auto; scrollbar-width: thin;
    padding: .5rem .55rem .6rem; }
  /* A CHIP here, whatever shape the row has on a wide screen: at 430px a picker is
     one row high, and the port and owner that earn their place in a 288px column are
     what has to go when the column becomes a strip. */
  .wrow { flex: 0 0 auto; display: flex; align-items: center; width: auto;
    max-width: 15rem; border-radius: 999px; padding: .35rem .8rem .35rem 1.35rem; }
  .wrow::before { left: .65rem; top: 50%; bottom: auto; width: 6px; height: 6px;
    margin-top: -3px; border-radius: 50%; }
  .wrow .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; }
  /* The group headings do not survive a horizontal strip -- the ORDER does, and
     wants-you is still first. */
  .rgrp { display: none; }
  .wrow .ver, .wrow .meta, .wrow .r3 { display: none; }

  /* The state actions become a bar that stays put while the panel scrolls, and the
     rest of the pane is one tap away rather than a screen and a half below. A
     wrapping grid of eleven items pushed the danger zone off the bottom of the
     document.

     ORDERED LAST, which is the whole reason it works: `bottom` stickiness holds an
     element that would fall BELOW the scrollport, and it does not drag one down from
     above -- so with the bar in its document position, above the panel body, the inset
     did nothing whatsoever and the bar scrolled away like any other row. Ordered to
     the end of the flex column it floats at the bottom of the screen while the panel
     scrolls and settles at the end of it. */
  .stage { display: flex; flex-direction: column; }
  .d-actions { order: 9; position: sticky; bottom: 0; z-index: 20; margin: 0 -1.1rem;
    padding: .6rem 1.1rem; background: var(--bar); border-top: 1px solid var(--line); }
  .btn.panetoggle { display: inline-flex; margin-left: auto; }
  .actpane { display: none; }
  body.pane-open .actpane { display: flex; }
}
@media (max-width: 560px) {
  .bar, .toolbar-controls { flex-wrap: wrap; }
  .topbar-actions { flex-wrap: wrap; }
  .kv-grid { grid-template-columns: 1fr; }
  dialog { width: calc(100vw - 1.5rem); }
}
/* `clip`, not `hidden`. Both stop a sideways scroll, and `hidden` on one axis makes
   the OTHER axis compute to `auto` -- which turns body into a scroll container that
   never scrolls, and a `position: sticky` descendant measures against that instead of
   the viewport and so never sticks. That is why the narrow layout's action bar scrolled
   away with the panel. `clip` creates no scroll container. */
body { overflow-x: clip; }

/* the job dialog has no <form>, so `form menu` never matched its footer */
#job-dialog menu { display: flex; justify-content: flex-end; padding: 0; margin: .8rem 0 0; }

/* Who ran a job, in the activity list. Only rendered when the GUI has named
   accounts; in token mode there is nobody to name. */
.jactor { color: var(--muted); font-size: 12px; }
.jactor::before { content: "by "; opacity: .6; }

/* ---- home ---- */
/* Shown when nothing is selected. It leads with capacity because that is the
   constraint that actually bites: `up` refuses without headroom, and "room for
   about 2 more" is the sentence that decides what you do next. */
.home-head { padding: 1.3rem 1.6rem .2rem; }
.home-head h1 { margin: 0; font-size: 1.35rem; letter-spacing: -.02em; }
.home-head p { margin: .35rem 0 0; font-family: var(--mono); font-size: .78rem; color: var(--muted); }
.home-body { padding: 1.1rem 1.6rem 2rem; display: grid; gap: 1rem; max-width: 68rem; }
.capcard { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--shadow-1), var(--edge);
  padding: 1rem 1.1rem 1.1rem; }
.caphead { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.caproom { font-size: .82rem; font-weight: 600; color: var(--green); }
.caproom.tight { color: var(--amber); }
.capnum { font-family: var(--mono); font-size: 1.6rem; font-weight: 700; letter-spacing: -.03em;
  margin-top: .35rem; }
.capnum small { font-family: var(--sans); font-size: .84rem; font-weight: 400;
  color: var(--muted); margin-left: .45rem; }
/* How full the machine is. Three segments of ONE quantity, so they are one hue
   stepping down in weight rather than three unrelated greys -- and the fill has
   round ends and grows into place, because the number it draws changes while you
   are looking at it. */
.capbar { display: flex; height: 10px; border-radius: var(--r-pill); overflow: hidden;
  background: var(--panel2); box-shadow: inset 0 0 0 1px var(--line);
  margin-top: .75rem; }
/* Segments stay square and the ROUND TRACK clips them: giving each one its own
   radius notches every internal join, which is worse than the hard stop it was
   meant to soften. */
.capbar i { display: block; height: 100%; transition: width var(--t-slow) var(--ease); }
.capbar .used { background: var(--accent-fill); }
.capbar .reserved { background: var(--line2); }
.caplegend { display: flex; gap: 1.1rem; flex-wrap: wrap; margin-top: .55rem;
  font-size: .72rem; color: var(--muted); }
.caplegend .k { display: inline-block; width: 9px; height: 9px; border-radius: var(--r-xs);
  margin-right: .4rem; vertical-align: -1px; box-shadow: inset 0 0 0 1px var(--line); }
.caplegend .k.used { background: var(--accent-fill); box-shadow: none; }
.caplegend .k.reserved { background: var(--line2); box-shadow: none; }
.caplegend .k.free { background: var(--panel2); box-shadow: inset 0 0 0 1px var(--line); }

.hcard { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--shadow-1), var(--edge);
  overflow: hidden; }
.hcard-h { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .7rem 1rem; border-bottom: 1px solid var(--line); }
.att { display: flex; align-items: center; gap: .8rem; padding: .75rem 1rem;
  border-bottom: 1px solid var(--line); }
.att:last-child { border-bottom: 0; }
.att-i { color: var(--amber); font-size: .8rem; }
.att-t { flex: 1; font-size: .84rem; line-height: 1.5; }
/* Two workspaces in a three-slot auto-fill grid leaves an empty cell painted in
   the line colour — a grey block sitting beside your workspaces. Same hole the
   fact grid had; same fix. */
/* ---- one running workspace, on Home ----
   A row, not a card in a wrapping grid: there is one of these per running
   workspace and they are a list, so they read down the page and every name,
   uptime and Open lines up with the one above it. */
.mini { display: grid; }
.wsrow { display: flex; align-items: center; gap: .9rem; padding: .55rem .75rem .55rem 1rem;
  border-top: 1px solid var(--line); }
.mini .wsrow:first-child { border-top: 0; }
/* The row is one big target for "show me this workspace"; Open sits outside it,
   because it goes somewhere else entirely. */
.wsrow-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: .8rem;
  padding: .35rem .4rem; margin: -.35rem -.4rem; border: 0; border-radius: var(--r-sm);
  background: none; font: inherit; color: var(--ink); text-align: left; cursor: pointer;
  transition: background-color var(--t) var(--ease); }
.wsrow-main:hover { background: var(--panel2); }
.wsrow-tile { flex: 0 0 auto; width: 2.35rem; height: 2.35rem; border-radius: var(--r-md);
  background: var(--panel2); box-shadow: inset 0 0 0 1px var(--line);
  display: grid; place-items: center; }
.wsrow-tile::before { content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--green); box-shadow: 0 0 0 4px rgba(74,222,128,.14); }
.wsrow-tile.bad::before { background: var(--amber); box-shadow: 0 0 0 4px rgba(240,180,41,.16); }
.wsrow-t { min-width: 0; display: grid; gap: .12rem; }
.wsrow-t b { font-size: .88rem; font-weight: 600; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.wsrow-m { font-family: var(--mono); font-size: .72rem; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wsrow-r { flex: 0 0 auto; display: flex; align-items: center; gap: .8rem; }
/* Uptime is the number; what it IS goes beside it, small, so the figure carries
   the row rather than a label competing with it. */
.wsrow-up { font-family: var(--mono); font-size: .8rem; font-weight: 600; color: var(--green); }
.wsrow-up i { font-style: normal; font-weight: 400; font-size: .7rem; color: var(--dim);
  margin-left: .35rem; }
.wsrow-up.bad { color: var(--amber); }
.wsrow-r .btn { text-decoration: none; }
.lbl-n { font-family: var(--mono); font-size: .7rem; color: var(--dim); }
@media (max-width: 780px) {
  .wsrow { flex-wrap: wrap; }
  .wsrow-r { width: 100%; justify-content: flex-end; }
}
.arow { display: flex; align-items: baseline; gap: .9rem; padding: .55rem 1rem;
  border-bottom: 1px solid var(--line); font-size: .82rem; }
.arow:last-child { border-bottom: 0; }
.arow .who { width: 5.5rem; flex: 0 0 auto; font-family: var(--mono); font-size: .76rem;
  color: var(--muted); }
.arow .kind { width: 8rem; flex: 0 0 auto; font-family: var(--mono); font-size: .74rem;
  color: var(--dim); }
.arow .what { flex: 1; overflow-wrap: anywhere; }
.arow .when { font-family: var(--mono); font-size: .7rem; color: var(--dim); }
.arow[data-denied="true"] { background: rgba(217,45,32,.06); }
.arow[data-denied="true"] .kind { color: var(--red); }

/* ---- scenarios ---- */
.scen-b { padding: .85rem 1rem; }
.scen-d { margin: 0; font-size: .86rem; line-height: 1.6; color: var(--muted); }
/* Notes are pre-wrapped for an 80-column terminal. Re-flowing them into a narrow
   column produces "…reaches Keycloak at the same / URL Rocket.Chat's backend
   uses", which reads as broken text rather than as instructions. */
.scen-n { margin: .7rem 0 0; padding: .7rem .8rem; background: var(--panel2);
  border-radius: var(--r-sm); font-family: var(--mono); font-size: .76rem; line-height: 1.7;
  color: var(--ink); white-space: pre; overflow-x: auto; }
.scen-f { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .65rem 1rem; border-top: 1px solid var(--line); font-size: .78rem;
  color: var(--muted); flex-wrap: wrap; }

/* Which page you are on. Without it Activity and Scenarios were reachable and
   indistinguishable — you could not tell where you were, or that there was
   anywhere else to be. */
/* Nav. The marker is a drawn rule rather than `text-decoration`, so it can grow
   from the centre when a page becomes current instead of blinking into place. */
.toplink { position: relative; font-size: .8rem; font-weight: 500;
  padding: .35rem .25rem; border: 0; background: none; color: var(--muted);
  cursor: pointer; font-family: inherit; }
.toplink::after { content: ""; position: absolute; left: 0; right: 0; bottom: -.1rem;
  height: 2px; border-radius: 2px; background: var(--accent);
  transform: scaleX(0); transition: transform var(--t) var(--ease); }
.toplink:hover { color: var(--ink); }
.toplink[aria-current] { color: var(--ink); font-weight: 600; }
.toplink[aria-current]::after { transform: scaleX(1); }
button.brand { border: 0; background: none; cursor: pointer; color: var(--ink); padding: 0; }
button.brand:hover { color: var(--blue); }

/* ---- account menu ---- */
.menuwrap { position: relative; }
.memenu { position: absolute; right: 0; top: calc(100% + .45rem); min-width: 15rem;
  background: var(--panel); border: 1px solid var(--line2); border-radius: var(--r-md);
  padding: .35rem; z-index: 60;
  box-shadow: 0 1px 2px rgba(20,23,28,.06), 0 14px 34px rgba(20,23,28,.12); }
[data-theme="dark"] .memenu { box-shadow: 0 14px 40px rgba(0,0,0,.5); }
.me-head { padding: .5rem .6rem .45rem; border-bottom: 1px solid var(--line);
  margin-bottom: .35rem; }
.me-head b { display: block; font-size: .86rem; }
.me-head span { font-family: var(--mono); font-size: .7rem; color: var(--muted); }
.memenu button { display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .45rem .6rem; border: 0; border-radius: var(--r-xs); background: none; color: var(--ink);
  font: inherit; font-size: .82rem; text-align: left; cursor: pointer; }
.memenu button:hover { background: var(--panel2); }
.memenu button small { margin-left: auto; font-family: var(--mono); font-size: .66rem;
  color: var(--dim); }
.memenu button.danger { color: var(--red); font-weight: 600; }
.memenu button.danger:hover { background: rgba(217,45,32,.08); }
.memenu hr { border: 0; border-top: 1px solid var(--line); margin: .35rem 0; }

/* ---- preset notes ---- */
/* A bordered block, like everything else that is content rather than chrome.
   Loose paragraphs under a grey label read as leftovers. */
.notes { display: grid; gap: .8rem; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 1rem 1.1rem; }
.notes code.inline { font-family: var(--mono); font-size: .82em; padding: .1em .35em;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--r-xs); }
/* NO `ch` cap. A 74ch measure is the right idea for a page of running text and the
   wrong one inside a card: it made a paragraph 647px wide in an 832px card at 1440, and
   the same 647px in a 1344px card at 1920 -- half the box, with the code block under it
   running the full width. The card is the measure; the panel column is what bounds it. */
.note-p { margin: 0; font-size: .84rem; line-height: 1.65; color: var(--muted); }
.note-p a { color: var(--blue); }
/* A grouped note's facts. A table because that is what they are -- a lookup, not a
   sentence -- and `tabular-nums` so versions and counts line up down the column. */
.notetable { width: 100%; border-collapse: collapse; font-size: .82rem;
  font-variant-numeric: tabular-nums; }
.notetable tr { border-top: 1px solid var(--line); }
.notetable tr:first-child { border-top: 0; }
.notetable th { text-align: left; font-weight: 500; color: var(--muted);
  padding: .42rem 1.15rem; width: 10.5rem; white-space: nowrap; }
.notetable td { font-family: var(--mono); font-size: .78rem; color: var(--ink);
  padding: .42rem 1.15rem .42rem 0; overflow-wrap: anywhere; }
@media (max-width: 560px) {
  .notetable th { width: auto; white-space: normal; }
}

/* The line you have to type. It was one <div> among five and read as prose. */
.note-cmd { display: flex; align-items: center; gap: .6rem; padding: .45rem .55rem;
  min-width: 0;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--r-sm); }
.note-cmd code { flex: 1; min-width: 0; font-family: var(--mono); font-size: .78rem;
  color: var(--ink); overflow-x: auto; white-space: pre; }
/* `min-width: 0` on BOTH -- the box and the code inside it -- is the whole fix, and
   without it the `overflow-x` here never engaged. A flex or grid item defaults to
   `min-width: auto`, which refuses to shrink below its content's intrinsic width, so
   a long line pushed its own container wide; `.note-cmd` is a grid item of
   `.panelcard-b.notes`, so it needed the same thing its `<code>` child did.
   Measured on a Kubernetes workspace, whose notes carry
   `export KUBECONFIG=<absolute path>`: `.note-cmd` came out 1147px inside an 824px
   panel, and `.panelcard`'s `overflow-x: hidden` silently CLIPPED the difference --
   so the reader was told to paste a path whose end they could not see. `white-space:
   pre` has to stay (a pasted line's spacing is load-bearing and a test asserts it),
   which is exactly why the box has to scroll rather than the text wrap. Same bug as
   `.rail-two .input`, two hundred lines up. */
.cp { font-size: .68rem; font-family: var(--sans); color: var(--muted); background: none;
  border: 1px solid var(--line2); border-radius: var(--r-xs); padding: .18rem .45rem; cursor: pointer;
  flex: 0 0 auto; }
.cp:hover { color: var(--blue); border-color: var(--blue); }

/* ---- where things are ---- */
.linkrows { display: grid; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.linkrow { display: flex; align-items: baseline; gap: 1rem; padding: .6rem .9rem;
  border-bottom: 1px solid var(--line); text-decoration: none; color: var(--ink);
  background: var(--panel); }
.linkrow:last-child { border-bottom: 0; }
.linkrow:hover { background: var(--panel2); }
.linkrow .l-n { flex: 0 0 auto; min-width: 8.5rem; white-space: nowrap;
  font-size: .82rem; font-weight: 600; }
.linkrow .l-u { flex: 0 1 auto; font-family: var(--mono); font-size: .76rem;
  color: var(--blue); overflow-wrap: anywhere; }
.linkrow .l-go { margin-left: auto; color: var(--dim); font-size: .74rem;
  opacity: 0; transition: opacity .12s; }
.linkrow:hover .l-go { opacity: 1; }
.linkrow.sidecar .l-n::after { content: " · sidecar"; font-weight: 400; color: var(--dim);
  font-size: .68rem; }
.linkrow.monitor .l-n::after { content: " · monitoring"; font-weight: 400; color: var(--dim);
  font-size: .68rem; }
/* A row carries up to four things — what it is, what it is for, where, and the
   password — plus a second line for anything the preset said about it. Wrapping
   rather than ellipsising: the description IS the sentence, and half of one is
   worse than a line break. */
.linkrow { flex-wrap: wrap; row-gap: .3rem; }
.linkrow .l-w { flex: 0 1 auto; min-width: 0; font-size: .78rem; color: var(--muted); }
.linkrow .l-u { margin-right: auto; }
.linkrow .l-c { flex: 0 0 auto; font-family: var(--mono); font-size: .71rem;
  color: var(--muted); background: var(--panel2); border: 1px solid var(--line);
  border-radius: var(--r-xs); padding: .08rem .38rem; }
/* Indented to the url column, so the second line reads as part of the row rather
   than as a new one that happens to sit under it. */
.linkrow .l-sub { flex: 0 0 100%; padding-left: 9.5rem; font-size: .78rem;
  line-height: 1.6; color: var(--muted); }
.linkrow .l-sub a { color: var(--blue); }
.linkrow .l-sub code.inline { font-family: var(--mono); font-size: .82em; padding: .1em .35em;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--r-xs); }
/* A place with no reachable address (livechat's "<repro-url>/livechat") is still
   a row — it just is not a link, and pretending otherwise gives a dead click. */
.linkrow.static { cursor: default; }
.linkrow.static:hover { background: var(--panel); }
.linkrow.static .l-u { color: var(--muted); }
/* A note-borne place standing on its own, inside the prose flow. */
.linkrows.one { border-radius: var(--r-sm); }
.notes .linkrows.one .linkrow { background: var(--panel2); }
.notes .linkrows.one .linkrow:hover { background: var(--line); }

/* ---- notes, read as documentation rather than a dump ---- */
.notes { gap: .7rem; }
/* Each remaining note is a POINT, and a marker says so. Without one, three
   paragraphs of equal weight under a heading read as a paragraph that has been
   broken up rather than as three separate things you are being told. */
.note-p { position: relative; padding-left: .95rem; font-size: .86rem; line-height: 1.7;
  color: var(--ink); opacity: .84; }
.note-p::before { content: ""; position: absolute; left: .1rem; top: .68em;
  width: 4px; height: 4px; border-radius: 50%; background: var(--dim); }
/* One paragraph is not a list of one -- see renderNoteItems. */
.note-p.solo { padding-left: 0; }
.note-p.solo::before { content: none; }
.note-cmd { border-radius: var(--r-md); padding: .55rem .65rem; }
.note-cmd code { font-size: .8rem; }
/* The scenario card: a document, not a form. */
.scen-b { padding: 1rem 1.15rem 1.15rem; }
.scen-d { font-size: .88rem; line-height: 1.7; color: var(--ink); opacity: .8; }
.scen-f { padding: .8rem 1.15rem; font-size: .74rem; }
.hcard, .capcard { border-radius: var(--r-lg); }
.hcard-h { padding: .8rem 1.1rem; }
.home-head h1 { font-size: 1.5rem; font-weight: 700; }
.home-body { gap: 1.15rem; }
.lbl, .hcard-h .lbl { font-size: .64rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); }
.att { padding: .85rem 1.1rem; }
.capcard { padding: 1.15rem 1.25rem 1.25rem; }
.capnum { font-size: 1.85rem; }
/* Inputs and buttons: proportional, so a form reads as a form. */
.input { font-family: var(--sans); font-size: .84rem; padding: .48rem .7rem; }
.btn { font-size: .82rem; font-weight: 500; padding: .45rem .85rem; }
.btn.small { font-size: .76rem; padding: .3rem .62rem; }
form label { font-size: .82rem; margin: .85rem 0; }
form input, form select { padding: .55rem .7rem; font-family: var(--sans); }
.hint { line-height: 1.65; }

/* ---- the one button you are meant to press ---- */
/* The one button a screen is asking you to press. It used to be `--ink` on
   `--bg` -- in dark that is a white slab, the brightest thing on the page, and
   the only thing on it carrying no signal. This file's own rule says blue means
   you can click it, and nothing is more clickable than this. */
.btn.solid { background: var(--accent-fill); color: var(--accent-ink);
  border-color: transparent; font-weight: 600; box-shadow: var(--shadow-accent); }
.btn.solid:hover { background: var(--accent-strong); border-color: transparent;
  color: var(--accent-ink); }
.btn.solid:active:not(:disabled) { box-shadow: var(--shadow-1); }
.btn.solid:disabled { box-shadow: none; }

/* ---- a card with a header, used by the scenario and the chart ---- */
.panelcard { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--shadow-1), var(--edge);
  overflow: hidden; margin: 1.4rem 0; }
.panelcard-h { display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .7rem 1rem; border-bottom: 1px solid var(--line); }
.panelcard-b { padding: .95rem 1rem 1.05rem; }
.panelcard-b .notes { border: 0; padding: 0; background: none; }
/* The notes ARE the card's body here, so they take its padding and none of the
   standalone block's own frame — a border inside a border reads as a mistake. */
.panelcard-b.notes { border: 0; border-radius: 0; background: none;
  padding: .95rem 1rem 1.05rem; }
.panelcard-b.notes .linkrows.one { border: 1px solid var(--line); border-radius: var(--r-sm); }
.panelcard-b.notes .linkrows.one .linkrow { border-radius: 0; }
.panelcard-b.notes .linkrows.one .linkrow:last-child { border-bottom: 0; }
.section-label.flat { margin: 0; }
.linkbtn { background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  font-size: .74rem; color: var(--blue); }
.linkbtn:hover { text-decoration: underline; }
.panelcard .linkrow { border-bottom: 1px solid var(--line); border-radius: 0; }
.panelcard .linkrow:last-child { border-bottom: 0; }

/* A setup step that BLOCKS the scenario. Amber, because it is the one thing here
   that will stop you, and only for a command that is not simply a url to open. */
.setup { display: flex; align-items: flex-start; gap: .8rem; padding: .9rem 1rem;
  background: rgba(180,83,9,.08); border-bottom: 1px solid var(--line); }
[data-theme="dark"] .setup { background: rgba(240,180,41,.09); }
.setup-i { color: var(--amber); font-size: .8rem; line-height: 1.5; }
.setup-t { flex: 1; font-size: .84rem; line-height: 1.6; }
.setup-t b { display: block; }
.setup-w { display: block; color: var(--muted); }
.setup-t code { display: inline-block; margin-top: .5rem; font-family: var(--mono);
  font-size: .8rem; background: var(--panel); border: 1px solid var(--line2);
  border-radius: var(--r-sm); padding: .28rem .55rem; }

/* ---- the chart, in its card ---- */
.chart-box { border: 0; border-radius: 0; background: var(--bg); padding: .7rem .85rem; }
/* No legend: one series per panel, and each panel is titled. What the header
   carries instead is the one thing the chart cannot say about itself. */
.chart-note { font-family: var(--mono); font-size: .68rem; color: var(--dim); }

/* ---- the Grafana / sidecar chips lost every rule in the layout swap ---- */
.linkchip { display: inline-flex; align-items: center; gap: .35rem; font-size: .74rem;
  color: var(--blue); text-decoration: none; border: 1px solid var(--line2);
  border-radius: 999px; padding: .22rem .65rem; background: var(--panel); }
.linkchip:hover { border-color: var(--blue); background: var(--panel2); }
.linkchip::after { content: "↗"; font-size: .68rem; color: var(--dim); }
.card-links { display: flex; flex-wrap: wrap; gap: .45rem; margin: .5rem 0 0; }

/* ---- home: a row you can tell you are hovering ---- */
[data-theme="dark"] .wsrow-main:hover { background: #1F2530; }

/* ---- rail search and filters ----
   Three controls that do one job together, so they get one treatment: the same
   height, the same ground, the same border, and the same answer to a pointer. */
.rail-controls .input { height: 2.15rem; border-radius: var(--r-sm);
  border: 1px solid var(--line2); background-color: var(--bg); color: var(--ink);
  font-size: .8rem; }
.rail-controls .input::placeholder { color: var(--dim); }
.rail-controls .input:hover { border-color: var(--muted); }
.rail-controls .input:focus { outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring); }
/* The chevron was two 4px gradient triangles in `--dim` — 3.3:1, and the size of
   a full stop. An inline SVG mark is one shape, at a size you can actually aim
   at, and it can answer the hover like the border does. */
.rail-two select.input { -webkit-appearance: none; appearance: none;
  padding-right: 1.9rem; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238B94A4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .7rem center; }
.rail-two select.input:hover, .rail-two select.input:focus {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%233B5BDB' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
[data-theme="dark"] .rail-two select.input:hover,
[data-theme="dark"] .rail-two select.input:focus {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%237FA8FF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.rail-two { gap: .5rem; }

/* ---- clusters: where workspaces run, and what that is costing ----
   A peer of the edge and surfaced like one, but it needed a colour the file did
   not have. The rule at the top gives amber to "wants you" and red to "about to
   destroy something", and the two states this view must show -- a cluster past
   the date somebody put on it, and one that would not answer -- are both the
   first. `.chip.down` is RED, so reaching for it here would have painted a
   cluster that is merely expensive in the colour reserved for destruction. */
.chip.warn::before { color: var(--amber); }
.pill.refusing { color: var(--amber); border-color: rgba(180,83,9,.4);
  background: rgba(180,83,9,.10); }

/* The four numbers that answer "what is this box costing" before any detail. */
.cl-tot { display: flex; flex-wrap: wrap; border: 1px solid var(--line);
  border-radius: var(--r); background: var(--panel); overflow: hidden;
  margin: 0 0 1rem; }
.cl-tot > div { flex: 1 1 8rem; padding: .7rem .95rem;
  border-right: 1px solid var(--line); }
.cl-tot > div:last-child { border-right: 0; }
.cl-tot dt { font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim); margin: 0 0 .2rem; }
.cl-tot dd { margin: 0; font-size: 1.2rem; font-weight: 650;
  font-variant-numeric: tabular-nums; }

/* One card per cluster. `--panel` because it stands on the stage, which is
   ground -- the distinction this file's own note warns about. */
.cl-card { border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); margin: 0 0 .75rem; overflow: hidden; }
.cl-card.attn { border-color: rgba(180,83,9,.45); }
.cl-h { display: flex; align-items: center; gap: .6rem; padding: .7rem .95rem;
  border-bottom: 1px solid var(--line); }
.cl-name { font-family: var(--mono, ui-monospace, monospace); font-size: .78rem;
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-sp { flex: 1; }
.cl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); }
.cl-cell { padding: .55rem .95rem; border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line); }
.cl-cell:last-child { border-right: 0; }
.cl-k { font-size: .62rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--dim); margin-bottom: .1rem; }
.cl-v { font-size: .85rem; font-weight: 600; font-variant-numeric: tabular-nums; }
/* `?` is the whole point of the ledger: never a zero it did not read. Dim and
   unbolded so it reads as absence rather than as a value. */
.cl-v.unknown { color: var(--dim); font-weight: 500; }
.cl-foot { padding: .6rem .95rem; display: flex; gap: .5rem; align-items: center;
  flex-wrap: wrap; }
.cl-foot .banner { margin: 0; padding: .4rem .6rem; }
.cl-note { font-size: .74rem; color: var(--muted); }
.cl-sweep { border: 1px dashed var(--line2); border-radius: var(--r);
  padding: .8rem .95rem; margin: 1rem 0 0; }
.cl-sweep h3 { margin: 0 0 .3rem; font-size: .88rem; }
.cl-warn { color: var(--amber); font-size: .74rem; }
.cl-doc { white-space: pre-wrap; word-break: break-all; font-size: .7rem;
  max-height: 14rem; overflow: auto; background: var(--panel2);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: .6rem; }
