:root {
  --ink: #1c2430;
  --ink-soft: #5b6675;
  --paper: #ffffff;
  --bg: #f4f5f7;
  --border: #dde1e7;
  --accent: #2f6f4f;
  --accent-ink: #ffffff;
  --refusal-bg: #fdf1ee;
  --refusal-ink: #9a3b1f;
  --answered-bg: #eef6f0;
  --answered-ink: #235c3d;
  --radius: 10px;

  /* TF-126 — True Fix Advisory brand tokens. Additive: everything above this
     line is the pre-existing chat-shell palette and is untouched by this
     card. These drive the login screen and the app shell (header +
     empty-state) only; the conversation transcript, badges, and citations
     keep the tokens above exactly as they were. */
  --navy: #0a1628;
  --amber: #c8882a;
  --cream: #f0ead6;
  --canvas: #e8e2d0;
  --nearblack: #1a1208;
  --midgray: #5a5040;
  --tan: #d4cbb0;
  --lighttext: #f8f4ed;

  --font-serif: "Libre Baskerville", Georgia, "Times New Roman", serif;
  --font-body: "Work Sans", Arial, Helvetica, sans-serif;
  --font-mono: "DM Mono", "Courier New", Courier, monospace;
}

/* --- TF-126: self-hosted brand fonts -------------------------------------
   Latin-subset woff2 only, OFL-licensed (Libre Baskerville, Work Sans, DM
   Mono — all Google Fonts catalog entries under the SIL Open Font License).
   Every src is same-origin under /static/fonts/ — CSP is style-src/font-src
   'self' and stays that way; nothing here is a Google Fonts network request
   at runtime. Each filename carries a content hash computed from that exact
   file's bytes (see cockpit/app.py's _ASSET_FILES comment for why: swapping
   a font's content without renaming it is exactly the kind of same-URL
   staleness BUG-1b's asset-versioning exists to prevent, and style.css's own
   url() references are NOT touched by the server-side ?v= stamping — that
   only rewrites index.html's src=/href= attributes). Weights are the ones
   actually used below: Baskerville 400/700, Work Sans 400/500/600, DM Mono
   400/500. */
@font-face {
  font-family: "Libre Baskerville";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/librebaskerville-400.3eb80fbca704.woff2") format("woff2");
}
@font-face {
  font-family: "Libre Baskerville";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/librebaskerville-700.96fa63376eed.woff2") format("woff2");
}
@font-face {
  font-family: "Work Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/worksans-400.418b06936717.woff2") format("woff2");
}
@font-face {
  font-family: "Work Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/worksans-500.943a4b2d8b49.woff2") format("woff2");
}
@font-face {
  font-family: "Work Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/worksans-600.c536b548fdcb.woff2") format("woff2");
}
@font-face {
  font-family: "DM Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/dmmono-400.e1896b13b2b1.woff2") format("woff2");
}
@font-face {
  font-family: "DM Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/dmmono-500.9964608a8493.woff2") format("woff2");
}

* {
  box-sizing: border-box;
}

/* Chat-harness shell (C-9 / UAT-6): the page itself never scrolls. The header
   and the ask bar are pinned by being fixed-size flex items in a viewport-tall
   column; only the transcript between them scrolls. Before this, the whole
   document scrolled, so a long conversation pushed the input off-screen. */
html,
body {
  height: 100%;
}

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* TF-126: two shell backdrops, chosen from which section the markup already
   shows/hides via the `hidden` attribute — no JS change. `:has()` is a CSS4
   selector (all evergreen browsers); fine for this internal tool, matching
   the precedent already in the codebase for state-driven CSS. Full-viewport
   navy while logged out (no app-header showing at all); canvas behind the
   chat shell once signed in. Neither branch touches an individual turn's own
   background (--paper / --refusal-bg / --answered-bg), which are untouched
   chat-internal tokens. */
body:has(#login-section:not([hidden])) {
  background: var(--navy);
}

body:has(#login-section:not([hidden])) .app-header {
  display: none;
}

body:has(#login-section:not([hidden])) main {
  align-items: center;
  justify-content: center;
}

body:has(#chat-section:not([hidden])) {
  background: var(--canvas);
}

/* The shell sets display on elements the markup toggles with the `hidden`
   attribute, which would otherwise beat the UA's [hidden] { display: none }.
   Without this the login form and the chat pane render at the same time. */
[hidden] {
  display: none !important;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.app-header {
  flex: 0 0 auto; /* pinned: never scrolls with the conversation */
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.5rem;
  /* TF-126: navy masthead + 1.5pt (~2px) amber bottom border, the
     established brand pattern. Only relevant post-login — the login-state
     rule above hides this element entirely, so it never has to coexist with
     the full-viewport navy login screen. */
  background: var(--navy);
  border-bottom: 2px solid var(--amber);
}

.app-header-logo {
  height: 32px;
  width: 32px;
  display: block;
  flex: 0 0 auto;
}

.app-header-titles {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.app-header h1 {
  font-family: var(--font-serif);
  font-size: 1.25rem;
  margin: 0;
  color: var(--lighttext);
}

.app-subtitle {
  margin: 0;
  color: var(--tan);
  font-size: 0.9rem;
  font-family: var(--font-body);
}

.app-header-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.85rem;
  color: var(--tan);
}

.link-button {
  background: none;
  border: none;
  color: var(--lighttext);
  cursor: pointer;
  padding: 0;
  font: inherit;
  text-decoration: underline;
}

/* TF-200: the header nav link between the chat shell ("/") and the Owner's
   Dashboard ("/dashboard"), on both shells. */
.header-nav-link {
  color: var(--lighttext);
  font: inherit;
  text-decoration: none;
}

/* TF-252: both header nav controls (the `.header-nav-link` <a>s above and
   the `.link-button` <button>s — "Log out", the rail's own header excepted,
   see its own override below) get the V1-1 wireframe's own `header.top
   .btn` treatment — a small bordered pill against the navy bar — instead of
   reading as bare underlined text bolted onto the header. Scoped to
   `.app-header` specifically so `.link-button`'s other, unrelated uses
   elsewhere in this file (the foreman rail header's own "Close", the recent-
   queries list, etc.) are untouched. */
.app-header .header-nav-link,
.app-header .link-button {
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.4rem 0.85rem;
  border-radius: 8px;
  background: rgba(248, 244, 237, 0.08);
  border: 1px solid rgba(248, 244, 237, 0.28);
}

.app-header .header-nav-link:hover,
.app-header .link-button:hover {
  background: rgba(248, 244, 237, 0.16);
}

.app-header a:focus-visible,
.app-header button:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 2px;
}

/* TF-365 (ROLE-2): the client-facing role label rendered next to the
   signed-in email (cockpit/web/role_labels.js's `renderWhoami`), never the
   internal tier word — see that file's own map for the exact vocabulary,
   not spelled out here. A small bordered pill, the same visual family as
   .header-nav-link, so it reads as a badge rather than as part of the
   email string it sits beside. Its accessible name ("Role: Member") comes
   from the element's own `aria-label` (role_labels.js), independent of
   this rule. */
