/* One theme, dark — the glass look is designed for it and there is no toggle.
   Look: a near-black page, surfaces told apart by translucent hairlines rather
   than solid borders, pill-shaped controls, 16 px cards, a monochrome primary
   button, geometric sans (DM Sans body / Montserrat brand) with a system
   fallback — no fonts are fetched at runtime. */
:root {
  --bg: #0a0a0a;
  --panel: #121214;           /* cards, windows, the assistant */
  --panel-2: #1c1c1f;         /* inputs, sunken areas */
  --panel-3: #2a2a2d;         /* raised: menus, bubbles, toasts, composer */
  --border: rgba(255, 255, 255, .08);
  --border-strong: rgba(255, 255, 255, .18);
  --hover: rgba(255, 255, 255, .06);
  --active: rgba(255, 255, 255, .12);
  --glass-bg: rgba(255, 255, 255, .12);
  --glass-hover: rgba(255, 255, 255, .2);
  --glass-border: rgba(255, 255, 255, .14);
  --text: #e7e9ec;
  --text-strong: #fff;
  --muted: #8b9098;
  --accent: #4d6bfe;
  --link: #7aaaff;
  --green: #4ed17e;
  --red: #f25a5a;
  --amber: #f7ad31;
  --out-bg: rgba(0, 0, 0, .28);          /* sunken: command output, logs, code */
  --field-bg: color-mix(in srgb, var(--text-strong) 6%, transparent);
  --field-bg-focus: color-mix(in srgb, var(--text-strong) 9%, transparent);
  --fg: var(--text);
  /* Primary button is monochrome (white on the dark page, ink on the light one);
     the deployment colour stays on the brand word and the charts. */
  --btn-primary-bg: #fff;
  --btn-primary-fg: #0a0a0a;
  --btn-primary-hover: #e4e6ea;
  --shadow-pop: 0 0 0 1px rgba(255, 255, 255, .04), 0 12px 32px rgba(0, 0, 0, .45);
  --shadow-win: 0 0 0 1px rgba(255, 255, 255, .04), 0 24px 64px rgba(0, 0, 0, .55);
  /* Glass: ONE recipe for every surface — cards, menus, tips, toasts, modals,
     the assistant — the deployment colour pulled into a translucent panel,
     blurred over whatever is behind. Popups are a notch more opaque so text
     stays legible over busy content; `strong` is the host card. */
  --glass: color-mix(in srgb, var(--brand-accent) 9%, color-mix(in srgb, var(--panel) 66%, transparent));
  --glass-strong: color-mix(in srgb, var(--brand-accent) 15%, color-mix(in srgb, var(--panel) 62%, transparent));
  --glass-pop: color-mix(in srgb, var(--brand-accent) 8%, color-mix(in srgb, var(--panel-3) 82%, transparent));
  --glass-win: color-mix(in srgb, var(--brand-accent) 8%, color-mix(in srgb, var(--panel) 84%, transparent));
  --glass-edge: color-mix(in srgb, var(--brand-accent) 14%, var(--border));
  --glass-blur: blur(28px) saturate(1.25);
  --font-sans: "DM Sans", "Avenir Next", "Segoe UI Variable", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: Montserrat, var(--font-sans);
  --font-mono: "Fragment Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  /* Color of the BOX_BRAND word in the header title. Set per-deployment via
     shared/.env -> BOX_COLOR (any CSS color, e.g. #0070ff) so several open
     admin panels are told apart at a glance; empty/unset = the default text
     color. Set on :root by app.js (loadBrand) and login.html; #brand mixes it
     towards the text colour for legibility. */
  --brand-color: var(--text-strong);
  /* Deployment accent — same BOX_COLOR value as --brand-color, but used where a
     fill color is needed (page glow, admin badge). Defaults to the
     built-in --accent when BOX_COLOR is unset; overridden on :root by app.js
     (loadBrand). */
  --brand-accent: var(--accent);
  /* atmosphere (body::before): derived from the deployment colour */
  --atm-top: color-mix(in srgb, var(--brand-accent) 42%, var(--bg));
  --atm-mid: color-mix(in srgb, var(--brand-accent) 20%, var(--bg));
  --atm-cloud: color-mix(in srgb, var(--brand-accent) 55%, #dfe6f2);
  color-scheme: dark;
}


* { box-sizing: border-box; }

body {
  margin: 0;
  font: 14px/1.5 var(--font-sans);
  -webkit-font-smoothing: antialiased;
  background: var(--bg);
  color: var(--text);
  /* A docked AI window pushes the page aside like DevTools (aiWinApply sets
     these on :root; 0 when the window is not docked or closed). */
  padding: 0 var(--ai-dock-right, 0px) var(--ai-dock-bottom, 0px) var(--ai-dock-left, 0px);
}
/* Atmosphere: large blurred glows in the deployment colour (BOX_COLOR via
   --brand-accent, the built-in blue when unset) over the page colour, plus a
   faint grain so the gradients read as a photograph rather than as flat CSS.
   Cards are frosted (see .card) so the glow shows through them. The glow is
   part of the PAGE, anchored to its top (absolute, not fixed): it scrolls away
   with the content like the reference hero, the plain page colour remains
   below. The grain stays fixed — it is texture, not scenery. */
body { position: relative; min-height: 100vh; }
body::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: max(100vh, 900px);
  z-index: -1; pointer-events: none;
  background:
    /* dark vignette on the left and at the bottom, like a lens falloff */
    radial-gradient(45% 55% at 0% 60%, color-mix(in srgb, var(--bg) var(--atm-vig, 70%), transparent), transparent 70%),
    radial-gradient(70% 40% at 50% 110%, color-mix(in srgb, var(--bg) var(--atm-vig, 70%), transparent), transparent 70%),
    /* the lit cloud: the deployment colour pulled towards white */
    radial-gradient(55% 45% at 24% 4%, color-mix(in srgb, var(--atm-cloud) 85%, transparent), transparent 70%),
    radial-gradient(45% 50% at 82% 30%, color-mix(in srgb, var(--atm-cloud) 45%, transparent), transparent 70%),
    /* base wash: tinted at the top, the page colour at the bottom */
    linear-gradient(180deg, var(--atm-top) 0%, var(--atm-mid) 55%, var(--bg) 100%);
  /* a haze, not a brand fill: pull the saturation down */
  filter: saturate(.72);
}
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: var(--grain, .06); mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  background-size: 160px 160px;
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 18px;
  position: sticky;
  top: 0;
  z-index: 5;
  /* frosted glass, like the ct.ms navbar: no fill and no border of its own,
     a heavy blur does all the work — the page is seen smeared through it */
  background: color-mix(in srgb, var(--bg) 14%, transparent);
  -webkit-backdrop-filter: blur(30px) saturate(1.4);
  backdrop-filter: blur(30px) saturate(1.4);
}

/* BOX_BRAND word tinted per deployment (shared/.env -> BOX_COLOR). */
/* The brand word sits on an atmosphere of its OWN colour, so the pure tint
   would vanish into it: pull it halfway towards the text colour and give it
   weight — the hue still reads, and so does the word. */
/* brand word: BOX_TITLE_COLOR when set (--brand-title), else BOX_COLOR mixed
   55 % into the strong text colour — readable on the glow of its own hue */
#brand { color: var(--brand-title, color-mix(in srgb, var(--brand-color) 55%, var(--text-strong))); font-weight: 600; }

h1 { font-family: var(--font-display); font-size: 17px; margin: 0; font-weight: 500; letter-spacing: -.02em; color: var(--text-strong); }
.muted { color: var(--muted); font-weight: 400; }

/* First-load placeholder for the stack list (combined /api/dashboard poll). */
.stacks-loading { display: flex; align-items: center; gap: 10px; padding: 24px 4px; }
.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* generic visibility utility — wins over element-specific display (flex/block) */
.hidden { display: none !important; }

.toolbar { display: flex; gap: 8px; align-items: center; }
/* ↻ — a glass pill (see .glass); the icon spins while the load is in flight */
.refresh-btn { display: inline-flex; align-items: center; gap: 7px; padding: 6px 14px 6px 12px; }
.refresh-icon { display: inline-block; font-size: 15px; line-height: 1; }
.refresh-btn.loading { cursor: default; }
.refresh-btn.loading .refresh-icon { animation: spin 0.7s linear infinite; }
.lang-toggle:hover, #btn-menu:hover { color: var(--text-strong); }
/* language toggle — the outlined mono "Preview" pill of the reference (shows the current language: EN / РУ) */
.lang-toggle { padding: 6px 11px; font-size: 12px; line-height: 1.75; letter-spacing: .06em; }

/* Ace theme picker — pushed to the right end of the editor's button row,
   i.e. over the editor's top-right corner */
.ace-theme-sel { margin-left: auto; font-size: 12px; color: var(--muted); }

