/* OSUplift web UI - stylesheet. Plain CSS, no external resources (CSP default-src 'self').
   Design system: every colour is a custom property on :root (the default theme) and the other themes override the same
   palette tokens from a [data-theme="<name>"] block, set on <html> by spa/theme.js before the first paint. The token
   contract (names, roles, which are theme-overridable) is spa/THEME-TOKENS.md. Every text/background pair of every theme
   is checked against WCAG AA by scripts/theme_contrast.py (tests/test_theme_contrast.py): 4.5:1 body text, 3:1 UI.
   Default: text #1f2328, muted #57606a (6.4:1 on white, 5.9:1 on the #f3f5f8 canvas), link #0550ae, ok #116329,
   warn #7d4e00, fail #a40e26 on white; status pills use dark text on light tints and an icon glyph, never colour alone. */
:root {
  color-scheme: light;
  --text: #1f2328; --muted: #57606a; --border: #d0d7de; --bg: #f3f5f8; --panel: #f6f8fa; --surface: #ffffff; --surface-hover: #f6f8fa;
  --surface-raised: #ffffff; --row-divider: #eaeef2; --input-border: #6e7781;
  --link: #0550ae; --focus: #0969da; --focus-on-header: #0969da; --ok: #116329; --ok-bg: #dafbe1; --warn: #7d4e00; --warn-bg: #fff8c5;
  --fail: #a40e26; --fail-bg: #ffebe9; --info: #0a3069; --info-bg: #ddf4ff; --running-bg: #e6f0fb;
  --role-admin: #512a97; --role-admin-bg: #f1e8ff; --role-approver: #7d4e00; --role-approver-bg: #fff8c5; --role-operator: #0a3069; --role-operator-bg: #ddf4ff; --role-viewer: #116329; --role-viewer-bg: #dafbe1;
  --btn-primary: #0969da; --btn-primary-hover: #0860ca; --btn-danger: #cf222e; --btn-success: #1a7f37; --btn-text: #ffffff;
  --notice-warn-border: #bf8700; --notice-fail-border: #cf222e; --notice-info-border: #0969da; --notice-ok-border: #1a7f37;
  --dot-done: #1a7f37; --dot-running: #0969da; --dot-failed: #cf222e; --dot-waiting: #bf8700;
  --header-bg: #ffffff; --header-text: #1f2328; --header-muted: #57606a; --header-link: #0550ae; --header-border: #d0d7de; --header-chip-bg: #f6f8fa;
  --sidebar-bg: #ffffff; --sidebar-hover-bg: #eef1f5; --sidebar-text: #1f2328; --sidebar-muted: #57606a; --sidebar-active-bg: #ddf4ff; --sidebar-active-text: #0a3069; --sidebar-border: #d0d7de;
  --footer-bg: #f3f5f8; --footer-text: #57606a; --footer-border: #d0d7de; --logo-plate: #ffffff;
  --code-bg: #0d1117; --code-text: #e6edf3; --code-warn: #f2cc60; --code-fail: #ff7b72; --code-key: #79c0ff;
  --frame-bg: #000000; --backdrop: rgba(31, 35, 40, .55); --shadow: rgba(31, 35, 40, .14);
  --skeleton: #e6eaef; --skeleton-shine: #f3f5f8;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, sans-serif; --font-heading: var(--font-body);
}
/* Dark: the default palette inverted on GitHub-dark greys; chips keep dark tints with light text. */
[data-theme="dark"] {
  color-scheme: dark;
  --text: #e6edf3; --muted: #a3acb8; --border: #3d444d; --bg: #0d1117; --panel: #161b22; --surface: #1c2129; --surface-hover: #22272e;
  --surface-raised: #22272e; --row-divider: #2a313a; --input-border: #8b949e;
  --link: #79c0ff; --focus: #79c0ff; --focus-on-header: #79c0ff; --ok: #7ee787; --ok-bg: #12261e; --warn: #f2cc60; --warn-bg: #2a2410;
  --fail: #ff9b93; --fail-bg: #3a1a1e; --info: #a5d6ff; --info-bg: #0f2a46; --running-bg: #132f4c;
  --role-admin: #d2a8ff; --role-admin-bg: #2b1d4a; --role-approver: #f2cc60; --role-approver-bg: #2a2410; --role-operator: #a5d6ff; --role-operator-bg: #0f2a46; --role-viewer: #7ee787; --role-viewer-bg: #12261e;
  --btn-primary: #1f6feb; --btn-primary-hover: #1a65d6; --btn-danger: #b62324; --btn-success: #1f7a3a; --btn-text: #ffffff;
  --notice-warn-border: #d29922; --notice-fail-border: #f85149; --notice-info-border: #58a6ff; --notice-ok-border: #3fb950;
  --dot-done: #3fb950; --dot-running: #58a6ff; --dot-failed: #f85149; --dot-waiting: #d29922;
  --header-bg: #161b22; --header-text: #e6edf3; --header-muted: #a3acb8; --header-link: #79c0ff; --header-border: #3d444d; --header-chip-bg: #0d1117;
  --sidebar-bg: #161b22; --sidebar-hover-bg: #22272e; --sidebar-text: #e6edf3; --sidebar-muted: #a3acb8; --sidebar-active-bg: #0f2a46; --sidebar-active-text: #a5d6ff; --sidebar-border: #3d444d;
  --footer-bg: #0d1117; --footer-text: #a3acb8; --footer-border: #3d444d; --logo-plate: #ffffff;
  --code-bg: #010409; --code-text: #e6edf3; --code-warn: #f2cc60; --code-fail: #ff7b72; --code-key: #79c0ff;
  --frame-bg: #000000; --backdrop: rgba(1, 4, 9, .7); --shadow: rgba(0, 0, 0, .6);
  --skeleton: #22272e; --skeleton-shine: #2a313a;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, sans-serif; --font-heading: var(--font-body);
}
/* Cenitex: colours and typography from www.cenitex.vic.gov.au (Neve customizer variables, read 2026-10-17):
   primary accent #06b1c4 (teal), secondary accent #87189d (purple), dark background #201547 (footer, primary buttons),
   header-top #261a5d with white text, link blue #0052c2 / #003174, text #0f0f0f, light background #f4f4f4.
   The teal #06b1c4 is 2.6:1 on white, so links use the brand blue #003174 (12.4:1) and the teal is kept for the header
   link underline/active state on the dark indigo, where it is 5.8:1. Fonts: the site uses "Spline Sans" (body) and
   "Outfit" (headings), both SIL Open Font License, plus the Victorian Government "VIC" face, which is not freely
   licensed. Nothing is downloaded (CSP, no external resources): the two OFL faces are named first and used when the
   operator has them installed; otherwise the system font stack applies. No logo or image is used. */
[data-theme="cenitex"] {
  color-scheme: light;
  --text: #0f0f0f; --muted: #4f5660; --border: #d6d9de; --bg: #f4f4f4; --panel: #f4f4f4; --surface: #ffffff; --surface-hover: #f7f7f7;
  --surface-raised: #ffffff; --row-divider: #e6e8ec; --input-border: #5e6570;
  --link: #003174; --focus: #87189d; --focus-on-header: #06b1c4; --ok: #116329; --ok-bg: #dafbe1; --warn: #7d4e00; --warn-bg: #fff8c5;
  --fail: #a40e26; --fail-bg: #ffebe9; --info: #201547; --info-bg: #e3f6f8; --running-bg: #dad0ec;
  --role-admin: #201547; --role-admin-bg: #dad0ec; --role-approver: #7d4e00; --role-approver-bg: #fff8c5; --role-operator: #003174; --role-operator-bg: #dce7f5; --role-viewer: #0e5a4a; --role-viewer-bg: #d4f2ee;
  --btn-primary: #201547; --btn-primary-hover: #2d1f66; --btn-danger: #b3261e; --btn-success: #1a7f37; --btn-text: #ffffff;
  --notice-warn-border: #bf8700; --notice-fail-border: #b3261e; --notice-info-border: #06b1c4; --notice-ok-border: #1a7f37;
  --dot-done: #1a7f37; --dot-running: #0052c2; --dot-failed: #b3261e; --dot-waiting: #bf8700;
  --header-bg: #261a5d; --header-text: #ffffff; --header-muted: #d9d4ea; --header-link: #ffffff; --header-border: #251a60; --header-chip-bg: #201547;
  --sidebar-bg: #201547; --sidebar-hover-bg: #2f2366; --sidebar-text: #fafafa; --sidebar-muted: #d9d4ea; --sidebar-active-bg: #87189d; --sidebar-active-text: #ffffff; --sidebar-border: #87189d;
  --footer-bg: #201547; --footer-text: #fafafa; --footer-border: #87189d; --logo-plate: #ffffff;
  --code-bg: #201547; --code-text: #fafafa; --code-warn: #ffd166; --code-fail: #ffb4a8; --code-key: #7fe3ee;
  --frame-bg: #0f0f0f; --backdrop: rgba(32, 21, 71, .6); --shadow: rgba(32, 21, 71, .22);
  --skeleton: #e6e8ec; --skeleton-shine: #f4f4f4;
  --font-body: "Spline Sans", "Segoe UI", system-ui, -apple-system, Roboto, Ubuntu, sans-serif;
  --font-heading: "Outfit", "Segoe UI", system-ui, -apple-system, Roboto, Ubuntu, sans-serif;
}
/* Semantic aliases and structural tokens (theme-independent; see THEME-TOKENS.md sections 2 and 3). Kept in a separate
   block so the palette blocks above stay plain hex for the contrast checker. */
html {
  --text-muted: var(--muted); --primary: var(--btn-primary); --primary-text: var(--btn-text);
  --success: var(--ok); --success-bg: var(--ok-bg); --warning: var(--warn); --warning-bg: var(--warn-bg);
  --danger: var(--fail); --danger-bg: var(--fail-bg); --info-text: var(--info); --info-tint: var(--info-bg);
  --space-0: 4px; --space-1: 8px; --space-2: 16px; --space-3: 24px; --space-4: 32px; --space-5: 40px; --space-6: 48px;
  --radius-sm: 6px; --radius-md: 10px; --radius-lg: 14px; --radius-full: 999px;
  --shadow-1: 0 1px 2px var(--shadow); --shadow-2: 0 2px 8px var(--shadow); --shadow-3: 0 12px 32px var(--shadow);
  --text-xs: .75rem; --text-sm: .85rem; --text-base: .95rem; --text-md: 1.05rem; --text-lg: 1.25rem; --text-xl: 1.6rem; --text-2xl: 2rem; --leading: 1.5;
  --topbar-h: 56px; --sidebar-w: 232px; --content-max: 1440px;
  --dur-fast: 120ms; --dur: 180ms; --ease: cubic-bezier(.2, .7, .2, 1);
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* inline SVG icons (stroke drawings, 24-unit grid) used as CSS masks coloured with currentColor: the sidebar nav, the
     menu button, the logo mark, the select arrow. img-src allows data: URIs. */
  --ic-servers: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4' width='18' height='7' rx='2'/><rect x='3' y='13' width='18' height='7' rx='2'/><path d='M7 7.5h.01M7 16.5h.01'/></svg>");
  --ic-databases: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><ellipse cx='12' cy='5.5' rx='8' ry='3'/><path d='M4 5.5v13c0 1.7 3.6 3 8 3s8-1.3 8-3v-13'/><path d='M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3'/></svg>");
  --ic-sources: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3v11'/><path d='m8 10 4 4 4-4'/><rect x='3' y='15' width='18' height='6' rx='2'/></svg>");
  --ic-targets: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='8'/><circle cx='12' cy='12' r='3'/><path d='M12 2v3M12 19v3M2 12h3M19 12h3'/></svg>");
  --ic-runs: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3 12h4l3-8 4 16 3-8h4'/></svg>");
  --ic-runbooks: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 19.5A2.5 2.5 0 0 1 6.5 17H20'/><path d='M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z'/></svg>");
  --ic-awaiting: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M12 7v5l3 2'/></svg>");
  --ic-admin: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6h9M17 6h3M4 12h3M11 12h9M4 18h11M19 18h1'/><circle cx='15' cy='6' r='2'/><circle cx='9' cy='12' r='2'/><circle cx='17' cy='18' r='2'/></svg>");
  --ic-about: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9'/><path d='M12 11v5M12 8h.01'/></svg>");
  --ic-menu: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'><path d='M4 7h16M4 12h16M4 17h16'/></svg>");
  --ic-close: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'><path d='M6 6l12 12M18 6 6 18'/></svg>");
  --ic-signout: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'/><path d='m16 17 5-5-5-5'/><path d='M21 12H9'/></svg>");
  --ic-logo: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 18V7'/><path d='m7.5 11.5 4.5-4.5 4.5 4.5'/><path d='M5 20h14'/></svg>");
  --ic-chevron: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  --ic-user: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='8' r='4'/><path d='M4 21a8 8 0 0 1 16 0'/></svg>");
  --ic-users: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='9' cy='8' r='3.5'/><path d='M2.5 20a6.5 6.5 0 0 1 13 0'/><path d='M15.5 4.6a3.5 3.5 0 0 1 0 6.8'/><path d='M17 13.6a6.5 6.5 0 0 1 4.5 6.4'/></svg>");
  --ic-chat: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H10l-5 4v-4H6.5A2.5 2.5 0 0 1 4 13.5z'/><path d='M8.5 8.5h7M8.5 12h4.5'/></svg>");
  --ic-compat-fleet: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4' width='18' height='6' rx='1.5'/><rect x='3' y='14' width='18' height='6' rx='1.5'/><path d='M7 7h.01M7 17h.01'/></svg>");
  --ic-compat-catalog: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 4.5A2.5 2.5 0 0 1 6.5 2H20v17H6.5A2.5 2.5 0 0 0 4 21.5z'/><path d='M4 19.5A2.5 2.5 0 0 1 6.5 17H20'/><path d='M9 7h7M9 11h7'/></svg>");
  --ic-compat-research: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='10.5' cy='10.5' r='6.5'/><path d='m15.5 15.5 5 5'/><path d='M8 10.5h5M10.5 8v5'/></svg>");
  --ic-compat: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='8' height='8' rx='1.5'/><rect x='13' y='3' width='8' height='8' rx='1.5'/><rect x='3' y='13' width='8' height='8' rx='1.5'/><path d='m14 17 2.2 2.2L21 14.5'/></svg>");
  --ic-compliance: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3 4 6.5v5c0 4.6 3.4 8.4 8 9.5 4.6-1.1 8-4.9 8-9.5v-5z'/><path d='m9 12 2 2 4-4'/></svg>");
  --ic-audit: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 2.5h8.5L19 7v14.5H6z'/><path d='M14.5 2.5V7H19'/><path d='M9 11.5h7M9 15h7M9 18.5h4'/></svg>");
  --ic-empty: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M3 8l9-5 9 5v8l-9 5-9-5z'/><path d='M3 8l9 5 9-5M12 13v8'/></svg>");
}

