/* =============================================================================
   teahubery hub — hub.css
   The SIGNED-IN lineage: /login, /change-password and the /hub console. ARB-03
   option A gave this build two design lineages, and this is the second one. The
   public landing keeps the thesis and Tufte look in landing.css; nothing here is
   loaded by / or /privacy, and nothing there is loaded by a Function page.

   THIS IS THE GOVERNED HUB DESIGN SYSTEM, NOT A NEW ONE.
   The component vocabulary is taken, name for name, from the estate's proven hub
   stylesheets so a reader who knows one hub knows this one:
     81-projects/260716-vtas/site/assets/site.css      (tokens, shell, top bar, footer)
     81-projects/260716-vtas/site/assets/auth.css      (card, field, btn, msg, badge)
     81-projects/260716-vtas/site/assets/hub-ui.css    (hub-empty, hub-section, hub-meta)
     81-projects/260624-eastaustralis/site/assets/auth.css
                                                       (tabs, stat, dash-panel, data-table,
                                                        toolbar, form-grid, detail-grid)
   Those hubs split the same vocabulary across site.css + auth.css + hub-ui.css
   because they carry a public marketing site in the same stylesheet family. This
   project does not: its public pages are a separate lineage, so the whole hub
   vocabulary is one self-contained file. That is a packaging difference, not a
   fork of the design system. DO NOT add a second component that does what one of
   the families below already does; extend the family with a modifier instead.

   Four deliberate adaptations to teahubery, each with its reason:
   1. --accent is the teahubery maroon (#800000 / #e0897a dark) already used by
      landing.css and quotes.teahubery.com, so the console reads as part of this
      estate rather than as vtas. No new hue is invented; the ground stays the
      neutral near-white of the governed console, because a dense table needs a
      flat ground and the parchment belongs to the reading surfaces.
   2. --sans is Lato, the estate's UI face, already vendored in /assets/fonts/ and
      declared by fonts.css. One sans family, self-hosted, no external fetch.
   3. --raised / --sunk tokens replace the reference files' per-component dark
      overrides, so every colour is defined once in the token block and the
      explicit data-theme toggle and the system preference both work. The
      reference files put those backgrounds inside a prefers-color-scheme block,
      which an explicit toggle cannot reach.
   4. --warn / --warn-tint are added because the design floor requires an input
      WARNING state and a staleness signal, and the reference files define only
      --ok and --danger.

   Design floor (wrap-rl-website-design.md + Part C of the detail doc):
     - spacing is the 4px scale only (--space-1..8); no ad-hoc pixel gaps
     - five type sizes, largest 24px (--text-xl), which is the dense-console cap
     - grid: the documented auto-fit variant of 12/8/4. .dash-grid at
       minmax(14rem, 1fr) resolves to 4 tracks at 1024px+, 2 at 768px and 1 at
       375px; .form-grid and .dash-cols do the same at their own minimums. A
       parallel 12-column utility would duplicate them, so there is none.
     - every interactive component carries default / hover / active / disabled /
       loading, and every input carries focus / error / warning
     - contrast: --muted 4.8:1, --accent 10.9:1, --ok 5.0:1, --danger 6.5:1,
       --warn 6.3:1 on the light ground, all at or above 4.5:1; the dark values
       are 7:1 or better on the dark ground
     - visible focus ring on everything focusable, never removed without a
       replacement ring on the same element
   ========================================================================== */