.role-badge {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: rgba(248, 244, 237, 0.14);
  border: 1px solid rgba(248, 244, 237, 0.32);
}

main {
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: 1.5rem;
  /* Takes the remaining column height. min-height:0 is what lets a nested
     overflow container actually shrink instead of stretching the flex item. */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

#login-section {
  max-width: 400px;
  width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  overflow-y: auto; /* logged out there is no transcript; keep it reachable */
}

#chat-section {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* TF-126: the login card. Wraps the compass mark / heading / micro-label /
   one-liner (new, presentational-only markup) plus the two UNCHANGED
   sign-in surfaces (#oidc-signin, #login-form) — exactly one of which the
   instance shows, per C-11, same as before. */
.login-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  background: var(--cream);
  border-bottom: 3px solid var(--amber); /* ~1.5pt accent rule */
  border-radius: var(--radius);
  padding: 2.5rem 2rem 2rem;
  box-shadow: 0 12px 32px rgba(10, 22, 40, 0.35);
  text-align: center;
}

.login-mark {
  width: 44px;
  height: 44px;
}

.login-microlabel {
  display: inline-block;
  margin: 0;
  padding: 0.25rem 0.7rem;
  background: var(--navy);
  color: var(--amber);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border-radius: 999px;
  /* amber-on-cream measures ~2.5:1 and fails WCAG AA at any size — this
     label sits on a small navy chip instead (amber-on-navy is ~6:1) so the
     brand's "micro-labels in amber" pattern still holds without the
     contrast failure. See the PR description for the full contrast table. */
}

.login-heading {
  margin: 0.35rem 0 0;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: 2rem;
  color: var(--navy);
}

.login-lede {
  margin: 0 0 0.5rem;
  font-family: var(--font-body);
  color: var(--midgray);
  font-size: 0.95rem;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: 100%;
}

/* C-11: the OIDC sign-in affordances. Same shape as .login-form so the two
   postures look like one product — never both on screen at once. Both now
   live inside .login-card, which supplies the visible card surface, so
   neither draws its own background/border any more. */
.oidc-signin {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: 100%;
}

.signin-lede {
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--midgray);
}

.provider-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 40px;
  padding: 0.6rem 1rem;
  border: 1px solid var(--tan);
  border-radius: 6px;
  text-align: center;
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--nearblack);
  background: #ffffff;
}

.provider-button:hover {
  box-shadow: 0 1px 3px rgba(10, 22, 40, 0.18);
}

/* Google's own third-party sign-in button guidelines: white surface, this
   exact neutral border tone, the standard multicolor G mark, "Sign in with
   Google" in the button's own prescribed style. #dadce0 is Google's
   specified border color, not a True Fix brand token — reproduced literally
   because the compliance requirement is Google's, not ours. */
#signin-google {
  border-color: #dadce0;
}

.google-g {
  flex: 0 0 auto;
  display: block;
}

.login-form label {
  align-self: flex-start;
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--midgray);
}

.login-form input {
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--tan);
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 1rem;
  background: #ffffff;
  color: var(--nearblack);
}

button {
  cursor: pointer;
  border: none;
  border-radius: 6px;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.95rem;
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* TF-126: the login submit button gets its own brand treatment, scoped to
   #login-form specifically so the shared `button` rule above — and every
   other button on the page, including #ask-button in the chat pipeline,
   which is explicitly out of scope — is untouched. */
#login-form button[type="submit"] {
  background: var(--navy);
  color: var(--lighttext);
  font-family: var(--font-body);
  font-weight: 600;
}

#login-form button[type="submit"]:hover:not(:disabled) {
  filter: brightness(1.15);
}

#login-section a:focus-visible,
#login-section button:focus-visible,
#login-section input:focus-visible {
  outline: 3px solid var(--navy);
  outline-offset: 2px;
}

.error-text {
  color: var(--refusal-ink);
  font-size: 0.85rem;
  min-height: 1.1em;
  margin: 0;
}

/* TF-126: the failure-state panel treatment. Scoped to #login-section only
   — .turn-error .error-text (the chat transcript's own system-error rows,
   `appendSystemError` in app.js) is chat message rendering and stays exactly
   as it was; this rule never reaches it. Covers all three failure paths
   (#login-error and #signin-error are the only two elements this selector
   can match), including the reportBrokenAssets() emergency path, since both
   write through plain textContent with no class changes. Collapses to
   nothing (:empty) so the card does not carry a permanent blank panel. */
#login-section .error-text {
  margin-top: 0.25rem;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--nearblack);
}

#login-section .error-text:not(:empty) {
  padding: 0.65rem 0.85rem;
  border: 1px solid var(--tan);
  border-radius: 6px;
  background: var(--canvas);
  text-align: left;
}

.login-footer {
  margin: 0;
  text-align: center;
  color: var(--tan);
  font-family: var(--font-body);
  font-size: 0.8rem;
  line-height: 1.6;
}

.chat-layout {
  display: grid;
  grid-template-columns: 1fr 280px;
  grid-template-rows: minmax(0, 1fr);
  gap: 1.5rem;
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
}

.chat-main {
  display: flex;
  flex-direction: column;
  min-height: 0; /* lets .conversation scroll rather than push the ask bar down */
  position: relative; /* anchor for the floating "new response" affordance */
}

/* UAT-9: shown only when a response arrives while the reader has scrolled up.
   Floats just above the pinned ask bar; clicking it jumps to the newest turn. */
.new-response {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 4.5rem;
  z-index: 2;
  font-size: 0.8rem;
  padding: 0.35rem 0.8rem;
  border-radius: 999px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

@media (max-width: 720px) {
  .chat-layout {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(0, 1fr) auto;
  }

  /* Narrow screens stack the sidebar under the transcript; cap it so the
     recent-queries list can't crowd out the conversation. */
  .chat-sidebar {
    max-height: 30vh;
  }
}

.conversation {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-height: 0;
  margin-bottom: 1rem;
  /* The ONLY scrolling region in the shell. */
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* TF-126: a branded empty-state, shown before the first question and never
   again — CSS-only, no JS. #conversation ships with zero children
   (<div id="conversation" ...></div>, no whitespace) and app.js only ever
   clears it back to that same state (`el.conversation.textContent = ""` in
   showLogin()); the FIRST appendChild of a real turn permanently disqualifies
   :empty for the rest of that session. This does not touch how a turn itself
   renders — .turn/.turn-body/.disposition-badge/.citation-list and the rest
   of the answer pipeline are untouched, out of scope, and this rule cannot
   match anything inside them. */
.chat-empty-state {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  color: var(--midgray);
  font-family: var(--font-body);
  font-size: 0.9rem;
  text-align: center;
}

.chat-empty-mark {
  opacity: 0.55;
}

#conversation:empty {
  flex: 0 0 0;
  min-height: 0;
  margin-bottom: 0;
}

#conversation:empty + .chat-empty-state {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}

.turn {
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  background: var(--paper);
  border: 1px solid var(--border);
}

.turn-user {
  background: #eef2fb;
  border-color: #d7e0f5;
}

/* BUG-1b: a submit that could not proceed renders as a turn in the transcript,
   so a failure is never invisible. Tinted toward the refusal palette so it
   reads as "this did not happen" at a glance rather than as an answer. */
.turn-error {
  background: #fdf0ee;
  border-color: #f3cfc8;
}

.turn-error .error-text {
  font-size: 0.95rem;
}

.turn-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
  margin-bottom: 0.25rem;
}

