/* Global: the browser's default [hidden] { display: none } is a
   user-agent rule with the same specificity as any class selector
   in this file. Because our stylesheet loads later, rules like
   .btn { display: inline-flex } silently shadow [hidden] and JS
   setting el.hidden = true does nothing visually. Force the issue
   once here so every element that sets `hidden` actually hides. */
[hidden] { display: none !important; }

/* Header freshness chip — small "Updated 3 days ago" pill that
   any dashboard can drop into its <header> via header-freshness.js. */
.header-freshness {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: var(--surface-2, #F5F2F1);
  color: var(--text-muted, #6D9698);
  border: 1px solid var(--border, #CEDBDD);
  margin-left: 10px;
  vertical-align: middle;
}
.header-freshness.fresh {
  background: var(--state-good-bg);
  color: var(--state-good);
  border-color: var(--state-good);
}
.header-freshness.stale {
  background: var(--state-warn-bg);
  color: var(--state-warn);
  border-color: var(--state-warn);
}
.header-freshness.empty {
  background: var(--surface-2, #F5F2F1);
  color: var(--text-subtle, #9CB9B9);
  font-style: italic;
}
.header-freshness-loading { opacity: 0.6; }

/* Global keyboard focus ring. Matches the .sidebar .brand pattern
   that was the only :focus-visible rule prior to this. Applies to
   anything natively focusable; specific elements (KPI tiles, scope
   tiles, etc.) override with their own focus ring as needed. */
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid #007575;
  outline-offset: 2px;
  border-radius: 4px;
}

/* ============================================================
   AO Continuum Insights — Unified Light Theme
   Used by: landing page, pipeline, financials, odos, upload
   Philosophy: executive-friendly, print-friendly, accessible.
   ============================================================ */

:root {
  /* Brand — Alpha Omega Primary Colors palette, pulled from the
     corporate PowerPoint template's theme1.xml clrScheme (theme name:
     "Alpha Omega Primary Colors"). Mapped onto the existing token
     names so every callsite (var(--brand-navy) / var(--brand-blue)
     etc.) cascades automatically without dashboard edits.

     Migration map (sprint 3.90):
       Token              Was             Now (AO theme)
       --brand-navy       <prior dark>    #0A5253  (DK1)
       --brand-blue       <prior blue>    #007575  (accent3)
       --brand-blue-hover <prior darker>  #005959  (darkened accent3)
       --brand-blue-soft  <prior tint>    #CEDBDD  (accent6, washed teal)
       --bg               <prior light>   #FCF9F8  (LT1, warm off-white)
       --surface          <kept white>    #ffffff
       --surface-2        <kept off-w>    #F5F2F1
       --border           <prior gray>    #CEDBDD  (accent6)
       --border-strong    <prior gray>    #9CB9B9  (accent5)
       --text             <prior dark>    #0A5253  (DK1)
       --text-muted       <prior slate>   #6D9698  (accent4)
       --text-subtle      <prior light>   #9CB9B9  (accent5)
       (literal)          —               #002425  (accent1, top strips)
       (literal)          —               #A3F748  (highlight green)
  */
  --brand-navy:       #0A5253;
  --brand-blue:       #007575;
  --brand-blue-hover: #005959;
  --brand-blue-soft:  #CEDBDD;

  /* Surfaces */
  --bg:         #FCF9F8;
  --surface:    #ffffff;
  --surface-2:  #F5F2F1;
  --border:     #CEDBDD;
  --border-strong: #9CB9B9;

  /* Text */
  --text:        #0A5253;
  --text-muted:  #6D9698;
  --text-subtle: #9CB9B9;

  /* Status */
  --success:      #15803d;
  --success-bg:   #ecfdf5;
  --warning:      #b45309;
  --warning-bg:   #fffbeb;
  --danger:       #b91c1c;
  --danger-bg:    #fef2f2;
  --info:         #1d4ed8;
  --info-bg:      #eff6ff;

  /* Semantic state colors — single source of truth (sprint 3.105).
     Data-meaning colors, distinct from the form/status family above.
     Use for: chart segments encoding goal coverage / health, tile
     accent borders, badge + chip backgrounds, stall / overdue / won /
     lost states. See CLAUDE.md "Color semantic system". */
  --state-good:        #16a34a;   /* won, healthy, at-or-above goal */
  --state-good-bg:     #ecfdf5;
  --state-warn:        #d97706;   /* approaching threshold, 70-99% of goal */
  --state-warn-bg:     #fffbeb;
  --state-bad:         #dc2626;   /* lost, stalled, overdue, deficit */
  --state-bad-bg:      #fef2f2;
  --state-neutral:     #94a3b8;   /* missing data, no target */
  --state-info:        #534AB7;   /* drill-in, radar, secondary action */

  /* Brand accents (existing palette, re-aliased for documentation) */
  --accent-pin:        #ea580c;   /* pinned items in sidebar */
  --accent-highlight:  #A3F748;   /* brand highlight strips */
  --rail-bg:           #002425;   /* dark icon rail */

  /* Categorical chart colors (colorblind-friendlier order) */
  --chart-1: #007575;  /* brand blue */
  --chart-2: #0891b2;  /* teal */
  --chart-3: #7c3aed;  /* violet */
  --chart-4: #059669;  /* emerald */
  --chart-5: #ea580c;  /* orange */
  --chart-6: #db2777;  /* pink */
  --chart-7: #475569;  /* slate */

  /* Elevation */
  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 14px;
  /* Shadow tints — uses the new AO DK1 (#0A5253 → rgb 10, 82, 83) so
     box-shadows pick up a subtle teal cast that matches the palette
     instead of the old slate-blue. Opacity values unchanged. */
  --shadow-sm: 0 1px 2px rgba(10, 82, 83, 0.06), 0 1px 3px rgba(10, 82, 83, 0.08);
  --shadow-md: 0 4px 12px rgba(10, 82, 83, 0.08), 0 2px 4px rgba(10, 82, 83, 0.06);
  --shadow-lg: 0 12px 28px rgba(10, 82, 83, 0.10), 0 4px 8px rgba(10, 82, 83, 0.06);
  /* Polished-depth: a layered card shadow (ambient + key + hairline top
     highlight) and a deeper hover state. The inset top line is what reads
     as "lifted off the page" without resorting to heavy/3D drop shadows. */
  --shadow-card: 0 1px 2px rgba(10, 82, 83, 0.05), 0 6px 16px rgba(10, 82, 83, 0.07), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --shadow-card-hover: 0 2px 4px rgba(10, 82, 83, 0.06), 0 14px 32px rgba(10, 82, 83, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.7);
  /* Faint top-down sheen layered under card content for subtle depth. */
  --surface-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(246, 250, 250, 0.5) 100%);

  /* Typography */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "Monaco", "Cascadia Code", "Roboto Mono", monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: var(--brand-blue); text-decoration: none; }
a:hover { color: var(--brand-blue-hover); text-decoration: underline; }

h1, h2, h3, h4 {
  color: var(--brand-navy);
  letter-spacing: -0.01em;
  margin: 0 0 8px;
}
h1 { font-size: 24px; font-weight: 700; }
h2 { font-size: 18px; font-weight: 600; }
h3 { font-size: 15px; font-weight: 600; }

/* ---------- Layout ---------- */
.app {
  display: grid;
  grid-template-columns: 304px 1fr;   /* rail 52 + drawer 252 — wider for readability */
  min-height: 100vh;
}
/* Collapsed — drawer hidden, rail stays visible. */
.app.sidebar-collapsed {
  grid-template-columns: 52px 1fr;
}

/* ============================================================
   Sidebar — rail + drawer shell (sprint 3.98, sidebar redesign
   phase 1). A thin dark icon RAIL (52px) sits flush-left and is
   always visible; a light DRAWER (210px) holds the nav tree.
   Collapsing hides the drawer and keeps the rail. On narrow
   viewports the whole sidebar slides off-canvas over the page.
   ============================================================ */
.sidebar {
  display: flex;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  /* Skeleton background — paint the rail (dark, 52px) + drawer (surface)
     the instant the document renders, BEFORE renderSidebar() fills in the
     nav tree. The empty <aside> is otherwise transparent, so on every
     navigation the whole sidebar appeared to vanish for a frame and pop
     back. The real .nav-rail / .nav-drawer paint over this in the same
     colors, so there is no seam once the tree mounts. */
  background: linear-gradient(
    to right,
    var(--rail-bg) 0, var(--rail-bg) 52px,
    var(--surface) 52px, var(--surface) 100%
  );
}
/* Collapsed — only the 52px rail is visible, so paint the panel all-dark. */
.app.sidebar-collapsed .sidebar { background: var(--rail-bg); }

/* ---- dark icon rail ---- */
.nav-rail {
  flex: 0 0 52px;
  width: 52px;
  background: var(--rail-bg);
  color: #FCF9F8;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 14px 0 14px;
  gap: 6px;
  z-index: 2;
}
.rail-brand {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-sm);
  color: #FCF9F8;
}
.rail-brand:hover { background: rgba(255, 255, 255, 0.08); text-decoration: none; }
.rail-brand:focus-visible { outline: 2px solid var(--accent-highlight); outline-offset: 1px; }
.rail-brand svg { width: 26px; height: 26px; }
.rail-spacer { flex: 1; }
.rail-btn {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 0;
  cursor: pointer;
  color: #9CB9B9;
}
.rail-btn .ti { font-size: 18px; }
.rail-btn:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }
/* The manuals rail item is an <a> (it navigates) rather than a <button>.
   Kill the global a:hover underline the way .rail-brand does. */
a.rail-btn:hover { text-decoration: none; }
.rail-btn:focus-visible { outline: 2px solid var(--accent-highlight); outline-offset: 1px; }
.rail-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(163, 247, 72, 0.16);
  color: var(--accent-highlight);
  border: 1px solid rgba(163, 247, 72, 0.32);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  text-transform: uppercase;
}
.rail-avatar:hover { background: rgba(163, 247, 72, 0.28); }
.rail-avatar:focus-visible { outline: 2px solid var(--accent-highlight); outline-offset: 1px; }