/* ---- 1. TOKENS: light is the default ------------------------------------- */
:root {
  /* palette */
  --ground: #ffffff;
  --raised: #ffffff;          /* cards and panels: flat on light, lifted on dark */
  --sunk:   #f5f5f6;          /* monospace chips, code, pre */
  --ink:    #18181b;
  --muted:  #6b7280;
  --line:   #e6e6e9;

  /* accent: the teahubery maroon, the same hue the public surfaces use */
  --accent:        #800000;   /* links, active tab, focus border */
  --accent-strong: #5e0000;   /* hover / emphasis */
  --accent-solid:  #800000;   /* solid fill: primary button, brand mark */
  --accent-tint:   #f3e3e3;   /* faint wash: focus ring, hover row */
  --on-accent:     #fdf7ef;   /* text on the solid fill */

  /* functional colours, used for meaning only. --ok is a shade darker than the
     reference files' #1a7f37, which measured 4.48:1 against its own tint band and so
     failed the floor by a rounding place; this reads 5.3:1 there and 6.0:1 on the
     page ground. */
  --ok:         #15722f;
  --ok-tint:    #e7f4ea;
  --warn:       #8a5300;
  --warn-tint:  #fbf0dd;
  --danger:     #b42318;
  --danger-tint:#fdecea;

  /* type scale: five steps, 24px cap on the console */
  --text-xs:   0.75rem;   /* 12px — table headers, chips, meta, eyebrows */
  --text-sm:   0.875rem;  /* 14px — table cells, labels, buttons, secondary text */
  --text-base: 1rem;      /* 16px — body, inputs */
  --text-lg:   1.25rem;   /* 20px — card and section titles */
  --text-xl:   1.5rem;    /* 24px — page titles, stat figures */

  /* spacing: 4px base, nothing off the scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-8: 3rem;

  --measure:     40rem;                /* capped measure for prose and auth cards */
  --app-measure: min(94vw, 80rem);     /* data views */
  --shadow-sm:   0 1px 2px rgba(24, 24, 27, 0.06);
  --shadow-md:   0 2px 10px rgba(24, 24, 27, 0.08);

  --sans: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Consolas, Menlo, monospace;
}

/* ---- dark: by explicit toggle (theme-boot.js sets data-theme) ------------ */
:root[data-theme="dark"] {
  --ground: #0f0f11;
  --raised: #141416;
  --sunk:   #1a1a1d;
  --ink:    #f4f4f5;
  --muted:  #9b9ba3;
  --line:   #2a262b;

  --accent:        #e0897a;
  --accent-strong: #f0b1a4;
  --accent-solid:  #e0897a;
  --accent-tint:   #38231f;
  --on-accent:     #1a1113;

  --ok:         #5bbf6a;
  --ok-tint:    #16281a;
  --warn:       #e0b168;
  --warn-tint:  #2c2211;
  --danger:     #f0a39a;
  --danger-tint:#3a1714;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.34);
  --shadow-md: 0 2px 12px rgba(0, 0, 0, 0.44);
}

/* ---- dark: by system preference, unless a light choice is stored --------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) {
    --ground: #0f0f11;
    --raised: #141416;
    --sunk:   #1a1a1d;
    --ink:    #f4f4f5;
    --muted:  #9b9ba3;
    --line:   #2a262b;

    --accent:        #e0897a;
    --accent-strong: #f0b1a4;
    --accent-solid:  #e0897a;
    --accent-tint:   #38231f;
    --on-accent:     #1a1113;

    --ok:         #5bbf6a;
    --ok-tint:    #16281a;
    --warn:       #e0b168;
    --warn-tint:  #2c2211;
    --danger:     #f0a39a;
    --danger-tint:#3a1714;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.34);
    --shadow-md: 0 2px 12px rgba(0, 0, 0, 0.44);
  }
}

/* ---- 2. RESET + BASE ---------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
html, body { margin: 0; padding: 0; }

body {
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--ground);
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-strong); }
a.link { color: var(--accent); }
a.link:hover { color: var(--accent-strong); }

/* One keyboard focus ring for the whole console. Inputs add their own box-shadow
   ring below; nothing removes an outline without replacing it. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--accent-solid); color: var(--on-accent);
  font-size: var(--text-sm); font-weight: 700; text-decoration: none;
  border-radius: 0 0 6px 0;
}
.skip-link:focus { left: 0; top: 0; color: var(--on-accent); }

h1, h2, h3 { letter-spacing: -0.025em; line-height: 1.15; }

code, .mono {
  font-family: var(--mono);
  font-size: var(--text-sm);
  background: var(--sunk);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: var(--space-1) var(--space-2);
}
pre.mono { display: block; overflow-x: auto; white-space: pre; padding: var(--space-3); }

/* ---- 3. TOP BAR --------------------------------------------------------- */
.masthead {
  position: sticky; top: 0; z-index: 40;
  border-bottom: 1px solid var(--line);
  background: var(--ground);
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(8px);
  backdrop-filter: saturate(180%) blur(8px);
}
.topbar-inner {
  width: 100%; max-width: var(--app-measure); margin-inline: auto; min-width: 0;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); padding: var(--space-3) clamp(1rem, 4vw, 2rem);
}
.topbar-left  { display: flex; align-items: center; gap: var(--space-4); min-width: 0; }
/* flex: 0 1 auto and min-width: 0, NOT flex: none. The signed-in email lives in here and had no
   size rule of its own, so at 375px a 28-character address pushed the whole top bar to 406px and
   the console overflowed horizontally. A flex item will not shrink below its content unless both
   of these are set. Found by measuring the deployed console at 375px, not by reading the markup. */