.turn-body {
  white-space: pre-wrap;
  word-break: break-word;
}

.answer-text {
  margin-top: 0.5rem;
}

.disposition-badge {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
}

.disposition-badge.is-answered {
  background: var(--answered-bg);
  color: var(--answered-ink);
}

.disposition-badge.is-refusal {
  background: var(--refusal-bg);
  color: var(--refusal-ink);
}

.disposition-badge.small {
  font-size: 0.7rem;
}

.citation-list {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.citation {
  font-size: 0.8rem;
  color: var(--ink-soft);
  word-break: break-word;
}

.citation-system {
  font-weight: 600;
}

.ask-form {
  flex: 0 0 auto; /* pinned at the bottom of the chat column */
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
  /* TF-126: matches the app shell's new --canvas backdrop (was --bg) so the
     ask bar doesn't sit on a mismatched gray strip; the input/button inside
     it are unchanged. */
  background: var(--canvas);
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.ask-form textarea {
  flex: 1;
  resize: vertical;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.chat-sidebar {
  min-height: 0;
  overflow-y: auto; /* scrolls independently; never pushes the shell taller */
}

.chat-sidebar h2 {
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin: 0 0 0.5rem;
}

.recent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.recent-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.65rem;
  font-size: 0.85rem;
}

.recent-query-text {
  word-break: break-word;
}

.recent-empty {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* ---------------------------------------------------------------------
   C-201 (TF-98) — the Owner's Dashboard. Additive only: everything above
   this line is the chat shell (untouched). Reuses the TF-126 brand tokens
   (--navy/--amber/--cream/--canvas/--tan/--midgray/--nearblack/--lighttext/
   --font-*) rather than inventing new ones.
   --------------------------------------------------------------------- */

.dashboard-body {
  background: var(--canvas);
}

.dashboard-main {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 1.5rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.dashboard-auth-gate {
  max-width: 480px;
  margin: 3rem auto;
  padding: 2rem;
  background: var(--cream);
  border-bottom: 3px solid var(--amber);
  border-radius: var(--radius);
  text-align: center;
  font-family: var(--font-body);
}

.dashboard-auth-message {
  color: var(--nearblack);
  margin: 0 0 0.75rem;
}

.dashboard-signin-link {
  color: var(--navy);
  font-weight: 600;
}

.dashboard-grid {
  display: grid;
  /* TF-314: closes TF-252's deviation #2 — the V1-1 wireframe's own
     12-column grid, now that `layout.span` exists on the spec schema to
     drive it (widget_schema.js). `.widget-tile`'s own default `grid-column:
     span 4` below is the wireframe's un-suffixed `.card`; a spec with no
     `layout.span` at all renders at that same width, unchanged from the
     pre-TF-314 auto-fill layout this replaces. */
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0.9rem;
  align-items: start;
}

/* TF-314: closes TF-252's deviation #1 — the wireframe's own `.qhead`
   section header, a full-width row inside the SAME flat `.dashboard-grid`
   (dashboard.js's renderDashboard groups tiles by `layout.section` but
   keeps every tile a direct grid child — see that function's own comment
   for why). */
.dashboard-qhead {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0.4rem 0 0;
}

.dashboard-qhead:first-child {
  margin-top: 0;
}

.dashboard-qhead-title {
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--navy);
}

.dashboard-qhead-sub {
  font-family: var(--font-body);
  font-size: 0.75rem;
  color: var(--midgray);
}

/* Lane B Major 4b: an explicit message when the payload resolves to zero
   tiles, rather than a silent blank grid. */
.dashboard-empty-message {
  grid-column: 1 / -1;
  padding: 2rem;
  text-align: center;
  color: var(--midgray);
  font-family: var(--font-body);
}

.widget-tile {
  background: var(--paper);
  border: 1px solid var(--border);
  /* TF-252: V1-1 wireframe's own `.card` radius (12px), a dedicated
     override rather than raising the shared `--radius` (10px) token, which
     also drives buttons/panels/forms this card does not touch. */
  border-radius: 12px;
  /* TF-252: matches the design preview's own `.card` shadow token
     (`0 1px 2px rgba(26,18,8,.05)`) so a tile reads the same weight of
     "raised surface" the operator console's cards already carry. */
  box-shadow: 0 1px 2px rgba(26, 18, 8, 0.05);
  padding: 1rem 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 120px;
  font-family: var(--font-body);
  position: relative; /* TF-268: anchor for the "Configure" corner button */
  /* TF-314: the wireframe's own un-suffixed `.card` default — a spec with no
     `layout.span` (every pre-TF-314 spec) renders at exactly this width. */
  grid-column: span 4;
}

/* TF-314: the wireframe's own `.span6`/`.span8`/`.span12` modifiers,
   applied by dashboard.js's tileClassName() from `layout.span`. */
.widget-tile--span-6 {
  grid-column: span 6;
}

.widget-tile--span-8 {
  grid-column: span 8;
}

.widget-tile--span-12 {
  grid-column: span 12;
}

/* TF-314: mirrors the wireframe's own sub-1100px breakpoint (`.card,
   .card.span6, .card.span8 { grid-column: span 12 }`) — below this width a
   6- or 8-wide tile is narrower in practice than the wireframe's own design
   assumed, so every span collapses to full-width, one tile per row, rather
   than rendering cramped. `.widget-tile--span-12` is already full-width and
   needs no rule here. */
@media (max-width: 900px) {
  .widget-tile,
  .widget-tile--span-6,
  .widget-tile--span-8 {
    grid-column: span 12;
  }
}

.widget-tile--needs-update {
  background: var(--tan);
  border-style: dashed;
}

.widget-tile--error {
  background: var(--refusal-bg);
  border-color: #f3cfc8;
}

.widget-tile--empty {
  color: var(--ink-soft);
}

.widget-title {
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--navy);
}

/* TF-252: found on the real running dashboard (a long wrapping title —
   "Days Sales Outstanding (new metric, unbanded)" in the demo fixture set —
   ran its second line straight under the absolutely-positioned "Configure"
   pill). Reserves the pill's own footprint out of the title's line box,
   scoped to tiles that actually carry the button so a tile without one
   (error/needs_update kinds never get it) keeps its full-width title. */
.widget-tile:has(.widget-configure-button) .widget-title {
  padding-right: 4.25rem;
}

.widget-needs-update-detail {
  font-size: 0.85rem;
  color: var(--midgray);
}

/* TF-252: matches the V1-1 wireframe's own `.empty` refusal treatment
   (dashed box, centered, muted text) — a `no_metric_mapped`/`no_rows`/
   `stale_beyond_threshold`/`refused`/`thresholds_not_set` empty state reads
   as a deliberate refusal, never as a blank or broken tile. Wording itself
   is untouched (EMPTY_STATE_LABELS in dashboard.js) — presentation only,
   per this card's own honest-state-semantics boundary. */
.widget-empty-state {
  font-size: 0.85rem;
  color: var(--midgray);
  text-align: center;
  border: 1px dashed var(--tan);
  border-radius: 10px;
  padding: 0.85rem 0.75rem;
}

.widget-error-banner {
  font-size: 0.85rem;
  color: var(--refusal-ink);
}

.widget-retry-button {
  align-self: flex-start;
  background: var(--navy);
  color: var(--lighttext);
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.4rem 0.75rem;
}

/* TF-268 (UX-F5): the discoverable per-tile "Configure" affordance — a
   small corner button on every populated/empty grid tile, wired (in
   foreman_rail.js) to fill and submit the SAME `configure <title>` rail
   command a user previously had to know to type by hand. */
.widget-configure-button {
  position: absolute;
  top: 0.6rem;
  right: 0.65rem;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--midgray);
  font-family: var(--font-body);
  font-size: 0.7rem;
  line-height: 1;
  padding: 0.25rem 0.6rem;
  cursor: pointer;
}

.widget-configure-button:hover,
.widget-configure-button:focus-visible {
  color: var(--navy);
  border-color: var(--navy);
}

.widget-kpi-value {
  font-family: var(--font-mono);
  font-size: 1.75rem;
  color: var(--nearblack);
}

/* TF-252: the V1-1 wireframe's own `.card-foot` pattern — chips + drill
   link + as-of stamp pinned to the bottom of the card behind a divider,
   rather than sitting wherever it falls in normal document flow. */
.widget-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-top: auto;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

/* TF-252: the wireframe's explicit "Ask the Foreman about this →" per-tile
   affordance. Added only for the tile types whose body already carries
   `data-drilldown-widget-id` (kpi_scalar, threshold_band, milestone_elapsed
   — see dashboard.js's own DRILLDOWN_HINT_TYPES) so this text is never
   painted onto a tile that isn't actually clickable. Nested inside the
   already-clickable container: no new attribute, no new listener, no
   behavior change — purely a visible label for a click path that already
   exists (findDrilldownTarget walks the parentNode chain regardless of
   which descendant node the click landed on). */
.widget-drill-hint {
  font-size: 0.75rem;
  font-weight: 650;
  color: var(--brass-deep, #7a4e00);
  /* Renders as its own row, right-aligned, directly under the chip row —
     `align-self`, not `margin-left: auto`, because `.widget-tile` is a
     COLUMN flex container: this is a cross-axis alignment, not a main-axis
     one. */
  align-self: flex-end;
  white-space: nowrap;
}

.widget-drill-hint:hover {
  text-decoration: underline;
}

.widget-chip {
  font-size: 0.7rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--canvas);
  color: var(--midgray);
  border: 1px solid var(--tan);
}

.widget-chip--stale {
  background: #fdf1ee;
  color: var(--refusal-ink);
  border-color: #f3cfc8;
}

.widget-chip--pinned {
  background: var(--cream);
  border-color: var(--amber);
  color: var(--nearblack);
}

.widget-freshness {
  font-size: 0.7rem;
  color: var(--ink-soft);
  font-family: var(--font-mono);
}

.widget-trend-list,
.widget-leaderboard-list,
.widget-queue-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
}