/* ---- light drawer ---- */
.nav-drawer {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: 16px 10px 10px;
}
.app.sidebar-collapsed .nav-drawer { display: none; }

.drawer-head {
  display: block;
  padding: 2px 8px 14px;
  color: var(--brand-navy);
}
.drawer-head:hover { text-decoration: none; opacity: 0.85; }
.drawer-head:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 2px; }
.drawer-head svg { width: 100%; max-width: 150px; height: auto; display: block; }
.drawer-sub {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin-top: 4px;
}

.nav-drawer nav { display: flex; flex-direction: column; }

/* ---- a drawer nav row ---- */
.nav-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  margin: 1px 0;
  border-radius: var(--radius-sm);
  border-left: 2px solid transparent;
  color: var(--text-muted);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
}
.nav-row:hover {
  background: var(--brand-blue-soft);
  color: var(--brand-blue);
  text-decoration: none;
}
.nav-row > .ti { font-size: 17px; flex: 0 0 17px; color: var(--text-subtle); }
.nav-row .nav-row-lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.nav-row.active {
  background: rgba(0, 117, 117, 0.10);
  border-left: 3px solid var(--brand-blue);
  padding-left: 8px; /* keep text aligned despite the thicker border — sub-row variants override below */
  color: var(--text);
  font-weight: 600;
}
.nav-row-sub.active {
  padding-left: 27px; /* 28px base − 1px for the extra border width */
}
.nav-sub-row.active {
  padding-left: 31px; /* 32px base − 1px for the extra border width */
}
.nav-row.active > .ti { color: var(--brand-blue); }
.nav-row:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: -2px; }
.nav-row .ti-ext { margin-left: auto; flex: none; font-size: 13px; color: var(--text-subtle); }
.nav-feedback-row { margin-top: 4px; }
/* A nav row nested one level under a collapsible group (e.g. the
   insight sub-pages under Pipeline). */
.nav-row-sub { padding-left: 28px; }
.nav-row-sub > .ti { font-size: 15px; flex-basis: 15px; }

/* Pin toggle (sprint 3.98). Faint and invisible until the row is
   hovered/focused or the row is itself pinned. Sits flush right of
   the row so it doesn't shift labels. */
.nav-pin {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: var(--text-subtle);
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.12s, color 0.12s, background 0.12s;
}
.nav-row:hover .nav-pin,
.nav-row:focus-within .nav-pin,
.nav-pin:focus-visible { opacity: 1; }
.nav-pin:hover { background: var(--surface-2); color: var(--brand-navy); }
.nav-pin.is-pinned { opacity: 1; color: var(--accent-pin); }
.nav-pin.is-pinned:hover { color: #c2410c; }
.nav-pin > .ti { font-size: 14px; }

/* Pinned section header — sits at the top of every group panel when
   the user has at least one pinned row. Subtle separator below so it
   reads as its own zone. */
.nav-pinned-section {
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.nav-pinned-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
  padding: 4px 9px 6px;
}
.nav-pinned-title > .ti { font-size: 12px; color: var(--accent-pin); }

/* ---- sub-rows (programs under a BU) ---- */
.nav-sub-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 9px 5px 32px;
  margin: 1px 0;
  border-radius: var(--radius-sm);
  border-left: 2px solid transparent;
  color: var(--text-muted);
  font-size: 13.5px;
  font-weight: 500;
}
.nav-sub-row:hover {
  background: var(--brand-blue-soft);
  color: var(--brand-blue);
  text-decoration: none;
}
.nav-sub-row.active {
  background: rgba(0, 117, 117, 0.10);
  border-left-color: var(--brand-blue);
  color: var(--text);
  font-weight: 600;
}
.nav-sub-row .nav-row-lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.nav-sub-row .ti { font-size: 13px; }
.nav-add-program { color: var(--brand-blue); font-size: 12px; }
.nav-add-program .ti { color: var(--brand-blue); }

/* ---- rail workflow-group icons (phase 2) ---- */
.rail-groups {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
}
.rail-group {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  border: 0;
  background: transparent;
  cursor: pointer;
  color: #9CB9B9;
  border-left: 2px solid transparent;
}
.rail-group .ti { font-size: 19px; }
.rail-group:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }
.rail-group:focus-visible { outline: 2px solid var(--accent-highlight); outline-offset: 1px; }
.rail-group.active {
  background: rgba(163, 247, 72, 0.16);
  border-left-color: var(--accent-highlight);
  color: var(--accent-highlight);
}

/* ---- drawer workflow-group panel (phase 2) ---- */
.nav-group-panel[hidden] { display: none; }
/* Section label in the single sectioned drawer (one heading per workflow
   group, stacked). Small uppercase + subtle, matching the design. */
.drawer-group-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-subtle);
  padding: 14px 9px 5px;
}
.nav-group-panel:first-of-type .drawer-group-title { padding-top: 6px; }