.topbar-utils { display: flex; align-items: center; gap: var(--space-1); flex: 0 1 auto; min-width: 0; }
.est-who { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.est-who-email {
  min-width: 0;
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brand {
  display: inline-flex; align-items: center; gap: var(--space-2);
  text-decoration: none; color: var(--ink); min-width: 0;
}
.brand-mark {
  display: inline-grid; place-items: center; width: 1.75rem; height: 1.75rem; flex: none;
  background: var(--accent-solid); color: var(--on-accent);
  border-radius: 6px; font-weight: 700; font-size: var(--text-sm);
}
.brand-word { font-weight: 700; font-size: var(--text-sm); white-space: nowrap; }
.brand-word em { color: var(--muted); font-style: normal; font-weight: 400; }

.topnav { display: flex; align-items: center; gap: var(--space-1); flex-wrap: wrap; justify-content: flex-end; }
.topnav a, .topnav-btn {
  font: inherit; font-size: var(--text-sm); font-weight: 400; line-height: 1;
  color: var(--ink); background: transparent; border: 0; text-decoration: none;
  cursor: pointer; padding: var(--space-2); border-radius: 6px; white-space: nowrap;
}
.topnav a:hover, .topnav-btn:hover { color: var(--accent-strong); background: var(--accent-tint); }
.topnav a:active, .topnav-btn:active { background: var(--accent-tint); filter: brightness(0.97); }
.topnav a.is-active { color: var(--accent); font-weight: 700; background: var(--accent-tint); }
.topnav-btn:disabled { opacity: 0.5; cursor: default; background: transparent; }
.topnav form { display: contents; }

/* Icon-only control. Every one carries a title and an aria-label; the design
   floor allows SVG icons only, sized to the 24px line height. */
/* INLINE ICONS. Every icon on this console is an inline SVG on a 24 viewBox: hub.js builds them
   with icon(paths, cls), defaulting to .est-ico-sm, and the shell inlines three more. An inline
   SVG with no width, no height and no CSS size has no intrinsic dimensions to honour, so it
   stretches to whatever box it lands in and paints with the default black fill. Until this rule
   existed the console rendered metre-high black arrowheads in the freshness panel and black
   discs in the surfaces table, and the page overflowed horizontally at 375px because of them.
   Found by screenshotting the deployed console rather than by reading the markup.

   The base rule sizes anything unclassed to the 24px the design floor asks for; .est-ico-sm is
   the 16px inline variant that sits in a line of text. .iconbtn svg keeps its own size and wins
   on specificity. */
svg {
  width: 1.5rem;
  height: 1.5rem;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.est-ico-sm {
  width: 1rem;
  height: 1rem;
  vertical-align: -0.15em;
}

/* The key-file input. The file picker itself is hidden and driven by its label, which is styled
   as a button so it matches every other control on the page. The input must stay in the layout
   rather than display:none, or a keyboard user cannot reach it. */
.est-key-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.est-key-input:focus-visible + .btn,
.btn:has(+ .est-key-input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* The composed link is the one string on this page that opens the material, so it is set apart
   from the address above it rather than looking like another field. */
#estFullLink {
  border-color: var(--accent);
  background: var(--accent-tint);
}

.iconbtn {
  display: inline-grid; place-items: center; width: 2.25rem; height: 2.25rem; padding: 0;
  color: var(--ink); background: transparent; border: 0; border-radius: 7px;
  cursor: pointer; text-decoration: none;
}
.iconbtn svg {
  width: 1.5rem; height: 1.5rem; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.iconbtn:hover { color: var(--accent-strong); background: var(--accent-tint); }
.iconbtn:active { filter: brightness(0.97); }
.iconbtn:disabled { opacity: 0.5; cursor: default; background: transparent; }
.iconbtn.is-active { color: var(--accent); background: var(--accent-tint); }

@media (max-width: 560px) {
  .brand-word em { display: none; }
  .topbar-left { gap: var(--space-2); }
  /* Truncated rather than hidden: who you are signed in as still matters on a phone, and the
     role chip beside it stays whole. */
  .est-who-email { max-width: 9ch; }
}

/* ---- 4. PAGE SHELLS ----------------------------------------------------- */
/* the narrow, centred shell: /login and /change-password */
.auth-wrap {
  flex: 1 1 auto;
  display: flex; align-items: flex-start; justify-content: center;
  padding: clamp(2rem, 7vw, 4.5rem) var(--space-4);
}
.card {
  width: 100%; max-width: 22rem;
  border: 1px solid var(--line); border-radius: 8px;
  padding: var(--space-5);
  background: var(--raised);
  box-shadow: var(--shadow-sm);
}
.card.wide   { max-width: 42rem; }
.card-fluid  { max-width: none; }
.stack { width: 100%; max-width: 42rem; margin: 0 auto; display: flex; flex-direction: column; gap: var(--space-5); }
.stack-narrow { max-width: 24rem; }

.auth-title { margin: 0 0 var(--space-1); font-size: var(--text-lg); font-weight: 700; }
.auth-sub   { margin: 0 0 var(--space-5); color: var(--muted); font-size: var(--text-sm); }

/* the wide shell: the console */
.hub-main {
  flex: 1 1 auto; width: 100%; max-width: var(--app-measure); margin: 0 auto;
  padding: clamp(1.25rem, 4vw, 2.25rem) clamp(1rem, 4vw, 2rem) var(--space-8);
}
.hub-main.hub-wide { max-width: min(98vw, 100rem); }
.hub-head { margin: 0 0 var(--space-5); }
.hub-h1   { margin: var(--space-1) 0; font-size: var(--text-xl); font-weight: 700; }
.hub-sub  { margin: 0; color: var(--muted); font-size: var(--text-sm); }

.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; margin: 0 0 var(--space-5); }
.page-head .titles { min-width: 0; }
.page-h1  { margin: var(--space-1) 0; font-size: var(--text-xl); font-weight: 700; }
.page-sub { margin: 0; color: var(--muted); font-size: var(--text-sm); }
/* flex-end so a labelled control lines up with a bare button on its bottom edge */
.page-actions { display: flex; gap: var(--space-2); align-items: flex-end; flex-wrap: wrap; }
.page-actions .field { margin: 0; }

.hub-section { margin: var(--space-6) 0 0; }
.hub-section:first-of-type { margin-top: 0; }
.hub-section > h2 { margin: 0 0 var(--space-1); font-size: var(--text-base); font-weight: 700; }
.hub-section > .note { margin: 0 0 var(--space-3); }

.foot {
  padding: var(--space-4) clamp(1rem, 4vw, 2rem);
  border-top: 1px solid var(--line);
  color: var(--muted); font-size: var(--text-xs);
  display: flex; gap: var(--space-2); align-items: center; flex: 0 0 auto; flex-wrap: wrap;
}
.foot a { color: var(--muted); }
.foot a:hover { color: var(--accent); }
.foot .sep { opacity: 0.5; }

/* ---- 5. FORMS ----------------------------------------------------------- */
.field { margin: 0 0 var(--space-4); }
.field label {
  display: block; font-size: var(--text-xs); font-weight: 700;
  color: var(--muted); margin: 0 0 var(--space-1);
}
.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: var(--text-base);
  color: var(--ink); background: var(--ground);
  border: 1px solid var(--line); border-radius: 6px;
  padding: var(--space-2) var(--space-3);
}
.field textarea { resize: vertical; min-height: 6rem; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--muted); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;                          /* replaced by the ring below */
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
.field input:disabled, .field select:disabled, .field textarea:disabled {
  opacity: 0.6; cursor: default; background: var(--sunk);
}
.field.error input, .field.error select, .field.error textarea, .field input.invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-tint);
}
.field.warn input, .field.warn select, .field.warn textarea {
  border-color: var(--warn);
  box-shadow: 0 0 0 3px var(--warn-tint);
}
.field-hint { display: block; margin: var(--space-1) 0 0; font-size: var(--text-xs); color: var(--muted); }
.field-hint.is-error { color: var(--danger); }
.field.inline { margin: 0; }
.field-wide, .field.full { grid-column: 1 / -1; }

