/* Glass Box dashboard, executive view.
   Built on tokens.css's Midnight Ledger system, second register: deep
   navy header, light powder blue body ground (see theme_token_spec.md),
   one typeface, Futura, for every role, statement-weight moments (KPI
   figures, section titles) carried by weight and size rather than a
   second face. Glass is now white glass over the blue ground, still
   reserved for panels genuinely floating above it (KPI tiles,
   portfolio cards, the Gantt), not applied as flat page texture.
   Color lives in the accent blue, the signal amber (idle/warning
   only, unchanged meaning), the semantic good/bad/alert set, and the
   ordinal --phase-* ramp, all from tokens.css, never a page-specific
   palette. */

.dash-main {
  position: relative;
  max-width: 1180px; margin: 0 auto; width: 100%;
  padding: clamp(14px, 2vh, 28px) 24px;
  /* One shared vertical gap between every major section (portfolio,
     KPIs, deep dive, CTA), so the rhythm reads as one consistent
     spacing decision rather than each section picking its own. */
  --section-gap: clamp(6px, 1vh, 14px);
}

/* ---- Locked to one viewport, no page scroll (desktop only) --------------
   The topbar stays pixel-identical to analysis.html/query.html/graph.html
   (same fixed padding, no viewport-relative sizing), so locking the page
   to 100dvh here only changes how .dash-main fills the remaining space,
   never the topbar. .dash-main is the single flex-filled region below
   it; its own children (portfolio, KPI strip, deep dive, CTA) share
   that fixed height via flex-basis weights and internal scrolling (see
   .timeline-wrap/.panel-wrap's own overflow-y) rather than the page as
   a whole scrolling.
   Mobile browsers report 100vh against the largest possible viewport
   (as if the address bar were already hidden), so a hard height+hidden
   lock clips real content on a phone with no way to scroll to it. Below
   861px the lock is dropped entirely and the page scrolls like a normal
   document instead; see the mobile override further down. */
body:has(.dash-main) {
  display: flex; flex-direction: column;
  height: 100vh; height: 100dvh; overflow: hidden;
}
.dash-main {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  padding-top: clamp(10px, 1.4vh, 20px);
  padding-bottom: clamp(10px, 1.4vh, 20px);
}

/* ---- Ambient background -------------------------------------------------
   The same Midnight Ledger gradient as every other page, applied via
   tokens.css's body.ml-ground class (see dashboard.html). Only the
   dashboard-specific stacking rule for the header lives here. */
body:has(.dash-main) > .topbar {
  position: relative; z-index: 1;
}

/* ---- KPI strip ---------------------------------------------------------
   Four glass tiles. Figures set large and heavy in Futura, the same
   statement register as the rest of the system; labels set small and
   quiet underneath in the same face. Each tile carries exactly one
   accent, mapped to what the metric means, never assigned arbitrarily. */
.kpi-strip-section { margin-bottom: var(--section-gap); flex: 0 0 auto; }
.kpi-strip {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(8px, 1.2vh, 16px);
}
.kpi-card {
  position: relative;
  background: var(--surface-glass);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--hairline-strong);
  border-top: 2px solid var(--kpi-accent, var(--accent));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: clamp(8px, 1.1vh, 14px) 18px clamp(6px, 0.8vh, 10px);
  display: flex; flex-direction: column; gap: 3px;
  text-align: center;
  max-height: clamp(90px, 24vh, 220px);
  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.8);
}
.kpi-card::before {
  content: ""; position: absolute; inset: -1px; border-radius: var(--radius-lg);
  background: radial-gradient(120px 80px at 50% -10%, var(--kpi-accent, var(--accent)), transparent 70%);
  opacity: 0.14; pointer-events: none; z-index: -1;
}
.kpi-card:hover, .kpi-card:focus-within {
  transform: translateY(-3px);
  box-shadow: var(--shadow-hi);
  background: var(--surface-glass-hi);
}
.kpi-card--slip     { --kpi-accent: var(--bad); }
.kpi-card--cost      { --kpi-accent: var(--accent); }
.kpi-card--risk       { --kpi-accent: var(--alert); }
.kpi-card--queries      { --kpi-accent: var(--good); }