/* ---- scope pills (phase 2) ---- */
.scope-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 2px 9px 8px;
}
.scope-pill {
  flex: 1 1 auto;
  padding: 4px 8px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}
.scope-pill:hover { border-color: var(--brand-blue); color: var(--brand-blue); }
.scope-pill:focus-visible { outline: 2px solid var(--brand-blue); outline-offset: 1px; }
.scope-pill.active {
  background: var(--rail-bg);
  border-color: var(--rail-bg);
  color: #fff;
}

/* ---- collapsible dashboard group (Pipeline + sub-pages, etc.) ---- */
.nav-group { margin: 0; }
.nav-group > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  margin: 1px 0;
  border-radius: var(--radius-sm);
  border-left: 2px solid transparent;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
}
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary > .ti:first-child { font-size: 17px; flex: 0 0 17px; color: var(--text-subtle); }
.nav-group > summary .nav-row-lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.nav-group > summary:hover { background: var(--brand-blue-soft); color: var(--brand-blue); }
.nav-group > summary.active {
  background: rgba(0, 117, 117, 0.10);
  border-left-color: var(--brand-blue);
  color: var(--text);
  font-weight: 600;
}
.nav-group-caret {
  font-size: 13px;
  color: var(--text-subtle);
  transition: transform 0.15s ease;
  margin-left: auto;
}
.nav-group[open] > summary .nav-group-caret { transform: rotate(90deg); }
.nav-group-body { display: flex; flex-direction: column; padding-bottom: 4px; }

/* Sprint 3.98 — linkable group header. The Pipeline group's <summary>
   wraps an <a> that occupies most of the row + the chevron at the
   right. The summary itself loses its hover/active background so the
   inner link can own them, matching the look of a regular nav-row. */
.nav-group-linkable > summary {
  padding: 0;
  background: transparent !important;
  border-left: 0;
}
.nav-group-linkable > summary:hover {
  background: transparent;
}
.nav-group-linkable > summary > .nav-group-caret {
  padding: 7px 9px;
  margin-left: 0;
}
.nav-group-link {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  margin: 1px 0;
  border-radius: var(--radius-sm);
  border-left: 2px solid transparent;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  min-width: 0;
}
.nav-group-link > .ti { font-size: 17px; flex: 0 0 17px; color: var(--text-subtle); }
.nav-group-link .nav-row-lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.nav-group-link:hover { background: var(--brand-blue-soft); color: var(--brand-blue); }
.nav-group-link.active {
  background: rgba(0, 117, 117, 0.10);
  border-left-color: var(--brand-blue);
  color: var(--text);
  font-weight: 600;
}
.nav-group-link.active > .ti { color: var(--brand-blue); }

/* ── Folder group (Business Development) ──────────────────────────
   A group whose body holds other groups (Pipeline, Teaming Agreements,
   Pipeline Identification). Its children step in one level, and their
   own sub-rows step in a second, so the three tiers read apart. */
.nav-group-folder > .nav-group-body { padding-bottom: 2px; }
.nav-group-folder > .nav-group-body > .nav-group > summary { padding-left: 14px; }
.nav-group-folder > .nav-group-body > .nav-group-linkable > summary { padding-left: 0; }
.nav-group-folder > .nav-group-body > .nav-group-linkable .nav-group-link { padding-left: 23px; }
.nav-group-folder .nav-group .nav-row-sub { padding-left: 44px; }
.nav-group-folder .nav-group .nav-row-sub.active { padding-left: 43px; }

/* ── Programs tree: Business Unit ▸ Account ▸ Program ───────────────
   Three tiers nested under the Programs group. Each tier steps in a
   consistent 16px (BU 14 → Account 30 → Program 46) AND a vertical
   guide rail descends through each parent's body, so the parent→child
   relationship reads at a glance. Without the rails + wider steps the
   three tiers visually collapse onto one indentation level. */

/* Tier 1 — Business Unit. Head of each subtree: darkest + boldest. */
.nav-group-nested > summary {
  padding-left: 14px;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}
.nav-group-nested > summary::before {
  content: '\25B8';
  font-size: 9px;
  color: var(--text-subtle);
  transition: transform 0.15s ease;
}
.nav-group-nested[open] > summary::before { transform: rotate(90deg); }
.nav-group-nested > summary .nav-row-lbl { flex: 1; }

/* Guide rail down the BU body — ties its accounts + "+ Program" to it. */
.nav-group-nested > .nav-group-body { position: relative; }
.nav-group-nested > .nav-group-body::before {
  content: '';
  position: absolute;
  left: 21px;
  top: 2px;
  bottom: 6px;
  width: 1px;
  background: var(--border);
}
/* BU-level rows (ungrouped programs + "+ Program") align to the account tier. */
.nav-group-nested > .nav-group-body > .nav-sub-row { padding-left: 30px; }

/* Tier 2 — Account. One step deeper than BU, lighter weight. */
.nav-group-account > summary {
  padding-left: 30px;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
}
.nav-group-account > summary::before {
  content: '\25B8';
  font-size: 8px;
  color: var(--text-subtle);
  transition: transform 0.15s ease;
}
.nav-group-account[open] > summary::before { transform: rotate(90deg); }
.nav-group-account > summary .nav-row-lbl { flex: 1; }

/* Guide rail down the account body — ties its programs to it. */
.nav-group-account > .nav-group-body { position: relative; }
.nav-group-account > .nav-group-body::before {
  content: '';
  position: absolute;
  left: 37px;
  top: 1px;
  bottom: 4px;
  width: 1px;
  background: var(--border);
}

/* Tier 3 — Program rows under an account. */
.nav-group-account > .nav-group-body .nav-sub-row { padding-left: 46px; }

/* Leaf rows in the tree are children — never larger than their parent
   tiers (the old 13.5px made programs read bigger than the BU/account
   headers above them, flattening the hierarchy). */
.nav-group-nested .nav-sub-row { font-size: 13px; }

/* ---- Today drawer feed (phase 4) ---- */
.today-feed { display: flex; flex-direction: column; gap: 1px; padding: 2px 0 4px; }
.today-feed-msg { padding: 8px 9px; font-size: 12px; color: var(--text-muted); }
.today-feed-empty { display: flex; align-items: center; gap: 6px; color: var(--success); }
.today-feed-empty .ti { font-size: 15px; }
.today-feed-err { color: var(--danger); }
.today-feed-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 12.5px;
}
a.today-feed-item:hover {
  background: var(--brand-blue-soft);
  color: var(--brand-blue);
  text-decoration: none;
}
.today-feed-lbl { flex: 1; line-height: 1.35; }
.today-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
.today-dot-info { background: var(--info); }
.today-dot-warning { background: var(--warning); }
.today-dot-danger { background: var(--danger); }

/* ---- pills + misc ---- */
.nav-pill {
  flex: 0 0 auto;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-subtle);
  border: 1px solid var(--border);
}
.nav-pill.beta {
  background: var(--brand-blue-soft);
  color: var(--brand-blue);
  border-color: transparent;
}
.nav-empty {
  display: block;
  padding: 5px 9px 5px 32px;
  font-size: 12px;
  color: var(--text-subtle);
  font-style: italic;
}
.nav-external-empty { opacity: 0.6; cursor: default; }
.nav-external-empty:hover { background: transparent; color: var(--text-muted); }