/* wide form layout for the console; the auth cards stay single-column */
.form-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.form-grid .field { margin: 0; }
.form-actions { display: flex; gap: var(--space-2); align-items: center; margin: var(--space-4) 0 0; flex-wrap: wrap; }

/* inline "checkbox + label" row */
.remember {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); margin: var(--space-3) 0 var(--space-4); cursor: pointer;
}
.check-inline { width: auto; margin: 0; flex: none; accent-color: var(--accent); }

/* ---- 6. BUTTONS -------------------------------------------------------- */
.btn {
  display: inline-block; width: 100%;
  font: inherit; font-weight: 700; font-size: var(--text-base);
  color: var(--on-accent); background: var(--accent-solid);
  border: 1px solid var(--accent-solid); border-radius: 6px;
  padding: var(--space-2) var(--space-4);
  cursor: pointer; text-align: center;
  text-decoration: none;   /* <a class="btn"> must not inherit the anchor underline */
}
.btn:hover  { background: var(--accent-strong); border-color: var(--accent-strong); color: var(--on-accent); }
.btn:active { filter: brightness(0.96); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: default; }
/* loading state: aria-busy="true", set by a submit handler where one exists. The
   forms work without script, so this is a progressive cue, not the mechanism. */
.btn[aria-busy="true"] { color: transparent !important; position: relative; pointer-events: none; }
.btn[aria-busy="true"]::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 1rem; height: 1rem; margin: -0.5rem 0 0 -0.5rem;
  border: 2px solid var(--on-accent); border-top-color: transparent; border-radius: 50%;
  animation: hub-spin 0.6s linear infinite;
}
.btn-ghost[aria-busy="true"]::after { border-color: var(--ink); border-top-color: transparent; }
@keyframes hub-spin { to { transform: rotate(360deg); } }