/* ================================================================ base */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, sans-serif; font-family: var(--font-body); }
body { min-height: 100vh; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
h1, h2, h3, h4, header.top .brand { font-family: var(--font-heading); letter-spacing: -.01em; }
a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: .15em; }
a:hover { text-decoration-thickness: 2px; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible, [tabindex]:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
header.top a:focus-visible, header.top button:focus-visible { outline-color: var(--focus-on-header); }
/* the sidebar shares the top bar's focus token (THEME-TOKENS.md: "focus ring on the top bar / sidebar"): on the Cenitex
   theme the sidebar is dark indigo, where the page focus purple is 2.1:1 and the header teal is 6.4:1 */
#nav a:focus-visible, #nav button:focus-visible { outline-color: var(--focus-on-header); }
.skip { position: absolute; left: -999px; top: 0; background: var(--surface-raised); color: var(--text); padding: .5rem 1rem; border: 2px solid var(--focus); border-radius: var(--radius-sm); }
.skip:focus { left: .5rem; top: .5rem; z-index: 60; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.hidden, [hidden] { display: none !important; }

/* ================================================================ app shell: top bar + sidebar (desktop) */
header.top { position: sticky; top: 0; z-index: 40; display: flex; align-items: center; gap: var(--space-2); height: var(--topbar-h);
  padding: 0 var(--space-3) 0 var(--space-2); border-bottom: 1px solid var(--header-border); background: var(--header-bg); color: var(--header-text); }
header.top .brand { display: inline-flex; align-items: center; gap: .6rem; font-weight: 700; font-size: 1.05rem; white-space: nowrap; min-width: calc(var(--sidebar-w) - var(--space-2)); }
header.top .brand .logo { width: 30px; height: 30px; border-radius: 9px; background: var(--btn-primary); position: relative; flex: none; box-shadow: var(--shadow-1); }
header.top .brand .logo::after { content: ""; position: absolute; inset: 5px; background: var(--btn-text); -webkit-mask: var(--ic-logo) center / contain no-repeat; mask: var(--ic-logo) center / contain no-repeat; }
/* The Cenitex organisation logo (signed-in UI only; app.js fills #org-logo after /me): the unaltered PNG as an <img> on a
   light plate (--logo-plate), so the grey #53565a / indigo #201547 mark keeps its own contrast on the dark and Cenitex
   top bars; on the light default bar the plate matches the bar. Sized by height; the width follows the 2275:525 image. */
header.top .brand .org-logo { display: inline-flex; align-items: center; flex: none; height: 34px; padding: 0 8px; border-radius: var(--radius-sm); background: var(--logo-plate); }
header.top .brand .org-logo img { display: block; height: 22px; width: auto; }
header.top.org .brand .logo { display: none; }
header.top .env { margin-left: auto; font-size: var(--text-xs); color: var(--header-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
header.top .who { display: inline-flex; align-items: center; gap: .45rem; max-width: 34vw; font-size: var(--text-sm); padding: .3rem .75rem .3rem .55rem; border: 1px solid var(--header-border); border-radius: var(--radius-full);
  background: var(--header-chip-bg); color: var(--header-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
header.top .who::before { content: ""; flex: none; width: 16px; height: 16px; background: currentColor; -webkit-mask: var(--ic-user) center / contain no-repeat; mask: var(--ic-user) center / contain no-repeat; }
header.top .who .role { color: var(--header-muted); }
/* Account menu (2026-10-18): #who is now a BUTTON showing only the username; the groups and Sign out live in the popover it
   opens (aria-expanded/aria-controls, Escape closes). Same chip look as before; the name ellipsises, the full value is its title. */
header.top .account { position: relative; display: inline-flex; align-items: center; flex: 0 1 auto; min-width: 0; }
header.top button.who { font: inherit; font-size: var(--text-sm); cursor: pointer; line-height: 1.25; }
header.top button.who::after { content: ""; flex: none; width: 10px; height: 10px; margin-left: .1rem; background: currentColor; -webkit-mask: var(--ic-chevron) center / contain no-repeat; mask: var(--ic-chevron) center / contain no-repeat; opacity: .8; }
header.top button.who[aria-expanded=true]::after { transform: rotate(180deg); }
header.top .who .who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
header.top .account-menu { position: absolute; top: calc(100% + .4rem); right: 0; z-index: 45; min-width: 16rem; max-width: min(24rem, calc(100vw - 1.5rem)); padding: var(--space-2);
header.top .account-menu.anchor-left { left: 0; right: auto; }   /* the chip has wrapped to the left of the bar (phones): open rightwards */
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--text); box-shadow: var(--shadow-3); }
header.top .account-menu[hidden] { display: none; }
header.top .account-menu p { margin: 0 0 .5rem; }
header.top .account-menu .account-menu-name { font-size: var(--text-sm); word-break: break-all; }
header.top .account-menu .account-menu-display-name { margin: -.2rem 0 .4rem; font-size: var(--text-sm); color: var(--muted); overflow-wrap: anywhere; }
header.top .account-menu .account-menu-roles { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; font-size: var(--text-sm); color: var(--muted); }
header.top .account-menu .account-menu-label { font-weight: 600; color: var(--text); }
header.top .account-menu button.link { color: var(--link); width: 100%; justify-content: flex-start; border-color: var(--border); margin-top: .25rem; }
header.top .account-menu button.link:hover { background: var(--surface-hover); }
header.top button, header.top .nav-toggle { color: var(--header-text); border-color: var(--header-border); background: var(--header-chip-bg); }
header.top button.link { display: inline-flex; align-items: center; gap: .4rem; color: var(--header-link); background: none; border: 1px solid transparent; border-radius: var(--radius-sm); padding: .4rem .7rem; text-decoration: none; font-weight: 600; }
header.top button.link::before { content: ""; width: 16px; height: 16px; background: currentColor; -webkit-mask: var(--ic-signout) center / contain no-repeat; mask: var(--ic-signout) center / contain no-repeat; }
header.top button.link:hover { background: var(--header-chip-bg); border-color: var(--header-border); }
/* the sidebar: a fixed rail under the top bar on wide screens; the same <nav> becomes the Menu sheet below 1000 px */
#nav { position: fixed; top: var(--topbar-h); left: 0; bottom: 0; width: var(--sidebar-w); display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-1);
  background: var(--sidebar-bg); border-right: 1px solid var(--sidebar-border); overflow-y: auto; z-index: 30; }
#nav a { display: flex; align-items: center; gap: .7rem; min-height: 40px; padding: .45rem .75rem; border-radius: var(--radius-sm); color: var(--sidebar-text); text-decoration: none; font-weight: 500; font-size: var(--text-base); position: relative;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
#nav a::before { content: ""; flex: none; width: 20px; height: 20px; background: var(--sidebar-muted); -webkit-mask: var(--ic) center / contain no-repeat; mask: var(--ic) center / contain no-repeat; transition: background-color var(--dur-fast) var(--ease); }
#nav a:hover { background: var(--sidebar-hover-bg); }
#nav a[aria-current=page] { background: var(--sidebar-active-bg); color: var(--sidebar-active-text); font-weight: 600; }
#nav a:hover::before { background: var(--sidebar-text); }
#nav a[aria-current=page]::before { background: var(--sidebar-active-text); }
#nav a[aria-current=page]::after { content: ""; position: absolute; left: -8px; top: 8px; bottom: 8px; width: 3px; border-radius: 3px; background: var(--sidebar-active-text); }
/* icon per destination, keyed on the link target so the anchors stay plain (several tests pin their exact markup) */
#nav a[href="#/servers"] { --ic: var(--ic-servers); } #nav a[href="#/databases"] { --ic: var(--ic-databases); } #nav a[href="#/sources"] { --ic: var(--ic-sources); }
#nav a[href="#/runs"] { --ic: var(--ic-runs); } #nav a[href="#/runbooks"] { --ic: var(--ic-runbooks); } #nav #nav-awaiting { --ic: var(--ic-awaiting); } #nav #nav-admin { --ic: var(--ic-admin); }
#nav #nav-targets { --ic: var(--ic-targets); } #nav #nav-about { --ic: var(--ic-about); } #nav #nav-chat { --ic: var(--ic-chat); } #nav #nav-users { --ic: var(--ic-users); } #nav #nav-audit { --ic: var(--ic-audit); }
/* Compatibility group (2026-10-19): the toggle keeps the checklist icon; the sub-items get a server grid, a book and a magnifier */
#nav #nav-compliance { --ic: var(--ic-compliance); } #nav #nav-compat-toggle { --ic: var(--ic-compat); } #nav #nav-compat-fleet { --ic: var(--ic-compat-fleet); } #nav #nav-compat-catalog { --ic: var(--ic-compat-catalog); } #nav #nav-compat-research { --ic: var(--ic-compat-research); }
/* a menu group (Databases: Overview, Sources, Targets): the toggle reads like a link row with its icon and a chevron that
   turns when expanded; the children are indented under a guide line and carry the current-page state; the toggle shows
   the section as current (aria-current=true, set by nav.js) so the active area is visible even when the group is folded */
#nav .nav-group { display: flex; flex-direction: column; gap: 2px; }
#nav .nav-group-toggle { display: flex; align-items: center; justify-content: flex-start; gap: .7rem; width: 100%; min-height: 40px; padding: .45rem .75rem; border: 0; border-radius: var(--radius-sm); background: transparent; box-shadow: none;
  color: var(--sidebar-text); font: inherit; font-weight: 500; font-size: var(--text-base); text-align: left; cursor: pointer; --ic: var(--ic-databases); position: relative;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
#nav .nav-group-toggle::before { content: ""; flex: none; width: 20px; height: 20px; background: var(--sidebar-muted); -webkit-mask: var(--ic) center / contain no-repeat; mask: var(--ic) center / contain no-repeat; }
#nav .nav-group-toggle::after { content: ""; flex: none; margin-left: auto; width: 16px; height: 16px; background: var(--sidebar-muted); -webkit-mask: var(--ic-chevron) center / contain no-repeat; mask: var(--ic-chevron) center / contain no-repeat;
  transform: rotate(-90deg); transition: transform var(--dur) var(--ease); }
#nav .nav-group-toggle[aria-expanded=true]::after { transform: rotate(0deg); }
#nav .nav-group-toggle:hover { background: var(--sidebar-hover-bg); transform: none; }
#nav .nav-group-toggle:hover::before, #nav .nav-group-toggle:hover::after { background: var(--sidebar-text); }
#nav .nav-group[data-current] > .nav-group-toggle { color: var(--sidebar-active-text); font-weight: 600; }
#nav .nav-group[data-current] > .nav-group-toggle::before { background: var(--sidebar-active-text); }
#nav .nav-group-items { display: flex; flex-direction: column; gap: 2px; margin-left: 1.35rem; padding-left: .4rem; border-left: 1px solid var(--sidebar-border); }
#nav .nav-group-items[hidden] { display: none; }
#nav .nav-group-items a { min-height: 36px; padding: .35rem .6rem; font-size: var(--text-sm); }
#nav .nav-group-items a::before { display: none; }
#nav .nav-group-items a[aria-current=page]::after { left: calc(-.4rem - 1px - 1px); top: 6px; bottom: 6px; width: 2px; }
#nav #nav-awaiting { font-variant-numeric: tabular-nums; }
header.top .nav-toggle::before { content: ""; width: 20px; height: 20px; background: currentColor; -webkit-mask: var(--ic-menu) center / contain no-repeat; mask: var(--ic-menu) center / contain no-repeat; }
header.top .nav-toggle[aria-expanded=true]::before { -webkit-mask-image: var(--ic-close); mask-image: var(--ic-close); }
/* laptop widths (1000-1439 px): the sidebar is a captioned rail (icon above a small label, every item readable without
   hovering) so a wide data table keeps its room; hovering or focusing the rail widens it to the full sidebar, where the
   Databases group's children appear. The full sidebar returns at 1440 px. */
@media (min-width: 1000px) and (max-width: 1439px) {
  html { --sidebar-w: 84px; --rail-open-w: 232px; }
  #nav { width: var(--sidebar-w); padding: var(--space-1) 6px; overflow-x: hidden; transition: width var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
  #nav a, #nav .nav-group-toggle { flex-direction: column; justify-content: center; gap: 3px; min-height: 56px; padding: .45rem .15rem; font-size: .66rem; line-height: 1.15; text-align: center; font-weight: 600; white-space: normal; overflow-wrap: anywhere; }
  #nav .nav-group-toggle::after { display: none; }
  #nav a[aria-current=page]::after { left: -6px; }
  #nav .nav-group-items { display: none; }
  #nav .nav-group[data-current] > .nav-group-toggle { background: var(--sidebar-active-bg); }
  #nav:hover, #nav:focus-within { width: var(--rail-open-w); box-shadow: var(--shadow-3); padding: var(--space-2) var(--space-1); }
  #nav:hover a, #nav:focus-within a, #nav:hover .nav-group-toggle, #nav:focus-within .nav-group-toggle { flex-direction: row; justify-content: flex-start; gap: .7rem; min-height: 40px; padding: .45rem .75rem; font-size: var(--text-base); line-height: 1.5; text-align: left; font-weight: 500; white-space: nowrap; }
  #nav:hover a[aria-current=page], #nav:focus-within a[aria-current=page], #nav:hover .nav-group[data-current] > .nav-group-toggle, #nav:focus-within .nav-group[data-current] > .nav-group-toggle { font-weight: 600; }
  #nav:hover .nav-group[data-current] > .nav-group-toggle, #nav:focus-within .nav-group[data-current] > .nav-group-toggle { background: transparent; }
  #nav:hover .nav-group-toggle::after, #nav:focus-within .nav-group-toggle::after { display: block; }
  #nav:hover .nav-group-items:not([hidden]), #nav:focus-within .nav-group-items:not([hidden]) { display: flex; }
  #nav:hover .nav-group-items a, #nav:focus-within .nav-group-items a { min-height: 36px; padding: .35rem .6rem; font-size: var(--text-sm); }
  #nav:hover a[aria-current=page]::after, #nav:focus-within a[aria-current=page]::after { left: -8px; }
  header.top .brand { min-width: 0; }
}
main { padding: var(--space-3) var(--space-4) var(--space-5); margin-left: var(--sidebar-w); max-width: calc(var(--sidebar-w) + var(--content-max)); min-height: calc(100vh - var(--topbar-h) - 3.5rem); }
footer.note { margin: 0 0 0 var(--sidebar-w); padding: var(--space-2) var(--space-4) var(--space-3); font-size: var(--text-xs); color: var(--footer-text); background: var(--footer-bg); border-top: 1px solid var(--footer-border); }

/* ================================================================ typography, page header */
h1 { font-size: var(--text-xl); font-weight: 700; line-height: 1.2; margin: 0 0 .25rem; }
h2 { font-size: var(--text-md); font-weight: 650; margin: var(--space-3) 0 var(--space-1); }
h3 { font-size: 1rem; font-weight: 650; margin: var(--space-2) 0 .4rem; }
main > h1, .run-monitor > h1, .page-head h1 { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .6rem; }
main > h1 .mono, .run-monitor > h1 .mono, .page-head h1 .mono { font-size: .82em; font-weight: 600; }
h1 .sub { font-size: .62em; font-weight: 500; }
.sub { color: var(--muted); margin: 0 0 var(--space-2); font-size: var(--text-base); }
main > h1 + p.sub, .run-monitor > h1 + p.sub, .page-head p.sub { max-width: 90ch; margin-bottom: var(--space-3); }
.page-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--space-2) var(--space-3); margin-bottom: var(--space-3); }
.page-head .page-head-text { flex: 1 1 32rem; min-width: 0; }
.page-head .page-head-text p.sub { margin-bottom: 0; }
.page-head .page-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; justify-content: flex-end; flex: 0 1 auto; padding-top: .15rem; }
.breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; font-size: var(--text-sm); color: var(--muted); margin-bottom: var(--space-1); }
.breadcrumb a { color: var(--muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--link); text-decoration: underline; }
code { font: 12.5px var(--font-mono); background: var(--panel); color: var(--text); padding: .1rem .35rem; border-radius: 4px; border: 1px solid var(--row-divider); }
.mono { font-family: var(--font-mono); font-size: .9em; }
.right { text-align: right; font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.pre-inline { white-space: pre-wrap; }
.mt1 { margin-top: var(--space-2); } .mt05 { margin-top: .6rem; } .w24 { min-width: 24rem; } .w8 { min-width: 8rem; }
details summary { cursor: pointer; font-weight: 600; }
details.panel > summary { font-weight: 600; }
details.panel[hidden] { display: none; }
section.panel ol > li, section.panel ul > li { margin-bottom: .35rem; }
.tag { display: inline-block; font-size: var(--text-xs); color: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 0 .35rem; margin: 0 .25rem .25rem 0; background: var(--panel); }

/* ================================================================ layout grids */
.grid { display: grid; gap: var(--space-2); }
.grid.two { grid-template-columns: 1fr 1fr; }
.grid.three { grid-template-columns: 1fr 1fr 1fr; }
.grid.monitor { grid-template-columns: 320px minmax(0, 1fr) 380px; align-items: start; }
.grid.monitor > section { min-width: 0; }
/* a run kind with no Console card and nothing else on the right (readiness): the column is dropped, the grid reflows (it_ac311aab follow-up) */
.grid.monitor.no-right { grid-template-columns: 320px minmax(0, 1fr); }
@media (max-width: 1439px) { .grid.monitor { grid-template-columns: 290px minmax(0, 1fr) 340px; } .grid.monitor.no-right { grid-template-columns: 290px minmax(0, 1fr); } }
@media (max-width: 1000px) { .grid.two, .grid.three, .grid.monitor, .grid.monitor.no-right { grid-template-columns: 1fr; } }   /* .no-right repeated: its 1439 px rule outranks .grid.monitor (found at 375 px: a 51 px second column) */

/* ================================================================ cards */
.panel { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); padding: var(--space-2) var(--space-2) var(--space-2); box-shadow: var(--shadow-1); }
.panel h2, .panel h3 { margin-top: 0; }
.panel h2 { padding-bottom: .55rem; margin-bottom: .75rem; border-bottom: 1px solid var(--row-divider); font-size: 1rem; display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; }
.panel.muted { background: var(--panel); box-shadow: none; }
.panel > :last-child { margin-bottom: 0; }
.grid.two > section.panel, .grid.two > .panel { margin-top: 0; }
/* KPI / stat tiles */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
.kpi { position: relative; border: 1px solid var(--border); border-radius: var(--radius-md); padding: .85rem 1rem .8rem; background: var(--surface); box-shadow: var(--shadow-1); overflow: hidden; }
.kpi::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--btn-primary); opacity: .85; }
.kpi .n { font-size: var(--text-2xl); font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.kpi .l { font-size: var(--text-sm); color: var(--muted); margin-top: .2rem; }
/* The tile's (i) button (2026-10-19): what the number counts opens in the step-help popover; nothing else is on the face.
   Pinned to the tile's top-right corner (DOM order keeps it after the label for readers); the padding keeps the number clear of it. */
.kpi { padding-right: 2.4rem; }
.kpi .l button.info { position: absolute; top: .7rem; right: .7rem; margin-left: 0; }

/* ================================================================ tables */
table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: .92rem; }
caption { text-align: left; font-weight: 600; padding: .3rem 0 .5rem; }
th, td { text-align: left; padding: .6rem .75rem; border-bottom: 1px solid var(--row-divider); vertical-align: top; }
th { background: var(--panel); font-weight: 600; font-size: var(--text-sm); color: var(--muted); letter-spacing: .01em; }
thead th { position: sticky; top: 0; z-index: 1; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background-color var(--dur-fast) var(--ease); }
tr:hover td { background: var(--surface-hover); }
td .sub { display: inline; font-size: var(--text-sm); margin: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.kv { border: 0; }
.kv th { width: 36%; background: transparent; color: var(--muted); font-weight: 500; white-space: normal; overflow-wrap: normal; word-break: normal; position: static; font-size: var(--text-sm); }
.kv th, .kv td { padding: .45rem .5rem .45rem 0; }
.kv tr:hover td { background: transparent; }
/* every data table (not .kv) is wrapped by app.js in .table-scroll: that region draws the card edge of the table */
.table-scroll { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-1); margin: 0 0 var(--space-2); }
.table-scroll > table > thead th:first-child { border-top-left-radius: var(--radius-md); }
.table-scroll > table > thead th:last-child { border-top-right-radius: var(--radius-md); }
.panel .table-scroll, .notice .table-scroll { box-shadow: none; }
.row-actions { display: inline-flex; flex-wrap: wrap; gap: .25rem .75rem; }
.row-actions a { font-weight: 600; text-decoration: none; }
.row-actions a:hover { text-decoration: underline; }
/* empty state (the "nothing to show" row and .empty blocks) */
td .empty, .empty { display: flex; flex-direction: column; align-items: center; gap: .35rem; padding: var(--space-3) var(--space-2); color: var(--muted); text-align: center; font-size: var(--text-base); }
.empty::before { content: ""; width: 32px; height: 32px; background: var(--muted); opacity: .6; -webkit-mask: var(--ic-empty) center / contain no-repeat; mask: var(--ic-empty) center / contain no-repeat; }
.empty .sub { margin: 0; }