/* burger dropdown — maintenance actions tucked away */
.menu { position: relative; }
/* icon-only burger: bigger glyph, line-height pinned so the button height
   matches the 14px/1.5 neighbors; asymmetric padding lifts the glyph to the
   optical center (☰ renders low on its baseline) */
#btn-menu { font-size: 18px; line-height: 21px; padding: 4px 10px 8px; color: var(--muted); }
.menu-dropdown {
  position: absolute; right: 0; top: calc(100% + 6px);
  display: flex; flex-direction: column; min-width: 210px;
  background: var(--glass-pop); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge);
  border-radius: 14px; padding: 6px; gap: 2px; z-index: 20;
  box-shadow: var(--shadow-pop);
}
.menu-dropdown button {
  background: transparent; border: none; border-radius: 8px;
  text-align: left; padding: 8px 12px; font-weight: 400;
}
.menu-dropdown button:hover { background: var(--hover); }
.menu-sep { height: 1px; background: var(--border); margin: 4px 2px; }
/* who is logged in — informational row above Log out (filled by loadSession) */
.menu-user { padding: 6px 12px 2px; font-size: 12px; color: var(--muted); cursor: default; }
.menu-user-login { color: var(--text); font-weight: 500; }

/* server selector — the header title becomes a dropdown button when
   shared/servers.json lists 2+ hosts (multi-host / multi-DC navigation) */
.brand-menu { position: relative; }
h1.brand-btn { cursor: pointer; user-select: none; border-radius: 10px; padding: 2px 8px; margin: -2px -8px; }
h1.brand-btn:hover { background: var(--hover); }
.brand-caret { color: var(--muted); font-size: 12px; margin-left: 6px; }
.server-dropdown { left: 0; right: auto; min-width: 220px; }
.server-dc {
  color: var(--muted); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .14em;
  padding: 6px 12px 2px;
}
.server-item {
  display: block; padding: 7px 12px; border-radius: 8px;
  color: var(--text); text-decoration: none; font-size: 13px;
}
a.server-item:hover { background: var(--hover); color: var(--text-strong); }
.server-item.current { color: var(--green); font-weight: 500; cursor: default; }
.server-item.nopanel { color: var(--muted); cursor: default; }

/* live host summary — "stack zero", the first card of the stack grid, styled
   like the reference app's sidebar: tinted glass in the deployment colour, a
   list of rows (icon · label · value) for the metrics, one wide soft action
   button, then a small-caps "Versions" section with the same row shape. */
/* .card.host-card: the plain .card rule comes later in the sheet and would win
   at equal specificity; the host card is a notch stronger than the stacks */
.card.host-card {
  background: var(--glass-strong);
  border-color: color-mix(in srgb, var(--brand-accent) 24%, var(--border));
}
/* both static cards share the head typography */
.host-card .card-head .desc, .activity-card .card-head .desc { flex: 0 1 auto; font-size: 13px; }
.host-card .card-head, .activity-card .card-head { padding-bottom: 2px; }
.hoststats {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 0 10px 12px;
  font-size: 13px;
}
/* two columns from tablet width (CPU | RAM, Disk | Uptime) — one metric per
   row made the card twice as tall as its neighbours */
.hs-list { display: grid; grid-template-columns: 1fr; gap: 0 8px; }
.hs-item {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px;
  padding: 4px 8px; border-radius: 10px; color: var(--text); min-width: 0;
}
.hs-item:hover { background: var(--hover); }
.hs-ico { display: inline-flex; color: var(--muted); flex: 0 0 auto; }
.hs-ico .ico { width: 1.2em; height: 1.2em; vertical-align: 0; stroke-width: 1.6; }
.hs-k { color: var(--muted); font-weight: 500; flex: 0 0 auto; min-width: 54px; }
.hs-v { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; min-width: 0; }
.hs-item.warm .hs-v { color: var(--amber); }
.hs-item.hot .hs-v { color: var(--red); }
/* disk usage bar after the value: green → amber (75 %) → red (90 %) */
.usage-bar {
  display: inline-block; width: 96px; height: 6px; vertical-align: middle;
  border-radius: 3px; background: var(--border); overflow: hidden;
}
.usage-bar .usage-fill { display: block; height: 100%; border-radius: 3px; background: var(--green); }
.usage-bar.warm .usage-fill { background: var(--amber); }
.usage-bar.hot .usage-fill { background: var(--red); }
/* a full-width sub-line inside a row (the disk action under its bar) */
.hs-item .hs-sub { flex: 0 0 100%; display: flex; }
.hs-item .hs-sub .hs-action { margin: 2px 0 2px 0; }
/* the one wide action of the card — the reference's "New Session" */
.hs-action {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  align-self: flex-start; margin: 4px 0 0 8px; padding: 5px 12px; border-radius: 12px;
  background: color-mix(in srgb, var(--text-strong) 10%, transparent);
  border-color: color-mix(in srgb, var(--text-strong) 6%, transparent);
  color: var(--text-strong); font-size: 13px;
}
.hs-action:hover { background: color-mix(in srgb, var(--text-strong) 16%, transparent); }
/* "N more networks" toggle under the host card's Network rows: a quiet text
   button in the value column, no plaque */