.btn-ghost {
  width: auto; color: var(--ink); background: transparent; border-color: var(--line);
}
.btn-ghost:hover { color: var(--accent-strong); background: transparent; border-color: var(--muted); }
/* For a destructive action, of which this read-only release has none. It exists so that
   the first one to be added does not arrive as a hand-rolled red button. --on-accent, not
   a literal white: on dark the --danger token is a light salmon, and white text on it
   measures 2:1. */
.btn-danger { background: var(--danger); border-color: var(--danger); color: var(--on-accent); }
.btn-danger:hover { background: var(--danger); border-color: var(--danger); color: var(--on-accent); filter: brightness(0.92); }
.btn-sm { width: auto; padding: var(--space-2) var(--space-3); font-size: var(--text-sm); }
.btn-block { width: 100%; }

/* ---- 7. MESSAGES, NOTES, RULES ---------------------------------------- */
.msg { margin: var(--space-4) 0 0; font-size: var(--text-sm); min-height: 1rem; }
.msg.err  { color: var(--danger); }
.msg.warn { color: var(--warn); }
.msg.ok   { color: var(--ok); }
.msg:first-child { margin-top: 0; }

.note { color: var(--muted); font-size: var(--text-sm); margin: var(--space-4) 0 0; }
.note-tight { margin-top: 0; }
.muted { color: var(--muted); }
.row    { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
.divider { height: 1px; background: var(--line); border: 0; margin: var(--space-5) 0; }
.h-sec {
  font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--muted); margin: 0 0 var(--space-3);
}
/* A section heading after other content needs air above it, or it reads as a caption on the
   paragraph it follows rather than the label of what comes next. The first heading in a panel
   sits flush, so it keeps the zero top margin. */
.h-sec:not(:first-child) { margin-top: var(--space-5); }
.hub-inline-msg { font-size: var(--text-sm); color: var(--muted); }
.hub-inline-msg.is-error { color: var(--danger); }
.hub-inline-msg.is-ok    { color: var(--ok); }

/* ---- 8. CONSOLE BANNER (freshness / degraded read) ---------------------- */
/* A PAGE-level state, not a form-level one: .msg is a line inside a form, this is
   a band above the content that says the whole view is stale, partial or failed.
   The console shows it when estate_sync says a publisher has not pushed since its
   last publish, and when a live read (Cloudflare, telemetry) could not be made. */
.notice {
  display: flex; gap: var(--space-3); align-items: flex-start; flex-wrap: wrap;
  border: 1px solid var(--line); border-left-width: 3px; border-radius: 8px;
  background: var(--raised); color: var(--ink);
  padding: var(--space-3) var(--space-4); margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
}
.notice > div { min-width: 0; flex: 1 1 20rem; }
.notice strong { display: block; font-size: var(--text-sm); font-weight: 700; }
/* Full ink, not --muted: the tint bands below lift the background to ~0.88 relative
   luminance, where --muted lands at 4.3:1 and misses the floor. The heading's weight
   and colour carry the hierarchy instead. */