/* ================================================================ status pills (icon + text; the icon glyph is decorative) */
.chip { display: inline-flex; align-items: center; gap: .3rem; padding: .1rem .6rem .1rem .5rem; border-radius: var(--radius-full); font-size: var(--text-xs); font-weight: 600; line-height: 1.5; border: 1px solid transparent; white-space: nowrap; vertical-align: middle; }
.chip::before { content: "" / ""; font-weight: 700; font-size: .9em; line-height: 1; }
.chip.ok { color: var(--ok); background: var(--ok-bg); } .chip.ok::before { content: "\2713" / ""; }
.chip.warn { color: var(--warn); background: var(--warn-bg); } .chip.warn::before { content: "!" / ""; }
.chip.fail { color: var(--fail); background: var(--fail-bg); } .chip.fail::before { content: "\2715" / ""; }
.chip.info { color: var(--info); background: var(--info-bg); } .chip.info::before { content: "i" / ""; font-style: italic; font-family: Georgia, serif; }
.chip.neutral { color: var(--muted); background: var(--panel); border-color: var(--border); } .chip.neutral::before { content: none; }   /* no glyph (UI review 2026-10-19, L1: the en-dash on "Idle" / "no" read as a stray dash) */
.chip.kind { color: var(--info); background: var(--info-bg); } .chip.kind::before { content: none; }   /* what a thing IS (a document type), not a status: the info colour without the "i" glyph (operator screenshot 2026-10-20: an "i" inside "Automation") */
.chip.running { color: var(--info); background: var(--running-bg); }
.chip.coming { color: var(--info); background: var(--info-bg); border-color: var(--info-bg); } .chip.coming::before { content: none; }   /* About page: work another lane is still building */
.chip.running::before { content: "" / ""; width: .55em; height: .55em; border-radius: 50%; background: currentColor; animation: pulse 1.4s ease-in-out infinite; }
/* Role chips (account menu, Users list, group checkbox labels; THEME-TOKENS.md "role colours", 2026-10-18): one colour per
   group, the same pill shape for every held role, the text label always present and a small glyph per role (decorative) */
.chip.role-admin { color: var(--role-admin); background: var(--role-admin-bg); } .chip.role-admin::before { content: "\2605" / ""; }
.chip.role-approver { color: var(--role-approver); background: var(--role-approver-bg); } .chip.role-approver::before { content: "\2691" / ""; }
.chip.role-operator { color: var(--role-operator); background: var(--role-operator-bg); } .chip.role-operator::before { content: "\2699" / ""; }
.chip.role-viewer { color: var(--role-viewer); background: var(--role-viewer-bg); } .chip.role-viewer::before { content: "\25C9" / ""; }
label .chip { vertical-align: baseline; }
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.75); } }

/* ================================================================ buttons */
button, .btn { font: inherit; font-weight: 600; font-size: var(--text-base); line-height: 1.25; padding: .5rem .95rem; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  box-shadow: var(--shadow-1); transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease); }
button:hover, .btn:hover { background: var(--surface-hover); border-color: var(--input-border); }
button:active, .btn:active { transform: translateY(1px); box-shadow: none; }
button.primary, .btn.primary { background: var(--btn-primary); border-color: var(--btn-primary); color: var(--btn-text); }
button.primary:hover, .btn.primary:hover { background: var(--btn-primary-hover); border-color: var(--btn-primary-hover); }
/* Destructive actions (UI review 2026-10-19, L5): on a page a Delete / Unmark / Remove / Stop button is a QUIET secondary button
   (danger-coloured text on the surface, normal border) so twenty Delete buttons in a list do not carry the loudest colour on the page;
   the filled red button appears only where the person confirms -- inside a dialog, and on the approval decision forms, where
   "Reject" is itself the final decision (a required comment, no second dialog). */
button.danger, .btn.danger { background: var(--surface); border-color: var(--border); color: var(--danger); }
button.danger:hover, .btn.danger:hover { background: var(--danger-bg); border-color: var(--danger); }
dialog button.danger, dialog .btn.danger, #approve button.danger, .approve-page button.danger { background: var(--btn-danger); border-color: var(--btn-danger); color: var(--btn-text); }
button.success, .btn.success { background: var(--btn-success); border-color: var(--btn-success); color: var(--btn-text); }
dialog button.danger:hover, #approve button.danger:hover, .approve-page button.danger:hover, button.success:hover, .btn.success:hover { filter: brightness(.94); }
button[disabled], .btn[aria-disabled=true] { opacity: .55; cursor: not-allowed; box-shadow: none; transform: none; }
button.small { padding: .25rem .6rem; font-size: var(--text-sm); font-weight: 600; }
button.ghost, .btn.ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--link); }
button.ghost:hover, .btn.ghost:hover { background: var(--surface-hover); }
button.link { border: none; background: none; box-shadow: none; color: var(--link); padding: 0; font-weight: 500; text-decoration: underline; cursor: pointer; display: inline; }
button.link:hover { background: none; text-decoration-thickness: 2px; }
.actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: var(--space-2); align-items: center; }

/* ================================================================ forms */
input[type=text], input[type=search], input[type=number], input[type=date], input[type=password], input[type=url], select, textarea {
  font: inherit; font-size: var(--text-base); padding: .45rem .65rem; border: 1px solid var(--input-border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); min-width: 10rem; min-height: 38px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
input:hover, select:hover, textarea:hover { border-color: var(--text); }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid var(--focus); outline-offset: 1px; border-color: var(--focus); }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .9; }
select { appearance: none; -webkit-appearance: none; padding-right: 2.1rem; background-image: var(--ic-chevron); background-repeat: no-repeat; background-position: right .55rem center; background-size: 16px 16px; }
textarea { width: 100%; min-height: 4.5rem; resize: vertical; }
input[type=checkbox], input[type=radio] { width: 18px; height: 18px; accent-color: var(--btn-primary); margin: 0; }
input[type=range] { accent-color: var(--btn-primary); }
fieldset { border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-2) var(--space-2) var(--space-1); margin: 0 0 var(--space-2); background: var(--surface); }
legend { font-weight: 650; padding: 0 .4rem; font-size: var(--text-md); }
.field { margin: .75rem 0; }
.field label, .field .label { display: block; font-weight: 600; margin-bottom: .3rem; font-size: var(--text-base); }
.field .help { display: block; font-size: var(--text-sm); color: var(--muted); margin-top: .3rem; }
.radio { display: flex; gap: .6rem; align-items: start; margin: .5rem 0; padding: .55rem .7rem; border: 1px solid var(--row-divider); border-radius: var(--radius-sm); }
.radio:has(input:checked) { border-color: var(--btn-primary); background: var(--info-bg); }
.radio input { margin-top: .25rem; }
.checkbox { display: flex; gap: .6rem; align-items: center; }
/* Start dialog: services-to-stop checklist (replaces the native multi-select). Rows reuse .radio; the list scrolls past ~8 rows. */
.svc-fieldset { border: 0; padding: 0; margin: .75rem 0; min-width: 0; }
.svc-fieldset > legend { font-weight: 600; margin-bottom: .3rem; font-size: var(--text-base); padding: 0; }
.svc-tools { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; margin-bottom: var(--space-1); }
.svc-tools input[type=search] { flex: 1 1 14rem; min-width: 0; }
.svc-count { font-size: var(--text-sm); color: var(--muted); }
.svc-list { max-height: 21rem; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0 .5rem; background: var(--surface); }
.svc-list .svc-row { margin: .4rem 0; min-height: 44px; align-items: center; }
.svc-list .svc-row[hidden] { display: none; }
.svc-list .svc-row label { display: flex; flex-wrap: wrap; gap: .25rem .5rem; align-items: baseline; margin: 0; font-weight: 400; cursor: pointer; flex: 1; }
.svc-list .svc-name { color: var(--muted); overflow-wrap: anywhere; }
/* Dependency notices under a ticked service (run 01M58WRM, 2026-10-19): the row wraps so the notice sits full-width below the label. */
.svc-list .svc-row { flex-wrap: wrap; }
.svc-list .svc-deps { flex-basis: 100%; }
.svc-list .svc-deps:empty { display: none; }
.svc-list .svc-dep { margin: .3rem 0 .2rem; padding: .5rem .7rem; font-size: var(--text-sm); }
.svc-list .svc-dep .btn { margin-left: .25rem; vertical-align: baseline; }
.svc-deps-note { display: block; margin-top: .5rem; }
.svc-list { max-height: 24rem; }
.svc-empty { margin: .4rem 0; }
/* Start dialog: live confirmation hint */
.help.confirm-ok { color: var(--ok, var(--text)); font-weight: 600; }
.help.confirm-pending { color: var(--muted); }
/* Users page (views_users.js): a group checkbox per role with its one-line help; chip rows in table cells; link buttons sized like button.small */
fieldset.groups .checkbox { align-items: start; margin: .45rem 0; }
fieldset.groups .checkbox input { margin-top: .3rem; flex: none; }
fieldset.groups .checkbox label { font-weight: 600; }
fieldset.groups .checkbox label .sub { font-weight: 400; }
.chips { display: inline-flex; flex-wrap: wrap; gap: .3rem; }
.btn.small { padding: .25rem .6rem; font-size: var(--text-sm); font-weight: 600; }
.actions.wrap { flex-wrap: wrap; }
form > .actions, fieldset .actions { margin-top: var(--space-2); }
/* filter toolbar: a card of fields with the buttons at the end */
.toolbar { display: flex; flex-wrap: wrap; gap: .75rem; align-items: end; margin: 0 0 var(--space-2); padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-1); }
.toolbar label { display: flex; flex-direction: column; font-size: var(--text-xs); font-weight: 600; color: var(--muted); gap: .3rem; text-transform: uppercase; letter-spacing: .04em; }
.toolbar label input, .toolbar label select { text-transform: none; letter-spacing: 0; font-weight: 400; }
.toolbar > button { margin-left: 0; }
/* Audit log pager (views_audit.js): Newer / "Showing a–b of n" / Older; disabled ends are plain muted text. */
.pager { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; margin: 0 0 var(--space-2); font-size: var(--text-sm); }
.pager a { font-weight: 600; }
.theme-form .help { font-size: var(--text-sm); color: var(--muted); margin: .4rem 0 0; }
.theme-form .radio label { font-weight: 600; }
.theme-form .radio label .sub { font-weight: 400; margin: 0; }

/* ================================================================ notices, banners, toasts, loading */
.notice { position: relative; border: 1px solid var(--border); border-left: 4px solid; padding: .8rem 1rem .85rem 1rem; border-radius: var(--radius-md); margin: var(--space-2) 0; background: var(--surface); }
.notice.warn { border-left-color: var(--notice-warn-border); background: var(--warn-bg); border-color: var(--warn-bg); border-left-color: var(--notice-warn-border); }
.notice.fail { border-left-color: var(--notice-fail-border); background: var(--fail-bg); border-color: var(--fail-bg); border-left-color: var(--notice-fail-border); }
.notice.info { border-left-color: var(--notice-info-border); background: var(--info-bg); border-color: var(--info-bg); border-left-color: var(--notice-info-border); }
.notice.ok { border-left-color: var(--notice-ok-border); background: var(--ok-bg); border-color: var(--ok-bg); border-left-color: var(--notice-ok-border); }
.notice h2, .notice h3 { margin: 0 0 .35rem; border: 0; padding: 0; font-size: 1rem; }
.notice > p:last-child, .notice > ul:last-child, .notice > ol:last-child { margin-bottom: 0; }
.notice .actions { margin-top: .75rem; }
.error-banner { border: 1px solid var(--fail-bg); border-left: 4px solid var(--notice-fail-border); background: var(--fail-bg); padding: .7rem 1rem; margin: var(--space-2) 0; border-radius: var(--radius-md); }
/* the CloudFront/WAF rate-rule notice (api.js network_rate_limited, UI review 2026-10-19 H6): a warning, not a failure of the site */
.error-banner.rate-limited { border-color: var(--warn-bg); border-left-color: var(--notice-warn-border); background: var(--warn-bg); color: var(--text); }
/* M14: an ARN's short resource name (full ARN in the title, Copy beside it); a small inline disclosure for a key or code kept out of plain view */
.arn-name { overflow-wrap: anywhere; }
details.inline-details { display: inline-block; vertical-align: baseline; margin: 0; }
details.inline-details > summary { display: inline; cursor: pointer; color: var(--link); font-size: var(--text-sm); }
details.inline-details[open] > summary { display: block; }
details.inline-details > code { display: inline-block; margin-top: .2rem; overflow-wrap: anywhere; }
/* M10: a connection result that a newer failed preflight has overtaken */
.notice .conn-stale { margin: 0 0 .6rem; color: var(--muted); font-size: var(--text-sm); }
/* loading skeleton: the text stays for assistive technology (transparent, not hidden); the bars shimmer */
.loading { position: relative; display: block; color: transparent; font-style: normal; padding: 0; margin: var(--space-2) 0; min-height: 3.6rem; overflow: hidden; user-select: none; }
.loading::before, .loading::after { content: ""; position: absolute; left: 0; height: .9rem; border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--skeleton) 0%, var(--skeleton-shine) 45%, var(--skeleton) 90%); background-size: 300% 100%; animation: shimmer 1.3s linear infinite; }
.loading::before { top: 0; width: 42%; }
.loading::after { top: 1.5rem; width: 78%; height: 1.6rem; }
.skeleton-rows { display: grid; gap: .6rem; margin-top: 3.9rem; }
.skeleton-rows span { display: block; height: 2.2rem; border-radius: var(--radius-sm); background: linear-gradient(90deg, var(--skeleton) 0%, var(--skeleton-shine) 45%, var(--skeleton) 90%); background-size: 300% 100%; animation: shimmer 1.3s linear infinite; }
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
/* toasts: bottom-right stack; each auto-dismisses; status text is always present */
.toasts { position: fixed; right: var(--space-2); bottom: var(--space-2); z-index: 70; display: flex; flex-direction: column; gap: .6rem; max-width: min(26rem, calc(100vw - 2rem)); pointer-events: none; }
.toast { pointer-events: auto; display: flex; align-items: flex-start; gap: .6rem; padding: .7rem .8rem .7rem 1rem; border-radius: var(--radius-md); border: 1px solid var(--border); border-left: 4px solid var(--notice-info-border); background: var(--surface-raised); color: var(--text); box-shadow: var(--shadow-3); font-size: var(--text-base);
  animation: toast-in var(--dur) var(--ease); }