/* ---- drawer footer — account + feedback ---- */
.drawer-foot {
  margin-top: auto;
  border-top: 1px solid var(--border);
  padding-top: 8px;
}
.nav-account {
  padding: 8px 9px;
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.4;
}
.nav-account-email {
  color: var(--text);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-account-role {
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 9.5px;
  margin-top: 2px;
  color: var(--text-subtle);
}
.nav-account-signout {
  display: inline-block;
  margin-top: 6px;
  color: var(--brand-blue);
  font-size: 12px;
}

/* ---- mobile chrome — hamburger + backdrop ---- */
.nav-mobile-toggle {
  display: none;
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 70;
  width: 40px;
  height: 40px;
  place-items: center;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--brand-navy);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}
.nav-mobile-toggle .ti { font-size: 22px; }
.nav-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(0, 36, 37, 0.45);
}

/* Sprint 3.52 — Skip-to-main-content link for keyboard users.
   Visually hidden until focused (so it doesn't clutter the UI), then
   pops into the top-left corner with strong visual treatment so
   keyboard users can't miss it. Required for 508 §1194.21(a) and
   WCAG 2.1 §2.4.1 Bypass Blocks. */
.skip-link {
  position: absolute;
  top: 8px; left: 8px;
  background: var(--brand-navy); color: #fff;
  padding: 8px 16px;
  border-radius: var(--radius-sm);
  font-weight: 600; font-size: 13px;
  z-index: 1000; text-decoration: none;
  transform: translateY(-150%);
  transition: transform .15s ease;
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 3px solid #fff;
  outline-offset: 2px;
}
main:focus { outline: none; } /* hide the JS-driven focus ring on main */

/* Visually hide content while keeping it accessible to assistive tech.
   Standard "sr-only" pattern. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap;
  border: 0;
}

/* Sidebar CSS is consolidated into the rail + drawer shell block
   above (sprint 3.98 redesign). This slot intentionally left empty. */

/* Full-width / responsive: no max-width cap, so content fills the column on
   wide and ultra-wide monitors instead of leaving a right-hand gutter. */
.main { padding: 24px 28px; max-width: none; }
/* A grid item's automatic minimum width is its min-content width, so one
   wide table (Pipeline → Projections' 5-year grid) stretched .main past
   the viewport and the whole page scrolled sideways — charts and KPI
   tiles cut off at the right edge. min-width: 0 keeps .main at the
   1fr column; wide tables scroll inside their own .table-wrap. */
.main { min-width: 0; }

/* ------------------------------------------------------------------
   EMBEDDED VIEW (?embed=1) — a standalone dashboard rendered as a TAB
   of another page.

   Three dashboards are folded into the AO Financials tab strip as
   iframes: Cash flow, Concentration, Top-deal margin. Each used to carry
   its own four-line copy of these rules in its own <style>, and the
   three had already drifted — which is how the announcement banner, the
   second freshness badge, the hamburger and the skip link all ended up
   rendering INSIDE the tabs ("the announcement shows up on top and on
   the bottom … it's looking like we just took that page and put it
   there"). One block, so a fourth folded-in page inherits it and a new
   chrome element is hidden once rather than three times.

   nav.js also skips injecting most of this when isEmbeddedView() — these
   rules are the belt to that braces, and they still matter on a client
   served a stale nav.js from the service-worker cache.
   ------------------------------------------------------------------ */
html.embed .sidebar { display: none; }
html.embed .app { grid-template-columns: 1fr; min-height: 0; }
html.embed .main { padding: 14px 16px; max-width: none; }
/* App chrome the PARENT page already shows above the tab strip. */
html.embed .aoi-announcement,
html.embed .skip-link,
html.embed .nav-mobile-toggle,
html.embed .nav-backdrop { display: none !important; }
/* The page's own title/subtitle is replaced by the parent's <h1> and
   breadcrumb, and its ↻ Refresh + freshness stamp by the parent's. But the
   header is NOT hidden wholesale any more: #composeEmailSlot lives inside
   it, so hiding the header made Compose Email unreachable from the tab
   while still building the button — a feature lost in silence. */
html.embed .page-header {
  margin: 0 0 8px;
  padding: 0;
  border: 0;
  min-height: 0;
  align-items: center;
}
html.embed .page-header > div:first-child,
html.embed .page-header #refreshBtn,
html.embed .page-header #freshness { display: none; }
/* The dual-source Pipeline/Financials freshness badge. The parent page
   carries its own freshness line above the tab strip, and this one states
   a different pair of facts — two freshness claims stacked on one screen
   read as a contradiction even when both are true. */
html.embed #dataFreshness { display: none; }

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.page-header .subtitle {
  color: var(--text-muted);
  font-size: 13px;
}
/* Shared page-chrome blocks (page-chrome.js). The main block holds
   title + subtitle + freshness; the actions block is the right-aligned
   button cluster. Pages with hand-wired action buttons keep their own
   markup and just don't use .page-header-actions. */
.page-header-main { min-width: 0; }
.page-header-meta { font-size: 12px; color: var(--text-muted); margin-top: 6px; }
.page-header-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* Cross-dashboard tile link (links.js). */
.tile-xlink { display: inline-block; margin-top: 6px; font-size: 11px; color: var(--brand-blue); }

/* Uniform empty / loading / error states (page-chrome.js). */
.page-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}
.page-state h3 {
  font-size: 16px;
  color: var(--brand-navy);
  margin: 0 0 8px;
  font-weight: 500;
}
.page-state p {
  font-size: 13px;
  max-width: 480px;
  margin: 0 auto 16px;
  line-height: 1.6;
}
.page-state .spinner {
  width: 28px; height: 28px;
  border: 3px solid var(--surface-2);
  border-top-color: var(--brand-blue);
  border-radius: 50%;
  margin: 0 auto 16px;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Compact variant — a "Nothing here" state that lives INSIDE a card,
   board column, or synthesis list rather than replacing a whole
   page/section. Left-aligned (most callers are one explanatory line in
   a card body); a caller wanting the old centered single-line look adds
   its own page-local class alongside this one (sprint redesign phase 0). */
.page-state-compact {
  padding: 10px 4px;
  text-align: left;
}
.page-state-compact h3 {
  font-size: 12px;
  color: var(--text-muted);
  margin: 0 0 2px;
  font-weight: 500;
}
.page-state-compact p {
  font-size: 12px;
  max-width: none;
  margin: 0;
  line-height: 1.5;
}
.page-state-compact .spinner {
  width: 18px; height: 18px;
  margin: 0 0 6px;
}

/* ---------- Cards ---------- */
.card {
  background: var(--surface);
  background-image: var(--surface-sheen);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 16px;
  transition: box-shadow 0.18s ease, border-color 0.18s ease;
}
.card:hover { box-shadow: var(--shadow-card-hover); border-color: var(--border-strong); }
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.card-title {
  font-size: 15px;
  font-weight: 500;
  color: var(--brand-navy);
  letter-spacing: 0;
  text-transform: none;
}
.card-header .subtitle {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 400;
}

/* KPI cards */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.kpi {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  background-image: var(--surface-sheen);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 15px 16px;
  box-shadow: var(--shadow-card);
  transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}
/* Polished accent cap — a thin brand gradient strip across the top of
   every tile. Gives the KPI row a designed, cohesive feel; hero + dark
   tiles opt out below. */
.kpi::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--brand-blue), var(--brand-navy));
  opacity: 0.85;
}
.kpi:hover {
  box-shadow: var(--shadow-card-hover);
  transform: translateY(-2px);
  border-color: var(--border-strong);
}
.kpi .label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
}
.kpi .value {
  font-size: 24px;
  font-weight: 700;
  color: var(--brand-navy);
  line-height: 1.1;
}
.kpi .sub {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-subtle);
}
.kpi.delta-up .sub { color: var(--success); }
.kpi.delta-down .sub { color: var(--danger); }