.notice p { margin: var(--space-1) 0 0; color: inherit; }
.notice .notice-act { flex: none; display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.notice.is-stale  { border-left-color: var(--warn);   background: var(--warn-tint); }
.notice.is-error  { border-left-color: var(--danger); background: var(--danger-tint); }
.notice.is-ok     { border-left-color: var(--ok);     background: var(--ok-tint); }
.notice.is-stale strong { color: var(--warn); }
.notice.is-error strong { color: var(--danger); }
.notice.is-ok strong    { color: var(--ok); }

/* ---- 9. TABS ---------------------------------------------------------- */
.tabs { display: flex; gap: var(--space-1); border-bottom: 1px solid var(--line); margin: 0 0 var(--space-5); flex-wrap: wrap; }
/* The -1px is a hairline overlap, not a spacing value: it pulls the tab's own 2px
   underline over the strip's 1px bottom border so the active tab sits on the line rather
   than above it. The same applies to .subtab below. */
.tab {
  font: inherit; font-size: var(--text-sm); font-weight: 700; text-decoration: none;
  color: var(--muted); background: transparent; border: 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: var(--space-2) var(--space-4); cursor: pointer;
}
.tab:hover  { color: var(--ink); }
.tab:active { background: var(--accent-tint); }
.tab:disabled { opacity: 0.5; cursor: default; }
.tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.tabpanel[hidden] { display: none; }

.subtabs { display: flex; gap: var(--space-1); border-bottom: 1px solid var(--line); margin: 0 0 var(--space-5); flex-wrap: wrap; }
.subtab {
  font: inherit; font-size: var(--text-sm); font-weight: 700; text-decoration: none;
  color: var(--muted); background: transparent; border: 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: var(--space-2) var(--space-3); cursor: pointer;
}
.subtab:hover { color: var(--ink); }
.subtab.is-active { color: var(--accent); border-bottom-color: var(--accent); }

/* ---- 10. DASHBOARD: STAT TILES, PANELS, ROLL-UP BARS ------------------- */
.dash-grid    { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); margin: 0 0 var(--space-5); }
.dash-cols    { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }
.dash-section { margin: 0 0 var(--space-5); }

.stat {
  border: 1px solid var(--line); border-radius: 10px; padding: var(--space-4);
  background: var(--raised); display: flex; flex-direction: column; gap: var(--space-1); min-width: 0;
}
.stat .stat-label { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); font-weight: 700; }
.stat .stat-num   { font-size: var(--text-xl); font-weight: 700; line-height: 1; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.stat .stat-foot  { font-size: var(--text-xs); color: var(--muted); }
.stat.attn .stat-num { color: var(--accent); }      /* maroon = needs attention */
.stat.warn .stat-num { color: var(--warn); }
.stat.bad  .stat-num { color: var(--danger); }
/* the whole tile is the anchor, so it must not underline its label or number */
.stat-link, .stat a.stat-link { text-decoration: none; color: inherit; }
a.stat:hover { border-color: var(--muted); background: var(--accent-tint); }
a.stat { text-decoration: none; color: inherit; }

.dash-panel { border: 1px solid var(--line); border-radius: 10px; padding: var(--space-4); background: var(--raised); }
.dash-panel > h2 { margin: 0 0 var(--space-2); font-size: var(--text-base); font-weight: 700; }
.dash-panel > .note { margin: 0 0 var(--space-3); }

.statbar { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-1) 0 0; }
.statbar-row { display: grid; grid-template-columns: 6.5rem 1fr 2rem; gap: var(--space-3); align-items: center; font-size: var(--text-xs); }
.statbar-track { background: var(--line); border-radius: 999px; height: 0.5rem; overflow: hidden; }
.statbar-fill  { background: var(--muted); height: 100%; border-radius: 999px; }
.statbar-row.attn .statbar-fill { background: var(--accent); }
.statbar-row.warn .statbar-fill { background: var(--warn); }
.statbar-row.bad  .statbar-fill { background: var(--danger); }
.statbar-row .statbar-n { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }

/* ---- 11. DATA TABLE --------------------------------------------------- */
/* Data drives the form: chips for enumerable fields, numbers right-aligned and
   tabular, long text truncated, inactive rows shaded, and a marker on any row the
   console could not confirm against the live estate. */
.data-wrap { border: 1px solid var(--line); border-radius: 10px; overflow: auto; max-height: 74vh; -webkit-overflow-scrolling: touch; }
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.data-table th, .data-table td {
  text-align: left; padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line); vertical-align: top;
}
.data-table thead th {
  position: sticky; top: 0; z-index: 1; background: var(--raised);
  color: var(--muted); font-weight: 700; font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap;
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr { cursor: default; }
.data-table tbody tr.clickable { cursor: pointer; }
.data-table tbody tr.clickable:hover { background: var(--accent-tint); }
.data-table tbody tr.inactive { opacity: 0.55; }              /* retired / deleted rows */
.data-table td.num, .data-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table td.trunc { max-width: 24rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.data-table td.nowrap, .data-table th.nowrap { white-space: nowrap; }
.data-table .t-empty { color: var(--muted); padding: var(--space-5); text-align: center; }
.data-table a.rowlink { color: inherit; text-decoration: none; }
.data-table a.rowlink:hover { color: var(--accent-strong); text-decoration: underline; }
.data-key { font-family: var(--mono); font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }
.data-title-cell { font-weight: 700; }
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--ink); }
th.sortable .sort-ind { color: var(--accent); }

/* the dense variant: the artefact index, 80+ rows on one screen */
.data-table.is-dense th, .data-table.is-dense td { padding: var(--space-1) var(--space-2); }
.data-table.is-dense { font-size: var(--text-xs); }
.data-table.is-dense .badge { font-size: var(--text-xs); padding: 0 var(--space-1); }

/* A row the console read from the index but could not confirm against the live
   estate. The stripe carries the meaning, the marker carries the tooltip, so it is
   never colour-only. */
.data-table tbody tr.unconfirmed { box-shadow: inset 3px 0 0 var(--warn); }
.data-table tbody tr.unconfirmed td:first-child { padding-left: calc(var(--space-3) + 3px); }
.data-table.is-dense tbody tr.unconfirmed td:first-child { padding-left: calc(var(--space-2) + 3px); }
.rowmark {
  display: inline-block; margin-left: var(--space-1);
  font-family: var(--mono); font-size: var(--text-xs); font-weight: 700;
  color: var(--warn); cursor: help; text-decoration: none; border-bottom: 1px dotted var(--warn);
}

/* ---- 12. CHIPS -------------------------------------------------------- */
/* ONE chip family. .badge is the estate's status/severity chip; the console's slug
   statuses are its modifiers, not a second component:
     active -> .badge.ok        cap     -> .badge.warn
     revoked -> .badge.danger   expired -> .badge.off
     deleted -> .badge.off      unexplained surface -> .badge.warn
   Each modifier changes text and border, never fill alone, so the chip still reads
   in greyscale and the label always states the status in words. */