.toast.ok { border-left-color: var(--notice-ok-border); } .toast.warn { border-left-color: var(--notice-warn-border); } .toast.fail { border-left-color: var(--notice-fail-border); }
.toast .toast-text { flex: 1 1 auto; min-width: 0; }
.toast .toast-close { flex: none; width: 28px; height: 28px; padding: 0; border: 0; background: transparent; box-shadow: none; color: var(--muted); border-radius: 50%; position: relative; }
.toast .toast-close::before { content: ""; position: absolute; inset: 6px; background: currentColor; -webkit-mask: var(--ic-close) center / contain no-repeat; mask: var(--ic-close) center / contain no-repeat; }
.toast .toast-close:hover { background: var(--surface-hover); color: var(--text); }
.toast.leaving { opacity: 0; transform: translateY(6px); transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ================================================================ dialog (confirmations) */
dialog.confirm { width: min(34rem, calc(100vw - 2rem)); max-width: 34rem; border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-3) var(--space-3); color: var(--text); background: var(--surface-raised); box-shadow: var(--shadow-3); }
dialog.confirm[open] { animation: dialog-in var(--dur) var(--ease); }
dialog.confirm::backdrop { background: var(--backdrop); backdrop-filter: blur(2px); }
dialog.confirm h2 { margin: 0 0 .75rem; font-size: var(--text-lg); border: 0; padding: 0; }
dialog.confirm p { margin: .5rem 0; }
dialog.confirm .actions { justify-content: flex-end; margin-top: var(--space-3); }
@keyframes dialog-in { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }

/* ================================================================ sign-in */
.signin { position: relative; max-width: 30rem; margin: 10vh auto 0; text-align: center; padding: calc(var(--space-4) + 64px) var(--space-4) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-2); }
/* the product mark: a primary rounded square (::before) with the uplift arrow cut from the button text colour (::after) */
.signin::before { content: ""; position: absolute; left: 50%; top: var(--space-4); width: 56px; height: 56px; margin-left: -28px; border-radius: 16px; background: var(--btn-primary); box-shadow: var(--shadow-2); }
.signin::after { content: ""; position: absolute; left: 50%; top: calc(var(--space-4) + 10px); width: 36px; height: 36px; margin-left: -18px; background: var(--btn-text); -webkit-mask: var(--ic-logo) center / contain no-repeat; mask: var(--ic-logo) center / contain no-repeat; }
.signin h1 { justify-content: center; }
.signin p { color: var(--muted); }
.signin p + p { margin-top: var(--space-2); }
.signin button.primary { min-width: 12rem; padding: .7rem 1.4rem; font-size: 1rem; }
main.signin { margin-left: auto; margin-right: auto; min-height: 0; }

/* ================================================================ run monitor: timeline, Gantt, console, snapshots, output */
ol.timeline { list-style: none; margin: 0; padding: 0; }
ol.timeline > li { padding: .45rem 0 .45rem 1.7rem; position: relative; border-left: 2px solid var(--row-divider); margin-left: .7rem; }
ol.timeline > li::before { content: ""; position: absolute; left: -8px; top: .85rem; width: 12px; height: 12px; border-radius: 50%; background: var(--border); border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border); }
ol.timeline > li.done::before { background: var(--dot-done); box-shadow: 0 0 0 1px var(--dot-done); }
ol.timeline > li.running::before { background: var(--dot-running); box-shadow: 0 0 0 4px var(--running-bg); animation: pulse 1.6s ease-in-out infinite; }
ol.timeline > li.failed::before { background: var(--dot-failed); box-shadow: 0 0 0 1px var(--dot-failed); }
ol.timeline > li.waiting::before { background: var(--dot-waiting); box-shadow: 0 0 0 1px var(--dot-waiting); }
ol.timeline > li.pending { color: var(--muted); }
/* Phase headings are labels on the line, not steps: no dot, small upper-case label. */
ol.timeline > li.stage::before { display: none; }
ol.timeline .stage { font-weight: 700; font-size: var(--text-xs); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-top: .6rem; }
/* Step row (operator ask 2026-10-18): a fixed two-line layout on every row so the list reads evenly. Line 1 = status dot +
   step name (+ the "i" button); line 2 = start time · duration (or "running"), left-aligned under the name in smaller muted
   tabular figures so the times line up row to row. A long name wraps on line 1 only, at its CamelCase joins (<wbr> from
   views.js) or, failing that, anywhere. Never clipped: the operator must be able to read "PreflightInstance", not
   "Preflig...". The dot (li::before, top .85rem) sits on line 1. */
ol.timeline .step { display: flex; flex-direction: column; align-items: flex-start; row-gap: .1rem; }
ol.timeline .step .n { min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: left; font-weight: 500; }
/* Name cell: the step name shrinks and wraps; the "i" (step help) button keeps its size beside it. */
ol.timeline .step .nw { display: flex; min-width: 0; max-width: 100%; align-items: baseline; }
ol.timeline .step .nw .n { flex: 0 1 auto; }
ol.timeline .step .nw button.info, ol.timeline .stage button.info, .gantt .g-stage button.info, .gantt .g-name button.info { flex: none; align-self: center; }
ol.timeline .stage button.info, .gantt .g-stage button.info { font-size: .8rem; text-transform: none; letter-spacing: 0; }
ol.timeline .step .t { display: block; color: var(--muted); font-size: var(--text-xs); line-height: 1.3; text-align: left; font-variant-numeric: tabular-nums; }
ol.timeline a.cur, ol.timeline button.link.cur { font-weight: 700; color: var(--text); text-decoration-color: var(--btn-primary); text-decoration-thickness: 2px; }
ol.timeline button.link { color: var(--link); }
/* Step timing (Gantt) for a successfully completed run: name | track | duration; bars sit at --g-left for --g-width. */
.gantt ol.gantt-rows { list-style: none; margin: 0; padding: 0; }
.gantt .g-row, .gantt .g-axis { display: grid; grid-template-columns: minmax(0, 12em) minmax(0, 1fr) 4.5em; column-gap: .75rem; align-items: center; padding: .25rem 0; }
.gantt .g-row { border-bottom: 1px solid var(--row-divider); }
.gantt .g-row:hover { background: var(--surface-hover); }
.gantt .g-stage { font-weight: 700; font-size: var(--text-xs); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-top: .7rem; padding-bottom: .2rem; }
.gantt .g-name { min-width: 0; display: flex; align-items: center; font-size: .9rem; }
.gantt .g-name .g-nt { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
.gantt .g-dur { color: var(--muted); font-size: var(--text-xs); text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.gantt .g-track { position: relative; height: 16px; background: var(--panel); border-radius: 4px; border: 1px solid var(--row-divider); }
.gantt .g-bar { position: absolute; top: 2px; bottom: 2px; left: var(--g-left, 0%); width: var(--g-width, 0%); min-width: 3px; border-radius: 3px; background: var(--dot-done); box-shadow: var(--shadow-1); }
.gantt .g-bar.failed { background: var(--dot-failed); }
.gantt .g-axis .g-track { background: none; border: 0; height: auto; display: flex; justify-content: space-between; color: var(--muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
@media (max-width: 480px) { .gantt .g-row, .gantt .g-axis { grid-template-columns: minmax(0, 6em) minmax(0, 1fr) 3.5em; } }
/* console output */
pre.out { background: var(--code-bg); color: var(--code-text); padding: .9rem 1rem; border-radius: var(--radius-md); font: 12.5px/1.5 var(--font-mono); overflow: auto; max-height: 420px; white-space: pre-wrap; margin: .5rem 0 0; }
pre.out .w { color: var(--code-warn); } pre.out .f { color: var(--code-fail); } pre.out .k { color: var(--code-key); }
/* Raw SSM messages (run failure banner, finalise warning, every step panel, the approval page) live behind one closed Details disclosure
   (views.js rawSsmDetails); the refusals list reads as prose. */
details.raw-failure { margin: .5rem 0 0; }
details.raw-failure > summary { cursor: pointer; font-size: var(--text-sm); color: var(--link); }
details.raw-failure pre.out { max-height: 260px; margin: .2rem 0 .5rem; }
.notice.step-failure p { margin: 0; }
.run-failure ul.failures { margin: .3rem 0 .5rem 1.2rem; padding: 0; }
.run-failure ul.failures li { margin: .15rem 0; }
/* console frame + scrubber */
.frame { background: var(--frame-bg); border-radius: var(--radius-md); aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; color: var(--code-text); position: relative; overflow: hidden; box-shadow: var(--shadow-1); }
.frame .fake { color: var(--code-text); text-align: center; font-size: .9rem; padding: 1rem; opacity: .8; }
.frame .fake .bar { width: 60%; height: 10px; background: var(--code-bg); margin: .6rem auto 0; border-radius: 5px; overflow: hidden; }
.frame .fake .bar span { display: block; width: 62%; height: 100%; background: var(--dot-running); }
.frame .overlay { position: absolute; left: 0; right: 0; bottom: 0; background: var(--backdrop); color: var(--code-text); font-size: var(--text-xs); padding: .35rem .6rem; display: flex; justify-content: space-between; font-variant-numeric: tabular-nums; }
.frame img { max-width: 100%; max-height: 100%; width: 100%; height: 100%; object-fit: contain; image-rendering: auto; }
/* The card's frame is a button that opens the lightbox (operator ask 2026-10-18: the text in the small frame is unreadable).
   A hover/focus "Enlarge" badge is the affordance; on touch screens it is always shown. Disabled until a frame exists. */
button.frame { padding: 0; border: 0; margin: 0; width: 100%; font: inherit; text-align: inherit; cursor: zoom-in; display: flex; }
button.frame:disabled { cursor: default; }
button.frame:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.frame .fake { display: block; }
.frame .enlarge-hint { position: absolute; top: .5rem; right: .5rem; background: var(--backdrop); color: var(--code-text); font-size: var(--text-xs); padding: .2rem .55rem; border-radius: var(--radius-sm); opacity: 0; transition: opacity var(--dur-fast) var(--ease); pointer-events: none; }
button.frame:not(:disabled):hover .enlarge-hint, button.frame:focus-visible .enlarge-hint { opacity: 1; }
@media (hover: none) { button.frame:not(:disabled) .enlarge-hint { opacity: 1; } }
/* Lightbox: full-viewport dialog on the frame background in every theme; head (title, time · step, close), the stage
   (the image scaled to fit, or at actual size with pan/scroll), the controls (previous, slider, next, counter, Actual size,
   Follow live). display: flex only while open, so the closed dialog stays display: none. */
dialog.lightbox { width: 100vw; height: 100dvh; max-width: 100vw; max-height: 100dvh; margin: 0; padding: 0; border: 0; background: var(--frame-bg); color: var(--code-text); }
dialog.lightbox[open] { display: flex; flex-direction: column; }
dialog.lightbox::backdrop { background: var(--backdrop); }
dialog.lightbox .lb-head { display: flex; align-items: flex-start; justify-content: space-between; gap: .75rem; padding: .6rem .9rem .4rem; }
dialog.lightbox .lb-head h2 { margin: 0; font-size: var(--text-lg); border: 0; padding: 0; color: var(--code-text); }
dialog.lightbox .lb-caption { margin: .15rem 0 0; font-size: var(--text-sm); color: var(--code-text); font-variant-numeric: tabular-nums; }
dialog.lightbox .lb-stage { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; }
dialog.lightbox .lb-stage:focus-visible { outline: 3px solid var(--focus); outline-offset: -3px; }
dialog.lightbox .lb-stage img { margin: auto; max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; image-rendering: auto; display: block; }
dialog.lightbox .lb-stage img.actual { max-width: none; max-height: none; }
dialog.lightbox .lb-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .6rem; padding: .5rem .9rem .7rem; }
dialog.lightbox .lb-controls input[type=range] { flex: 1 1 10rem; min-width: 6rem; }
dialog.lightbox .lb-controls output { font-size: var(--text-sm); font-variant-numeric: tabular-nums; min-width: 7rem; }
dialog.lightbox button { background: var(--code-bg); color: var(--code-text); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .4rem .7rem; min-height: 36px; font: inherit; cursor: pointer; }
dialog.lightbox button:hover:not(:disabled) { border-color: var(--code-text); }
dialog.lightbox button:disabled { opacity: .45; cursor: default; }
dialog.lightbox button:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
dialog.lightbox button[aria-pressed="true"] { background: var(--btn-primary); border-color: var(--btn-primary); color: var(--btn-text); }
dialog.lightbox .lb-prev, dialog.lightbox .lb-next, dialog.lightbox .lb-close { font-size: 1.4rem; line-height: 1; min-width: 44px; min-height: 44px; padding: 0 .6rem; }
dialog.lightbox .lb-follow[hidden] { display: none; }
@media (max-width: 480px) { dialog.lightbox .lb-controls output { flex-basis: 100%; min-width: 0; } }
.scrubber { margin-top: .6rem; }
.scrubber input[type=range] { width: 100%; }
.scrubber .marks { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--muted); }
.scrubber output { display: block; font-size: var(--text-sm); color: var(--muted); margin-top: .2rem; }
.scrubber .new-frames { display: block; font-size: var(--text-sm); margin-top: .3rem; }
.scrubber .new-frames[hidden] { display: none; }
progress { width: 100%; height: 10px; accent-color: var(--btn-primary); border-radius: 5px; overflow: hidden; }
/* Snapshots card: the recovery-point summary first, then one group per interim original with its recovery copy nested under
   it (ul.copies, indented, "Recovery copy of …"), then the consistency line with a Details disclosure. The bar's fill width is
   set from aria-valuenow by script. */
.snapshots .card-sub { margin: -.3rem 0 .6rem; }
.snapshots .rp-summary .rp-line { margin: 0 0 .7rem; padding: .55rem .7rem; border-left: 3px solid var(--dot-done); background: var(--surface); border-radius: var(--radius-sm); line-height: 1.45; }
.snapshots .rp-summary .rp-line .mono { font-variant-numeric: tabular-nums; }
.snapshots .interim-head { font-size: var(--text-xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin: .6rem 0 .1rem; }
.snapshots .interim-head[hidden] { display: none; }
.snapshots-list { list-style: none; margin: .4rem 0 0; padding: 0; }
.snapshots-list li.snapshot { margin: 0 0 .75rem; padding-bottom: .6rem; border-bottom: 1px solid var(--row-divider); }
.snapshots-list ul.copies { list-style: none; margin: .5rem 0 0 .6rem; padding: 0 0 0 .7rem; border-left: 2px solid var(--row-divider); }
.snapshots-list ul.copies:empty { display: none; }
.snapshots-list ul.copies li.snapshot.copy { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.snapshots-list li.snapshot.copy .disk { font-weight: 600; }
.snapshots-list .snapmeta .kind { color: var(--muted); font-style: italic; }
.snapshots .snap-consistency .consistency { margin: .4rem 0 .2rem; line-height: 1.5; }
.snapshots .snap-consistency .consistency .reason { color: var(--muted); }
.snapshots .snap-consistency details.vss-details { margin: .2rem 0 .5rem; }
.snapshots .snap-consistency details.vss-details > summary { cursor: pointer; font-size: var(--text-sm); color: var(--link); }
.snapshots .snap-consistency details.vss-details .jv { margin-top: .4rem; }
.snapshots-list li.snapshot:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.snapshots-list .disk { font-weight: 600; }
.snapshots-list .progress { display: flex; align-items: center; gap: .6rem; margin: .3rem 0; }
.snapbar { flex: 1; height: 10px; background: var(--panel); border: 1px solid var(--row-divider); border-radius: 5px; overflow: hidden; }
.snapbar .fill { display: block; height: 100%; width: 0; background: var(--btn-primary); transition: width .4s ease; }
.snapshots-list .pct { min-width: 3.2rem; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.snapshots-list .snapmeta { font-size: var(--text-sm); color: var(--muted); }
.snapshots-list .snapmeta .id { margin-left: .3rem; }
/* run monitor page header: the run id and the state pill sit with the title; the meta line reads as a definition strip */
.run-monitor > h1 { margin-bottom: .35rem; }
/* run summary card: label row with the live indicator, a responsive key/value grid, the reason as its own clamped block */
.run-summary { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-1); padding: .8rem 1rem .9rem; margin: 0 0 var(--space-2); }
.run-summary .label, .run-reason .label { display: block; font-size: var(--text-xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.run-summary-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .6rem; flex-wrap: wrap; }
.run-summary-head .refresh-run { margin-left: auto; }
.run-summary-head .ended { margin: 0; font-size: var(--text-xs); }
.live-dot { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--text-xs); font-weight: 600; color: var(--info); }
.live-dot::before { content: ""; width: .55rem; height: .55rem; border-radius: 50%; background: var(--dot-running); animation: pulse 1.6s ease-in-out infinite; }
.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: .6rem 1.25rem; margin: 0; }
.kv-grid .kv-item { min-width: 0; }
.kv-grid dt { font-size: var(--text-xs); font-weight: 600; letter-spacing: .03em; text-transform: uppercase; color: var(--muted); margin-bottom: .1rem; }
.kv-grid dd { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: .15rem .4rem; min-width: 0; overflow-wrap: anywhere; }
.kv-grid dd .sub { margin: 0; font-size: var(--text-sm); }
.kv-grid dd .exec { white-space: nowrap; }
.kv-grid dd button.copy { padding: .05rem .45rem; font-size: var(--text-xs); min-height: 0; }
.run-reason { margin-top: .8rem; padding-top: .7rem; border-top: 1px solid var(--row-divider); }
.run-reason .reason-text { margin: .15rem 0 0; white-space: pre-line; overflow-wrap: anywhere; }
.run-reason .reason-text.clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.run-reason button.link { margin-top: .25rem; font-size: var(--text-sm); }
@media (max-width: 480px) { .kv-grid { grid-template-columns: 1fr; } }
.run-monitor details.panel > summary { padding: .2rem 0; }
#approve .actions button { min-width: 11rem; }

/* ================================================================ step help (popover.js): "i" buttons and the anchored, non-modal popover */
button.info { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; padding: 0; margin-left: .3rem; box-shadow: none;
  border: 1px solid var(--border); border-radius: 50%; background: var(--panel); color: var(--link); font: italic 700 .8rem/1 Georgia, "Times New Roman", serif;
  vertical-align: middle; cursor: pointer; }
button.info:hover, button.info[aria-expanded=true] { background: var(--info-bg); border-color: var(--link); transform: none; }
.popover.step-help { position: fixed; left: var(--pop-left, 8px); top: var(--pop-top, 8px); z-index: 50; width: min(22rem, calc(100vw - 16px));
  max-height: calc(100vh - 16px); overflow: auto; background: var(--surface-raised); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-3); padding: .8rem 1rem 1rem; font-size: .92rem; }
.popover.step-help:focus { outline: none; }
.popover.step-help:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.popover.step-help .step-help-head { display: flex; align-items: start; justify-content: space-between; gap: .5rem; }
.popover.step-help h2, .popover.step-help h3 { margin: 0 0 .4rem; font-size: 1rem; word-break: break-word; }
.popover.step-help .step-help-close { flex: none; line-height: 1; padding: .1rem .45rem; }
.popover.step-help dl.step-help-dl { margin: 0; }
.popover.step-help dl.step-help-dl dt { font-weight: 600; margin-top: .55rem; color: var(--muted); font-size: var(--text-sm); }
.popover.step-help dl.step-help-dl dd { margin: .1rem 0 0; white-space: pre-line; }
.popover.step-help p { margin: .3rem 0 0; }
@media (prefers-reduced-motion: no-preference) { .popover.step-help { animation: step-help-in .12s ease-out; } }
@keyframes step-help-in { from { opacity: 0; } to { opacity: 1; } }

/* ================================================================ About page (views_about.js) */
figure.arch { margin: 0; }
/* The diagram is 1600 px wide; above 1000 px it scales to the content width. Below that it keeps a readable minimum width inside a
   region that scrolls sideways (focusable, named: axe scrollable-region-focusable); the page itself never scrolls horizontally. */
figure.arch .arch-scroll { overflow-x: auto; overflow-y: hidden; max-width: 100%; border-radius: var(--radius-md); -webkit-overflow-scrolling: touch; }
figure.arch .arch-scroll:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
figure.arch .arch-img { display: block; width: 100%; height: auto; max-width: 1400px; border: 1px solid var(--border); border-radius: var(--radius-md); background: #fff; box-shadow: var(--shadow-1); }
figure.arch .arch-hint { display: none; margin: .4rem 0 0; }
@media (max-width: 1000px) { figure.arch .arch-img { width: 1100px; max-width: none; } figure.arch .arch-hint { display: block; } }
figure.arch figcaption { font-size: .9rem; color: var(--muted); margin-top: .6rem; }
/* popover-less fallback of the About page's (i) buttons (tests) and the "coming" marker next to work another lane is building */
details.about-inline { margin: .3rem 0 .5rem; } details.about-inline > summary { cursor: pointer; color: var(--muted); }
/* About > The Cenitex logo: the same unaltered PNG on the light plate, at a readable size on every width */
.org-logo-plate { display: block; width: max-content; max-width: 100%; box-sizing: border-box; margin: 0 0 var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--logo-plate); border: 1px solid var(--border); }
.org-logo-plate .org-logo-about { display: block; width: 260px; max-width: 100%; height: auto; }
ol.flows { columns: 2; column-gap: 2rem; margin: .4rem 0 0; padding-left: 1.4rem; }
ol.flows li { break-inside: avoid; margin-bottom: .25rem; color: var(--text); }
@media (max-width: 900px) { ol.flows { columns: 1; } }

/* ================================================================ motion: short transitions only; none when the person asked for none */
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; } }