.widget-trend-point,
.widget-leaderboard-row {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
}

.widget-leaderboard-rank {
  font-family: var(--font-mono);
  color: var(--midgray);
  width: 1.5rem;
}

.widget-queue-row {
  padding: 0.2rem 0;
  border-bottom: 1px dashed var(--border);
}

.widget-threshold-band-body {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.widget-band {
  align-self: flex-start;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
}

.widget-band--green {
  background: var(--answered-bg);
  color: var(--answered-ink);
}

.widget-band--amber {
  background: #fff4e0;
  color: #8a5a12;
}

.widget-band--red {
  background: var(--refusal-bg);
  color: var(--refusal-ink);
}

/* TF-144 (C-202a) — benchmark_comparison ---------------------------------- */

.widget-benchmark-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 0.85rem;
}

.widget-benchmark-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.widget-benchmark-label {
  color: var(--midgray);
  flex: 1 1 auto;
}

.widget-benchmark-self-value {
  font-weight: 600;
}

.widget-benchmark-value {
  font-family: var(--font-mono);
  color: var(--ink-soft);
}

.widget-benchmark-distance {
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
}

.widget-benchmark-distance--ahead {
  background: var(--answered-bg);
  color: var(--answered-ink);
}

.widget-benchmark-distance--behind {
  background: var(--refusal-bg);
  color: var(--refusal-ink);
}

/* TF-144 (C-202a) — milestone_elapsed -------------------------------------- */

.widget-milestone-body {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.widget-milestone-pair {
  font-size: 0.75rem;
  color: var(--midgray);
}

.widget-milestone-target {
  font-size: 0.75rem;
  color: var(--ink-soft);
  font-family: var(--font-mono);
}

.widget-milestone-warning {
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  align-self: flex-start;
  background: #fff4e0;
  color: #8a5a12;
}

/* TF-145 (C-202b) — aging_list, exception_list (single_number reuses
   .widget-kpi-value, no new rule needed) ------------------------------- */

.widget-aging-list,
.widget-exception-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
}

.widget-aging-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.widget-aging-label {
  color: var(--midgray);
  flex: 1 1 auto;
}

.widget-aging-value {
  font-family: var(--font-mono);
  color: var(--nearblack);
}

.widget-exception-row {
  padding: 0.2rem 0;
  border-bottom: 1px dashed var(--border);
}

/* TF-103 (C-206) — the drill-down affordance. `[data-drilldown-widget-id]`
   marks every clickable "ask about this" surface dashboard.js renders
   (kpi_scalar/threshold_band/milestone_elapsed's whole body, plus
   aging_list's own red-count control below); the click LISTENER lives in
   foreman_rail.js, this file only has to look clickable. ------------------ */

[data-drilldown-widget-id] {
  cursor: pointer;
}