.hs-net-toggle {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: var(--muted); font: inherit; font-size: 13px; text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.hs-net-toggle:hover, .hs-net-toggle:focus-visible { color: var(--text-strong); outline: none; }
.hs-net-toggle-row:hover { background: none; }
.hs-action .hs-ico { color: inherit; }
.hs-section {
  padding: 8px 8px 0; font-size: 10px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
/* versions: the section label and the (long) infra line take a full row;
   panel + core sit side by side when they fit and wrap when they do not */
.hoststats .host-version { display: flex; flex-wrap: wrap; gap: 0 8px; font-size: 12px; }
.hoststats .host-version > :nth-child(-n+2) { flex: 0 0 100%; }
.hoststats .host-version .hs-item { padding-top: 3px; padding-bottom: 3px; flex-wrap: nowrap; }
.hoststats .host-version .hs-item .hs-v { flex-wrap: nowrap; }
.hoststats .host-version > .hs-item:first-of-type .hs-v { flex-wrap: wrap; }
@media (min-width: 760px) {
  .hs-list { grid-template-columns: 1fr 1fr; }
}
.hoststats .hv-val { opacity: .9; }
.hoststats .hv-info { width: 15px; height: 15px; font-size: 9px; border-width: 1px; }
.hoststats .host-version .hv-update { color: var(--amber); opacity: 1; }

/* ----- AI assistant bar — collapsed: a hoststats-style strip; expanded: a
   resizable chat that pushes the stack list down (normal flow) --------------- */
.ai-bar {
  position: relative;
  border-bottom: 1px solid var(--border);
  background: var(--glass-win);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  font-size: 13px;
}
.ai-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 22px;
  user-select: none;
}
.ai-title { font-weight: 500; color: var(--text-strong); }
/* operating-mode chip: neutral for read-only, amber once the agent can act,
   red at full control — the colour IS the warning. */
.ai-mode {
  font-size: 11px; font-weight: 500; padding: 1px 8px; border-radius: 100px;
  border: 1px solid var(--border); color: var(--muted); white-space: nowrap;
}
.ai-mode.act { color: var(--amber); border-color: var(--amber); }
.ai-mode.full { color: var(--red); border-color: var(--red); }
/* pending approval of a mutating tool call */
.ai-confirm {
  align-self: flex-start; max-width: 100%;
  border: 1px solid var(--amber); border-radius: 14px; padding: 10px 14px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px;
}
.ai-confirm.destructive { border-color: var(--red); }
.ai-confirm .ai-confirm-tool { font-weight: 500; }
.ai-confirm .ai-confirm-args { color: var(--muted); font-size: 12px; word-break: break-all; }
.ai-confirm.done { opacity: .6; border-style: dashed; }
.ai-controls { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
/* 28 px tall like the title-bar buttons: the global 7px padding would leave
   the text no room and clip its descenders, so the padding is horizontal only */
.ai-controls select { max-width: 220px; height: 28px; padding: 0 28px 0 10px; line-height: 26px; font-size: 13px; background-position: right 9px center; }
/* closed = not there at all, in every placement; the header ✦ opens it.
   !important: the window/dock rules below set display:flex at the same
   specificity and come later — they used to win, leaving a "closed" window on
   screen that could no longer be dragged */
.ai-bar.collapsed { display: none !important; }

.ai-body {
  display: flex;
  height: 380px;               /* runtime: box.aiHeight from localStorage */
  min-height: 240px;
  border-top: 1px solid var(--border);
}
.ai-side {
  flex: 0 0 200px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-right: 1px solid var(--border);
  overflow-y: auto;
  background: color-mix(in srgb, var(--text-strong) 4%, transparent);
}
.ai-chatlist { display: flex; flex-direction: column; gap: 2px; overflow-y: auto; }
.ai-chat-item {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 10px; border-radius: 8px; cursor: pointer;
  color: var(--text); font-size: 12px;
}
.ai-chat-item:hover { background: var(--hover); }
.ai-chat-item.active { background: var(--active); color: var(--text-strong); }
.ai-chat-item .ai-chat-title {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ai-chat-item .ai-chat-del {
  border: none; background: none; color: var(--muted); cursor: pointer;
  padding: 0 2px; font-size: 12px; visibility: hidden;
}
.ai-chat-item:hover .ai-chat-del { visibility: visible; }
.ai-chat-item .ai-chat-del:hover { color: var(--red); }

.ai-main { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; position: relative; }
.ai-messages { flex: 1 1 auto; overflow-y: auto; padding: 14px 18px; display: flex; flex-direction: column; gap: 10px; }
.ai-msg { max-width: 85%; border-radius: 18px; padding: 10px 14px; line-height: 1.5; }
.ai-msg.user {
  align-self: flex-end;
  background: color-mix(in srgb, var(--text-strong) 10%, transparent);
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
/* send time, tucked to the bottom-right of the user's bubble */
.ai-msg.user .ai-time {
  display: block; text-align: right; margin-top: 2px;
  font-size: 10px; color: var(--muted); white-space: nowrap;
}
.ai-msg.assistant { align-self: flex-start; max-width: 100%; padding: 0 2px; }
.ai-msg.err { color: var(--red); }
.ai-usage { color: var(--muted); font-size: 11px; align-self: flex-start; padding-left: 2px; }
.ai-tool {
  align-self: flex-start; font-size: 12px; color: var(--muted);
  border: 1px solid var(--border); border-radius: 100px; padding: 2px 10px; background: var(--hover);
}
/* a finished answer's chips, folded behind one "Tools used: N" line */
.ai-tools { align-self: flex-start; max-width: 100%; font-size: 12px; color: var(--muted); }
.ai-tools > summary { cursor: pointer; list-style: none; user-select: none; }
.ai-tools > summary::-webkit-details-marker { display: none; }
.ai-tools > summary::before { content: "▸ 🔧 "; }
.ai-tools[open] > summary::before { content: "▾ 🔧 "; }
.ai-tools > summary:hover { color: var(--text); }
.ai-tools-list { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; margin: 4px 0 0 14px; }
.ai-tool summary { cursor: pointer; list-style: none; }
.ai-tool summary::before { content: "🔧 "; }
.ai-file { align-self: flex-start; }
.ai-inputrow {
  display: flex; gap: 8px; align-items: flex-end;   /* buttons sit on the composer's baseline, never stretch into tall capsules */
  padding: 10px 18px; border-top: 1px solid var(--border);
}
.ai-inputrow > button { flex: 0 0 auto; height: 38px; display: inline-flex; align-items: center; gap: 6px; }
.ai-inputrow textarea {
  flex: 1 1 auto; resize: none; font: inherit;
  background: var(--field-bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px; padding: 9px 14px;
}
.ai-inputrow textarea:focus { outline: none; border-color: var(--border-strong); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
/* composer side buttons: same width, icon centred */
.ai-inputrow > .ai-attach-btn, .ai-inputrow > .ai-mic {
  width: 38px; padding: 0; font-size: 16px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}
/* screenshots waiting in the composer + the ones shown in the sent bubble */
.ai-attach { padding: 8px 18px 0; border-top: 1px solid var(--border); }
.ai-attach + .ai-inputrow { border-top: 0; }
.ai-attach-list { display: flex; flex-wrap: wrap; gap: 6px; }
.ai-attach-note { font-size: 11px; margin-top: 5px; }
.ai-thumb {
  position: relative; width: 64px; height: 64px; overflow: hidden;
  border: 1px solid var(--border); border-radius: 10px; background: var(--panel-2);
}
.ai-thumb.busy { display: grid; place-items: center; }
.ai-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ai-thumb-size {
  position: absolute; left: 0; right: 0; bottom: 0; text-align: center;
  font-size: 9px; color: #fff; background: rgba(0, 0, 0, .55);
}
.ai-thumb-rm {
  position: absolute; top: 2px; right: 2px; width: 16px; height: 16px; padding: 0;
  border: 0; border-radius: 50%; font-size: 9px; line-height: 16px;
  color: #fff; background: rgba(0, 0, 0, .65); cursor: pointer;
}
.ai-thumb-rm:hover { background: var(--red); }
.ai-msg-imgs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
.ai-msg-imgs img { max-height: 90px; max-width: 120px; border-radius: 4px; object-fit: contain; }
.ai-msg-note { display: block; margin-top: 3px; font-size: 11px; color: var(--muted); }
.ai-drop {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  border: 2px dashed var(--accent); border-radius: 6px;
  background: var(--bg); opacity: .92; color: var(--accent); font-size: 14px;
}
.ai-resize {
  position: absolute; left: 0; right: 0; bottom: -3px; height: 7px;
  cursor: ns-resize; z-index: 5;
}
.ai-resize:hover { background: var(--accent); opacity: .35; }

/* ----- AI window: placement chrome (aiWin* in app.js) -------------------------
   inline = the strip in the page (rules above); float / left / right / bottom
   and .full are the SAME element made fixed. z-index 15: over the sticky header
   (5), under modals (20) — an approval never hides behind the chat. */
/* title-bar buttons: one square size, glyphs centred, whatever the glyph */
.ai-hbtn {
  width: 28px; height: 28px; padding: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1;
}
.ai-hbtn.hidden { display: none; }
/* monochrome line icons (currentColor) sized like a font glyph — not emoji */
.ico {
  width: 1.15em; height: 1.15em; flex: 0 0 auto; vertical-align: -0.2em;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
/* ☰ is always there in an open window: it folds the conversation list away and
   brings it back. The compact "new chat" only stands in for the folded list's own. */
#ai-list-btn, #ai-new2 { display: none; }
.ai-bar:not(.collapsed) #ai-list-btn,
.ai-bar.no-list:not(.collapsed) #ai-new2 { display: inline-flex; }
.ai-bar:is(.mode-float, .mode-left, .mode-right, .mode-bottom, .full) {
  position: fixed; z-index: 15;
  display: flex; flex-direction: column;
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow-win);
}
.ai-bar:is(.mode-float, .mode-left, .mode-right, .mode-bottom, .full) .ai-body {
  flex: 1 1 auto; height: auto; min-height: 0;
}
.ai-bar:is(.mode-float, .mode-left, .mode-right, .mode-bottom, .full) .ai-resize { display: none; }
.ai-bar:is(.mode-float, .mode-left, .mode-right, .mode-bottom):not(.phone) .ai-head { cursor: move; }
.ai-bar.mode-float { border-radius: 20px; }
.ai-bar:is(.mode-float, .mode-left, .mode-right, .mode-bottom, .full) { overflow: hidden; }
.ai-title { white-space: nowrap; }
/* a narrow window wraps its title bar instead of spilling the pickers over the page */
.ai-bar.narrow .ai-head { flex-wrap: wrap; row-gap: 6px; padding: 8px 44px 8px 12px; position: relative; }
.ai-bar.narrow #ai-close { position: absolute; top: 7px; right: 10px; }
.ai-bar.narrow .ai-controls { flex-wrap: wrap; justify-content: flex-end; min-width: 0; gap: 4px; }
.ai-bar.narrow .ai-controls select { max-width: 150px; min-width: 0; }
.ai-bar.mode-left { top: 0; bottom: 0; left: 0; border-width: 0 1px 0 0; }
.ai-bar.mode-right { top: 0; bottom: 0; right: 0; border-width: 0 0 0 1px; }
.ai-bar.mode-bottom { left: 0; right: 0; bottom: 0; border-width: 1px 0 0 0; }
.ai-bar.full {
  inset: 0 !important; width: auto !important; height: auto !important;
  border: 0; border-radius: 0;
}
/* expanded, the transcript and the composer keep a readable column */
.ai-bar.full .ai-messages, .ai-bar.full .ai-inputrow, .ai-bar.full .ai-attach {
  padding-left: max(18px, calc((100% - 960px) / 2));
  padding-right: max(18px, calc((100% - 960px) / 2));
}
/* phone: always full screen (aiWinApply sets .full + .phone) — nothing to
   expand, nothing to drag, only ✕ closes it */
.ai-bar.phone #ai-full { display: none; }
.ai-bar.phone .ai-head { cursor: default; }
.ai-bar.dragging, .ai-bar.dragging * { user-select: none; }
/* resize handles: edges + corners of a window, the inner edge of a dock */
.ai-h { position: absolute; z-index: 6; display: none; }
.ai-h-n, .ai-h-s { left: 8px; right: 8px; height: 6px; cursor: ns-resize; }
.ai-h-e, .ai-h-w { top: 8px; bottom: 8px; width: 6px; cursor: ew-resize; }
.ai-h-n { top: -2px; } .ai-h-s { bottom: -2px; } .ai-h-e { right: -2px; } .ai-h-w { left: -2px; }
.ai-h-ne, .ai-h-nw, .ai-h-se, .ai-h-sw { width: 12px; height: 12px; }
.ai-h-ne { top: -2px; right: -2px; cursor: nesw-resize; }
.ai-h-sw { bottom: -2px; left: -2px; cursor: nesw-resize; }
.ai-h-nw { top: -2px; left: -2px; cursor: nwse-resize; }
.ai-h-se { bottom: -2px; right: -2px; cursor: nwse-resize; }
.ai-bar.mode-float:not(.full):not(.phone) .ai-h { display: block; }
.ai-bar.mode-left:not(.full):not(.phone) .ai-h-e,
.ai-bar.mode-right:not(.full):not(.phone) .ai-h-w,
.ai-bar.mode-bottom:not(.full):not(.phone) .ai-h-n { display: block; top: 0; bottom: 0; left: auto; right: auto; }
.ai-bar.mode-left .ai-h-e { right: -3px; }
.ai-bar.mode-right .ai-h-w { left: -3px; }
.ai-bar.mode-bottom:not(.full):not(.phone) .ai-h-n { left: 0; right: 0; top: -3px; bottom: auto; }
.ai-h:hover { background: var(--accent); opacity: .35; }
.ai-dock-preview {
  position: fixed; z-index: 16; pointer-events: none;
  background: var(--accent); opacity: .18; border: 2px solid var(--accent);
}
/* conversation list folded into ☰: an overlay over the transcript when opened */
.ai-bar.no-list .ai-side { display: none; }
.ai-bar.no-list.list-open .ai-side {
  display: flex; position: absolute; z-index: 7; top: 0; bottom: 0; left: 0;
  width: min(260px, 80%); box-shadow: 4px 0 18px rgba(0, 0, 0, .3);
}
.ai-body { position: relative; }
/* title-bar menu: placement + voice */
.ai-menu-wrap { position: relative; display: inline-flex; }
.ai-menu { min-width: 230px; z-index: 18; overflow-y: auto; }
.ai-menu .menu-label { padding: 6px 12px 2px; font-size: 10px; text-transform: uppercase; letter-spacing: .14em; }
.ai-menu button.active { color: var(--accent); }
/* header launcher: ✦ + microphone segmented like the refresh pill; ✦ is lit
   while open and carries a pulsing dot while an answer runs unseen */
.ai-launch-group {
  display: inline-flex; border-radius: 100px; overflow: hidden;
  background: var(--glass-bg); color: var(--text-strong);
  border: 1px solid var(--glass-border);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
.ai-launch-group.hidden { display: none; }
.ai-launch-group > button {
  border: none; border-radius: 0; background: transparent; color: inherit;
  padding: 5px 12px; font-size: 14px; line-height: 1.5;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.ai-launch-group > button:hover { background: var(--hover); }
.ai-launch-group > button + button { border-left: 1px solid var(--glass-border); padding: 5px 11px; }
.ai-launch-group > button.hidden { display: none; }
.ai-launch { position: relative; padding-left: 14px; }
.ai-launch.open { background: var(--active); }
.ai-launch.live:not(.open)::after {
  content: ""; position: absolute; top: 3px; right: 3px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--accent); animation: ai-pulse 1.2s ease-in-out infinite;
}
@keyframes ai-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }
/* per-answer actions (copy, read aloud) */
.ai-msg-actions { display: flex; gap: 2px; align-self: flex-start; margin-top: -6px; }
.ai-act { padding: 0 6px; font-size: 13px; line-height: 1.6; color: var(--muted); border-color: transparent; display: inline-flex; align-items: center; }
.ai-act:hover { color: var(--text); }
.ai-act.on { color: var(--accent); }

/* microphone: red while recording, a ring that follows the input level */
.ai-mic { position: relative; --lvl: 0; }
.ai-mic.recording {
  color: #fff; background: var(--red); border-color: var(--red);
  box-shadow: 0 0 0 calc(2px + var(--lvl) * 7px) color-mix(in srgb, var(--red) 35%, transparent);
  transition: box-shadow .08s linear;
}
.ai-mic.busy { opacity: .6; cursor: progress; }
/* recording wins over every hover/background rule, the header pill's included */
.ai-mic.recording, .ai-mic.recording:hover,
.ai-launch-group > button.ai-mic.recording, .ai-launch-group > button.ai-mic.recording:hover {
  color: #fff; background: var(--red); border-color: var(--red);
}
.ai-act.busy { opacity: .5; }

/* markdown inside assistant messages */
.ai-md :is(p, ul, ol, pre, table, blockquote) { margin: 0 0 8px; }
.ai-md :is(p, ul, ol):last-child { margin-bottom: 0; }
.ai-md pre {
  background: var(--out-bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 12px; overflow-x: auto; font-size: 12px; font-family: var(--font-mono);
}
.ai-md code { background: var(--hover); border-radius: 6px; padding: 1px 5px; font-size: 12px; font-family: var(--font-mono); }
.ai-md pre code { background: none; padding: 0; }
.ai-md table { border-collapse: collapse; font-size: 12px; display: block; overflow-x: auto; }
.ai-md th, .ai-md td { border: 1px solid var(--border); padding: 4px 8px; }
.ai-md a { color: var(--link); }
.ai-md h1, .ai-md h2, .ai-md h3, .ai-md h4 { margin: 10px 0 6px; font-size: 14px; }

@media (max-width: 700px) {
  .ai-side { flex-basis: 140px; }
  .ai-controls select { max-width: 130px; }
}
@media (max-width: 560px) {
  /* phone composer: the send button is the arrow alone, round like the mic */
  .ai-inputrow > #ai-send { width: 38px; padding: 0; border-radius: 50%; justify-content: center; }
  .ai-inputrow > #ai-send .ico { width: 1.3em; height: 1.3em; }
}

main { max-width: 1480px; margin: 0 auto; padding: 24px 22px 48px; }
/* Stack cards in a grid: two per row from a laptop width up, one on narrow
   screens. Host groups (multi-host) and the loading placeholder span the row. */
#subsystems, .host-cards {
  /* stretch: cards of one row share its height */
  display: grid; gap: 16px; align-items: stretch;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 580px), 1fr));
}
#subsystems > :not(.card) { grid-column: 1 / -1; }
/* Drag to reorder: the card header is the handle. */
.card-head.drag-handle { cursor: grab; }
.card-head.drag-handle :is(a, button, .remarks-btn) { cursor: pointer; }
.card.dragging { opacity: .35; outline: 2px dashed var(--accent); }

/* Pills: a translucent fill that works on any surface, a hairline border;
   hover is a tone brighter, never an accent outline. */
button {
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--hover);
  color: var(--text);
  padding: 6px 14px;
  border-radius: 100px;
  transition: background .12s, border-color .12s, color .12s;
}
button:hover { background: var(--active); border-color: var(--border-strong); }
button.ghost { background: transparent; border-color: transparent; }
button.ghost:hover { background: var(--hover); border-color: transparent; }
button.primary {
  background: color-mix(in srgb, var(--brand-accent) 38%, var(--glass-pop));
  border-color: color-mix(in srgb, var(--brand-accent) 55%, transparent);
  color: var(--text-strong);
}
button.primary:hover { background: color-mix(in srgb, var(--brand-accent) 52%, var(--glass-pop)); border-color: color-mix(in srgb, var(--brand-accent) 70%, transparent); }
button.danger { color: var(--red); }
/* The reference title bar's three button kinds. .glass — a brighter frosted
   pill ("GitHub"); .outline — a hairline-only pill in the mono face
   ("Preview"); .primary + .split — the white pill with a divided segment
   ("Download ▾"). */
button.glass, .glass {
  background: var(--glass-bg); border-color: var(--glass-border); color: var(--text-strong);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
button.glass:hover { background: var(--glass-hover); border-color: var(--border-strong); }
button.outline {
  background: transparent; border-color: var(--border-strong); color: var(--muted);
  font-family: var(--font-mono); font-weight: 400;
}
button.outline:hover { background: var(--hover); color: var(--text); }
.btn-label { white-space: nowrap; }
button.danger:hover { background: color-mix(in srgb, var(--red) 14%, transparent); border-color: color-mix(in srgb, var(--red) 40%, transparent); }
/* Anchor styled as a button (file download links). */
a.btn-link {
  font: inherit; font-weight: 500; cursor: pointer; text-decoration: none;
  border: 1px solid var(--border); background: var(--hover); color: var(--text);
  padding: 6px 14px; border-radius: 100px; transition: background .12s, border-color .12s;
}
a.btn-link:hover { background: var(--active); border-color: var(--border-strong); }
button:disabled { opacity: .5; cursor: not-allowed; }

.card {
  min-width: 0;
  /* tinted glass in the deployment colour, like the reference app's sidebar */
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  border-radius: 16px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04);
  overflow: hidden;
}
.card-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
}
.card-head .name { font-weight: 500; font-size: 15px; color: var(--text-strong); letter-spacing: -.01em; }
.card-head .open {
  text-decoration: none;
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 100px;
  padding: 3px 9px;
}
.card-head .open:hover { color: var(--text-strong); background: var(--hover); border-color: var(--border-strong); }
/* the public domain inside the pill (a link); the domain may be cut */
.card-head .open { display: inline-flex; align-items: center; max-width: 42%; font-size: 12px; }
.card-head .open .open-host { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-size: 11.5px; }
.card-head .open.hidden { display: none; }
/* everything else in the head keeps its size; only the description gives way */
.card-head > :not(.desc) { flex-shrink: 0; }
/* one line, cut with "…" when it does not fit (full text in the tooltip) —
   a long description must not wrap into a column of letters on a phone */
.card-head .desc {
  color: var(--muted); flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.badge {
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 100px;
  border: 1px solid;
  font-weight: 500;
}
/* soft tinted chips: text in the state colour over a 12 % wash of it */
.badge.running { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent); background: color-mix(in srgb, var(--green) 12%, transparent); }
.badge.stopped { color: var(--muted); border-color: var(--border); background: var(--hover); }
/* host down in multi-host mode — the stack is expected there but unpollable */
.badge.unreachable { color: var(--red); border-color: color-mix(in srgb, var(--red) 35%, transparent); background: color-mix(in srgb, var(--red) 12%, transparent); }
.card.unreachable .actions { opacity: .45; }

/* host group — one section per host in multi-host mode */
.host-group { margin-bottom: 22px; }
.host-head {
  display: flex; align-items: baseline; gap: 10px;
  padding: 0 2px 8px; flex-wrap: wrap;
}
.host-head .host-name { font-weight: 600; font-size: 15px; }
.host-head .host-dc {
  color: var(--muted); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .12em;
  border: 1px solid var(--border); border-radius: 100px; padding: 1px 8px;
}
.host-head .host-summary { color: var(--muted); font-size: 12px; }
.host-head .host-stats { color: var(--muted); font-size: 12px; margin-left: auto; }
.host-head .host-stats.down { color: var(--red); font-weight: 500; }

/* App health probe badge — tinted by latency (see loadHealth in app.js). */
.health {
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 100px;
  border: 1px solid;
  font-weight: 500;
  white-space: nowrap;
}
.health.hidden { display: none; }
.health.checking { color: var(--muted); border-color: var(--border); background: var(--hover); }
.health.up { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent); background: color-mix(in srgb, var(--green) 12%, transparent); }
.health.warn { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 35%, transparent); background: color-mix(in srgb, var(--amber) 12%, transparent); }
.health.down { color: var(--red); border-color: color-mix(in srgb, var(--red) 35%, transparent); background: color-mix(in srgb, var(--red) 12%, transparent); }