/* ================================================================================================================
   Responsive (phone 375 px, tablet 768 px; desktop unchanged). Breakpoints: 1000 px (grids, above; the sidebar folds into
   the Menu button), 700 px (compact header, touch-size controls, 16 px form text), 1280 px (data tables become stacked
   label/value cards). Markup support lives in app.js (menu button, table wrapper + data-label) and index.html (the button).
   Everything else is this section. Rules are scoped to the breakpoint so wide screens render exactly as before.
   ================================================================================================================ */
/* -- all widths: long tokens (instance ids, ARNs, S3 keys, UNC paths, step names) may break anywhere instead of
      forcing a wider page; media never exceeds its box; nothing in <main> can push the page wider than the viewport. */
main { overflow-wrap: anywhere; }
main .mono, main code, main .breadcrumb, main h1, main .kv td, main .chip, pre.out { overflow-wrap: anywhere; word-break: break-word; }
pre.out { max-width: 100%; overflow-wrap: anywhere; word-break: break-word; }
.frame { max-width: 100%; }
.frame img { max-width: 100%; }
.scrubber input[type=range] { max-width: 100%; min-width: 0; }
img, svg, video, canvas { max-width: 100%; }
/* a fieldset's default min-inline-size is min-content and a grid item's is auto: a long <option> or token could widen them past the
   viewport; the control shrinks to its box instead */
fieldset, .grid > *, .field, .toolbar label { min-width: 0; }
select { max-width: 100%; }
/* data tables: a focusable, labelled scroll region (app.js wraps every non-.kv table). Scrolls only when it must. */
.table-scroll { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }
.table-scroll:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.table-scroll > table { min-width: 100%; }
/* ONE shared responsive-table pattern (UI review 2026-10-19, H2/H3/M13): at >= 1280 px a table shares its region's width and never
   scrolls sideways -- words wrap at spaces, and long tokens (run ids, ARNs, hashes, user agents: .mono / code / .sub) may break
   anywhere, so no single cell can force the table wider than its card; the one exception is an EC2 instance id (.iid, below), which
   stays on one line. Below 1280 px every table is the stacked-card layout (table.stack-wide, added by app.js enhanceTables). No
   per-page width rules.
   Regression 2026-10-20 (operator screenshot, the Runbooks table at desktop width: headers "De/fau/lt" and "La/te/st", versions
   "v1/2", the Type chip "Auto/mati/on", names split "...CheckUpgradeNotS/tarted"): overflow-wrap: anywhere on EVERY cell let the
   auto table layout treat each cell's minimum width as one character, so the two prose columns took the width and every other
   column was squeezed to a few characters. A plain cell now uses break-word: a word still breaks when it alone is wider than its
   column (so no sideways scroll), but the column is never sized narrower than its longest word. A header never breaks inside a
   word. Short tokens that must stay whole carry .nowrap (a version, a target type); a hyphenated name carries .hy and breaks at
   its hyphens only (views.js hyName puts a <wbr> after each "-"). Measured by scripts/a11y_check.js (midWord). */