.widget-aging-body {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.widget-aging-red-count {
  align-self: flex-start;
  background: var(--refusal-bg);
  color: var(--refusal-ink);
  border: none;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
}

/* ---------------------------------------------------------------------
   C-205 (TF-101) — the Foreman chat rail. Fixed-position, layered above
   .dashboard-main's own scroll region rather than inside it, so opening it
   never depends on — or fights with — that container's height (see the
   comment on #foreman-rail in dashboard.html for the TF-145 lesson this
   sidesteps). Reuses the chat-shell's own turn/disposition-badge/citation
   tokens from the section above so a rail answer and index.html's own
   chat answer look identical, and every widget-tile token from the
   dashboard section above so a reused tile node-spec (AC 3/AC 4's byte-
   identical tile-parity mechanism) renders pixel-identical here too.
   --------------------------------------------------------------------- */

.foreman-rail-toggle {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--navy);
  color: var(--lighttext);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.65rem 1.1rem;
  border-radius: 999px;
  border: 2px solid var(--amber);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}

.foreman-rail-toggle:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 2px;
}

/* TF-252: while the rail is open, `#foreman-rail-collapse` ("Close", inside
   the rail's own header) is the one control that closes it — the floating
   toggle stays in markup (foreman_rail.js never hides it) but would
   otherwise sit visually on top of the now-full-height dock below. Presentation
   only: no attribute changes, `el.toggle.hidden` in foreman_rail.js is
   untouched. */
body:has(#foreman-rail:not([hidden])) .foreman-rail-toggle {
  display: none;
}

/* TF-252 (V1-1 brief section 7, "Layout"): "Persistent collapsible rail,
   right side, present on every dashboard view... one click to open" — a
   full-height dock, not a floating chat bubble. Converts the rail from a
   bottom-right popover into the wireframe's own right-side panel spanning
   the full viewport height; `.dashboard-main` cedes width to it below via
   `:has()` (pure CSS — no JS change, same technique this file already uses
   for the login/chat shell backdrops above). */
.foreman-rail {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  width: min(380px, 100vw);
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: none;
  border-left: 1px solid var(--border);
  border-radius: 0;
  box-shadow: -6px 0 24px rgba(0, 0, 0, 0.18);
  overflow: hidden;
}

/* Reserves room for the open rail rather than letting it cover the last
   grid column — mirrors the wireframe's own `.main.rail-open { margin-right:
   var(--rail-w) }`. Applied to `body` (the flex COLUMN container), not to
   `.dashboard-main` itself: `.dashboard-main` centers a `max-width: 1100px`
   box via `margin: 0 auto` inside body's full width, and `#foreman-rail` is
   `position: fixed` (out of normal flow) — a `margin-right` added directly
   to `.dashboard-main` does not shrink that box's own width, so its
   centered content simply keeps its pre-rail width and renders UNDER the
   fixed-position rail rather than reflowing narrower (caught on the real
   running page, not assumed from the CSS alone — the whole reason this
   card's own gate requires a real click-through). Padding the flex
   CONTAINER shrinks the space `.dashboard-main`'s own width/centering is
   computed against, which reflows correctly; `.app-header` (body's other
   flex child, not nested inside `.dashboard-main`) narrows the same way,
   which is the correct read of the wireframe's own layout too — its rail
   sits beside the whole page, header included. Narrow viewports (where the
   rail already goes full-width via the min() above) skip the push,
   matching the wireframe's own sub-1100px breakpoint collapsing the rail to
   a stacked, non-overlaying block instead. */
@media (min-width: 860px) {
  body:has(#foreman-rail:not([hidden])) {
    padding-right: min(380px, 100vw);
  }
}

.foreman-rail-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--navy);
  color: var(--lighttext);
  padding: 0.65rem 0.9rem;
  border-bottom: 2px solid var(--amber);
}

.foreman-rail-header h2 {
  margin: 0;
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--lighttext);
}

.foreman-rail-header .link-button {
  color: var(--amber);
}

.foreman-rail-conversation {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.foreman-rail-ask-form {
  flex: 0 0 auto;
  display: flex;
  gap: 0.5rem;
  align-items: flex-end;
  background: var(--canvas);
  padding: 0.6rem 0.75rem;
  border-top: 1px solid var(--border);
}

.foreman-rail-ask-form textarea {
  flex: 1;
  resize: vertical;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  font-size: 0.85rem;
}

.rail-metric-tile-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.rail-metric-tile-wrap .widget-tile {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

.rail-work-item-offer {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.foreman-preview-slot {
  margin-bottom: 1rem;
  padding: 1rem;
  background: var(--cream);
  border: 2px dashed var(--amber);
  border-radius: var(--radius);
}

.foreman-preview-badge {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--navy);
  margin-bottom: 0.5rem;
}

.foreman-preview-error {
  color: var(--refusal-ink);
  font-size: 0.85rem;
}

.foreman-preview-clear {
  margin-top: 0.6rem;
  background: transparent;
  border: 1px solid var(--navy);
  color: var(--navy);
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.35rem 0.75rem;
  border-radius: 6px;
}

/* TF-103 (C-206) — the Tier 2 picker panel. Reuses the rail's own turn/
   preview-badge tokens above so it reads as part of the same conversation,
   not a second visual language. ------------------------------------------ */

.tier2-panel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: var(--cream);
  border: 1px solid var(--tan);
  border-radius: var(--radius);
  padding: 0.75rem;
}

.tier2-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.tier2-field-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--midgray);
}

.tier2-metric-options,
.tier2-dim-options,
.tier2-window-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.tier2-option {
  background: var(--paper);
  border: 1px solid var(--border);
  color: var(--nearblack);
  font-family: var(--font-body);
  font-size: 0.75rem;
  padding: 0.25rem 0.65rem;
  border-radius: 999px;
}

.tier2-option--selected {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--lighttext);
}

.tier2-green-max,
.tier2-amber-max,
.tier2-warn-offset {
  width: 6rem;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.tier2-preview-slot {
  padding: 0.75rem;
  background: var(--paper);
  border: 2px dashed var(--amber);
  border-radius: var(--radius);
}

.tier2-errors {
  color: var(--refusal-ink);
  font-size: 0.8rem;
}

.tier2-empty-note {
  color: var(--midgray);
  font-size: 0.8rem;
}

.tier2-actions {
  display: flex;
  gap: 0.5rem;
}

.tier2-confirm-button {
  background: var(--navy);
  color: var(--lighttext);
  border: none;
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.4rem 0.85rem;
  border-radius: 6px;
}

.tier2-preview-button,
.tier2-discard-button {
  background: transparent;
  border: 1px solid var(--navy);
  color: var(--navy);
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.4rem 0.85rem;
  border-radius: 6px;
}

.tier2-rollback-button {
  margin-top: 0.4rem;
  background: transparent;
  border: 1px solid var(--navy);
  color: var(--navy);
  font-family: var(--font-body);
  font-size: 0.75rem;
  padding: 0.3rem 0.65rem;
  border-radius: 6px;
}

/* TF-312 (C-208c) — the Tier 3 authoring confirm-before-save panel. Mirrors
   .tier2-panel's own tokens exactly (same conversation-turn visual
   language), kept as its own named class rather than reusing "tier2-*" so
   the two unrelated features stay distinguishable in devtools. ---------- */

.authoring-panel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: var(--cream);
  border: 1px solid var(--tan);
  border-radius: var(--radius);
  padding: 0.75rem;
}