.remarks-btn {
  flex-shrink: 0;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600; font-style: normal;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  line-height: 1;
  transition: opacity 0.1s;
}
/* same green as the Running badge, so a stack with notes reads as "healthy + info" */
.remarks-btn.has-remarks { color: var(--green); border-color: var(--green); }
.remarks-btn:hover { opacity: 0.7; }

.remarks-tip {
  position: fixed;
  max-width: 260px;
  background: var(--glass-pop); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge);
  border-radius: 12px;
  padding: 10px 12px;
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--fg);
  z-index: 200;
  box-shadow: var(--shadow-pop);
  pointer-events: none;
  display: none;
}

.remarks-tip.wide { max-width: min(440px, calc(100vw - 24px)); }

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0 18px 16px; }
/* the same soft plaques as the host card's action: icon + label, 12 px corners */
.actions button {
  font-size: 13px; padding: 6px 12px; border-radius: 12px;
  display: inline-flex; align-items: center; gap: 7px;
  background: color-mix(in srgb, var(--text-strong) 8%, transparent);
  border-color: color-mix(in srgb, var(--text-strong) 6%, transparent);
}
.actions button:hover { background: color-mix(in srgb, var(--text-strong) 14%, transparent); border-color: color-mix(in srgb, var(--text-strong) 12%, transparent); }
.actions .hs-ico { color: var(--muted); }
.actions button:hover .hs-ico { color: inherit; }
.admin-btn { margin-left: auto; }