/* One emphasized "hero" KPI per view (redesign directive #4). Add the
   `hero` class to the lead tile in a .kpi-grid; the rest stay demoted
   supporting stats. Navy fill so the headline metric reads first. */
.kpi.hero {
  background: var(--brand-navy);
  background-image: linear-gradient(150deg, #0c6364 0%, var(--brand-navy) 55%, #073e3f 100%);
  border-color: var(--brand-navy);
  box-shadow: 0 2px 4px rgba(10, 82, 83, 0.12), 0 14px 30px rgba(10, 82, 83, 0.22);
}
.kpi.hero::before { background: linear-gradient(90deg, var(--accent-highlight), rgba(163, 247, 72, 0.2)); opacity: 0.9; }
.kpi.hero .label { color: rgba(255, 255, 255, 0.82); }
.kpi.hero .value { color: #ffffff; }
.kpi.hero .sub { color: var(--accent-highlight); }
.kpi.hero .kpi-hint { color: rgba(255, 255, 255, 0.7); }

/* Grids */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 16px; }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }

/* ---------- Tables ---------- */
.table-wrap { overflow-x: auto; }
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
table.data th,
table.data td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
table.data th {
  background: var(--surface-2);
  color: var(--text-muted);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  position: sticky;
  top: 0;
}
table.data tbody tr:hover { background: var(--brand-blue-soft); }

/* ---------- Frozen leading columns on a wide table ----------
   Opt in per table: <div class="table-wrap" data-sticky-cols="3">.
   Scrolling a wide table sideways otherwise loses the row's identity —
   you end up reading a Customer and an Award date with no idea which
   opportunity they belong to.

   NOTHING here applies until assets/js/table-sticky-cols.js has MEASURED
   the real column widths and stamped `data-stick-active` on the wrap.
   That is deliberate, and it is the difference from the first attempt
   (PR #812), which hardcoded `left: calc(10px + 12px + 10px + 12px)` — a
   formula that adds a cell's VERTICAL padding to its horizontal padding
   and lands on a flat 44px for every table in the app. Column widths here
   are content-driven (an opportunity name is nowhere near 44px), so
   columns 2 and 3 landed on top of column 1 the moment anyone scrolled.
   Measure, or don't freeze: if the script never runs, or the table isn't
   wide enough to need this, the table renders exactly as it does today.

   `data-stick-active` carries the EFFECTIVE count, which the script drops
   below the requested one when the frozen columns would leave too little
   room to scroll — so a narrow window degrades to two columns, then one,
   then none, rather than freezing a table nobody can scroll.

   `:not([colspan])` keeps the full-width banner rows (an empty state, a
   group header) out of it — a single cell spanning the table has no
   column to be frozen into. */
.table-wrap[data-stick-active="1"] table.data > * > tr > :nth-child(-n+1):not([colspan]),
.table-wrap[data-stick-active="2"] table.data > * > tr > :nth-child(-n+2):not([colspan]),
.table-wrap[data-stick-active="3"] table.data > * > tr > :nth-child(-n+3):not([colspan]) {
  position: sticky;
  background: var(--surface);
  z-index: 1;
}
/* The header row is already `position: sticky; top: 0`, so a frozen header
   cell is stuck on BOTH axes and the layering has to be explicit — without
   it a frozen body cell paints over the header on vertical scroll. Scoped
   to opted-in tables so no other table's stacking order moves. */
.table-wrap[data-stick-active] table.data th { z-index: 2; }
.table-wrap[data-stick-active="1"] table.data > thead > tr > :nth-child(-n+1),
.table-wrap[data-stick-active="2"] table.data > thead > tr > :nth-child(-n+2),
.table-wrap[data-stick-active="3"] table.data > thead > tr > :nth-child(-n+3) {
  background: var(--surface-2);
  z-index: 3;
}
/* A frozen cell carries its own background — it has to, the scrolling
   content passes underneath — so the row-hover highlight must be
   repainted on it or the hovered row visibly breaks in half. */
.table-wrap[data-stick-active="1"] table.data tbody tr:hover > td:nth-child(-n+1):not([colspan]),
.table-wrap[data-stick-active="2"] table.data tbody tr:hover > td:nth-child(-n+2):not([colspan]),
.table-wrap[data-stick-active="3"] table.data tbody tr:hover > td:nth-child(-n+3):not([colspan]) {
  background: var(--brand-blue-soft);
}
/* Measured offsets, written onto the table by the script and read here.
   `left` is inert on a cell that isn't sticky, so these are safe to apply
   to all three columns whatever the active count is. */
.table-wrap[data-stick-active] table.data > * > tr > :nth-child(1) { left: 0; }
.table-wrap[data-stick-active] table.data > * > tr > :nth-child(2) { left: var(--stick-2, 0px); }
.table-wrap[data-stick-active] table.data > * > tr > :nth-child(3) { left: var(--stick-3, 0px); }
/* A seam on the last frozen column, shown only once something is actually
   hidden behind it. */
.table-wrap[data-stick-active="1"].is-scrolled table.data > * > tr > :nth-child(1):not([colspan]),
.table-wrap[data-stick-active="2"].is-scrolled table.data > * > tr > :nth-child(2):not([colspan]),
.table-wrap[data-stick-active="3"].is-scrolled table.data > * > tr > :nth-child(3):not([colspan]) {
  box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.28);
}
@media print {
  .table-wrap[data-stick-active] table.data > * > tr > * { position: static; }
}
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.data th.num { text-align: right; }

/* A wide table's own horizontal scrollbar sits at the BOTTOM of the
   table, which on a long table is off-screen — so the columns to the
   right can't be reached without scrolling to the end of the page first.
   assets/js/table-sticky-scrollbar.js mirrors it to one bar pinned to the
   bottom of the viewport (a SINGLETON — see the module header for why it
   must not be one per table) and positions it over whichever table is
   currently in that state. Hidden whenever no table qualifies, so a page
   with nothing wide on it looks exactly as it did before. */
.table-sticky-scrollbar {
  position: fixed;
  bottom: 0;
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--surface);
  border-top: 1px solid var(--border);
  height: 16px;
  z-index: 40; /* over page content; under modals/drawers, which own 100+ */
}
.table-sticky-scrollbar-spacer {
  /* Invisible. Its width is the table's scrollable extent scaled to the
     bar's own width, so the thumb matches the real scrollbar's travel. */
  height: 1px;
  display: block;
}
@media print { .table-sticky-scrollbar { display: none; } }