.authoring-errors {
  color: var(--refusal-ink);
  font-size: 0.8rem;
}

.authoring-actions {
  display: flex;
  gap: 0.5rem;
}

.authoring-confirm-button {
  background: var(--navy);
  color: var(--lighttext);
  border: none;
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.4rem 0.85rem;
  border-radius: 6px;
}

.authoring-discard-button {
  background: transparent;
  border: 1px solid var(--navy);
  color: var(--navy);
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.4rem 0.85rem;
  border-radius: 6px;
}

.tier2-rollback-confirm {
  margin-top: 0.3rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------------------
   TF-170 (V1-3a) — Operator Console shell (TF-26, child 1 of 6). A
   DIFFERENT page from the Owner's Dashboard above (C-201/TF-98): this is
   the design-preview-governed console at docs/loop/design/
   TF-26_operator_console_preview.html, reusing the SAME brand tokens
   (--navy/--amber/--cream/--canvas/--tan/--midgray/--nearblack/--lighttext/
   --font-*) and the app-header/dashboard-auth-gate rules above rather than
   inventing a second brand language. `.chip` / `.askbtn` / `.preview-badge`
   are new — the preview defines them inline; this file gives them the same
   names so a future panel story's own markup can keep using them without
   guessing at new class names.
   --------------------------------------------------------------------- */

.operator-console-body {
  background: var(--canvas);
}

.operator-console-body .app-header {
  flex-wrap: wrap;
  row-gap: 0.4rem;
}

.chip {
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.25rem 0.65rem;
  border-radius: 999px;
  background: rgba(200, 136, 42, 0.18);
  color: var(--amber);
  border: 1px solid rgba(200, 136, 42, 0.5);
}

.askbtn {
  font-size: 0.85rem;
  font-weight: 700;
  text-decoration: none;
  padding: 0.45rem 1rem;
  border-radius: 6px;
  background: var(--amber);
  color: var(--navy);
  white-space: nowrap;
}

.askbtn:hover {
  filter: brightness(1.1);
}

.preview-badge {
  margin-left: auto;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 0.3rem 0.65rem;
  border-radius: 4px;
  text-transform: uppercase;
  background: var(--cream);
  color: var(--brass-deep, #7a4e00);
  border: 1px dashed var(--amber);
}

/* The real, live badge — solid rather than dashed, so it never reads as the
   design mockup's own "not real" treatment. */
.preview-badge--live {
  border-style: solid;
  background: rgba(12, 163, 12, 0.14);
  color: #0c5c0c;
  border-color: #0c5c0c;
}

.operator-console-body .asof {
  font-size: 0.75rem;
  color: var(--tan);
  width: 100%;
}

.operator-console-main {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 1.5rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.operator-console-modules {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.modchip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--midgray);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
  user-select: none;
}

.modchip input {
  accent-color: var(--brass-deep, #7a4e00);
}

/* TF-295 (UX-C4/F15): the toggle above persists nothing (it only flips a
   body class for the current view), so this qualifier keeps it from reading
   as a saved client setting. */
.modchip-hint {
  font-size: 0.75rem;
  font-style: italic;
  color: var(--midgray);
}

/* ---- KPI row: container + loading/empty states only. A tile's real VALUE
   is a separate, still-blocked panel story — see operator_console.js's own
   module docstring for why no `.kpi--value` state exists here yet. ---- */

.kpis {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.9rem;
  margin: 0 0 1rem;
}

@media (max-width: 900px) {
  .kpis {
    grid-template-columns: repeat(2, 1fr);
  }
}

.kpi {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.kpi-label {
  font-size: 0.75rem;
  color: var(--midgray);
  font-family: var(--font-body);
}

.kpi-value {
  font-size: 1.75rem;
  font-weight: 700;
  font-family: var(--font-mono);
  color: var(--nearblack);
  line-height: 1.05;
}

.kpi--loading .kpi-value {
  color: var(--ink-soft);
}

.kpi-note {
  font-size: 0.75rem;
  color: var(--midgray);
}

/* TF-173 (V1-3d) — a KPI tile patched with a REAL value (patchKpiTileValue,
   operator_console.js), distinct from the generic "empty" placeholder dash
   state above — same "one class per state" discipline the audit strip
   below establishes, rather than reusing .kpi--empty's class for a tile
   that now carries real data. */
.kpi--value .kpi-value {
  color: var(--nearblack);
}

/* Module toggle: the preview's own literal mechanism, reused as-is so any
   future panel's own pg-only/pg-off markup (activity feed rows, the permit
   pipeline panel, ...) is gated by this SAME rule pair rather than a second
   one. */
body.no-genie .pg-only {
  display: none !important;
}

body:not(.no-genie) .pg-off {
  display: none !important;
}

/* ---------------------------------------------------------------------
   TF-173 (V1-3d) — Foreman Q&A analytics (TF-26, child 4 of 6): the 14-day
   "questions your team asked" bar chart and the "how the Foreman answered"
   disposition mix. Renders operator_console.js's own
   renderQaDailyChart()/renderQaDisposition() output. Boxed the same way
   .audit-strip below is (border/background/radius) — there is no shared
   `.card` chrome rule in this file to reuse (see operator_console.js's own
   KPI-tile comment: `class="card kpi"` predates this file having a `.card`
   rule at all, a pre-existing gap, not this card's to fix).
   --------------------------------------------------------------------- */

.operator-console-charts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
  margin-top: 1rem;
}

@media (max-width: 900px) {
  .operator-console-charts {
    grid-template-columns: 1fr;
  }
}

.qa-daily-chart,
.qa-disposition {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--paper);
  padding: 0.9rem 1.1rem 1.1rem;
}

.qa-daily-chart h2,
.qa-disposition h2 {
  font-size: 0.95rem;
  margin: 0 0 0.7rem;
  color: var(--nearblack);
}

.qa-chart-status {
  font-size: 0.8rem;
  color: var(--midgray);
}

/* A genuinely empty window (window_total === 0) and a route this file could
   not reach are TWO DISTINCT classes/colors — never conflated, same
   discipline as the audit strip's own failed/unavailable pair below. */
.qa-chart-status--unavailable {
  color: #8f1f1f;
}

/* ---- 14-day bar chart ---- */

.qa-bars {
  display: flex;
  align-items: flex-end;
  gap: 0.3rem;
  height: 120px;
}

.qa-bar-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  height: 100%;
}

.qa-bar-track {
  flex: 1 1 auto;
  width: 100%;
  display: flex;
  align-items: flex-end;
}

/* height is set inline, per-bar, by operator_console.js's own
   buildQaDailyChartElement — a real CSSOM property assignment (never a
   template string built into markup) driven by a real per-day count. */
.qa-bar {
  width: 100%;
  min-height: 2px;
  background: var(--amber);
  border-radius: 3px 3px 0 0;
}

.qa-bar-label {
  font-size: 0.6rem;
  color: var(--midgray);
  white-space: nowrap;
}

.qa-chart-legend {
  margin-top: 0.6rem;
  font-size: 0.72rem;
  color: var(--midgray);
}

/* ---- disposition stacked bar ---- */

.qa-disposition-bar {
  display: flex;
  height: 26px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--tan);
}