.table-scroll > table td { overflow-wrap: break-word; word-break: normal; }
.table-scroll > table th { overflow-wrap: normal; word-break: normal; }
.table-scroll > table .mono, .table-scroll > table code { white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
.table-scroll > table .nowrap, .table-scroll > table .mono.nowrap, .table-scroll > table td .sub.nowrap { white-space: nowrap; overflow-wrap: normal; word-break: normal; }
.hy, .table-scroll > table .mono.hy { white-space: normal; overflow-wrap: break-word; word-break: normal; }
/* one date-time format site-wide (M5, views.js fmt/when): "19 Oct 16:49" on one line; the full timestamp with the time zone is the title */
.when, .table-scroll > table .when { white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Audit log (H3): the source address and browser string live in a closed disclosure under the detail; the user agent may break anywhere */
.audit-from { margin-top: .3rem; font-size: var(--text-sm); }
.audit-from > summary { cursor: pointer; color: var(--muted); }
.audit-from .audit-ua { overflow-wrap: anywhere; word-break: break-word; }
/* Runs (H2): the People cell -- "started: op" over "decided: alice (Approve)" */
.people-started, .people-decided { display: inline-block; }
/* the primary nav button exists only in the compact header */
header.top .nav-toggle { display: none; }

/* -- desktop: a table is as tall as its rows and the PAGE scrolls (M6, UI review 2026-10-19: the old 72vh / 960 px height cap
      clipped row 7 of Servers and showed 8 of 50 runs with no cue). The sticky column header still works while the table is taller
      than its region only when the region scrolls, so it is simply not sticky any more; the caption and headers are at the top. */

/* -- below the sidebar breakpoint: the nav folds into a Menu sheet under the top bar (tablet and phone) */
@media (max-width: 999px) {
  header.top { flex-wrap: wrap; gap: .4rem .6rem; padding: 0 .75rem; height: auto; min-height: var(--topbar-h); align-items: center; }
  header.top .brand { min-width: 0; flex: 1 1 auto; font-size: 1rem; }
  header.top .brand .logo { width: 28px; height: 28px; }
  header.top .brand .org-logo { height: 30px; padding: 0 6px; }
  header.top .brand .org-logo img { height: 18px; }
  header.top .nav-toggle { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; order: -1; min-width: 44px; min-height: 44px; padding: .4rem .7rem; box-shadow: none; }
  #nav { position: static; display: none; order: 10; flex: 1 1 100%; width: auto; flex-direction: column; gap: 2px; border-right: 0; border-top: 1px solid var(--header-border); padding: .5rem 0 .6rem; margin: 0; background: transparent; overflow: visible; }
  header.top.nav-open nav { display: flex; }
  header.top.nav-open #nav { display: flex; }   /* the id selector above (#nav { display: none }) outranks the class rule */
  #nav a { color: var(--header-text); min-height: 44px; padding: .4rem .6rem; }
  #nav a::before { background: var(--header-muted); }
  #nav a:hover { background: var(--header-chip-bg); }
  #nav a[aria-current=page] { background: var(--header-chip-bg); color: var(--header-text); font-weight: 600; }
  #nav a:hover::before, #nav a[aria-current=page]::before { background: var(--header-text); }
  #nav a[aria-current=page]::after { display: none; }
  #nav #nav-about { margin-top: 0; }
  #nav .nav-group-toggle { color: var(--header-text); min-height: 44px; padding: .4rem .6rem; }
  #nav .nav-group-toggle::before, #nav .nav-group-toggle::after { background: var(--header-muted); }
  #nav .nav-group-toggle:hover { background: var(--header-chip-bg); }
  #nav .nav-group[data-current] > .nav-group-toggle { color: var(--header-text); }
  #nav .nav-group[data-current] > .nav-group-toggle::before { background: var(--header-text); }
  #nav .nav-group-items { border-left-color: var(--header-border); }
  #nav .nav-group-items a { min-height: 44px; font-size: var(--text-base); }
  header.top .env { display: none; }                                   /* environment, account and Region: on the About page */
  header.top .account { flex: 0 1 auto; min-width: 0; max-width: 46vw; }
  header.top .who { flex: 0 1 auto; min-width: 0; max-width: 100%; font-size: var(--text-sm); min-height: 44px; }
  header.top .account-menu { min-width: 14rem; max-width: calc(100vw - 1rem); }   /* anchor side and exact max-width are set by placeAccountMenu() from the chip's position */
  header.top #signout { min-height: 44px; padding: .4rem .5rem; }
  main { margin-left: 0; max-width: var(--content-max); padding: var(--space-2) var(--space-2) var(--space-4); }
  footer.note { margin-left: 0; padding: var(--space-2); }
  .w24 { min-width: 0; width: 100%; max-width: 24rem; }
  input[type=text], input[type=search], input[type=number], input[type=date], select, textarea { max-width: 100%; }
  .toolbar label { min-width: 0; }
}

/* -- compact header and touch targets (phones and small tablets) */
@media (max-width: 700px) {
  header.top .account { max-width: 44vw; }
  header.top .who { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  header.top .who::before { display: none; }
  header.top .brand-text { overflow: hidden; text-overflow: ellipsis; }
  /* with the organisation logo beside it, the app name may take two lines on a narrow phone instead of being cut */
  header.top.org .brand-text { min-width: 0; flex: 1 1 auto; white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.15; font-size: .95rem; }
  header.top.org .brand { flex: 1 1 0; min-width: 180px; }   /* zero basis + a floor: stays on the Menu button's row (the user chip wraps below), the text wraps inside it */
  header.top button.link::before { display: none; }
  main { padding: .75rem; }
  footer.note { margin: 1.5rem 0 0; }
  h1 { font-size: 1.3rem; }
  .page-head { gap: .75rem; }
  .page-head .page-actions { width: 100%; justify-content: stretch; }
  .page-head .page-actions > * { flex: 1 1 auto; }
  /* controls: at least 44 px tall (WCAG 2.5.8 / Apple HIG); 16 px form text stops iOS zooming the page on focus */
  button:not(.info):not(.link):not(.toast-close), .btn, input[type=text], input[type=search], input[type=number], input[type=date], select, textarea { min-height: 44px; }
  .chip { white-space: normal; }
  input[type=text], input[type=search], input[type=number], input[type=date], select, textarea { font-size: 16px; min-width: 0; width: 100%; }
  textarea { min-height: 5.5rem; }
  button.small { min-height: 44px; padding: .4rem .8rem; font-size: .95rem; }
  .actions { gap: .5rem; }
  .actions > button, .actions > .btn { flex: 1 1 10rem; text-align: center; }
  .toolbar { flex-direction: column; align-items: stretch; gap: .5rem; padding: .75rem; }
  .toolbar label { width: 100%; }
  .toolbar > button { width: 100%; }
  /* inline text buttons and tiny "i" buttons keep their size in the text flow but gain a 44 px hit area */
  button.link, button.info { position: relative; }
  button.link::before, button.info::before { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: 44px; transform: translate(-50%, -50%); background: none; -webkit-mask: none; mask: none; }
  ol.timeline .step button.link { display: inline-flex; align-items: center; min-height: 44px; text-align: left; }
  ol.timeline .step button.link::before { content: none; }
  ol.timeline > li { padding-top: .15rem; padding-bottom: .15rem; }
  ol.timeline .step .t { align-self: flex-start; }
  .row-actions a { display: inline-block; padding: .55rem .25rem; margin-right: .4rem; }
  .scrubber input[type=range] { height: 44px; }
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
  .kpi { padding: .6rem 2.1rem .55rem .9rem; }
  .kpi .l button.info { top: .5rem; right: .5rem; }
  .kpi .n { font-size: 1.4rem; }
  .panel { padding: .8rem .85rem; }
  /* run monitor: the console image, slider and output fit the width; approve/reject are full-width, in reach of a thumb */
  .grid.monitor { gap: .75rem; }
  .run-monitor > .panel.mt1 { margin-top: .75rem; }   /* the cards below the grid (Gantt, Evidence, audit) keep the grid's gap at this width too */
  pre.out { max-height: 60vh; font-size: 12px; }
  #approve .actions { flex-direction: column; }
  #approve .actions button { flex: 0 0 auto; width: 100%; min-height: 48px; font-size: 1rem; }
  .frame .overlay { font-size: .72rem; flex-wrap: wrap; gap: .2rem .5rem; }
  /* dialogs and popovers fit the screen */
  dialog.confirm { width: calc(100vw - 1.5rem); max-width: calc(100vw - 1.5rem); max-height: calc(100vh - 1.5rem); overflow: auto; padding: 1rem 1rem 1.1rem; margin: auto; }
  dialog.confirm .actions { flex-direction: column-reverse; }
  dialog.confirm .actions button { flex: 0 0 auto; width: 100%; }
  .popover.step-help { width: calc(100vw - 16px); max-height: calc(100vh - 16px); }
  .signin { margin: 2rem auto; padding: var(--space-3) var(--space-2); }
  .toasts { right: .75rem; left: .75rem; bottom: .75rem; max-width: none; }
  figure.arch .arch-img { width: 1100px; max-width: none; }   /* scrolls inside .arch-scroll (About page, 2026-10-19) */
  .run-summary { padding: .7rem .8rem; }
}

/* -- ONE stacked-card layout for EVERY data table (UI review 2026-10-19, M13/H2/H3; before, only the Servers and Compatibility
      tables stacked below 1280 px and everything else became a horizontal scroller at 768 px). app.js enhanceTables adds
      table.stack-wide to every table it wraps (not .kv, not .no-stack); the views keep building plain tables. Below 1280 px the
      <thead> is kept for assistive technology (visually hidden), each row is a card and each cell is labelled from its column header
      (data-label, set by app.js); tablets get two or three cards per row, phones one. A table with .no-stack keeps its columns and
      scrolls inside its region. Instance ids never break (.iid, every selector repeats the wrapping rules' specificity so it wins;
      operator steer 2026-10-19, ~1500 px: "i-" / "0237cdc31d135e03" / "8"): the Servers rows, the detail/start heading and
      breadcrumb, the run page's Server row, the recovery dialog; the stacked cards keep the id on one line and only truncate with an
      ellipsis where it truly cannot fit (no tested width down to 320 px needs it). */
main .iid, main .mono.iid, main h1 .iid, main .breadcrumb .iid, main .kv td .iid, .table-scroll > table .iid, .servers-table .iid, .table-scroll > table.servers-table .iid,
.grid > .panel .table-scroll > table .iid, dialog .iid, .kv-grid dd .iid { white-space: nowrap; overflow-wrap: normal; word-break: normal; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
.table-scroll > table th, .table-scroll > table td { padding-left: .6rem; padding-right: .6rem; }
.table-scroll > table td .sub { overflow-wrap: anywhere; }
/* the Servers table keeps a floor on its Instance column so the id fits on one line; its ids and names may wrap (steer 2026-10-18) */
.servers-table .mono, .table-scroll > table.servers-table .mono { white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
.servers-table td:first-child, .servers-table th:first-child { min-width: 12.5rem; }
/* M4 (UI review 2026-10-19): the Status cell is one chip; a chip never breaks inside a word, and the column keeps room for the longest state */
.servers-table .chip { white-space: nowrap; }
.servers-table td:nth-child(6), .servers-table th:nth-child(6) { min-width: 7.5rem; }
.servers-table td:nth-child(3), .servers-table th:nth-child(3) { white-space: nowrap; }   /* "m5.large" / "Type" never split (the table shares width with the Status chip) */
/* the long "Already on / Upgraded -- on <platform>" status chip wraps and keeps its pill shape */
.chip.target { white-space: normal; text-align: left; line-height: 1.35; padding-top: .15rem; padding-bottom: .15rem; max-width: 100%; }
/* a chip inside a table cell may wrap between its words (a long outcome phrase on a narrow column must not force a horizontal scroll)
   but never inside a word ("Auto/mati/on", operator screenshot 2026-10-20): its longest word is the floor of its column */
.table-scroll > table .chip { white-space: normal; overflow-wrap: normal; word-break: normal; text-align: left; max-width: 100%; }
@media (max-width: 1279px) {
  .table-scroll:has(> table.stack-wide) { border: 0; background: transparent; box-shadow: none; max-height: none; overflow: visible; }
  main table.stack-wide { display: block; border: 0; font-size: .95rem; }
  main table.stack-wide > caption { display: block; }
  main table.stack-wide > thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  main table.stack-wide > tbody { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); gap: .75rem; }
  main table.stack-wide > tbody > tr, main table.stack-wide > tbody > tr > td { display: block; }
  main table.stack-wide > tbody > tr { border: 1px solid var(--border); border-radius: var(--radius-md); padding: .2rem .85rem; margin: 0; background: var(--surface); box-shadow: var(--shadow-1); }
  main table.stack-wide > tbody > tr:hover td { background: transparent; }
  main table.stack-wide > tbody > tr > td { border: 0; border-top: 1px solid var(--row-divider); padding: .5rem 0 .55rem; overflow-wrap: anywhere; word-break: break-word; }
  main table.stack-wide > tbody > tr > td:first-child { border-top: 0; }
  main table.stack-wide > tbody > tr > td:empty { display: none; }
  main table.stack-wide > tbody > tr > td[data-label]::before { content: attr(data-label); display: block; color: var(--muted); font-size: .74rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; margin-bottom: .2rem; }
  main table.stack-wide > tbody > tr > td > br { display: none; }
  main table.stack-wide > tbody > tr > td .sub { display: block; }
  main table.stack-wide > tbody > tr > td .row-actions { display: flex; flex-wrap: wrap; gap: .25rem .75rem; }
  main table.stack-wide > tbody > tr > td .row-actions a { display: inline-flex; align-items: center; min-height: 44px; margin: 0; }
  main table.stack-wide td .empty { padding: var(--space-2) 0; }
  /* a table nested in another table's card (jsonView lists) is plain rows inside the card, not cards in a card */
  main table.stack-wide > tbody > tr > td table.stack-wide > tbody { display: block; }
  main table.stack-wide > tbody > tr > td table.stack-wide > tbody > tr { border: 0; border-radius: 0; box-shadow: none; padding: 0 0 .4rem; margin: 0 0 .4rem; border-bottom: 1px solid var(--row-divider); }
}
@media (max-width: 599px) {
  main table.stack-wide > tbody { display: block; }
  main table.stack-wide > tbody > tr { margin: 0 0 .6rem; }
  .table-scroll { border: 0; background: transparent; box-shadow: none; }
  /* key/value tables: a narrower label column */
  .kv th { width: 36%; }
  .grid.two, .grid.three { gap: .75rem; }
}
/* The same stacked layout by CONTAINER width: a table whose own card is narrow (a JSON list nested inside a run step panel, a table in
   the run page's side column) can never fit as columns however wide the window is, so below 36 rem of region width it stacks too
   (the region is an inline-size container; the rules mirror the 1279 px block above). */
.table-scroll { container-type: inline-size; }
@container (max-width: 36rem) {
  table.stack-wide { display: block; border: 0; font-size: .95rem; }
  table.stack-wide > caption { display: block; }
  table.stack-wide > thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  table.stack-wide > tbody, table.stack-wide > tbody > tr, table.stack-wide > tbody > tr > td { display: block; }
  table.stack-wide > tbody > tr { border: 0; border-bottom: 1px solid var(--border); border-radius: 0; padding: .2rem .5rem; margin: 0; background: transparent; box-shadow: none; }
  table.stack-wide > tbody > tr:last-child { border-bottom: 0; }
  table.stack-wide > tbody > tr:hover td { background: transparent; }
  table.stack-wide > tbody > tr > td { border: 0; border-top: 1px solid var(--row-divider); padding: .4rem 0 .45rem; overflow-wrap: anywhere; word-break: break-word; }
  table.stack-wide > tbody > tr > td:first-child { border-top: 0; }
  table.stack-wide > tbody > tr > td:empty { display: none; }
  table.stack-wide > tbody > tr > td[data-label]::before { content: attr(data-label); display: block; color: var(--muted); font-size: .74rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; margin-bottom: .2rem; }
  table.stack-wide > tbody > tr > td > br { display: none; }
  table.stack-wide > tbody > tr > td .sub { display: block; }
}

/* ---- focused approval page (/#/approve/<runId>, 2026-10-17): two large buttons, stacked and full-width on phones */
.approve-page .approve-actions { gap: 1rem; }
.approve-page button.big { font-size: 1.15rem; padding: .9rem 1.6rem; min-width: 14rem; min-height: 3.25rem; }
.approve-page .countdown { font-variant-numeric: tabular-nums; }
.approve-page .approve-evidence img.frame { width: 100%; height: auto; aspect-ratio: auto; display: block; }
.approve-page .approve-decide { border-left: 4px solid var(--warn); }
@media (max-width: 600px) {
  .approve-page .grid.two { grid-template-columns: 1fr; }
  .approve-page .approve-actions { flex-direction: column; align-items: stretch; }
  .approve-page button.big { width: 100%; min-width: 0; }
}

/* Snapshot consistency line and services-stopped panel on the run page (VSS / ServicesToStop, 2026-10-17). Tokens only. */
.consistency { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: .5rem 0 0; }
.consistency .help { display: block; flex-basis: 100%; font-size: var(--text-sm); color: var(--muted); }
.service-outage table td:first-child { white-space: nowrap; }
select[multiple] { min-height: 6.5rem; }

/* Chat page (spa/views_chat.js, 2026-10-18): a transcript log, the composer and the safe-Markdown reply body. Tokens only. The log
   scrolls on its own (max-height) so the composer stays in reach; messages are cards with a role header; the assistant's Markdown
   gets compact spacing; starter questions are small buttons that wrap. Under 600 px the composer row stacks. */
.chat-panel { display: flex; flex-direction: column; gap: var(--space-2); }
.chat-log { display: flex; flex-direction: column; gap: var(--space-2); max-height: 60vh; min-height: 8rem; overflow: auto; padding: .25rem .25rem .25rem 0; scroll-behavior: smooth; }
.chat-msg { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); padding: .7rem .9rem; max-width: 56rem; }
.chat-msg.me { background: var(--panel); align-self: flex-end; border-color: var(--row-divider); }
.chat-msg.bot { align-self: flex-start; }
.chat-msg.pending { color: var(--muted); display: flex; align-items: center; gap: .4rem; }
.chat-msg-head { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin-bottom: .35rem; font-size: var(--text-sm); color: var(--muted); }
.chat-who { font-weight: 700; color: var(--text); }
.chat-msg-head .chat-copy { margin-left: auto; }
.chat-msg p { margin: 0 0 .5rem; } .chat-msg p:last-child, .chat-msg .md > :last-child { margin-bottom: 0; }
.chat-msg .md ul, .chat-msg .md ol { margin: .25rem 0 .6rem 1.25rem; padding: 0; } .chat-msg .md li { margin: .15rem 0; }
.chat-msg .md .md-h { font-size: var(--text-md); margin: .6rem 0 .3rem; }
.chat-msg .md blockquote { margin: .4rem 0; padding: .2rem .8rem; border-left: 3px solid var(--border); color: var(--muted); }
.chat-msg .md pre.out { margin: .4rem 0; max-height: 300px; }
.chat-msg .md table.md-table { margin: .4rem 0; font-size: var(--text-sm); }
.chat-msg .md hr { border: 0; border-top: 1px solid var(--row-divider); margin: .6rem 0; }
.chat-sources { margin: .5rem 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.chat-sources a.chip { text-decoration: none; }
.chat-meta { margin: .35rem 0 0; font-size: var(--text-xs); }
.chat-starters { display: flex; flex-wrap: wrap; gap: .45rem; }
.chat-starter { white-space: normal; text-align: left; }
.chat-empty { color: var(--muted); padding: .5rem 0; }
.chat-form textarea { margin-bottom: .5rem; }
.chat-form-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.chat-form-row .sub { flex: 1 1 14rem; }
.ask-about { margin-left: auto; font-size: var(--text-sm); }
@media (max-width: 600px) {
  .chat-msg { max-width: 100%; }
  .chat-log { max-height: 50vh; }
  .chat-form-row button { flex: 1 1 8rem; }
}

/* JSON view (spa/jsonview.js, 2026-10-18): a toolbar (what is shown, the Raw JSON toggle, Copy) over a readable body (table / key-value
   list / tag chips) or the raw pretty-printed JSON. Tokens only. Nested tables inside a .kv cell get the data-table card treatment from
   app.js (.table-scroll + .stack-wide), so a list of objects becomes stacked label/value cards below 1280 px; the key/value list (.jv-kv) is
   two columns at every width. Inside a table cell the toolbar is compact. */
/* position: relative on the view and on its scroll regions: the visually-hidden full ARN (.sr-only is position: absolute) sits at the
   end of a possibly wide table row, and must be placed relative to the clipped region, not the page (found at 768 px: the page scrolled) */
.jv { min-width: 0; max-width: 100%; position: relative; }
.jv .table-scroll { position: relative; }
.jv-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .6rem; margin: 0 0 .35rem; }
.jv-bar .jv-kind { margin: 0; font-size: var(--text-xs); }
.jv-bar .jv-toggle, .jv-bar .jv-copy { min-height: 0; padding: .1rem .5rem; font-size: var(--text-xs); border: 1px solid var(--border); border-radius: var(--radius-full); color: var(--link); background: var(--surface); }
.jv-bar .jv-toggle:hover, .jv-bar .jv-copy:hover { background: var(--surface-hover); }
.jv-bar .jv-toggle[aria-pressed="true"], .jv-bar .jv-toggle[aria-pressed="true"]:hover { background: var(--btn-primary); color: var(--btn-text); border-color: var(--btn-primary); }
.jv-bar .jv-toggle[aria-pressed="true"]:hover { background: var(--btn-primary-hover); }

.jv-body { min-width: 0; }
.jv-raw { margin: 0; max-height: 480px; }
/* key/value rows (a <dl> of .jv-row): a two-column grid whose key column sizes to its content up to 40 % of the width (a long key wraps at
   its <wbr> joins and never runs into the value), a fixed 1 rem gap, and ONE full-width separator per row (the row carries the border, not
   the cells, so the lines align; the first row has none). Rows are subgrids so every row shares the same key column; browsers without
   subgrid size each row's key column on its own. Under 480 px the key sits above its value. */
.jv-kv { display: grid; grid-template-columns: fit-content(40%) minmax(0, 1fr); column-gap: 1rem; margin: 0; }
.jv-kv > .jv-row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; column-gap: 1rem; align-items: start; padding: .4rem 0; border-top: 1px solid var(--row-divider); }
.jv-kv > .jv-row:first-child { border-top: 0; padding-top: 0; }
.jv-kv > .jv-row:last-child { padding-bottom: 0; }
.jv-kv > .jv-row > dt { color: var(--muted); font-weight: 500; font-size: var(--text-sm); min-width: 0; overflow-wrap: anywhere; word-break: break-word; padding-top: .1rem; }
.jv-kv > .jv-row > dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.jv-kv > .jv-row > dd > .jv-nest > summary { padding-top: 0; }
@supports not (grid-template-columns: subgrid) { .jv-kv > .jv-row { grid-template-columns: fit-content(40%) minmax(0, 1fr); } }
.jv-table { font-size: var(--text-sm); }
.jv-table th, .jv-table td { vertical-align: top; }
/* cells keep the data-table wrapping rules (.table-scroll: words wrap at spaces, tokens never break mid-word) so a column is never one
   character wide; under 600 px the table is stacked cards and the .stack rules let tokens wrap */
.jv-nest { margin: 0; }
.jv-nest > summary { font-weight: 500; color: var(--muted); font-size: var(--text-sm); padding: .1rem 0; }
.jv-nest[open] > summary { margin-bottom: .3rem; }
.jv-tags { display: inline-flex; flex-wrap: wrap; gap: .25rem; vertical-align: middle; }
.jv-tags .tag { margin: 0; }
/* EC2 tags on the server page: key / value rows, the non-OSUplift ones behind a small Details (UI review 2026-10-19) */
.tags-kv { display: grid; grid-template-columns: max-content 1fr; gap: .15rem .75rem; margin: 0; font-size: var(--text-sm); }
.tags-kv dt { color: var(--muted); font-weight: 500; overflow-wrap: anywhere; }
.tags-kv dd { margin: 0; overflow-wrap: anywhere; }
.tags-more { margin-top: .35rem; }
.tags-more > summary { font-size: var(--text-sm); font-weight: 500; color: var(--muted); }
.tags-more[open] > summary { margin-bottom: .25rem; }
.jv-arn { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .3rem; max-width: 100%; }
.jv-arn .jv-copy { min-height: 0; padding: .05rem .45rem; font-size: var(--text-xs); }
.jv-num { font-variant-numeric: tabular-nums; }
.jv-time { white-space: normal; }
.jv-list { overflow-wrap: anywhere; }
.jv-decoded > .sub { margin: 0; font-size: var(--text-xs); }
.jv-more { margin: .4rem 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.jv-more .sub { margin: 0; font-size: var(--text-sm); }
td .jv .jv-bar { margin-bottom: .25rem; }
details.prestate > summary { font-weight: 600; }
details.prestate[open] > summary { margin-bottom: .5rem; }
@media (max-width: 700px) {
  /* the toolbar buttons take the page's 44 px touch-target rule; only the type and the time wrap change here */
  .jv-bar .jv-toggle, .jv-bar .jv-copy, .jv-arn .jv-copy { padding: .3rem .8rem; font-size: var(--text-sm); }

  .jv-raw { max-height: 60vh; }
}
@media (max-width: 480px) { .jv-kv, .jv-kv > .jv-row { grid-template-columns: minmax(0, 1fr); } .jv-kv > .jv-row { row-gap: .15rem; } }

/* Compatibility page (views_compat.js, 2026-10-19): source/evidence link lists, suggestion cards with quoted evidence, compact row actions */
.src-list, .evidence { margin: 0; padding-left: 1.1rem; }
.src-list li, .evidence li { margin: .1rem 0; }
.src-link { overflow-wrap: anywhere; }   /* host/…/tail labels may exceed a 375 px viewport (a11y sweep 2026-10-19): let them break rather than scroll the page */

/* -- the catalog table (views_compat.js, .catalog-table.stack-wide; operator screenshot 2026-10-19 03:58Z: URLs broke every few characters,
      Versions far wider than its content, dates wrapping mid-value, Actions cut off at ~1500 px). TEN columns that must fit >= 1280 px with
      no horizontal scroll (2026-10-20 live check: the deployed 2022 table with its real entries -- a 15-character SQL Server version, AI-overridden
      origin chips, an e-mail inside a note -- measured 1222 px in a 1096 px region with twelve, so Match now rides under the pattern as a chip and
      the vendor-page checked date rides under the Sources list): 1 Product pattern (+ WS target chip + match chip), 2 Publisher, 3 Applies to,
      4 Versions, 5 Verdict, 6 Origin, 7 Set by, 8 Sources (+ checked date), 9 Notes, 10 Actions/Enabled. The narrow columns (Applies to, Versions,
      Set by, Actions) shrink-wrap; Verdict and Origin chips wrap at spaces; Product pattern, Publisher, Sources and Notes take the remaining
      width; a version breaks only at its dots, an e-mail / URL after the @ and at its dots, prose by word (softBreaks <wbr>). Sources are one short domain link per line. Notes
      show two clamped lines until opened. The last column sticks to the right edge as a safety net should the region ever scroll.
      Below 1280 px the rows become the stacked cards of .stack-wide (the same rules as the Servers table). */
.catalog-table { table-layout: auto; font-size: .92rem; }
/* the catalog filter bar (views_compat.js viewCatalog): selects and the search box on one row, wrapping on narrow screens; Apply aligned with the fields */
.catalog-filters { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-2); align-items: flex-end; margin: 0 0 var(--space-2); }
.catalog-filters .field { margin: 0; }
.catalog-filters .field input[type="search"] { min-width: 16rem; }
/* -- target switch (views_compat.js targetSwitch, 2026-10-20): one small button per Windows Server target; the current one is the primary button
   and carries aria-current="page". Shown on the Fleet / Catalog / Research pages and inside the server page's compatibility panel. */
.target-switch { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; margin: 0 0 var(--space-2); }
.target-switch .btn[aria-current="page"] { pointer-events: none; }
.chip.target-chip { font-weight: normal; }
.catalog-filters .actions { margin: 0; display: flex; gap: .5rem; align-items: center; }
.catalog-table th, .catalog-table td { padding-left: .3rem; padding-right: .3rem; vertical-align: top; }
/* shrink-wrapped, never wrapping: Applies to (3), Set by (7), Actions/Enabled (10) */
.catalog-table td:nth-child(3), .catalog-table td:nth-child(7), .catalog-table td:nth-child(10),
.catalog-table th:nth-child(3), .catalog-table th:nth-child(7), .catalog-table th:nth-child(10) { width: 1%; white-space: nowrap; }
/* shrink-wrapped but allowed to wrap at spaces: Verdict and Origin chips */
.catalog-table td:nth-child(5), .catalog-table td:nth-child(6), .catalog-table th:nth-child(5), .catalog-table th:nth-child(6) { width: 1%; white-space: normal; }
/* Versions (4): "6.0 – 6.0.3.1221" breaks at the dash first and, only when the row is squeezed, at a dot (softBreaks <wbr>), never inside a
   number. Not shrink-wrapped: it keeps a floor and shares the leftover width, so a version is whole whenever there is room. */
.catalog-table td:nth-child(4), .catalog-table th:nth-child(4) { white-space: normal; min-width: 6.5rem; }
.catalog-table td:nth-child(4) .mono { white-space: normal; overflow-wrap: normal; word-break: keep-all; }
.catalog-table td:nth-child(5), .catalog-table td:nth-child(6) { min-width: 5.25rem; overflow-wrap: normal; word-break: normal; }
.catalog-table td:nth-child(5) .chip, .catalog-table td:nth-child(6) .chip { white-space: normal; overflow-wrap: normal; word-break: normal; text-align: left; line-height: 1.35; padding-top: .15rem; padding-bottom: .15rem; }
/* Set by (7): the account breaks only after its @ and before its dots (views_compat.js softBreaks puts a <wbr> there), the date never */
.catalog-table td:nth-child(7) { white-space: normal; overflow-wrap: normal; word-break: normal; min-width: 6.5rem; }
.catalog-table td:nth-child(7) .sub { white-space: nowrap; display: block; }
/* the match-kind chip under the pattern (views_compat.js matchChip) and the checked date under the sources (checkedLine): one line each */
.chip.match-chip { font-weight: normal; }
.catalog-table .src-checked { display: block; margin-top: .2rem; white-space: nowrap; }
/* Notes (9): prose breaks by word; an e-mail address, URL or version inside it breaks after the @ and before its dots (softBreaks <wbr>), never
   elsewhere, so a long token no longer widens the column (live 2022 catalog, 2026-10-20) */
.catalog-table td:nth-child(9) { max-width: 12rem; min-width: 8rem; overflow-wrap: normal; word-break: normal; }
/* 2026-10-20 (a11y table-fit census at 1280: content 1173 px in a 1096 px region): a domain link wraps only at the <wbr> before each dot that
   views_compat.js sourceLink("domain") inserts (keep-all: no other break inside the host), and "Applies to" may wrap at its space. */
.catalog-table .src-link { white-space: normal; overflow-wrap: normal; word-break: keep-all; }
.catalog-table .src-list { padding-left: .9rem; }
.catalog-table .src-list li { white-space: normal; overflow-wrap: normal; word-break: keep-all; }
.catalog-table td:nth-child(3), .catalog-table th:nth-child(3) { white-space: normal; }
.catalog-table details.notes > summary { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; cursor: pointer; }
.catalog-table details.notes[open] > summary { display: list-item; -webkit-line-clamp: none; }
.catalog-table details.notes[open] > p { margin: .3rem 0 0; }
.catalog-table .row-actions { display: flex; flex-direction: column; align-items: flex-start; gap: .25rem; }   /* Edit / Disable / Delete stacked: a narrow Actions column */
.table-scroll > table.catalog-table th:last-child, .table-scroll > table.catalog-table td:last-child { position: sticky; right: 0; background: var(--surface); box-shadow: -6px 0 6px -6px rgba(0, 0, 0, .25); }
.table-scroll > table.catalog-table tbody tr:hover td:last-child { background: var(--surface-hover); }
@media (min-width: 1280px) and (max-width: 1439px) { .catalog-table { font-size: .88rem; } .catalog-table td:nth-child(9) { max-width: 10rem; } }   /* the narrow desktop band: ten columns in ~1096 px */
@media (max-width: 1279px) {
  main table.catalog-table.stack-wide > tbody > tr > td, main table.catalog-table.stack-wide > tbody > tr > td .src-link, main table.catalog-table.stack-wide .src-list li { width: auto; min-width: 0; white-space: normal; position: static; box-shadow: none; }
  main table.catalog-table.stack-wide > tbody > tr > td:nth-child(9) { max-width: none; }
  main table.catalog-table.stack-wide .src-checked { white-space: normal; }
  main table.catalog-table.stack-wide .row-actions { flex-direction: row; flex-wrap: wrap; gap: .25rem .75rem; }
}
}
.suggestion { margin-top: var(--space-2); }
.suggestion h3 { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.suggestion h4 { margin: .6rem 0 .3rem; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.evidence blockquote { margin: 0 0 .2rem; padding: .3rem .7rem; border-left: 3px solid var(--border); background: var(--panel); border-radius: var(--radius-sm); }
.research-cell { display: inline-flex; flex-direction: column; gap: .25rem; align-items: flex-start; }
.row-actions { display: inline-flex; flex-wrap: wrap; gap: .3rem; }
.catalog-form, .settings-form, .override-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-1) var(--space-2); align-items: start; }
.catalog-form .field.checkbox, .catalog-form .actions, .settings-form .field.checkbox, .settings-form .sub, .settings-form .actions, .override-form .sub, .override-form .actions, .override-form .notice, .override-form .field:has(textarea) { grid-column: 1 / -1; }
.override-wrap { margin-top: var(--space-2); padding: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-alt, var(--surface)); }
.override-form textarea { width: 100%; }
details.notes summary { cursor: pointer; }