.kpi-label {
  font-family: var(--font-body); font-weight: 500; font-size: var(--fs-micro);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-2);
}
.kpi-value {
  font-family: var(--font-display); font-size: clamp(22px, 3vh, 34px); font-weight: 600;
  color: var(--ink-0); line-height: 1.1; font-variant-numeric: tabular-nums;
}
.kpi-value.warn { color: var(--bad); }
/* Clickable KPI values otherwise carry no affordance besides a
   desktop-only cursor:pointer, invisible on touch. The underline gives
   a visible "this is tappable" cue on every pointer type; the coarse-
   pointer rule below also widens the actual hit area. */
.kpi-value--clickable {
  text-decoration: underline; text-decoration-color: var(--hairline-strong);
  text-underline-offset: 4px; border-radius: 4px;
}
.kpi-value--clickable:hover, .kpi-value--clickable:focus-visible {
  text-decoration-color: currentColor;
}
@media (hover: none) and (pointer: coarse) {
  .kpi-value--clickable {
    display: inline-block; padding: 8px 4px; margin: -8px -4px;
  }
}
.kpi-sub {
  font-family: var(--font-body); font-weight: 300;
  font-size: 11px; color: var(--ink-2); line-height: 1.35;
  /* Fixed to a two-line height so every KPI card reserves the same
     vertical space regardless of whether its own sub-text wraps to one
     line or two; a sub longer than two lines still scrolls (the mask
     and scrollbar below stay as the overflow safety net). */
  height: calc(11px * 1.35 * 2);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--hairline-strong) transparent;
  mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent 100%);
}
.kpi-sub::-webkit-scrollbar { width: 6px; }
.kpi-sub::-webkit-scrollbar-thumb {
  background: var(--hairline-strong); border-radius: 999px;
}
.kpi-sub::-webkit-scrollbar-track { background: transparent; }

/* ---- Section heads ------------------------------------------------------ */
.section-head { text-align: center; max-width: 640px; margin: 0 auto clamp(4px, 0.7vh, 10px); }
.section-head h2 {
  font-family: var(--font-display); font-weight: 500; font-size: clamp(17px, 2.1vh, 23px);
  color: var(--ink-0); margin-bottom: 3px;
}
.section-head p {
  font-family: var(--font-body); font-weight: 300;
  font-size: 12px; color: var(--ink-2); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ---- Portfolio grid -----------------------------------------------------
   Live card gets an animated gradient border built from a padding trick:
   an outer wrapper paints the gradient, an inner glass layer sits inset,
   so the border reads as light passing through, not a flat stroke.
   Fixed to a true 6-column row (Core Banking, AML, Data Center, Open
   Banking, Release Automation Pilot, Regulatory Reporting), matching
   the six cards that actually populate it; the earlier 8-column
   definition was a stale mismatch against CLAUDE.md's own documented
   6-column layout. */
.portfolio-section {
  /* Wider than the shared --section-gap on purpose: the portfolio row
     sits directly under six visually heavy cards, so it reads tighter
     against the KPI strip below it than the other section boundaries
     do at the same gap value. The extra room comes from the Gantt
     section's own flexible space (it's the only flex: 1 1 auto section
     in .dash-main), not from the fixed top padding above the portfolio
     row, so the portfolio row's position relative to the header is
     unchanged. */
  margin-bottom: clamp(14px, 2.2vh, 28px); flex: 0 0 auto;
  display: flex; flex-direction: column; min-height: 0;
  /* Deliberately wider than .dash-main's own 1180px cap: the portfolio
     row reads better with more breathing room per card than the rest
     of the page's narrower column. A percentage margin here would
     resolve against .dash-main's own content width, not the viewport,
     so centering against the true viewport instead needs the
     position:relative/left:50%/translateX(-50%) break-out, which is
     containing-block independent. */
  position: relative; left: 50%; transform: translateX(-50%);
  width: 90vw; max-width: 90vw;
}
.portfolio-grid {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: clamp(8px, 1.2vh, 14px);
}
.portfolio-card {
  position: relative;
  background: var(--surface-glass);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  height: clamp(180px, 26vh, 260px);
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.portfolio-card.live {
  background: var(--surface-glass-hi);
  border: none;
  background-clip: padding-box;
  box-shadow: var(--shadow);
}
.portfolio-card.live::before {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg); padding: 2px;
  background: linear-gradient(135deg, var(--bg-1), var(--accent) 50%, var(--accent-bright));
  background-size: 220% 220%;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
}
@media (prefers-reduced-motion: no-preference) {
  .portfolio-card.live::before { animation: borderflow 6s ease-in-out infinite alternate; }
  @keyframes borderflow { from { background-position: 0% 0%; } to { background-position: 100% 100%; } }
}
.portfolio-card:hover, .portfolio-card:focus-visible {
  transform: translateY(-3px);
  box-shadow: var(--shadow-hi);
  outline: none;
}
.portfolio-card.live:hover, .portfolio-card.live:focus-visible {
  background: var(--surface-glass-hi);
}

/* Persistent marker for whichever project's deep dive is currently
   shown below, distinct from the transient :hover state. A ring, not
   just a border tint, so it stays legible even once the mouse has
   moved away and the card is no longer hovered. */
.portfolio-card.selected {
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-hi);
}
.portfolio-card[role="button"] { cursor: pointer; }