.qa-disposition-segment {
  height: 100%;
}

/* Three distinct brand-consistent tokens, one per bucket — never a shared
   color between "answered" and either refusal reason, and never a shared
   color between the two refusal reasons either, so AC 4's "a refusal is
   counted as a refusal, never folded into answered" is visually true, not
   only true in the underlying counts. */
.qa-disp--answered {
  background: var(--amber);
}

.qa-disp--no-match {
  background: #2a78d6;
}

.qa-disp--unreachable {
  background: #2e7d4f;
}

.qa-disposition-legend {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.qa-disposition-legend-item {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.78rem;
  color: var(--nearblack);
}

.qa-disposition-swatch {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 2px;
  flex: 0 0 auto;
}

.qa-disposition-note {
  margin: 0.6rem 0 0;
  font-size: 0.72rem;
  color: var(--midgray);
}

/* ---------------------------------------------------------------------
   TF-172 (V1-3b) — the automation activity feed (TF-26, child 3 of 6).
   Renders operator_console.js's own renderAutomationFeed() output. A failed
   outcome always gets its OWN dot color (automation-feed-dot--failure),
   independent of waiting/rejected, so a failure is never visually
   indistinguishable from a clean run that merely took a different gate
   path — same "a broken state must never share a color with a healthy one"
   discipline as the audit strip block below. The KPI tile's own
   .kpi--flagged rule (>= 1 failure today) lives at the end of this block,
   next to the KPI row rules it patches.
   --------------------------------------------------------------------- */

.operator-console-automation-feed {
  margin: 0 0 1rem;
  padding: 0.9rem 1.1rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--paper);
}

.operator-console-automation-feed .section-heading {
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--midgray);
  margin: 0 0 0.6rem;
}

.section-heading-live {
  float: right;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--midgray);
}

.automation-feed-item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-top: 1px solid var(--tan);
  font-size: 0.82rem;
}

.automation-feed-item:first-child {
  border-top: none;
}

.automation-feed-time {
  flex: 0 0 58px;
  color: var(--midgray);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  padding-top: 0.1rem;
}

.automation-feed-dot {
  flex: 0 0 10px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-top: 0.25rem;
}

/* Four distinct colors, one per dot variant — no shared token between
   "good" and "failure" the way a single "status: boolean" flag would risk. */
.automation-feed-dot--good {
  background: #0c5c0c;
}

.automation-feed-dot--waiting {
  background: var(--amber, #c8882a);
}

.automation-feed-dot--failure {
  background: #d03b3b;
}

.automation-feed-dot--rejected {
  background: var(--midgray);
}

.automation-feed-body {
  flex: 1 1 auto;
  min-width: 0;
}

.automation-feed-headline {
  color: var(--nearblack);
}

.automation-feed-note {
  font-size: 0.72rem;
  color: var(--midgray);
  margin-top: 0.15rem;
}

.automation-feed-empty {
  font-size: 0.82rem;
  color: var(--midgray);
  padding: 0.4rem 0;
}

/* AC4's own "not the unavailable case" distinction, made visual: a genuinely
   empty result reads as ordinary muted text; an unreachable/unconfigured
   source reads red, matching the audit strip's own failed/unavailable
   color. */
.automation-feed-empty--unavailable {
  color: #8f1f1f;
}

/* The "Automations run today" KPI tile, patched in place by
   applyAutomationsKpiTile() once GET /engine/automation-feed answers.
   .kpi--flagged is added ONLY when failures_today >= 1 (AC2's own "visibly
   flagged") — a second, independent visual signal beyond the note text's
   own wording, same discipline as the audit strip's border-color shift on a
   failed chain below. */
.kpi--flagged .kpi-value {
  color: #8f1f1f;
}

.kpi--flagged .kpi-note {
  color: #8f1f1f;
  font-weight: 650;
}

/* ---------------------------------------------------------------------
   TF-174 (V1-3e) — the run-log chain-verify audit strip (TF-26, child 5 of
   6). Renders operator_console.js's own renderAuditStrip() output — see
   that file's module comment for why "failed" gets its OWN status class and
   OWN color token here rather than the same ".ok" treatment the design
   preview uses for its one, always-verified mockup state: a broken chain
   (verified: false, a REAL field on a REAL response body) must never read
   as the same green check as a healthy one.
   --------------------------------------------------------------------- */

.operator-console-audit-strip {
  display: block;
  margin-top: 1rem;
}

.audit-strip {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  padding: 0.9rem 1.1rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--paper);
}

.audit-strip-status {
  font-weight: 650;
  font-size: 0.85rem;
  white-space: nowrap;
}

.audit-strip-status--loading {
  color: var(--midgray);
}

/* The ONLY state that reads as a green check, on purpose. */
.audit-strip-status--verified {
  color: #0c5c0c;
}

/* A failed/unavailable check is red text, never green — no shared class,
   no shared color variable with .audit-strip-status--verified above. */
.audit-strip-status--failed,
.audit-strip-status--unavailable {
  color: #8f1f1f;
}

.audit-strip-detail {
  font-size: 0.78rem;
  font-weight: 600;
}

.audit-strip-detail--failed,
.audit-strip-detail--unavailable {
  color: #8f1f1f;
}

.audit-strip-meta {
  font-size: 0.75rem;
  color: var(--midgray);
}

/* The whole strip's own border/background shift red on a failed or
   unreachable check — a second, independent visual signal beyond the status
   text's own color, so the failed state is unmistakable even at a glance. */
.audit-strip--failed,
.audit-strip--unavailable {
  border-color: #d03b3b;
  background: rgba(208, 59, 59, 0.06);
}

.operator-console-footer {
  max-width: 1180px;
  margin: 0 auto;
  padding: 1.1rem 1.5rem 1.75rem;
  font-size: 0.75rem;
  color: var(--midgray);
  border-top: 1px solid var(--tan);
}

/* ---------------------------------------------------------------------
   TF-104 (V1-203) — the Tier 1 owner settings panel. Reuses the SAME
   .dashboard-auth-gate rule (C-201, above) and the Tier 2 panel's own
   input/button tokens (.tier2-* just above) rather than inventing a third
   visual language for what is, structurally, one more form-shaped surface
   on this brand.
   --------------------------------------------------------------------- */