/* Readiness additions (2026-10-19): Setup compatibility scan details and the software inventory filters (views_readiness.js). */
.inv-kinds { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: var(--space-1) 0; }
.inv-kinds .btn[aria-pressed="true"] { background: var(--btn-primary); color: var(--btn-text); border-color: var(--btn-primary); }
.raw-xml { max-height: 24rem; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--text-sm); }
.compat-raw h3 { font-size: var(--text-md); margin: var(--space-1) 0 .25rem; }

/* ---- RDS compatibility assessment panel (views_assessment.js) ---- */
#assessment .assessment-cat details > summary { cursor: pointer; list-style: revert; }
#assessment .assessment-cat details > summary h3 { display: inline; margin: 0; font-size: 1rem; }
#assessment .assessment-table .wrap, #assessment .assessment-options .wrap { overflow-wrap: anywhere; white-space: normal; }
#assessment .assessment-table td:nth-child(4) { min-width: 18rem; }
#assessment .assessment-table .chip { margin-right: .25rem; }
#assessment .grid.three > section.panel { margin-top: 0; }

/* ================================================================ Admin page layout (operator screenshot 2026-10-19, ~1550 px)
   The cards (Media sources, Defaults, Appearance, RDS licence rates, AI research settings) flow down two columns and each
   is as tall as its own content: CSS multi-column with break-inside: avoid, so no card is stretched to the height of its
   neighbour and no row leaves a card two-thirds empty. One spacing token (--space-3) between columns and between cards.
   One column at <= 1024 px (the sidebar is a rail there); the phone rules below 700 px are the shared responsive ones.
   Sibling sections keep their own content; only their card spacing comes from here. */
.admin-sections { columns: 2; column-gap: var(--space-3); column-fill: balance; }
.admin-sections > .panel { break-inside: avoid; page-break-inside: avoid; -webkit-column-break-inside: avoid; margin: 0 0 var(--space-3); display: block; width: 100%; box-sizing: border-box; }
.admin-sections > .panel:focus { outline: none; }                     /* focus moved by the section index (tabindex=-1): no ring for a click */
.admin-sections > .panel:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.admin-sections .panel h2 .info { margin-left: .2rem; }
.admin-audit-pointer { margin-top: 0; }
/* the media table needs the page width for its seven columns: its card spans both columns at the top; the other cards flow below it */
.admin-sections > #admin-media { column-span: all; }
.admin-sections > #admin-failed-services { column-span: all; }   /* six columns of service / reason / source text: the full width, like Media sources (2026-10-20) */
/* ---- Failed services (warn only; views_failedsvc.js, 2026-10-20): the run / server page panel per phase, the collapsed "ignored" block and
   the Admin > Failed services to ignore card. Tokens only (css_tokens.test.js); every value from the server is a text node. Service
   names carry .mono.nowrap (the table-scroll convention): a Windows service name never breaks mid-word. */