/* ---------- Badges ---------- */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.6;
  border: 1px solid transparent;
}
.badge-success { background: var(--success-bg); color: var(--success); border-color: #bbf7d0; }
.badge-warning { background: var(--warning-bg); color: var(--warning); border-color: #fde68a; }
.badge-danger  { background: var(--danger-bg);  color: var(--danger);  border-color: #fecaca; }
.badge-info    { background: var(--info-bg);    color: var(--info);    border-color: #bfdbfe; }
.badge-muted   { background: #f1f5f9;           color: var(--text-muted); border-color: var(--border); }

.badge.priority-R     { background: var(--state-bad-bg); color: var(--danger); border-color: #fecaca; }
.badge.priority-Aplus { background: #fff1f2; color: #be123c; border-color: #fecdd3; }
.badge.priority-A     { background: #fff7ed; color: #c2410c; border-color: #fed7aa; }
.badge.priority-B     { background: #fefce8; color: #a16207; border-color: #fef08a; }
.badge.priority-C     { background: #f1f5f9; color: var(--text-muted); border-color: var(--border); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.btn:hover { background: var(--brand-blue-soft); border-color: var(--brand-blue); color: var(--brand-blue); text-decoration: none; }
.btn-primary {
  background: var(--brand-blue);
  color: #fff;
  border-color: var(--brand-blue);
}
.btn-primary:hover { background: var(--brand-blue-hover); border-color: var(--brand-blue-hover); color: #fff; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--brand-blue-soft); }
.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.btn-danger:hover { background: #991b1b; border-color: #991b1b; color: #fff; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------- Toasts (toast.js) ---------- */
.toast-host {
  position: fixed; right: 16px; bottom: 16px; z-index: 1000;
  display: flex; flex-direction: column; gap: 8px;
  max-width: min(380px, calc(100vw - 32px));
}
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 4px solid var(--state-neutral);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-md);
  padding: 10px 12px; font-size: 13px; color: var(--text);
  animation: toast-in 0.18s ease-out;
}
.toast-out { animation: toast-out 0.18s ease-in forwards; }
.toast-success { border-left-color: var(--state-good); }
.toast-error   { border-left-color: var(--state-bad); }
.toast-warning { border-left-color: var(--state-warn); }
.toast-info    { border-left-color: var(--state-info); }
.toast-body { flex: 1; line-height: 1.45; }
.toast-action {
  background: transparent; border: none; color: var(--brand-blue);
  font-size: 13px; font-weight: 600; cursor: pointer; padding: 0 4px;
}
.toast-dismiss {
  background: transparent; border: none; color: var(--text-subtle);
  font-size: 18px; line-height: 1; cursor: pointer; padding: 0 2px;
}
.toast-dismiss:hover { color: var(--text); }
@keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { from { opacity: 1; } to { opacity: 0; transform: translateY(8px); } }

/* Sidebar "Jump to…" command-palette discovery button (opens the
   existing command-palette.js quick-switcher). */
.cmdk-launch {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 9px; margin-bottom: 8px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text-muted);
  font-size: 12px; cursor: pointer;
}
.cmdk-launch:hover { border-color: var(--brand-blue); color: var(--brand-blue); }
.cmdk-launch .cmdk-kbd { margin-left: auto; font-size: 10px; border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }

/* ---------- App dialog (dialog.js) ---------- */
.app-dialog {
  border: none; border-radius: var(--radius); padding: 0;
  width: min(440px, 94vw); box-shadow: var(--shadow-lg);
  color: var(--text); background: var(--surface);
}
.app-dialog::backdrop { background: rgba(10, 82, 83, 0.35); }
.app-dialog-form { display: flex; flex-direction: column; gap: 14px; padding: 22px 24px; }
.app-dialog-title { margin: 0; font-size: 17px; color: var(--brand-navy); font-weight: 600; }
/* pre-line lets multi-line bodies (e.g. the Pipeline TM-★ reconcile
   review list) render their \n breaks; single-line callers unchanged. */
.app-dialog-body { margin: 0; font-size: 13px; color: var(--text-muted); line-height: 1.6; white-space: pre-line; max-height: 50vh; overflow-y: auto; }
.app-dialog-field, .app-dialog-typing { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--text-muted); }
.app-dialog-input {
  padding: 8px 10px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); font-size: 14px; background: var(--surface); color: var(--text);
}
.app-dialog-error { font-size: 12px; color: var(--danger); }
.app-dialog-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }

/* ---------- Forms ---------- */
input[type="text"], input[type="search"], select {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font: inherit;
  color: var(--text);
}
input:focus, select:focus {
  outline: none;
  border-color: var(--brand-blue);
  box-shadow: 0 0 0 3px var(--brand-blue-soft);
}
/* Invalid form fields (sprint 3.105) — only after the user has typed
   something, so a required field doesn't flash red on initial load. */
input:invalid:not(:placeholder-shown),
select:invalid:not(:placeholder-shown) {
  border-color: var(--danger);
  background: var(--danger-bg);
}

/* ---------- Empty / Loading states ---------- */
.empty {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-muted);
}
.empty h3 { color: var(--text); margin-bottom: 6px; }

/* Error state — same shape as .empty but with danger coloring. Gives
   pages a consistent error display for failed loads / fetch errors. */
.error-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--danger);
  background: var(--danger-bg);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
}
.error-state h3 { color: var(--danger); margin-bottom: 6px; }
.spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid var(--border);
  border-top-color: var(--brand-blue);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.spinner-lg {
  width: 32px; height: 32px;
  border-width: 3px;
  display: block;
  margin: 0 auto 12px;
}
.chart-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 160px;
  padding: 24px 16px;
  text-align: center;
  color: var(--text-muted);
  font-size: 13px;
}

/* ---------- Meta bar (updated, source, etc.) ---------- */
.meta-bar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 16px;
}

/* ---------- Responsive ---------- */
/* Tablet & phone: the sidebar slides off-canvas and overlays the
   page. A fixed hamburger toggle (injected by nav.js) opens it; a
   click-to-close backdrop sits behind it. The canvas takes the full
   width so dashboards aren't cramped on small screens. */
@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }
  .app.sidebar-collapsed { grid-template-columns: 1fr; }
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    height: 100vh;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: var(--shadow-lg);
  }
  .app.nav-open .sidebar { transform: translateX(0); }
  /* Collapse is meaningless off-canvas — always show the full drawer. */
  .app.sidebar-collapsed .nav-drawer { display: flex; }
  .rail-collapse { display: none; }
  .nav-mobile-toggle { display: grid; }
  .app.nav-open .nav-backdrop { display: block; }
}

/* Tablet portrait (sprint 3.105) — fills the gap between the 900px
   sidebar-off-canvas breakpoint and the 480px phone tweaks. Two-up KPIs,
   single-column multi-grids, tighter tables. */
@media (max-width: 768px) {
  .kpi-grid { grid-template-columns: repeat(2, 1fr); }
  .grid-2 { grid-template-columns: 1fr; }
  .grid-3 { grid-template-columns: 1fr; }
  table.data { font-size: 12px; }
  table.data th, table.data td { padding: 6px 8px; }
  .page-header { padding: 12px 14px; }
}

/* Phone: tighter spacing, single-column tile grids, table cells
   that wrap instead of overflow, KPI tile font sizes that fit on a
   375px viewport. The COO checks dashboards from his phone before
   meetings — these are the diffs that make that workable. */