.owner-settings-main {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding: 1.5rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.owner-settings-intro {
  font-size: 0.85rem;
  color: var(--midgray);
  margin-bottom: 1rem;
}

.owner-settings-tiles {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.owner-settings-tile-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
}

.owner-settings-tile-heading {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.owner-settings-tile-title {
  font-weight: 700;
  color: var(--nearblack);
}

.owner-settings-tile-type {
  font-size: 0.7rem;
  color: var(--midgray);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.owner-settings-tile-controls {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* TF-293 (F14): Move up/down at the respective edge of the list. */
.owner-settings-tile-controls button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.owner-settings-band-rows {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.owner-settings-band-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.owner-settings-band-row input,
.owner-settings-band-row select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.owner-settings-drift {
  font-size: 0.75rem;
  color: var(--brass-deep, #7a4e00);
}

/* TF-260: inline save feedback. Error uses the same refusal tokens the
   widget error banner (.widget-error-banner) already carries; status
   (success) reuses the answered tokens the QA disposition pill uses —
   no new color language invented for what is structurally the same
   confirm/refuse pair as the rest of this file. */
.owner-settings-save-error {
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  background: var(--refusal-bg);
  color: var(--refusal-ink);
}

.owner-settings-save-status {
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  background: var(--answered-bg);
  color: var(--answered-ink);
}

.owner-settings-rollback {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.3rem;
}

.owner-settings-rollback select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.owner-settings-empty {
  color: var(--midgray);
  font-size: 0.85rem;
}

/* TF-259 (C-207b): owner retrieval of action audit records — the read-only
   history table mounted inside the owner-settings shell above. */
.owner-audit-history {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.owner-audit-history-heading {
  font-size: 1rem;
  margin: 0 0 0.75rem;
}

.owner-audit-history-note {
  margin: 0;
  color: var(--midgray);
  font-size: 0.9rem;
}

.owner-audit-history-filter {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.owner-audit-history-filter-label {
  font-size: 0.85rem;
  color: var(--midgray);
}

.owner-audit-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.owner-audit-history-table th,
.owner-audit-history-table td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
}

.owner-audit-history-escalation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* TF-102 (C-207): the "waiting on you" panel + its Foreman-rail compact
   twin. Navigation-only styling — no button here ever submits a decision,
   only Retry (a re-fetch of this same read-only list) and the tap targets
   that hand the owner off to the Approval Desk's own origin. */
.pending-items-panel {
  margin-bottom: 1rem;
  padding: 1rem;
  background: var(--cream);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.pending-items-heading {
  margin: 0 0 0.6rem;
  font-size: 1rem;
  color: var(--navy);
}

.pending-items-note {
  margin: 0;
  color: var(--midgray);
  font-size: 0.9rem;
}

.pending-items-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.pending-items-row {
  display: block;
}

.pending-items-row--broken .pending-items-link {
  opacity: 0.6;
  cursor: not-allowed;
}

/* TF-316: a row whose desk_url failed the real origin-boundary check —
   distinct from a row with no desk_url at all, so the misconfiguration note
   above the list has a visual anchor. */
.pending-items-row--misconfigured .pending-items-link {
  border-left: 3px solid var(--refusal-ink);
}

.pending-items-misconfigured {
  margin-bottom: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--refusal-bg);
  color: var(--refusal-ink);
  border-radius: var(--radius);
  font-size: 0.85rem;
}

/* AC 6 (mobile-usable): every clickable node in this feature carries this
   class, sized to a real touch target regardless of viewport — no
   hover-only reveal anywhere in cockpit/web/pending_items.js. */
.pending-items-tap-target {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0.5rem 0.75rem;
  font: inherit;
}

.pending-items-link {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--navy);
  text-decoration: none;
}

.pending-items-link:hover,
.pending-items-link:focus-visible {
  border-color: var(--navy);
}

.pending-items-escalation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--refusal-bg);
  color: var(--refusal-ink);
  border-radius: var(--radius);
}

.pending-items-retry {
  border: 1px solid var(--refusal-ink);
  border-radius: var(--radius);
  background: transparent;
  color: var(--refusal-ink);
  cursor: pointer;
}

.pending-items-compact {
  margin-bottom: 0.5rem;
}

.pending-items-compact-link {
  width: 100%;
  justify-content: center;
  text-align: center;
}

/* ---------------------------------------------------------------------
   TF-368 (USR-2) — the Owner-only "Users" page. Reuses the SAME
   .dashboard-auth-gate rule (C-201) and the owner-settings tokens above
   (.owner-settings-save-error/.owner-settings-save-status) rather than
   inventing a third confirm/refuse color language.
   --------------------------------------------------------------------- */

.owner-users-main {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding: 1.5rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.owner-users-intro {
  font-size: 0.85rem;
  color: var(--midgray);
  margin-bottom: 1rem;
}

.owner-users-section-heading {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  color: var(--nearblack);
  margin: 1.25rem 0 0.6rem;
}

.owner-users-invite-form {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
}

.owner-users-invite-fields {
  display: grid;
  grid-template-columns: auto 1fr auto 1fr auto;
  gap: 0.5rem 0.6rem;
  align-items: center;
}

.owner-users-invite-fields input,
.owner-users-invite-fields select {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.owner-users-save-error {
  margin-top: 0.6rem;
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  background: var(--refusal-bg);
  color: var(--refusal-ink);
}

.owner-users-save-status {
  margin-top: 0.6rem;
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  background: var(--answered-bg);
  color: var(--answered-ink);
}

.owner-users-invite-link-result {
  margin-top: 0.6rem;
  font-size: 0.82rem;
  color: var(--midgray);
}

.owner-users-invite-link-list {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.owner-users-security-key-hint {
  margin-top: 0.6rem;
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  background: var(--cream);
  border: 1px solid var(--border);
  color: var(--nearblack);
}

.owner-users-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.5rem;
  font-size: 0.85rem;
}

.owner-users-table th,
.owner-users-table td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.owner-users-table select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
}

.owner-users-table button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.owner-users-last-owner-note {
  margin: 0.35rem 0 0;
  font-size: 0.72rem;
  color: var(--brass-deep, #7a4e00);
}

.owner-users-empty {
  color: var(--midgray);
  font-size: 0.85rem;
  margin-top: 0.5rem;
}

/* TF-368 AC3 — an in-page confirm dialog, never the browser's native
   confirm(). A simple fixed overlay + centered panel; owner_users.js hides
   it by default (`hidden`) and traps Tab focus between its own two buttons
   while open. */
.owner-users-confirm-dialog {
  position: fixed;
  inset: 0;
  background: rgba(10, 22, 40, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
}

.owner-users-confirm-panel {
  background: var(--paper);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  max-width: 420px;
  width: calc(100% - 2rem);
  box-shadow: 0 12px 32px rgba(10, 22, 40, 0.35);
}

.owner-users-confirm-panel h2 {
  margin: 0 0 0.5rem;
  font-family: var(--font-serif);
  font-size: 1.1rem;
  color: var(--nearblack);
}

.owner-users-confirm-panel p {
  margin: 0 0 1rem;
  font-size: 0.88rem;
  color: var(--midgray);
}

.owner-users-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
}