.badge {
  display: inline-block; font-size: var(--text-xs); font-weight: 700; line-height: 1.4;
  padding: var(--space-1) var(--space-2); border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted); background: var(--raised);
  white-space: nowrap;
}
.badge.ink    { color: var(--ink); border-color: var(--muted); }
.badge.attn   { color: var(--accent-strong); border-color: var(--accent); background: var(--accent-tint); }
.badge.ok     { color: var(--ok);     border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }
.badge.warn   { color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
.badge.danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
/* "no longer in force" reads as a dashed outline, not as a grey fill: --muted on the
   --sunk band measures 4.44:1, just under the floor, and the dash carries the meaning
   without relying on colour at all. */
.badge.off    { color: var(--muted); border-color: var(--line); border-style: dashed; background: var(--raised); }

/* ---- 13. TOOLBAR ------------------------------------------------------ */
.toolbar { display: flex; gap: var(--space-2); align-items: flex-end; flex-wrap: wrap; margin: 0 0 var(--space-4); }
.toolbar .field { margin: 0; }
.toolbar .field label { margin: 0 0 var(--space-1); }
.toolbar select, .toolbar input {
  font: inherit; font-size: var(--text-sm); color: var(--ink); background: var(--ground);
  border: 1px solid var(--line); border-radius: 6px; padding: var(--space-2);
}
.toolbar select:hover, .toolbar input:hover { border-color: var(--muted); }
.toolbar select:focus, .toolbar input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
.toolbar .grow { flex: 1 1 12rem; min-width: 9rem; }
.toolbar .spacer { flex: 1 1 auto; }
.hub-count { font-size: var(--text-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- 14. EMPTY, LOADING AND ERROR STATES ----------------------------- */
/* Panel-level states. .t-empty above is the in-table variant; this is what a whole
   section shows when it has nothing, is waiting, or could not read. An error is
   never rendered as an empty state: it says what failed and offers the retry. */
.hub-empty {
  border: 1px dashed var(--line); border-radius: 10px;
  padding: var(--space-6) var(--space-4); text-align: center; color: var(--muted);
}
.hub-empty strong { display: block; color: var(--ink); font-size: var(--text-base); margin-bottom: var(--space-1); }
.hub-empty p { margin: 0 auto var(--space-4); max-width: 34rem; font-size: var(--text-sm); }
.hub-empty p:last-child { margin-bottom: 0; }
.hub-empty.is-error { border-style: solid; border-color: color-mix(in srgb, var(--danger) 40%, var(--line)); }
.hub-empty.is-error strong { color: var(--danger); }
.hub-empty.is-loading { font-style: italic; }

/* ---- 15. DETAIL VIEW, META, KEY/VALUE, TIMELINE ---------------------- */
.detail-grid { display: grid; gap: var(--space-5); grid-template-columns: minmax(0, 2fr) minmax(15rem, 1fr); align-items: start; }
.detail-main, .detail-side { border: 1px solid var(--line); border-radius: 10px; padding: var(--space-4); background: var(--raised); }
.detail-body { white-space: pre-wrap; word-break: break-word; margin: var(--space-2) 0 0; }

/* discrete labelled facts, not a run-on sentence of "Label: value" pairs */
.hub-meta { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: var(--space-3) 0 0; padding: 0; }
.hub-meta > div { min-width: 0; }
.hub-meta dt { font-size: var(--text-xs); color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; margin: 0; }
.hub-meta dd { margin: 0; font-size: var(--text-sm); color: var(--ink); overflow-wrap: anywhere; }

.kv { display: grid; grid-template-columns: 7rem 1fr; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-2); font-size: var(--text-sm); }
.kv dt { color: var(--muted); font-weight: 700; }
.kv dd { margin: 0; color: var(--ink); word-break: break-word; }

.timeline { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.timeline li { padding: var(--space-2) 0; border-bottom: 1px solid var(--line); font-size: var(--text-sm); }
.timeline li:last-child { border-bottom: 0; }
.timeline .t-meta { color: var(--muted); font-size: var(--text-xs); }
.linklist { list-style: none; margin: var(--space-1) 0 0; padding: 0; }
.linklist li { padding: var(--space-1) 0; }

/* ---- 16. DISCLOSURE -------------------------------------------------- */
.hub-disclosure > summary {
  display: inline-flex; align-items: center; gap: var(--space-2);
  cursor: pointer; font-size: var(--text-sm); color: var(--muted);
  padding: var(--space-1) 0; list-style: none;
}
.hub-disclosure > summary::-webkit-details-marker { display: none; }
.hub-disclosure > summary::before {
  content: ''; width: 0; height: 0; flex: none;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform 0.12s ease;
}
.hub-disclosure[open] > summary::before { transform: rotate(90deg); }
.hub-disclosure > summary:hover { color: var(--accent-strong); }
.hub-disclosure > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---- 17. RESPONSIVE ------------------------------------------------- */
/* Checked at 375px, 768px and 1024px. The wide shells collapse to one column and
   every table keeps its own horizontal scroll, so the page body never scrolls
   sideways. */
@media (max-width: 720px) {
  .detail-grid { grid-template-columns: 1fr; }
  .statbar-row { grid-template-columns: 5.5rem 1fr 2rem; }
}
@media (max-width: 420px) {
  .card { padding: var(--space-4); }
  .data-table td.trunc { max-width: 12rem; }
  .kv { grid-template-columns: 1fr; gap: 0 0; }
  .kv dd { margin-bottom: var(--space-2); }
}

/* ---- 18. MOTION ----------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy="true"]::after { animation-duration: 1.6s; }
  * { transition-duration: 0.01ms !important; }
}