@media (max-width: 480px) {
  .main { padding: 16px 14px; }
  .page-header { gap: 8px; }
  .page-header h1 { font-size: 22px; }
  .page-header .subtitle { font-size: 12px; }

  /* KPI grid: was 4 cols at desktop, 2 at tablet — collapse to 1
     col with smaller numbers so each tile is fully visible. */
  .kpi-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
  .kpi { padding: 12px; }
  .kpi .value { font-size: 20px; }
  .kpi .label { font-size: 10px; }

  /* Tile grids on home page + APR navigator: single column. */
  .tile-grid { grid-template-columns: 1fr !important; }

  /* Filters & actions wrap onto multiple rows with bigger tap
     targets so fingers (not mice) can hit them. */
  .filters select, .filters input, .filters button {
    min-height: 38px;
    font-size: 14px;
  }
  .actions { gap: 8px; }
  .actions .btn { font-size: 13px; padding: 8px 12px; }

  /* Tables: let cells wrap rather than punching out a horizontal
     scrollbar. Drop padding so column count stays visible. */
  table.data th, table.data td {
    padding: 6px 8px;
    font-size: 12px;
    white-space: normal;
  }

  /* Cards: remove the desktop drop shadow on phone (it doubles up
     the visual weight when stacked vertically). */
  .card { padding: 12px; box-shadow: none; }

  /* Hero: shrink down or it dominates the first viewport. */
  .hero h1 { font-size: 22px; }
  .hero p { font-size: 13px; }

  /* Off-canvas drawer takes most of the screen width on a phone so
     long program labels stay readable. */
  .sidebar { width: 86vw; max-width: 320px; }

  /* Header freshness chip line-wraps under the H1 instead of inline
     so the H1 still fits. */
  .header-freshness { display: inline-block; margin-top: 4px; margin-left: 0; }

  /* Table-to-card collapse — opt-in via class="data responsive". Each
     row becomes a stacked card; each cell shows its column name (from
     the <td data-label="…">) on the left, value on the right. Apply
     only to tables meant to be read on a phone (skip dense workbook
     tables). */
  table.data.responsive thead { display: none; }
  table.data.responsive tbody tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    margin-bottom: 8px;
  }
  table.data.responsive td {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    border: none;
    padding: 4px 0;
    font-size: 12px;
    white-space: normal;
  }
  table.data.responsive td::before {
    content: attr(data-label);
    color: var(--text-muted);
    font-size: 11px;
    flex: none;
  }

  /* Task form: side-by-side title/assignee doesn't fit a phone-width
     drawer — stack instead. */
  .task-thread .task-form-fields { flex-direction: column; }
  .task-thread .task-assignee-picker { flex-basis: auto; width: 100%; }
}

/* ---------- Print ---------- */
@media print {
  html { font-size: 12px; }
  .sidebar, .no-print, .nav-mobile-toggle, .nav-backdrop,
  .filters, .pipeline-sticky-bar, .pipeline-tabs, .page-header-actions,
  .toast-host, .cmdk-backdrop, .cmdk-launch, #dataAsOfBadge,
  .metric-toggle, .cols-menu, .saved-views-menu { display: none !important; }
  .app { grid-template-columns: 1fr; }
  .main { padding: 0; max-width: 100%; }
  .card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; margin-bottom: 10px; }
  /* All tab panels print (the on-screen tab-visibility CSS is overridden
     so a printout isn't just the active tab). Cards explicitly hidden
     via the `hidden` attr (empty / not-yet-loaded) stay hidden. */
  [data-tab]:not([hidden]) { display: block !important; }
  /* Active-filter chips print as a small context line so the reader
     knows what the figures are filtered to. */
  .filter-chips { display: block !important; font-size: 11px; color: #555; }
  .filter-chips .chip-clear-all { display: none !important; }
  a[href]::after { content: ''; } /* don't expand link URLs inline */
}

/* ---------- Comment threads (sprint 3.25) ---------- */
.comment-thread {
  font-size: 13px;
  line-height: 1.5;
}
.comment-thread .comment-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 12px;
  max-height: 300px;
  overflow-y: auto;
  padding-right: 4px;
}
.comment-thread .comment-empty {
  color: var(--text-muted);
  font-style: italic;
  font-size: 12px;
  padding: 6px 0;
}
.comment-thread .comment-item {
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  border-left: 2px solid transparent;
}
.comment-thread .comment-item.is-deleted {
  opacity: 0.5;
  font-style: italic;
}
.comment-thread .comment-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--text-muted);
  margin-bottom: 3px;
}
.comment-thread .comment-author {
  font-weight: 600;
  color: var(--brand-navy);
}
.comment-thread .comment-time { font-variant-numeric: tabular-nums; }
.comment-thread .comment-deleted {
  background: #fee2e2;
  color: #991b1b;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 10px;
}
.comment-thread .comment-delete {
  margin-left: auto;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0 4px;
  border-radius: 4px;
}
.comment-thread .comment-delete:hover {
  background: #fee2e2;
  color: #991b1b;
}
.comment-thread .comment-body {
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}
.comment-thread .comment-form textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13px;
  resize: vertical;
  min-height: 56px;
  background: #fff;
}
.comment-thread .comment-form textarea:focus {
  outline: 2px solid var(--brand-blue, #007575);
  outline-offset: 1px;
}
.comment-thread .comment-form-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}
.comment-thread .comment-form-row .btn {
  font-size: 12px;
  padding: 5px 14px;
}
.comment-thread .comment-form-status {
  font-size: 12px;
  color: var(--text-muted);
}

/* ---------- Task / action items (2026-07-17) ---------- */
.task-thread {
  font-size: 13px;
  line-height: 1.5;
}
.task-thread .task-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
  max-height: 300px;
  overflow-y: auto;
  padding-right: 4px;
}
.task-thread .task-empty {
  color: var(--text-muted);
  font-style: italic;
  font-size: 12px;
  padding: 6px 0;
}
.task-thread .task-item {
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
}
.task-thread .task-item.is-done { opacity: 0.6; }
.task-thread .task-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--text-muted);
  margin-bottom: 3px;
}
.task-thread .task-status-pill {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--brand-blue-soft);
  color: var(--state-info);
}
.task-thread .task-status-pill.done {
  background: var(--state-good-bg);
  color: var(--state-good);
}
.task-thread .task-assignee { font-weight: 600; color: var(--brand-navy); }
.task-thread .task-assignee.is-unassigned { font-weight: 400; font-style: italic; color: var(--text-muted); }
.task-thread .task-time { font-variant-numeric: tabular-nums; }
.task-thread .task-done-note {
  color: var(--text-muted);
  font-style: italic;
}
.task-thread .task-done-btn {
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--brand-blue);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 3px 8px;
  border-radius: var(--radius-sm);
}
.task-thread .task-done-btn:hover { background: var(--brand-blue-soft); }
.task-thread .task-done-btn:disabled { opacity: 0.5; cursor: default; }
.task-thread .task-title {
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}
.task-thread .task-add-toggle { font-size: 12px; padding: 5px 12px; }
.task-thread .task-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.task-thread .task-form-fields {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.task-thread .task-title-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 110px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13px;
  resize: vertical;
  background: #fff;
}
.task-thread .task-title-input:focus {
  outline: 2px solid var(--brand-blue, #007575);
  outline-offset: 1px;
}
.task-thread .task-form input[type="text"] { width: 100%; }
.task-thread .task-assignee-picker { position: relative; flex: 0 0 180px; }
.task-thread .task-assignee-results {
  position: absolute;
  z-index: 5;
  top: calc(100% + 4px);
  left: 0; right: 0;
  max-height: 200px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 4px;
}
.task-thread .task-assignee-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}
.task-thread .task-assignee-item.sel { background: var(--brand-blue-soft); }
.task-thread .task-assignee-name { color: var(--text); }
.task-thread .task-assignee-email { color: var(--text-muted); font-size: 11px; }
.task-thread .task-assignee-empty {
  padding: 8px;
  color: var(--text-muted);
  font-size: 12px;
  text-align: center;
}
.task-thread .task-form-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.task-thread .task-form-row .btn { font-size: 12px; padding: 5px 14px; }
.task-thread .task-form-status { font-size: 12px; color: var(--text-muted); }