.failedsvc-panel .failedsvc-phase { margin-top: var(--space-3); padding-top: var(--space-2); border-top: 1px solid var(--row-divider); }
.failedsvc-panel .failedsvc-phase:first-of-type { border-top: 0; padding-top: 0; }
.failedsvc-panel .failedsvc-phase h3 { margin: 0 0 .4rem; font-size: var(--text-md); display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.failedsvc-panel table.failedsvc td { font-size: var(--text-sm); vertical-align: top; }
.failedsvc-panel details.failedsvc-ignored, .failedsvc-panel details.failedsvc-raw { margin-top: .5rem; }
.failedsvc-panel details.failedsvc-ignored summary, .failedsvc-panel details.failedsvc-raw summary { cursor: pointer; color: var(--link); font-size: var(--text-sm); }
.failedsvc-panel .warn-text { color: var(--warn); font-size: var(--text-sm); }
.admin-section form.failedsvc-add { margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--row-divider); }
.admin-section table.admin-failedsvc td { font-size: var(--text-sm); vertical-align: top; }
.admin-section table.admin-failedsvc .chip, .admin-section table.admin-failedsvc button { white-space: nowrap; }
@media (max-width: 1024px) { .admin-sections { columns: 1; } }
/* section index: anchor links under the page header, one row that wraps */
.section-index { margin: calc(-1 * var(--space-1)) 0 var(--space-3); }
.section-index ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .4rem .5rem; }
.section-index li { margin: 0; }
.section-index a { display: inline-flex; align-items: center; min-height: 32px; padding: .2rem .75rem; border: 1px solid var(--border); border-radius: var(--radius-full); background: var(--surface); color: var(--link); font-size: var(--text-sm); font-weight: 600; text-decoration: none; box-shadow: var(--shadow-1); }
.section-index a:hover { background: var(--surface-hover); border-color: var(--input-border); text-decoration: underline; }
/* inputs follow their content, up to the card width: numbers ~12ch, ids/short codes ~24ch, text fields the card width up to 40rem */
input.w-num { width: 12ch; min-width: 0; max-width: 100%; }
input.w-id, select.w-id { width: 24ch; min-width: 0; max-width: 100%; }
input.w-text, textarea.w-text { width: 100%; min-width: 0; max-width: 40rem; display: block; }
input.mono { font-family: var(--font-mono); font-size: .92rem; }
.field-row { display: flex; flex-wrap: wrap; gap: 0 var(--space-3); }
.field-row > .field { flex: 0 1 auto; min-width: 0; }
/* Runbooks table (views_docs.js, .docs-table; operator screenshot 2026-10-20): the four short columns -- Type, Default, Latest,
   Last updated -- take only the width of their content, so the Name (breaks at its hyphens, .hy), the Description and the Used by
   columns share the rest and wrap by words. The "newer than default" chip sits under its version on its own line. Below 1280 px the
   shared stacked-card layout applies (a stacked cell is a block), so the column widths only exist at column widths. */
@media (min-width: 1280px) {
  .table-scroll > table.docs-table th:nth-child(2), .table-scroll > table.docs-table td:nth-child(2), .table-scroll > table.docs-table th:nth-child(3), .table-scroll > table.docs-table td:nth-child(3),
  .table-scroll > table.docs-table th:nth-child(4), .table-scroll > table.docs-table td:nth-child(4), .table-scroll > table.docs-table th:nth-child(5), .table-scroll > table.docs-table td:nth-child(5) { width: 1%; white-space: nowrap; }
  .table-scroll > table.docs-table td:nth-child(6) { min-width: 14rem; }
}
.table-scroll > table.docs-table .chip { white-space: nowrap; }
.wrap-any { overflow-wrap: anywhere; }
/* media table: follows the shared responsive-table pattern (app.js enhanceTables adds .stack-wide; stacked cards below 1280 px). Two
   exceptions at the pattern's own specificity, like .iid: the media id and the elided SHA stay on one line (UI review M9: the id was
   squeezed to one character per line), and the headers never break mid-word ("SHA-" / "256" read as two words). The URI may wrap. */
.table-scroll > table.admin-media .media-id, .table-scroll > table.admin-media .nowrap { white-space: nowrap; overflow-wrap: normal; word-break: normal; }
.table-scroll > table.admin-media th { white-space: nowrap; }
table.admin-media th { white-space: nowrap; }
table.admin-media td { vertical-align: middle; }
table.admin-media .copy { margin-left: .3rem; }
table.admin-media .sub { display: inline; }
.media-register { margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--row-divider); }
.media-register h3 { margin-top: 0; }
.media-register[hidden] { display: none; }
/* Required services as chips: the stored comma-separated string is edited chip by chip */
.svc-chips { display: block; }
.svc-chip-list { list-style: none; margin: 0 0 .4rem; padding: 0; display: flex; flex-wrap: wrap; gap: .35rem .4rem; }
.svc-chip { display: inline-flex; align-items: center; gap: .15rem; margin: 0; padding: .15rem .35rem .15rem .65rem; border: 1px solid var(--border); border-radius: var(--radius-full); background: var(--panel); font-size: var(--text-sm); font-weight: 600; line-height: 1.5; max-width: 100%; }
.svc-chip .svc-chip-name { font-family: var(--font-mono); font-weight: 600; overflow-wrap: anywhere; }
.svc-chips.readonly .svc-chip { padding-right: .65rem; }
.svc-chip.empty-chip { border-style: dashed; background: transparent; font-weight: 500; }
.svc-chip .svc-remove { width: 1.5rem; height: 1.5rem; padding: 0; margin: 0; border: 0; border-radius: 50%; background: transparent; box-shadow: none; color: var(--muted); font-size: 1rem; line-height: 1; }
.svc-chip .svc-remove:hover { background: var(--fail-bg); color: var(--fail); }
.svc-add-row { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.svc-add-row input.svc-add { width: 24ch; min-width: 0; max-width: 100%; }
@media (max-width: 700px) {   /* the shared phone rules (above) make every input full width; these size classes follow */
  .field-row { display: block; }
  input.w-num, input.w-id, select.w-id, .svc-add-row input.svc-add { width: 100%; }
  .svc-add-row { display: block; } .svc-add-row .svc-add-btn { margin-top: .4rem; width: 100%; }
  .svc-chip .svc-remove { width: 2rem; height: 2rem; }
}

/* ---- Run pages, outcomes and run links (UI review 2026-10-19, findings A: H1 H4 H5 M1 M2 M3 M7 M11 M12 L2 L8 L9 L12) ----
   .run-link: the one run link ("Readiness, ready with warnings · 19 Oct 16:49"; the id in the title); words wrap at spaces, never inside
   a word. .run-id: the id row of a detail page (elided in the middle, Copy beside it). .step-data: the decoded step data closed by
   default. .step-results td: a long RDSMIG_RESULT message wraps inside its column instead of scrolling the card sideways. */
a.run-link { overflow-wrap: normal; word-break: normal; white-space: normal; }
.table-scroll > table a.run-link { white-space: normal; }
.run-id { display: inline-flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
details.step-data > summary { cursor: pointer; font-weight: 600; }
details.step-data h3 { font-size: var(--text-sm); color: var(--muted); margin: .75rem 0 .35rem; }
.step-results .table-scroll > table { table-layout: fixed; width: 100%; min-width: 0; }
.step-results .table-scroll > table th:nth-child(1), .step-results .table-scroll > table td:nth-child(1) { width: 11.5rem; }
.step-results .table-scroll > table th:nth-child(2), .step-results .table-scroll > table td:nth-child(2) { width: 4.5rem; }
.step-results .table-scroll > table td, .step-results .table-scroll > table td .pre-inline, .step-results .table-scroll > table td .jv { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
@media (max-width: 1279px) { .step-results .table-scroll > table th:nth-child(1), .step-results .table-scroll > table td:nth-child(1), .step-results .table-scroll > table th:nth-child(2), .step-results .table-scroll > table td:nth-child(2) { width: auto; } }
.monitor-wide { margin-top: var(--space-2); min-width: 0; }   /* the full-width row under the monitor grid (M12: the Step results table) */
.monitor-wide:empty { display: none; margin: 0; }
details.raw-output > summary { cursor: pointer; font-weight: 600; margin-top: .5rem; }
details.raw-output pre.out { margin-top: .4rem; }
.recovery-panel.pending { opacity: .85; }
.recovery-panel .skeleton-rows { margin: .5rem 0; }
.readiness-checks h2 .chip { margin-left: .4rem; vertical-align: middle; }
.run-monitor .breadcrumb .run-link { font-weight: 500; }

/* -- Evidence section (run page, it_6f4f6ae4): the file table keeps sizes, times and the per-row Download button on one line;
      a long file name wraps at its slashes instead of pushing the table wider. Stacked-card layout below 1280 px as every table.
      The card is section#evidence.panel.mt1.run-evidence: NOT .evidence, which is the compatibility page's quote-list class
      (its margin: 0 / padding-left rule removed this card's gap and shifted its edge, operator screenshot 2026-10-20). The card's
      own box comes from .panel and .mt1 like every other run-page card; nothing here sets its margin or padding. */
.run-evidence .ev-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 0.75rem; }
.run-evidence .ev-table td:nth-child(3), .run-evidence .ev-table td:nth-child(4), .run-evidence .ev-table td:nth-child(6), .run-evidence .ev-table button { white-space: nowrap; }
.run-evidence .ev-name { overflow-wrap: anywhere; }
.run-evidence .ev-status:empty { display: none; }
.run-evidence .ev-status.ok { color: var(--ok, inherit); }
.run-evidence .ev-status.fail { color: var(--fail, inherit); }

/* ---- Account page (self-service, 2026-10-19, it_85ca89bc) ---- */
.account-sections { display: grid; gap: var(--space-2); max-width: 64rem; }
.account-sections .panel h3 { font-size: 1rem; margin: 0 0 .5rem; }
.account-sections .notice h3 { font-size: 1rem; margin: 0 0 .4rem; }
.policy-wrap .label { font-weight: 600; margin: 0 0 .3rem; font-size: var(--text-base); }
ul.policy { margin: 0 0 var(--space-2); padding-left: 1.3rem; color: var(--muted); font-size: var(--text-sm); }
ul.policy li { margin: .15rem 0; }
.secret-key { font-size: 1.25rem; letter-spacing: .12em; overflow-wrap: anywhere; user-select: all; padding: .6rem .8rem; border: 1px dashed var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); display: inline-block; max-width: 100%; }
#mfa-otpauth, .mono.wrap { overflow-wrap: anywhere; white-space: normal; font-size: var(--text-sm); }
.checkbox.locked label { color: var(--muted); }
.checkbox .help { display: block; flex-basis: 100%; font-size: var(--text-sm); color: var(--muted); margin-top: .2rem; }
.checkbox { flex-wrap: wrap; }
.form-messages:empty { display: none; }
table.sign-ins td, table.sign-ins th { font-size: var(--text-sm); }
@media (max-width: 600px) { .secret-key { font-size: 1.05rem; letter-spacing: .08em; } }

/* ================================================================ Compliance dashboard (views_compliance.js, 2026-10-19)
   Tiles: the status colour is an accent bar; the words on the tile carry the meaning. Charts are inline SVG: the status classes
   (.s-mainstream / .s-extended / .s-out-of-support / .s-unknown) take the theme's ok / warn / fail / muted colours, so the three
   themes and the dark top bar need no chart-specific palette; every bar and point also carries its number as text and a data table
   sits under each chart. No animation anywhere on the page. */
.compliance .kpi .n a { color: inherit; text-decoration: none; } .compliance .kpi .n a:hover, .compliance .kpi .n a:focus-visible { text-decoration: underline; }
.compliance .kpi[data-tile=mainstream]::before { background: var(--ok); } .compliance .kpi[data-tile=extended]::before { background: var(--warn); }
.compliance .kpi[data-tile=out]::before { background: var(--fail); } .compliance .kpi[data-tile=losing6]::before, .compliance .kpi[data-tile=losing12]::before { background: var(--info); }
.compliance .kpi[data-tile=unknown]::before { background: var(--muted); }
.compliance .kpi .kpi-note { font-size: var(--text-xs); color: var(--muted); margin-top: .25rem; }
.compliance .asof { margin-bottom: var(--space-2); }
.compliance .compliance-section { margin-top: var(--space-3); }
.compliance .charts.two { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-3); align-items: start; }
.compliance figure.chart-figure { margin: 0 0 var(--space-3); padding: .75rem 1rem 1rem; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-1); }
.compliance figure.chart-figure figcaption h3 { margin: 0 0 .5rem; font-size: var(--text-md); }
.compliance svg.chart { display: block; width: 100%; height: auto; max-height: 360px; font-family: inherit; }
.compliance svg.chart text { fill: var(--text); font-size: 12px; } .compliance svg.chart text.lbl { fill: var(--muted); font-size: 12px; } .compliance svg.chart text.val { font-weight: 700; font-variant-numeric: tabular-nums; }
.compliance svg.chart text.on-mark { fill: var(--btn-text); font-size: 11px; }
.compliance svg.chart line.axis { stroke: var(--border); stroke-width: 2; } .compliance svg.chart line.grid { stroke: var(--row-divider); stroke-width: 1; }
.compliance svg.chart rect.bar.s-plain { fill: var(--btn-primary); }
.compliance .s-mainstream { fill: var(--ok); stroke: var(--ok); } .compliance .s-extended { fill: var(--warn); stroke: var(--warn); }
.compliance .s-out-of-support { fill: var(--fail); stroke: var(--fail); } .compliance .s-unknown { fill: var(--muted); stroke: var(--muted); }
.compliance svg.chart polyline.line { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.compliance svg.chart polyline.line.s-extended { stroke-dasharray: 6 3; } .compliance svg.chart polyline.line.s-out-of-support { stroke-dasharray: 2 3; } .compliance svg.chart polyline.line.s-unknown { stroke-dasharray: 10 4; }
.compliance svg.chart g.mark circle { stroke: var(--surface); stroke-width: 1.5; }
.compliance ul.legend { list-style: none; display: flex; flex-wrap: wrap; gap: .4rem 1rem; padding: 0; margin: .5rem 0 0; font-size: var(--text-sm); color: var(--muted); }
.compliance ul.legend li { display: inline-flex; align-items: center; gap: .4rem; }
.compliance .swatch { display: inline-block; width: .9rem; height: .9rem; border-radius: 3px; background: currentColor; }
.compliance .swatch.s-mainstream { background: var(--ok); } .compliance .swatch.s-extended { background: var(--warn); } .compliance .swatch.s-out-of-support { background: var(--fail); } .compliance .swatch.s-unknown { background: var(--muted); }
.compliance details.chart-data { margin-top: .5rem; } .compliance details.chart-data summary { cursor: pointer; color: var(--link); font-size: var(--text-sm); }
.compliance table.chart-table { font-size: var(--text-sm); margin-top: .5rem; }
.compliance table.compliance-table td, .compliance table.lifecycle-table td { font-size: var(--text-sm); }
/* "Recovery snapshots kept" (it_54bf0b88): a row past its keep-until day is tinted with the theme's fail background and marked at its left edge;
   the words in its Status cell carry the meaning, the tint only draws the eye. The tiles reuse .kpi; the stage note wraps under the count. */
.compliance table.recovery-table td { font-size: var(--text-sm); vertical-align: top; }
.compliance table.recovery-table tr.past > td { background: var(--fail-bg); }
.compliance table.recovery-table tr.past > td:first-child { box-shadow: inset 4px 0 0 var(--fail); }
.compliance .recovery .kpi[data-tile=past]::before { background: var(--fail); } .compliance .recovery .kpi[data-tile=oldest]::before { background: var(--info); }
.compliance .recovery .kpi[data-tile=count]::before, .compliance .recovery .kpi[data-tile=size]::before, .compliance .recovery .kpi[data-tile=servers]::before { background: var(--muted); }
.compliance .recovery .empty-note { color: var(--muted); margin: var(--space-2) 0; }
@media (max-width: 1279px) { main .compliance table.recovery-table.stack-wide > tbody > tr.past { background: var(--fail-bg); border-left: 4px solid var(--fail); } main .compliance table.recovery-table.stack-wide > tbody > tr.past > td { background: transparent; box-shadow: none; } }
/* the Origin chip ("seed") and the Edit control (an a.btn) are single words: never break them mid-word on a wide screen (main .chip allows anywhere-wrap) */
.compliance table.lifecycle-table .chip, .compliance table.lifecycle-table button, .compliance table.lifecycle-table .btn, .compliance table.lifecycle-table th { white-space: nowrap; overflow-wrap: normal; word-break: normal; }
.compliance .vc-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; } .compliance .vc-head h2 { margin: 0; }
.compliance ul.vc-diff { margin: 0; padding-left: 1rem; font-size: var(--text-sm); } .compliance .vc-pick { display: inline-flex; align-items: center; gap: .4rem; }
.compliance .edit-form .field { display: flex; flex-direction: column; gap: .25rem; margin: .5rem 0; max-width: 32rem; }
.compliance .edit-form input, .compliance .edit-form textarea { font: inherit; padding: .4rem .6rem; border: 1px solid var(--input-border, var(--border)); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); }
.btn.small { padding: .25rem .6rem; font-size: var(--text-sm); }
@media (max-width: 700px) { .compliance .kpis { grid-template-columns: repeat(2, 1fr); } .compliance .kpi .n { font-size: var(--text-xl); } }