/* Lifecycle pair (Start / Stop) — segmented group with action-coloured icons. */
.life-group { display: inline-flex; border-radius: 12px; overflow: hidden; }
.life-group .life-btn {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 0; border-right-width: 0;
  padding-left: 8px; padding-right: 8px;
}
.life-group .life-btn:first-child { border-top-left-radius: 12px; border-bottom-left-radius: 12px; padding-left: 12px; }
.life-group .life-btn:last-child { border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-right-width: 1px; padding-right: 12px; }
/* Collapsed borders leave a button's right edge to its neighbour, so hovering a
   middle button wouldn't outline all sides — draw a full ring via box-shadow. */
.life-group .life-btn:hover { position: relative; z-index: 1; border-color: var(--border-strong); box-shadow: 0 0 0 1px var(--border-strong); }
.life-ico { display: inline-flex; line-height: 0; color: var(--muted); }

/* Compact mode (set by reflowActions when the row wraps): hide the secondary
   actions — they move into the Admin dropdown. Lifecycle pair + Admin stay. */
.actions.compact > button:not(.admin-btn) { display: none !important; }

.admin-drop {
  position: fixed;
  z-index: 150;
  background: var(--glass-pop); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge);
  border-radius: 14px;
  padding: 6px;
  box-shadow: var(--shadow-pop);
  display: none;
  min-width: 150px;
  overflow-y: auto;
}
.admin-drop.open { display: flex; flex-direction: column; gap: 2px; }
.admin-drop-item {
  text-align: left;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 400;
  background: none;
  border: none;
  color: var(--fg);
  cursor: pointer;
  width: 100%;
  flex-shrink: 0;
}
.admin-drop-item:hover { background: var(--hover); }

.containers {
  border-top: 1px solid var(--border);
  padding: 10px 18px 12px;
  font-size: 13px;
}
/* the image ref is the longest cell — it must not set the card's width:
   it takes what is left and ends in "…" (full ref in the tooltip) */