/* Homepage "you have open tasks" pop-up (reuses .app-dialog shell). */
.task-popup-dialog { width: min(480px, 94vw); }
.task-popup-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 50vh;
  overflow-y: auto;
}
.task-popup-item {
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.task-popup-title { font-weight: 600; color: var(--text); margin-bottom: 2px; }
.task-popup-deal { font-size: 12px; color: var(--brand-navy); margin-bottom: 2px; }
.task-popup-by { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; }
.task-popup-link { font-size: 12px; padding: 5px 12px; display: inline-block; }

/* ── TM/SF feed affordances (shared by the Market Analysis pages via
   assets/js/tm-sf.js) ──────────────────────────────────────────────
   TM match pills flag whether a source-site record is already tracked
   in TechnoMile/Salesforce (per the latest pipeline pull). Matched
   pills and "In SF ↗" render as links into the Pipeline opp drawer. */
.tm-pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 1.4;
  white-space: nowrap;
  /* This renders as a <button>, <a>, or <span> depending on match status.
     Without resetting native form-control chrome, a <button class="tm-pill">
     renders ~6px taller than a <span class="tm-pill"> with identical
     padding — the browser's default button font metrics survive even
     though font-size/line-height are set above. That made board-view
     cards whose action slot happened to render a <span> (no confident
     match) shorter than cards rendering a <button> (matched or
     addable), producing uneven column heights. */
  appearance: none; -webkit-appearance: none; font-family: inherit; border: none; background: none;
}
.tm-pill.in   { background: var(--success-bg); color: #166534; border: 1px solid #bbf7d0; }
.tm-pill.maybe{ background: #fef3c7; color: #92400e; border: 1px solid #fde68a; }
.tm-pill.out  { background: #f3f4f6; color: #4b5563; border: 1px solid #e5e7eb; }
/* "We don't know" is NOT "not in TM". Both used .tm-pill.out, so a failed
   pipeline.csv load rendered a whole table of grey pills indistinguishable
   from a confident "Not in TM" — the systematic version of the false
   negative BD reported. Dashed border + amber text reads as unresolved. */
.tm-pill.unknown { background: #fffbeb; color: #92400e; border: 1px dashed #fcd34d; }
/* The closest pipeline opportunity, rendered under a "Not in TM" pill.
   "Not in TechnoMile" is the most consequential claim these pages make and
   it used to be asserted with the runners-up discarded, so a reader had no
   way to tell a real miss from a matcher failure. Emitted by tmPillHtml(),
   so it has to live here rather than on one page: the by-capability view
   renders the same pill. */
.tm-pill-near {
  display: block; font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 9.5px; color: var(--text-muted); margin-top: 3px; line-height: 1.35;
  max-width: 210px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The "in"/"maybe" pill also carries .lu-view-btn (it's clickable — opens
   the match detail). That class has its own page-local `button.lu-view-btn
   { font: inherit }` reset (needed where lu-view-btn is used bare, as a
   plain text-sized link), and `button.lu-view-btn`'s specificity otherwise
   beats plain `.tm-pill`, wiping the font-size/line-height above and
   making this pill render ~6px taller than the plain-span "?" variant.
   Two classes beats one class + one tag, so this wins regardless of
   source order without touching the bare lu-view-btn usages. */
.tm-pill.lu-view-btn { font-size: 11px; font-weight: 600; line-height: 1.4; font-family: inherit; }
a.tm-pill { text-decoration: none; cursor: pointer; }
a.tm-pill:hover { filter: brightness(0.95); text-decoration: underline; }
.tm-notify {
  display: inline-block; padding: 2px 8px;
  font-size: 11px; font-weight: 500;
  color: var(--brand-blue); border: 1px solid var(--brand-blue);
  border-radius: 4px; text-decoration: none; white-space: nowrap;
}
.tm-notify:hover { background: var(--brand-blue-soft); }
.tm-notify.disabled { opacity: 0.4; pointer-events: none; }
/* "+ SF" add-to-Salesforce button / "In SF ↗" link in the feed rows. */
.sf-add-btn {
  display: inline-block; padding: 2px 8px;
  font-size: 11px; font-weight: 600; line-height: 1.4; cursor: pointer;
  color: var(--brand-teal, var(--brand-blue));
  border: 1px solid var(--brand-teal, var(--brand-blue));
  background: var(--surface); border-radius: 4px; white-space: nowrap;
  /* Same native-button-chrome reset as .tm-pill, and for the same reason:
     renders as a real <button> in most callsites, so without this it's
     a few px taller than its .tm-pill sibling despite matching padding. */
  appearance: none; -webkit-appearance: none; font-family: inherit;
}
.sf-add-btn:hover { background: var(--brand-teal, var(--brand-blue)); color: #fff; }
.sf-add-btn.disabled { opacity: 0.45; pointer-events: none; cursor: default; }
a.sf-add-btn { text-decoration: none; }

/* "Link…" on a Not-in-TM row: the matcher missed a pursuit we're already
   running, and creating a second opportunity would manufacture the
   duplicate. Quieter than "+ Add" — the common case really is a new opp. */
.tm-link-btn {
  font: inherit; font-size: 11px; padding: 2px 7px; cursor: pointer;
  color: var(--brand-blue); background: none;
  border: 1px dashed var(--border); border-radius: 999px;
}
.tm-link-btn:hover { border-style: solid; }

/* ── Admin announcement banner (assets/js/announcement.js) ─────────────
   One announcement at a time, written at /admin/announcements/, shown at
   the top of <main> on every page until the user dismisses it. In flow
   (not fixed) so it never overlaps the View-As bar. Lives here rather
   than in a page because nav.js mounts it on EVERY page — which is also
   why adding it required a SHELL_VERSION bump (theme.css is precached). */
.aoi-announcement {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin: 0 0 20px;
  padding: 14px 16px 14px 18px;
  border: 1px solid var(--brand-blue-soft);
  border-left: 4px solid var(--brand-blue);
  border-radius: var(--radius);
  background: linear-gradient(180deg, #f2fafa 0%, #eaf5f5 100%);
  box-shadow: var(--shadow-sm);
  color: var(--text);
}
.aoi-announcement-icon {
  flex: none;
  font-size: 22px;
  line-height: 1;
  margin-top: 2px;
  color: var(--brand-blue);
}
.aoi-announcement-text { flex: 1 1 auto; min-width: 0; font-size: 14px; line-height: 1.5; }
.aoi-announcement-title { font-weight: 600; color: var(--brand-navy); font-size: 15px; margin-bottom: 4px; }
.aoi-announcement-text p { margin: 0 0 8px; }
.aoi-announcement-text p:last-child { margin-bottom: 0; }
.aoi-announcement-actions { margin-top: 10px; }
.aoi-announcement-link {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--brand-blue);
  border-radius: var(--radius-sm);
  background: var(--brand-blue);
  color: #fff;
  font: 600 13px/1.2 var(--font);
  text-decoration: none;
  cursor: pointer;
}
.aoi-announcement-link:hover { background: var(--brand-blue-hover); border-color: var(--brand-blue-hover); color: #fff; }
.aoi-announcement-dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-muted);
  font: 500 12px/1.2 var(--font);
  cursor: pointer;
}
.aoi-announcement-dismiss:hover { color: var(--brand-navy); border-color: var(--border-strong); }
@media (max-width: 640px) {
  .aoi-announcement { flex-wrap: wrap; padding: 12px 12px 12px 14px; }
  .aoi-announcement-dismiss { margin-left: auto; }
  .aoi-announcement-dismiss span { display: none; }
}
@media print { .aoi-announcement { display: none; } }