/* ---- Card artwork: full-bleed 16:9 image, badge floating on top -----------
   The premium-card pattern: image first, edge to edge within the
   card's rounded corners, with the status badge overlaid rather than
   sitting in the text flow below it. aspect-ratio holds the 16:9 box
   even before the image has loaded, so the layout never jumps once it
   does. A failed image degrades to a plain tinted gradient rather than
   a broken-image icon, so a missing asset never looks like an error. */
.pf-media {
  position: relative; width: 100%;
  background: linear-gradient(135deg, var(--bg-1), var(--bg-2));
  overflow: hidden; flex: 1 1 auto; min-height: 0;
}
.pf-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  filter: saturate(0.92) brightness(0.92);
}
.pf-media--fallback {
  background: linear-gradient(135deg, var(--bg-1), var(--accent) 140%);
}
.pf-media .pf-badge {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 1;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.pf-badge.context, .pf-badge.control {
  background: rgba(8, 13, 34, 0.35); border-color: var(--hairline-strong); color: var(--ink-1);
}
.pf-badge.partial {
  background: rgba(8, 13, 34, 0.3); border-color: rgba(127, 160, 242, 0.5); color: var(--accent-bright);
}

.pf-content {
  padding: clamp(4px, 0.7vh, 8px) clamp(6px, 1vh, 10px); text-align: center; display: flex;
  flex-direction: column; gap: 1px; flex: 0 0 auto; overflow: hidden;
  /* Not a guessed fixed height: every card now always renders the same
     four text nodes (title, meta, status, note), left empty rather than
     omitted when a given card has nothing to say there (see
     renderPortfolio in dashboard.js), and .pf-name/.pf-note are each
     clamped below to a fixed line count. That makes this block's
     content height identical, and therefore its natural (auto) height
     identical, across all six cards without needing a magic-number
     override that can clip a longer title or leave a shorter card's
     image unevenly taller than its siblings. */
}

.pf-badge {
  font-family: var(--font-body); font-weight: 500; font-size: 8px; letter-spacing: 0.07em;
  text-transform: uppercase; padding: 2px 8px; border-radius: var(--radius-pill);
  align-self: center; border: 1px solid var(--hairline-strong); white-space: nowrap;
}
.pf-badge.live {
  background: linear-gradient(135deg, rgba(20, 30, 70, 0.55), rgba(127, 160, 242, 0.55) 55%, rgba(180, 210, 255, 0.55));
  color: #0A1230; font-weight: 700; border-color: transparent;
}
.pf-name {
  font-family: var(--font-display); font-size: 13px; font-weight: 500;
  color: var(--ink-0); margin-top: 1px; line-height: 1.2;
  /* Clamped to exactly 2 lines so title height is fixed regardless of
     name length: a 1-line name still reserves 2 lines of height, a
     longer name truncates with an ellipsis rather than pushing the
     rest of the card's text (and therefore the image above it) taller. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; height: calc(13px * 1.2 * 2);
}
.pf-meta {
  font-family: var(--font-body); font-weight: 300; font-size: 9.5px; color: var(--ink-3);
  line-height: 1.3; height: calc(9.5px * 1.3); overflow: hidden;
}
.pf-status {
  font-family: var(--font-body); font-weight: 500; font-size: 9px; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--accent); margin-top: auto;
}
.portfolio-card.live .pf-status { color: var(--accent-bright); }

/* ---- Expandable tier: prj_aml, prj_openbanking --------------------------
   These carry a real verb behind them (team_allocation, run_scheduler)
   but aren't the hero deep dive, so the treatment sits deliberately
   between .live and the plain context cards: a faint accent hint on
   hover/focus, never the full animated gradient border, so the one
   true deep dive still reads as visually first. */
.portfolio-card.expandable:hover, .portfolio-card.expandable:focus-visible {
  border-color: rgba(127, 160, 242, 0.4);
}

/* ---- Control-group tier: prj_datacenter ---------------------------------
   Deliberately the least visually interesting card on the page. This is
   the honest baseline, a project with zero causal wiring, shown next to
   three tiers of increasing instrumentation rather than looking like an
   unfinished copy of them. Desaturated, dashed border, no hover lift,
   no CTA, on purpose. */
.portfolio-card.control {
  background: rgba(255, 255, 255, 0.3);
  border: 1px dashed var(--hairline);
  filter: saturate(0.7);
}
.portfolio-card.control:hover, .portfolio-card.control:focus-within {
  transform: none; box-shadow: var(--shadow);
}
.pf-note {
  font-family: var(--font-body); font-weight: 300; font-style: italic;
  font-size: 8px; color: var(--ink-3); line-height: 1.25;
  margin-top: auto;
  /* Clamped to exactly 2 lines, same reasoning as .pf-name: present
     (empty) on every non-control card so this line's height is
     reserved everywhere, fixed height so the control card's actual
     sentence can't grow taller than the space every other card already
     set aside for it. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; height: calc(8px * 1.25 * 2);
}

/* ---- The signature element: the causal timeline, as a Gantt chart -------
   Each row is a real phase derived from two adjacent dated events, run
   as a bar from the earlier date to the next event's date, true spans
   rather than illustration. The final event and any deadline render as
   a diamond milestone marker instead of a bar, since nothing follows
   them to derive a duration from. */
.timeline-section {
  margin-bottom: 0; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
}
.timeline-section .section-head { flex: 0 0 auto; }
.timeline-wrap {
  background: var(--surface-glass);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  padding: clamp(10px, 1.6vh, 20px) clamp(20px, 2.4vw, 36px);
  position: relative;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
}
.timeline-loading {
  text-align: center; color: var(--ink-2); font-size: 13px; padding: 40px 0;
  font-family: var(--font-body); font-weight: 300;
}

/* ---- Panel view: AML and Open Banking's inline deep dive -----------------
   Same glass treatment as .timeline-wrap, shown instead of it when a
   project without a milestone/Gantt shape is selected. The diagram
   itself (when present, run_scheduler's fan-out) reuses style.css's
   existing .tv-* rules verbatim, already loaded on this page, so no
   diagram styling is duplicated here. */
.panel-wrap {
  background: var(--surface-glass);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  padding: clamp(10px, 1.4vh, 18px) clamp(20px, 2.4vw, 36px);
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--hairline-strong) transparent;
}
.panel-wrap::-webkit-scrollbar { width: 8px; }
.panel-wrap::-webkit-scrollbar-thumb {
  background: var(--hairline-strong); border-radius: 999px;
}
.panel-wrap::-webkit-scrollbar-track { background: transparent; }
.panel-loading {
  text-align: center; color: var(--ink-2); font-style: italic; font-size: 14px;
  font-family: var(--font-body); font-weight: 300;
  padding: 40px 0;
}
.panel-narration {
  font-family: var(--font-body); font-weight: 300;
  font-size: 13px; line-height: 1.5; color: var(--ink-1);
  text-align: center; max-width: 760px; margin: 0 auto;
}
#panel-traversal-wrap { margin-top: 12px; }
.panel-wrap .traversal-wrap { margin-top: 10px; padding: 10px 14px 8px; }

.gantt {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; justify-content: space-evenly; gap: clamp(4px, 1vh, 12px);
  position: relative;
}
.gantt.has-today { padding-top: 2px; }
.gantt-row {
  display: grid; grid-template-columns: minmax(120px, 180px) 1fr; align-items: center; gap: 14px;
  flex: 1 1 0; min-height: 0;
}
/* A row holding only a point marker (milestone/deadline), not a bar,
   reads as an empty gap at full row height; a lighter flex weight lets
   it settle to a shorter row instead of matching the bar rows 1:1. */
.gantt-row--point { flex: 0.6 1 0; }
.gantt-row-label {
  font-family: var(--font-body); font-weight: 500;
  font-size: clamp(10.5px, 1.3vh, 12.5px); color: var(--ink-1);
  text-align: right; line-height: 1.25;
}
.gantt-track {
  position: relative; height: clamp(18px, 3vh, 30px);
  background: rgba(22, 36, 76, 0.06); border-radius: 8px;
}

.gantt-bar {
  position: absolute; top: 3px; height: 24px; min-width: 10px;
  border-radius: 7px; border: none; cursor: pointer;
  background: var(--bar-accent, var(--accent));
  box-shadow: 0 4px 14px -6px rgba(22, 36, 76, 0.35);
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 10px; overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
/* Enlarges the tap target without changing the bar's visual height: a
   transparent :before extends the hit area to ~44px, centered on the
   same bar, so touch taps land reliably without the bar itself looking
   bulkier than the rest of the Gantt. */
.gantt-bar::before {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; height: 44px; transform: translateY(-50%);
}
.gantt-bar:hover, .gantt-bar:focus-visible {
  transform: scaleY(1.12); filter: brightness(1.12);
  box-shadow: 0 6px 18px -6px rgba(22, 36, 76, 0.45);
  outline: none;
}
.gantt-bar-date {
  font-family: var(--font-mono); font-size: 10px; color: rgba(255, 255, 255, 0.92);
  white-space: nowrap; font-weight: 600;
}

.gantt-milestone {
  position: absolute; top: 50%; width: 18px; height: 18px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--surface-glass-hi);
  backdrop-filter: blur(6px);
  border: 2.5px solid var(--accent);
  border-radius: 4px;
  box-shadow: 0 0 0 6px var(--accent-dim), 0 4px 14px -4px rgba(127, 160, 242, 0.5);
  cursor: pointer; transition: transform 0.15s, box-shadow 0.15s;
}
/* Same enlarged-hit-area trick as .gantt-bar, unrotated so the tap
   target stays a plain axis-aligned square rather than the diamond's
   own rotated bounding box. */
.gantt-milestone::before {
  content: ""; position: absolute; inset: 50% auto auto 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%) rotate(-45deg);
}
.gantt-milestone:hover, .gantt-milestone:focus-visible {
  transform: translate(-50%, -50%) rotate(45deg) scale(1.25); outline: none;
  box-shadow: 0 0 0 9px var(--accent-dim), 0 6px 18px -4px rgba(127, 160, 242, 0.6);
}
.gantt-milestone.deadline {
  border-color: var(--alert);
  box-shadow: 0 0 0 6px rgba(224, 80, 154, 0.15), 0 4px 14px -4px rgba(224, 80, 154, 0.5);
}
.gantt-milestone.deadline:hover, .gantt-milestone.deadline:focus-visible {
  box-shadow: 0 0 0 9px rgba(224, 80, 154, 0.2), 0 6px 18px -4px rgba(224, 80, 154, 0.6);
}
.gantt-milestone.overdue {
  background: var(--alert); border-color: var(--alert);
}
.gantt-date-tag {
  position: absolute; top: 100%; margin-top: 3px; transform: translateX(-50%);
  font-family: var(--font-mono); font-size: 9px; color: var(--ink-3); white-space: nowrap;
}

.gantt-today {
  position: absolute; top: -6px; bottom: -6px;
  left: calc(var(--gantt-label-w, 150px) + 14px + var(--today-pct) * (100% - var(--gantt-label-w, 150px) - 14px) / 100%);
  width: 2px; z-index: 2; pointer-events: none;
  background: repeating-linear-gradient(
    to bottom, var(--signal) 0, var(--signal) 4px, transparent 4px, transparent 8px);
}
.gantt-today::after {
  content: "Today"; position: absolute; top: -20px; left: 50%;
  transform: translateX(-50%); white-space: nowrap;
  font-family: var(--font-body); font-weight: 600; font-size: 9.5px; letter-spacing: 0.06em;
  color: var(--signal);
}

/* ---- Causal connector overlay -------------------------------------------
   The one addition that turns "a nice chart" into "a visible causal
   proof": a single SVG layer over the whole Gantt, drawing one path
   from the idle bar to the slip gap between due date and actual
   completion. Amber where it leaves the idle bar (matching the signal
   token, reserved for the idle period only), the semantic "bad" red
   where it lands on the slip (matching the slip KPI card). Nothing
   here is decorative; both colors are the same semantic tokens used
   elsewhere in the app. */
.gantt-causal-layer {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  overflow: visible;
}
.gantt-causal-path {
  fill: none; stroke: url(#causalGradient); stroke-width: 2;
  stroke-dasharray: 5 4; stroke-linecap: round;
  opacity: 0.85;
}
@media (prefers-reduced-motion: no-preference) {
  .gantt-causal-path { animation: causalflow 22s linear infinite; }
  @keyframes causalflow { to { stroke-dashoffset: -180; } }
}
.gantt-causal-label {
  position: absolute; z-index: 4; transform: translate(-50%, -50%);
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
  color: var(--ink-0); white-space: nowrap; text-align: center;
  background: var(--surface-glass-hi);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--hairline-strong);
  border-radius: 999px; padding: 4px 12px;
  box-shadow: var(--shadow);
  pointer-events: none;
}
.gantt-causal-label b.amber { color: var(--signal); }
.gantt-causal-label b.coral { color: var(--bad); }
.gantt-causal-label .arrow { color: var(--ink-3); padding: 0 2px; }

@media (max-width: 860px) {
  .gantt-causal-label { font-size: 9.5px; padding: 3px 9px; }
}

.tl-fingerprint {
  text-align: center; margin-top: clamp(6px, 1.2vh, 16px);
  font-family: var(--font-body); font-weight: 300; font-size: 12px; color: var(--ink-2);
  flex: 0 0 auto;
}
.tl-fingerprint b {
  color: var(--alert); font-family: var(--font-mono);
}

@media (min-width: 861px) and (max-width: 1000px) {
  .kpi-strip { grid-template-columns: repeat(2, 1fr); }
  .portfolio-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ---- Mobile: drop the one-viewport lock, let the page scroll -----------
   The 100vh/overflow:hidden lock above assumes a desktop-shaped window;
   on a phone the browser reports 100vh against the largest possible
   viewport (address bar hidden), so the real visible area is smaller
   than the box the layout thinks it has to fill. Below 860px, drop the
   lock entirely and let the page scroll like a normal document instead:
   every section (portfolio, KPI strip, Gantt/panel, CTA) reverts to its
   natural auto height, and the Gantt reflows to a normal vertical stack
   in its own container rather than horizontal-scrolling inside a page
   that itself cannot scroll. */
@media (max-width: 860px) {
  body:has(.dash-main) { height: auto; overflow: visible; }
  .dash-main { flex: none; }
  .kpi-strip-section, .portfolio-section, .timeline-section, .dash-cta {
    flex: none;
  }
  .kpi-card { max-height: none; }
  .portfolio-card { height: auto; min-height: 200px; }
  .timeline-wrap, .panel-wrap { flex: none; max-height: none; overflow: visible; }
  .gantt { min-width: 0; gap: 14px; }
  .gantt-row { grid-template-columns: 108px 1fr; gap: 10px; }
  .gantt-row-label { font-size: 11px; }
  .gantt-track { height: 30px; }
  .kpi-strip { grid-template-columns: 1fr; }
  .portfolio-grid { grid-template-columns: 1fr; }
  .timeline-wrap { padding: 40px 20px 24px; }
}

/* ---- CTA back to the query tool ------------------------------------------ */
.dash-cta {
  text-align: center; margin-top: var(--section-gap);
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.cta-button {
  display: inline-block; font-family: var(--font-body); font-size: 13.5px;
  font-weight: 600; text-decoration: none;
  padding: clamp(9px, 1.2vh, 13px) 28px; color: var(--on-accent-ink);
  background: linear-gradient(135deg, var(--accent), var(--accent-grad-end));
  border-radius: 999px; border: none;
  box-shadow: 0 10px 30px -12px rgba(47, 85, 184, 0.4);
  transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.cta-button:hover, .cta-button:focus-visible {
  transform: translateY(-2px);
  background: linear-gradient(135deg, var(--accent-grad-end), var(--accent));
  box-shadow: 0 14px 36px -12px rgba(47, 85, 184, 0.5);
  outline: none;
}