.containers td.ct-image { width: 100%; max-width: 0; min-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.containers td:not(.ct-image) { white-space: nowrap; }
/* the status is the one text cell allowed to wrap ("Exited (0) 20 minutes ago")
   so that, with the IP column, the row never grows past the card and the last
   cell keeps its padding from the edge instead of touching it */
.containers td.ct-up { white-space: normal; min-width: 8em; }
.containers td:last-child { padding-right: 10px; }
.containers .ct-name { display: flex; align-items: center; gap: 7px; }
.containers .ct-name .hs-ico { color: var(--muted); }
.containers tr:hover td { background: var(--hover); }
.containers td:first-child { border-radius: 8px 0 0 8px; }
.containers td:last-child { border-radius: 0 8px 8px 0; }
/* dead container (Exited/Restarting) — status cell in the details table */
.containers td.ct-down { color: var(--red); font-weight: 500; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 5px 8px; }
th { color: var(--muted); font-weight: 500; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
td.mono, .mono { font-family: var(--font-mono); font-size: 12px; }

/* modal */
.modal {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, .55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  z-index: 20; padding: 20px;
}
.modal.hidden { display: none; }
.modal-box {
  background: var(--glass-win); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge);
  border-radius: 20px;
  box-shadow: var(--shadow-win);
  width: 100%; max-width: 760px;
  max-height: 86vh; display: flex; flex-direction: column;
}
/* content-heavy modals (config editor / history) */
.modal-box.wide { max-width: min(1200px, 96vw); }
/* A modal is a window like the assistant's: dragged by its title bar and resized
   by its edges and corners. Once moved or resized it is "placed" — fixed at the
   rect kept in localStorage — until full screen takes over. */
.modal-box { position: relative; }
.modal-box.placed {
  position: fixed; margin: 0; max-width: none; max-height: none;
}
.modal-box.placed .modal-body { flex: 1 1 auto; min-height: 0; }
.modal-head { cursor: move; }
.modal-head button, .modal-head .modal-tab { cursor: pointer; }
.modal-box.dragging, .modal-box.dragging * { user-select: none; }
.win-h { position: absolute; z-index: 6; }
.win-h-n, .win-h-s { left: 8px; right: 8px; height: 6px; cursor: ns-resize; }
.win-h-e, .win-h-w { top: 8px; bottom: 8px; width: 6px; cursor: ew-resize; }
.win-h-n { top: -2px; } .win-h-s { bottom: -2px; } .win-h-e { right: -2px; } .win-h-w { left: -2px; }
.win-h-ne, .win-h-nw, .win-h-se, .win-h-sw { width: 12px; height: 12px; }
.win-h-ne { top: -2px; right: -2px; cursor: nesw-resize; }
.win-h-sw { bottom: -2px; left: -2px; cursor: nesw-resize; }
.win-h-nw { top: -2px; left: -2px; cursor: nwse-resize; }
.win-h-se { bottom: -2px; right: -2px; cursor: nwse-resize; }
.win-h:hover { background: var(--accent); opacity: .35; }
.modal-box.full .win-h { display: none; }
.modal-box.full .modal-head { cursor: default; }
@media (max-width: 700px) {
  /* a phone has no room for a window: the modal stays a centred sheet */
  .win-h { display: none; }
  .modal-head { cursor: default; }
}
/* A backup run row: caret, facts on the left, state and actions on the right. */
.bk-run > summary { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; cursor: pointer; }
.bk-run > summary::-webkit-details-marker { display: none; }
.bk-run .bk-caret { display: inline-block; color: var(--muted); transition: transform .12s; }
.bk-run[open] .bk-caret { transform: rotate(90deg); }
.bk-run .acts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* the full-screen toggle's inline icon (modal head, assistant window head) */
.ai-hbtn .full-ico { width: 15px; height: 15px; vertical-align: -2px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* Full screen (the expand icon in the head): the box takes the whole viewport and every
   vh-sized child inside it (editor, log viewer, lists, output panes) grows with
   it — otherwise the modal would be full screen with a 62vh editor floating in
   the middle. Rules sit AFTER .wide so they win regardless of the modal type. */
.modal.full { padding: 0; }
.modal-box.full {
  width: 100%; max-width: 100%;
  height: 100%; max-height: 100%;
  border-radius: 0;
}
.modal-box.full .modal-body { flex: 1 1 auto; min-height: 0; }
.modal-box.full pre.output { max-height: calc(100vh - 200px); }
.modal-box.full .tag-list { max-height: calc(100vh - 300px); }
.modal-box.full .logbrowse .tag-list { max-height: calc(100vh - 230px); }
.modal-box.full .envd { max-height: calc(100vh - 260px); }
.modal-box.full .logview { height: calc(100vh - 260px); min-height: 240px; }
.modal-box.full textarea { min-height: calc(100vh - 260px); }
.modal-head-acts { display: flex; align-items: center; gap: 4px; }
/* page behind an open modal must not scroll (e.g. when the editor hits its end) */
body.modal-open { overflow: hidden; }
.modal-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 18px; border-bottom: 1px solid var(--border); font-weight: 500; color: var(--text-strong);
}
.modal-body { padding: 18px; overflow: auto; overscroll-behavior: contain; }

/* Tabbed modal (openTabbedModal): a segmented pill ON the header row — the tab
   bar replaces the title, so no stacked title+tabs eating two rows. */
.modal-head.has-tabs {
  padding: 10px 12px 10px 12px;
  align-items: center;
  gap: 8px;
}
/* Title kept but faded, filling the space between the last tab and the ✕. */
.modal-head.has-tabs #modal-title {
  display: block; flex: 1; align-self: center;
  color: var(--muted); font-weight: 400; font-size: 13px;
  text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modal-head.has-tabs .modal-head-acts { align-self: center; margin: 0 0 0 20px; }
.modal-tabs {
  display: flex; gap: 2px; align-items: center; flex-wrap: wrap;
  padding: 3px; border: 1px solid var(--border); border-radius: 100px;
}

/* Tabbed modal body: min height so switching short tabs doesn't shrink the box;
   grows for content-heavy tabs (log browser). The tab's primary action sits in
   a sticky footer pinned to the bottom edge. */
.modal-body:has(> .modal-tab-panel) { display: flex; flex-direction: column; padding-bottom: 0; }
.modal-tab-panel { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 300px; }
.tab-body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.tab-main { flex: 1 1 auto; padding-bottom: 14px; }
.tab-foot {
  position: sticky; bottom: 0; margin: auto -18px 0;
  display: flex; gap: 8px; justify-content: flex-end; align-items: center;
  padding: 12px 18px;
  background: var(--glass-win);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--border);
}
/* the native file picker in the dialog's own clothes */
input[type=file] { font: inherit; color: var(--muted); font-size: 13px; }
input[type=file]::file-selector-button {
  font: inherit; font-weight: 500; font-size: 13px; cursor: pointer; margin-right: 10px;
  padding: 6px 14px; border-radius: 100px; color: var(--text);
  background: var(--hover); border: 1px solid var(--border);
}
input[type=file]::file-selector-button:hover { background: var(--active); border-color: var(--border-strong); }
.modal-tab {
  background: transparent; color: var(--muted);
  border: none; border-radius: 100px;
  padding: 5px 14px; cursor: pointer;
  font-size: 13px; line-height: 1.5;
  /* no font-weight swap on .active — bold text reflows and shifts sibling tabs */
}
.modal-tab:hover { color: var(--text); background: var(--hover); }
.modal-tab.active { background: var(--active); color: var(--text-strong); }

/* config editor (Ace mounts into this box; also sizes the textarea fallback) */
/* The editor FILLS the dialog instead of a fixed height: the dialog takes a
   definite height while an editor is open, every wrapper down to the editor
   is a flex column, and the Save row below it stays in view at any size
   (a placed or full-screen dialog brings its own height). */
.modal-box:has(.code-edit) { height: 86vh; }
.modal-box:has(.code-edit) .modal-body,
.modal-box:has(.code-edit) .modal-body > div,
.modal-box:has(.code-edit) .logbrowse { display: flex; flex-direction: column; min-height: 0; }
.modal-box:has(.code-edit) .modal-body,
.modal-box:has(.code-edit) .modal-body > div,
.modal-box:has(.code-edit) .logbrowse { flex: 1 1 auto; }
.modal-box:has(.code-edit) .logbrowse > .row,
.modal-box:has(.code-edit) .hint { flex: 0 0 auto; }
.code-edit {
  width: 100%; flex: 1 1 auto; min-height: 200px;
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
}

/* config editor: json/yaml/xml syntax status next to Save */
.lint-status { font-size: 12px; min-width: 0; overflow-wrap: anywhere; }
.lint-status.ok { color: var(--green); }
.lint-status.err { color: var(--red); }
.lint-status.warn { color: var(--amber); }
.lint-status.clickable { cursor: pointer; }
.lint-status.clickable:hover { text-decoration: underline; }

pre.output {
  background: var(--out-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-mono);
  font-size: 12px;
  max-height: 52vh; overflow: auto;
  margin: 0;
}
/* config editor history: +/- lines of a git diff rendered into pre.output */
pre.output .diff-add { color: var(--green); }
pre.output .diff-del { color: var(--red); }

.row { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
label { color: var(--muted); }
/* fields are glass too: a faint fill over the dialog, a hairline, an inset
   shadow for depth; focus brightens the fill and adds the accent ring */
input[type=text], input[type=number], input[type=password], select, textarea {
  font: inherit;
  background-color: var(--field-bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 6px 12px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .12);
  transition: background .12s, border-color .12s, box-shadow .12s;
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .8; }
textarea { border-radius: 12px; padding: 8px 12px; }
input[type=text]:focus, input[type=number]:focus, input[type=password]:focus, select:focus, textarea:focus {
  outline: none; background-color: var(--field-bg-focus); border-color: var(--border-strong);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .08), 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
/* selects: no system arrow — a chevron of our own with room on the right */
select {
  -webkit-appearance: none; appearance: none;
  padding-right: 32px; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b9098' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 11px center; background-size: 12px 12px;
}
select:hover { border-color: var(--border-strong); }
select option { background: var(--panel-3); color: var(--text); }
input[type=text] { min-width: 220px; }
input.req-empty { border-color: var(--amber); }

.tag-list { display: flex; flex-direction: column; gap: 4px; max-height: 28vh; overflow: auto; }
/* eyebrow caption, like the reference's "✦ PREVIEW" */
h4.section { margin: 18px 0 6px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: .14em; color: var(--muted); }
h4.section:first-child { margin-top: 0; }
.tag-list .tag {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 7px 12px; border: 1px solid var(--border); border-radius: 12px;
}
/* meta carries three facts now (build date · size · how long it ran here), so
   it wraps instead of squeezing the tag name or the buttons. */
.tag-list .tag .meta {
  color: var(--muted); font-size: 12px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  flex: 1 1 auto; justify-content: flex-end; text-align: right;
}
.tag-list .tag .meta .sep { opacity: .5; }
/* Branch · commit an image was built from (image labels). */
.tag-list .tag .meta .build { color: var(--text); white-space: nowrap; }
.tag-list .tag .meta .build-slot:empty { display: none; }
.tag-list .tag .acts { display: flex; gap: 6px; align-items: center; }
.tag-list .tag.current { border-color: var(--accent); }

/* Time this image was actually the pinned one on this host (from the audit
   trail) — the green dot marks the ones that held for more than a day, i.e.
   the versions worth rolling back to. */
.tag-list .tag .ran { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.tag-list .tag .ran.stable { color: var(--green); }
.dot-ok {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); flex: 0 0 auto;
}
/* Freshest push in the registry — the remote list is sorted by version, so the
   newest build is not the first row. */
.tag-badge {
  font-size: 11px; line-height: 1; padding: 3px 7px; border-radius: 10px;
  border: 1px solid var(--border); color: var(--muted); white-space: nowrap;
}
.tag-badge.new { border-color: var(--green); color: var(--green); }
.tag-list .tag.newest { border-color: var(--green); }
.tag-list .tag.newest.current { border-color: var(--accent); }

.env-form { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.env-form .field { display: flex; flex-direction: column; gap: 4px; }
.env-form .field > label { font-family: var(--font-mono); font-size: 12px; color: var(--muted); letter-spacing: .02em; }
.env-form .field .req { color: var(--amber); font-size: 11px; }
/* pulled in from the _env template, not yet saved to .env */
.env-form .field .added-tag { color: var(--accent); font-size: 11px; margin-left: 6px; }
.env-form .field.added input { border-color: var(--accent); }

/* ---- .env ←→ _env compare view (envModal → compareView in app.js) ---------
   Flexbox, not grid, to match the rest of the sheet: each row is
   [key] [current value] [action] [template default], the two value panes
   sharing the leftover width equally. */
.envd { display: flex; flex-direction: column; gap: 2px; max-height: 56vh; overflow: auto; overscroll-behavior: contain; margin-bottom: 12px; }
.envd-row { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 10px; border: 1px solid transparent; }
.envd-key { flex: 0 0 190px; min-width: 0; font-family: var(--font-mono); font-size: 12px; word-break: break-all; }
.envd-side { flex: 1 1 0; min-width: 0; }
.envd-mid { flex: 0 0 34px; display: flex; justify-content: center; }
/* Beat the global input min-width — at 220px the two panes stop sharing. */
.envd-side input[type=text] { width: 100%; min-width: 0; }
.envd-val { font-family: var(--font-mono); font-size: 12px; word-break: break-all; padding: 6px 2px; }
.envd-val .req { color: var(--amber); font-size: 11px; margin-left: 6px; }
.envd-act { padding: 2px 8px; line-height: 1.4; }
/* a knob the template ships commented out — offered, but off by default */
/* Under the key, not beside it: a long translation would otherwise wrap and
   push the row out of line with the rest of the column. */
.envd-tag { display: block; color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .04em; }
.envd-row.optional .envd-key { opacity: .85; }
.envd-act.add { color: var(--green); }
.envd-act.del { color: var(--red); }

.envd-head { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .12em; position: sticky; top: 0; z-index: 1; background: color-mix(in srgb, var(--panel) 70%, transparent); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
.envd-comment { color: var(--muted); font-size: 11px; font-family: var(--font-mono); padding: 2px 6px; word-break: break-word; }
.envd-sect { margin: 10px 0 2px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: .14em; color: var(--muted); }
.envd-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.envd-foot { margin: 0; }

/* Drift states: only missing/extra are actionable; `diff` is information. */
.envd-row.missing { border-color: var(--green); }
.envd-row.extra { border-color: var(--red); }
.envd-row.diff .envd-key { color: var(--amber); }
/* Staged, not yet written: a solid edge bar reads at a glance while scrolling. */
.envd-row.added { background: var(--panel-2); box-shadow: inset 3px 0 0 var(--green); }
.envd-row.removed { background: var(--panel-2); box-shadow: inset 3px 0 0 var(--red); }
/* text-decoration does not cross into a form control — strike the input itself. */
.envd-row.removed .envd-side.left, .envd-row.removed .envd-side.left input {
  text-decoration: line-through; opacity: .65;
}
/* "only differences": same-valued rows and the template's prose step aside. */
.envd.only-diff .envd-row.same, .envd.only-diff .envd-comment { display: none; }

/* Narrow: the panes stack, so the column head is meaningless — each value
   carries its own file name (data-label) instead. */
@media (max-width: 560px) {
  .envd-row { flex-wrap: wrap; gap: 4px; }
  .envd-head { display: none; }
  .envd-key { flex-basis: 100%; }
  .envd-side { flex-basis: 100%; display: flex; align-items: center; gap: 8px; }
  .envd-side::before {
    content: attr(data-label);
    flex: 0 0 54px; color: var(--muted); font-size: 11px;
  }
  .envd-side input[type=text] { flex: 1 1 auto; }
  .envd-mid { flex: 0 0 auto; margin-left: 62px; }
}

.toast {
  position: fixed; bottom: 18px; right: 18px;
  background: var(--glass-pop); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge);
  border-radius: 14px; padding: 12px 16px; z-index: 40;
  box-shadow: var(--shadow-pop);
  max-width: 360px;
}
.toast.ok { border-color: var(--green); }
.toast.err { border-color: var(--red); }
.hint { color: var(--muted); font-size: 12px; margin: 6px 0 0; }
/* A hint that needs the eye: stale/misconfigured state the user should fix. */
.hint.attn, .attn { color: var(--amber); }

/* min-width 0 so a long BOX_BRAND can be cut with "…" instead of pushing the toolbar out */
/* align-items: flex-start — the server-selector button (h1.brand-btn) must be
   as wide as the name, not as wide as the header */
.header-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; flex: 1 1 auto; min-width: 0; }
.brand-menu { min-width: 0; max-width: 100%; }
h1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the phone-only menu entry (language) stays hidden on wider screens */
.menu-phone { display: none; }

/* log browser */
.crumbs { font-size: 12px; color: var(--muted); margin-bottom: 8px; word-break: break-all; }
a.crumb { cursor: pointer; color: var(--link); }
a.crumb:hover { text-decoration: underline; }
.tag-list .tag a.crumb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 8px; }
.logbrowse .tag-list { max-height: 52vh; }

/* log viewer: sliding byte window over a multi-GB file (see logViewer in app.js) */
.logview {
  background: var(--out-bg);
  border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  white-space: pre-wrap; word-break: break-word;
  height: 52vh; overflow: auto; overscroll-behavior: contain;
}
.logview mark { background: var(--amber); color: #000; border-radius: 2px; }
.lv-chunk { display: block; }
.lv-pad { padding: 4px 0; }
.lv-bar { margin-bottom: 6px; }
.lv-bar .lv-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lv-bar .meta { color: var(--muted); font-size: 12px; white-space: nowrap; }
.lv-seek { flex: 1; min-width: 90px; accent-color: var(--accent); }
.lv-search { flex: 2; min-width: 140px; }
@media (max-width: 560px) {
  .logview { height: 46vh; }
  .lv-bar .lv-name { flex-basis: 100%; }
}

/* ---- mobile / narrow-width tidy-up --------------------------------------- */
/* Below ~560px the CPU/RAM/Disk metrics wrap onto several rows. Tighten the
   side paddings, and reserve a bottom strip so the corner-pinned panel version
   sits under the metrics instead of overlapping the last row. Also keep the
   card head from pushing the desc / remarks badge past the right edge. */
@media (max-width: 560px) {
  header { padding: 7px 12px; gap: 8px; }
  /* Header stays ONE row on a phone: the language toggle moves into the
     burger (.menu-phone), the brand is cut with "…" when it does not fit,
     the toolbar never wraps. */
  h1 { font-size: 16px; }
  #btn-lang { display: none; }
  .menu-phone { display: block; }
  .menu-dropdown .menu-phone { display: block; }
  .toolbar { gap: 6px; flex: 0 0 auto; flex-wrap: nowrap; }
  .btn-label { display: none; }
  .refresh-btn { padding: 6px 10px; }
  .ai-launch { padding-left: 12px; }
  /* The burger dropdown grew taller than a phone viewport — cap it below the
     sticky header and let it scroll. */
  .menu-dropdown { max-height: calc(100vh - 64px); max-height: calc(100dvh - 64px); overflow-y: auto; }
  .hoststats { padding: 0 6px 10px; }
  main { padding: 16px 12px; }
  .card-head { padding: 12px; gap: 8px; }
  .card-head .name { min-width: 0; }
  .actions { padding: 0 12px 12px; }
  /* the trio + Admin must share ONE row on a phone: tighter plaques, the
     Admin label folds into its icon (the caret stays), icons a touch smaller */
  .actions button { padding: 5px 9px; font-size: 12px; gap: 5px; }
  .life-group .life-btn:first-child { padding-left: 9px; }
  .life-group .life-btn:last-child { padding-right: 9px; }
  .admin-btn .life-lbl { display: none; }
  .admin-btn::after { content: "▾"; font-size: 10px; margin-left: -2px; }
  /* the container table is wider than a phone: scroll it inside the card
     instead of clipping the status and image columns */
  .containers { padding: 8px 12px 10px; overflow-x: auto; font-size: 12px; }
  .containers td.ct-image { max-width: 220px; }
  /* version values go UNDER their label, indented past the icon, instead of
     squeezing into whatever is left on the right */
  .hoststats .host-version .hs-item { flex-wrap: wrap; }
  .hoststats .host-version .hs-item .hs-v { flex: 0 0 100%; padding-left: 26px; flex-wrap: wrap; }
}

/* ---- login page (BOX_AUTH_MODE=jwt) --------------------------------------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.login-box {
  display: flex; flex-direction: column; gap: 12px;
  width: 100%; max-width: 360px; padding: 34px 30px 30px;
  background: var(--glass-win); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border: 1px solid var(--glass-edge); border-radius: 20px;
  box-shadow: var(--shadow-win);
}
.login-box h1 { text-align: center; margin-bottom: 10px; font-size: 20px; }
.login-box input {
  font: inherit; color: var(--text);
  background: var(--field-bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 12px;
}
.login-box input:focus { outline: none; border-color: var(--border-strong); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.login-box button.primary { margin-top: 4px; padding: 9px 14px; }
.login-error { color: var(--red); font-size: 13px; text-align: center; }

/* ---- user management modal ------------------------------------------------ */
.users-table { width: 100%; border-collapse: collapse; margin: 10px 0 14px; font-size: 13px; }
.users-table th {
  text-align: left; color: var(--muted); font-weight: 400; font-size: 12px;
  padding: 4px 8px; border-bottom: 1px solid var(--border);
}
.users-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.users-table td.user-actions { text-align: right; white-space: nowrap; }
/* config history: the open revision + its inline diff/view detail row */
.users-table tr.hist-active td { background: var(--out-bg); border-bottom-color: transparent; }
.users-table tr.hist-detail td { padding: 0 8px 10px; background: var(--out-bg); }
.users-table td.user-actions button { padding: 3px 8px; font-size: 12px; margin-left: 6px; }
.user-badge {
  display: inline-block; padding: 1px 7px; margin-right: 4px;
  border-radius: 100px; font-size: 11px; border: 1px solid var(--border);
}
.user-badge.admin { color: var(--brand-accent); border-color: var(--brand-accent); }
.user-badge.disabled { color: var(--red); border-color: var(--red); }
/* inline admin/disabled checkboxes in the add/edit form */
label.check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
/* masked text input for the admin-set password: dots like type=password, but
   invisible to browser password managers (no save prompt, no autofill) */
input.pw-mask { -webkit-text-security: disc; }
.users-transfer { border-top: 1px solid var(--border); padding-top: 12px; }
/* segmented import control: mode select + Import button in one pill
   (same pattern as the segmented pills in the toolbar) */
.users-transfer .import-group {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--border); border-radius: 100px; overflow: hidden;
}
.users-transfer .import-group select {
  font: inherit; color: var(--text); background-color: var(--field-bg);
  border: none; border-radius: 0; padding: 6px 30px 6px 14px;
}
.users-transfer .import-group button {
  border: none; border-radius: 0; border-left: 1px solid var(--border);
}

/* ---- activity card (loadActivity) — host-card rows, sections two-up ------- */
.card.activity-card { background: var(--glass-strong); border-color: color-mix(in srgb, var(--brand-accent) 24%, var(--border)); }
.activity-card .hoststats { display: grid; grid-template-columns: 1fr; gap: 2px 12px; align-content: start; }
.act-sec { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.act-sec .hs-section { padding-top: 6px; }
.act-row { flex-wrap: nowrap; }
.act-row .hs-k { min-width: 118px; white-space: nowrap; }
.act-row .hs-v { flex: 1 1 auto; min-width: 0; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.act-row .act-text { font-family: var(--font-mono); font-size: 12px; }
.act-row .act-text .muted { font-family: var(--font-sans); }
.act-click { cursor: pointer; }
.act-small { font-size: 11px; }
.act-chip { font-size: 11px; padding: 0 7px; border-radius: 100px; border: 1px solid; font-weight: 500; }
.act-chip.ok { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent); background: color-mix(in srgb, var(--green) 12%, transparent); }
.act-chip.warn, .act-chip.run { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 35%, transparent); background: color-mix(in srgb, var(--amber) 12%, transparent); }
.act-chip.err { color: var(--red); border-color: color-mix(in srgb, var(--red) 35%, transparent); background: color-mix(in srgb, var(--red) 12%, transparent); }
/* a row that needs the eye: the whole row carries a wash of its colour */
.act-row.hot { background: color-mix(in srgb, var(--red) 10%, transparent); }
.act-row.warm { background: color-mix(in srgb, var(--amber) 10%, transparent); }
.act-row.hot .hs-ico { color: var(--red); }
.act-row.warm .hs-ico { color: var(--amber); }
@media (min-width: 760px) {
  /* six sections, two columns: whole sections side by side */
  .activity-card .hoststats { grid-template-columns: 1fr 1fr; }
}

/* ---- disk usage popup (diskUsageModal) ------------------------------------- */
.du-summary { font-size: 14px; margin-bottom: 10px; color: var(--text-strong); }
.du-bar {
  display: flex; height: 14px; border-radius: 100px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--border); margin-bottom: 12px;
}
.du-seg { display: block; height: 100%; min-width: 0; }
.du-legend { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 5px 12px; align-items: baseline; font-size: 13px; }
.du-sw { width: 10px; height: 10px; border-radius: 3px; display: inline-block; align-self: center; }
.du-name { min-width: 0; }
.du-path { display: block; font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.du-val { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.du-pct { text-align: right; white-space: nowrap; font-size: 12px; }
.du-recl { color: var(--amber); }
.du-c-images { background: var(--accent); }
.du-c-containers { background: #8b5cf6; }
.du-c-volumes { background: #06b6d4; }
.du-c-buildCache { background: #f59e0b; }
.du-c-logs { background: #22c55e; }
.du-c-backups { background: #14b8a6; }
.du-c-dumps { background: #84cc16; }
.du-c-config { background: #ec4899; }
.du-c-snapshots { background: #f472b6; }
.du-c-repo { background: #a3e635; }
.du-c-other { background: var(--muted); }
.du-c-free { background: transparent; border: 1px solid var(--border); }
.du-table { font-size: 12px; margin: 6px 0 4px; }
.du-table td, .du-table th { padding: 4px 8px; }
.du-table td:not(:first-child) { white-space: nowrap; font-variant-numeric: tabular-nums; }
.du-cut { display: inline-block; max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.du-free { margin-top: 14px; padding: 12px 14px 10px; border: 1px solid var(--border); border-radius: 14px; background: var(--hover); }
.du-free h4.section { margin-top: 0; }
.du-fkind { padding: 4px 0; }
.du-frow { display: flex; align-items: baseline; gap: 8px; cursor: pointer; }
.du-frow input { align-self: center; }
.du-fname { flex: 1 1 auto; }
.du-fsize { white-space: nowrap; font-variant-numeric: tabular-nums; }
.du-frule { margin: 6px 0 0 24px; font-size: 13px; }
.du-frule input { padding: 3px 8px; }
.du-flist { margin-left: 24px; font-size: 12px; }
.du-flist summary { cursor: pointer; }
.du-fitems { margin: 4px 0 0; padding-left: 18px; font-size: 12px; max-height: 180px; overflow: auto; }
.du-fitems li { margin: 1px 0; overflow-wrap: anywhere; }
.du-fstatus { font-size: 12px; margin-top: 8px; }
.du-fgo { margin: 10px 0 0; justify-content: flex-end; }
.du-foot { margin-top: 12px; margin-bottom: 0; }
.du-root { font-size: 12px; }
@media (max-width: 560px) {
  .du-legend { grid-template-columns: auto minmax(0, 1fr) auto; }
  .du-pct { display: none; }
  .du-cut { max-width: 180px; }
  /* a wide table scrolls by itself — otherwise it drags the whole popup body
     sideways, legend and all */
  .du-table { display: block; overflow-x: auto; max-width: 100%; }
  .du-frule { margin-left: 0; }
}

/* ---- chrome: thin scrollbars, accent selection ---------------------------- */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 100px; border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background-color: var(--muted); }
::selection { background: color-mix(in srgb, var(--accent) 35%, transparent); }
