/* ============================================================
   Journey Map Kit -- Starter Styles
   ------------------------------------------------------------
   Tokens, shared layout, and the components that travel across
   every artifact built from the kit. Project-specific styles
   should live in your project's own stylesheet, not here.
   ============================================================ */


/* ---------- Reset + base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* Anchor jumps land below the sticky top bar (rather than under it). */
  scroll-padding-top: var(--top-bar-height);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}


/* ---------- Brand typography ----------
   Ember Modern Display + Ember Modern Text, Amazon's current
   canonical brand fonts (per design.amazon.com/styleguide brand
   expression guidelines). Self-hosted from the kit's fonts/
   folder so the kit works offline, behind corp-network blocks,
   and on any internal Amazon hosting (Protozoa, Harmony) without
   external CDN dependencies.

   The fallback chain is descending brand alignment:
   1. Ember Modern Display/Text (canonical, self-hosted)
   2. Amazon Ember Display/Ember (older Amazon brand, cdnfonts mirror)
   3. System font (last resort)

   The cdnfonts @import is a graceful fallback only. If Ember
   Modern files load (which they should, being local), cdnfonts
   styles never apply visually. If local fonts fail (corruption,
   404, parsing issue), older Amazon Ember loads from cdnfonts.
   If both fail (corp network block on cdnfonts.com, complete
   offline), system fonts catch everything.

   font-display: swap means the page renders immediately with the
   system-font fallback below, then swaps to Ember Modern when
   the local fonts load. format("opentype") is correct for .otf
   files; browsers handle them natively at slight size cost vs
   .woff2 (acceptable until we convert).

   --font-body: Ember Modern Text for paragraphs, UI, nav.
   --font-display: Ember Modern Display for hero and large
   headlines. Display is optically tuned for large sizes; using
   it on body text would make small copy feel anemic.

   The Amazon brand guideline (per the typography style guide)
   makes the Display vs Text distinction explicit -- Display
   for headlines and subheads, Text for body copy.
   ============================================================ */
@import url("https://fonts.cdnfonts.com/css/amazon-ember");

@font-face {
  font-family: "Ember Modern Display";
  src: url("fonts/EmberModernDisplay-Regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ember Modern Display";
  src: url("fonts/EmberModernDisplay-Bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ember Modern Text";
  src: url("fonts/EmberModernText-Regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ember Modern Text";
  src: url("fonts/EmberModernText-Italic.otf") format("opentype");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Ember Modern Text";
  src: url("fonts/EmberModernText-Bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Ember Modern Text";
  src: url("fonts/EmberModernText-BoldItalic.otf") format("opentype");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

:root {
  --font-body: "Ember Modern Text", "Amazon Ember", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-display: "Ember Modern Display", "Amazon Ember Display", "Ember Modern Text", "Amazon Ember", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}


body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  /* Brand-correct kerning + ligatures + contextual alternates.
     Ember Modern's foundry-supplied kerning tables produce
     better results than the browser's default character-pair
     spacing. Per Amazon brand guidelines, optical kerning is
     suggested on headlines; setting at body level so it applies
     everywhere. "kern" enables pair kerning, "liga" enables
     standard ligatures (fi, fl), "calt" enables contextual
     alternates (Ember Modern uses these for letter combinations
     that need slight glyph adjustment). */
  font-kerning: normal;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  /* Modern CSS line-breaking discipline. Browsers that support
     text-wrap will avoid orphan words on the last line of every
     paragraph -- a kit-wide typographic standard so no single
     word ever hangs alone at the end of a paragraph. Older
     browsers fall through gracefully (text-wrap is unknown,
     CSS-cascade ignores it).
     "pretty" tunes the algorithm for body text: prevents orphans
     while preserving readable line lengths. Headlines get
     "balance" via the rule below for evenly distributed lines. */
  text-wrap: pretty;
}
/* Headlines use balance for evenly distributed wrap (h1-h3 are
   typically 1-3 lines; balance produces visually weighted line
   lengths). Pretty would be fine but balance is calibrated for
   shorter type. */
h1, h2, h3 {
  text-wrap: balance;
}


/* ---------- Theme tokens (light default) ---------- */
:root {
  /* Light theme: off-white over pure white. Softens the page so
     it doesn't feel sterile while keeping ink contrast strong
     (--ink #1f2026 against --bg #f7f6f1 is ~14:1, well above WCAG
     AAA). The "softer feel" comes from no element being pushed to
     visual extremes (no pure white, no pure black), not from
     reduced contrast. Pre-session-9 used --bg #fafaf8 and
     --bg-elev #ffffff which read as glaring on calibrated
     displays. */
  --bg: #f7f6f1;
  --bg-elev: #fcfbf7;
  --ink: #1f2026;
  --ink-soft: #555;
  --ink-muted: #6c6c6c;
  --rule: #e2e1dc;
  --accent: #2a4d8f;
  --accent-soft: #e8efff;

  /* State tokens: hold / flex / break / hold-mitigated.
     Dual-encoded with color, but every component using these
     must also include a glyph and a text label. Color alone
     fails accessibility. */
  --hold-ink: #1f7a4e;
  --hold-bg: #e6f4ec;
  --hold-edge: #5fb389;

  --flex-ink: #8a5a00;
  --flex-bg: #fbefcf;
  --flex-edge: #d4a72c;

  --break-ink: #a8281f;
  --break-bg: #fbe9e6;
  --break-edge: #d76b62;

  /* Spacing scale */
  --pad-xs: 0.5rem;
  --pad-s: 1rem;
  --pad-m: 1.5rem;
  --pad-l: 2.5rem;
  --pad-xl: 4rem;
  --section-pad: 5rem;

  /* Top bar height (sticky nav). Used by `scroll-padding-top` on
     html and `scroll-margin-top` on sections so anchor jumps
     don't get covered by the bar. Padding (0.75rem * 2) + content
     (~2.4rem for h1 + subtitle) + border = ~4.5rem. */
  --top-bar-height: 4.5rem;

  /* Layout */
  --container-max: 72rem;
  --reading-max: 60ch;

  /* Blueprint grid: number of stage columns. Override per artifact:
     <div class="blueprint" style="--bp-cols: 5"> for 5 stages.
     Default sized for 7 stages. */
  --bp-cols: 7;
  --bp-label-col: 12rem;
  --bp-stage-min: 10rem;
}

body[data-theme="warm"] {
  --bg: #f7f2ea;
  --bg-elev: #fdfaf4;
  --ink: #2a1f14;
  --ink-soft: #4a4035;
  --ink-muted: #685c50;
  --rule: #e8dfd0;
  --accent: #8a5a2a;
  --accent-soft: #f3e5d0;

  --hold-ink: #4a6b3a;
  --hold-bg: #ebf0d8;
  --hold-edge: #8aa56a;

  --flex-ink: #8a5a1a;
  --flex-bg: #f4e3c0;
  --flex-edge: #c89a4a;

  --break-ink: #8a3520;
  --break-bg: #f0d8c8;
  --break-edge: #c87560;
}

body[data-theme="dark"] {
  --bg: #14161a;
  --bg-elev: #1c1f24;
  --ink: #f0eee8;
  --ink-soft: #b8b6b0;
  --ink-muted: #9c9a94;
  --rule: #2a2d33;
  --accent: #8cb0ff;
  --accent-soft: #1d2a45;

  --hold-ink: #7fd4a3;
  --hold-bg: #1a2e24;
  --hold-edge: #4a8c6b;

  --flex-ink: #f0c060;
  --flex-bg: #2e2618;
  --flex-edge: #a08040;

  --break-ink: #f08070;
  --break-bg: #2e1a18;
  --break-edge: #a05040;
}


/* ---------- Skip link ---------- */
.skip-link {
  position: absolute;
  top: -100px;
  left: 0.5rem;
  padding: 0.5rem 1rem;
  background: var(--accent);
  color: var(--bg-elev);
  text-decoration: none;
  border-radius: 4px;
  z-index: 9999;
}
.skip-link:focus { top: 0.5rem; }


/* ---------- Top bar ---------- */
.top-bar {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pad-m);
  padding: 0.75rem var(--pad-m);
  /* Translucent + backdrop-blur: the modern nav treatment. As
     content scrolls under the bar, the page beneath blurs and
     tints through, giving the bar a sense of being "above" the
     content rather than a solid lid. The fallback (browsers
     without backdrop-filter) is a near-opaque bg so legibility
     is preserved. color-mix derives the tint from --bg-elev so
     it follows theme tokens automatically. */
  background: color-mix(in srgb, var(--bg-elev) 75%, transparent);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid color-mix(in srgb, var(--rule) 70%, transparent);
}
/* Fallback for browsers without backdrop-filter: keep the bar
   visible by upping the bg opacity. Detected via @supports so
   modern browsers don't get hit. */
@supports not (backdrop-filter: blur(1px)) {
  .top-bar {
    background: color-mix(in srgb, var(--bg-elev) 96%, transparent);
  }
}
.top-bar h1 {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
}
.top-bar .subtitle {
  display: block;
  font-size: 0.75rem;
  color: var(--ink-muted);
  margin-top: 2px;
}
.top-bar-right {
  display: flex;
  align-items: center;
  gap: var(--pad-s);
}
.top-nav {
  display: flex;
  gap: 0.15rem;
}
.nav-link {
  font-size: 0.82rem;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  transition: color 0.15s ease, background 0.15s ease;
}
.nav-link:hover {
  color: var(--ink);
  background: color-mix(in srgb, var(--accent-soft) 70%, transparent);
}
.nav-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  color: var(--ink);
}
.nav-link.nav-active {
  color: var(--ink);
  background: var(--accent-soft);
}
/* CTA variant: the action-oriented nav link ("Make one" on
   start-here) gets accent-fill treatment so it reads as the
   conversion moment, distinct from the four understanding-
   oriented links. The kit's discipline reserves chrome for
   interactive surfaces; here the chrome's saturation also
   reserves itself for the surface that's actually the goal
   of the page. Hover and focus states mirror the standard
   nav-link affordances. */
.nav-link.nav-cta {
  color: var(--bg);
  background: var(--accent);
  font-weight: 600;
}
.nav-link.nav-cta:hover {
  color: var(--bg);
  background: color-mix(in srgb, var(--accent) 88%, var(--ink));
}
.nav-link.nav-cta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  color: var(--bg);
}
.nav-link.nav-cta.nav-active {
  color: var(--bg);
  background: color-mix(in srgb, var(--accent) 88%, var(--ink));
}
/* Reduced motion: drop the color/background transitions; the state
   change still happens, it just doesn't ease. */
@media (prefers-reduced-motion: reduce) {
  .nav-link { transition: none; }
}


/* ---------- Theme toggle ----------
   Single-icon toggle: one circular button that swaps between
   light and dark modes. Icon shows what you'd switch TO, not
   the current state -- this is the modern standard for two-
   state theme controls (GitHub, Vercel, Linear, Stripe).
   The button uses two stacked SVGs (sun + moon); CSS hides the
   one that matches the current theme so only the "switch to"
   icon is visible at any time. No JS required for the swap. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0;
  cursor: pointer;
  color: var(--ink-soft);
  transition: color 0.18s ease, background 0.18s ease, border-color 0.18s ease;
}
.theme-toggle:hover {
  color: var(--ink);
  background: var(--bg-elev);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.theme-toggle-icon {
  width: 16px;
  height: 16px;
  display: block;
}
/* Show the icon that's the OPPOSITE of the current theme --
   what the user would switch to. Default page state is light;
   show moon (switch to dark). When data-theme="dark", show sun
   (switch to light). Smooth swap via opacity. */
.theme-toggle-icon.is-sun { display: none; }
.theme-toggle-icon.is-moon { display: block; }
body[data-theme="dark"] .theme-toggle-icon.is-sun { display: block; }
body[data-theme="dark"] .theme-toggle-icon.is-moon { display: none; }


/* ---------- Section shell (every Part uses this) ---------- */
.section {
  padding: var(--section-pad) var(--pad-m);
  border-bottom: 1px solid var(--rule);
  /* When a nav link jumps to this section, leave room above so
     the sticky top bar doesn't cover the section title. */
  scroll-margin-top: var(--top-bar-height);
}
.section:last-of-type { border-bottom: 0; }
.section-inner {
  max-width: var(--container-max);
  margin: 0 auto;
}

/* Part label -- the kit's signature spine vocabulary */
.section-label {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  margin: 0 0 0.5rem;
  position: relative;
  padding-bottom: 0.5rem;
}
.section-label::after {
  content: "";
  display: block;
  width: 2rem;
  height: 1px;
  background: var(--rule);
  position: absolute;
  bottom: 0;
  left: 0;
}
/* Hero / atmospheric Parts: drop the trailing rule */
.section-label-hero::after { display: none; }

/* Section-job: the question this Part answers (visible, not tooltip) */
.section-job {
  font-size: 1.1rem;
  font-weight: 500;
  color: var(--ink-soft);
  margin: 0 0 0.75rem;
  font-style: italic;
}

.section-title {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 600;
  margin: 0 0 var(--pad-s);
  line-height: 1.25;
  letter-spacing: 0;
}

.section-intro {
  font-size: 1.1rem;
  color: var(--ink-soft);
  line-height: 1.6;
  max-width: 55ch;
  margin: 0 0 var(--pad-l);
}

/* Section 2 elevation: scale gets a different background tone */
.section-scale { background: var(--bg-elev); }


/* ---------- Hero atmosphere (kit-level) ----------
   Adaptive backdrop for any artifact's Part 1 hero. Three layered
   elements (dot grid + radial bloom + flowing curve) derive their
   position and shape from a hashed seed. Default seed is the
   page <title>; override via data-flare-seed on the hero element.

   Discipline: apply ONLY to the Part 1 hero. The rest of the
   artifact has too much content competing for attention; an
   atmosphere there would compete with the journey grid, evidence
   cards, or takeaways. One backdrop per artifact, in the place
   that sets the tone.

   Markup contract:
     <section class="section section-problem hero-atmos-host">
       <div class="hero-atmos" aria-hidden="true">
         <div class="hero-atmos-grid"></div>
         <div class="hero-atmos-bloom"></div>
         <svg class="hero-atmos-flow" ...>
           <defs><linearGradient id="hero-flow-grad">...</linearGradient></defs>
           <path class="hero-atmos-flow-path" .../>
         </svg>
       </div>
       <div class="section-inner">...content...</div>
     </section>
*/
.hero-atmos-host {
  position: relative;
  overflow: hidden;
}
.hero-atmos-host > .section-inner {
  position: relative;
  z-index: 1;
}

.hero-atmos {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Layer 1: dot-grid fabric. Suggests "infrastructure" without
   being literal. Mask fades it out toward the bottom-right so
   it doesn't fight content below. */
.hero-atmos-grid {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    circle at center,
    color-mix(in srgb, var(--ink) 22%, transparent) 1px,
    transparent 1px
  );
  background-size: 28px 28px;
  background-position: 0 0;
  opacity: 0.32;
  -webkit-mask-image: linear-gradient(
    to bottom right,
    rgba(0,0,0,0.85) 0%,
    rgba(0,0,0,0.4) 55%,
    rgba(0,0,0,0) 100%
  );
          mask-image: linear-gradient(
    to bottom right,
    rgba(0,0,0,0.85) 0%,
    rgba(0,0,0,0.4) 55%,
    rgba(0,0,0,0) 100%
  );
}

/* Layer 2: radial bloom. Position derived from --flare-x and
   --flare-y CSS variables (set by JS from the seed). Soft accent
   wash, generous spread, long feather so the gradient never reads
   as a "spotlight" -- it's atmospheric tint, not a focal point. */
/* Bloom: a soft accent wash whose horizontal position is driven
   by JS in lockstep with the thickness flow on the line. As the
   bell of thickness travels left-to-right through the curve, the
   bloom drifts right at the same cadence -- the eye reads the
   bloom as a light source the line is reflecting. Luminance
   breathes on a separate longer cycle so the relationship between
   the two motions never reads as mechanical lock-step.
   
   The gradient stops are gentle (no hot core) so the bloom feels
   like atmospheric tint rather than a localized spotlight. */
.hero-atmos-bloom {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle 70rem at var(--flare-x, 22%) var(--flare-y, 38%),
    color-mix(in srgb, var(--accent) 18%, transparent) 0%,
    color-mix(in srgb, var(--accent) 12%, transparent) 30%,
    color-mix(in srgb, var(--accent) 5%, transparent) 60%,
    transparent 95%
  );
  will-change: opacity;
  animation: hero-bloom-breath 24s ease-in-out infinite;
}
@keyframes hero-bloom-breath {
  0%, 100% { opacity: var(--bloom-opacity-low, 0.26); }
  50%      { opacity: var(--bloom-opacity-high, 0.46); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-atmos-bloom {
    animation: none;
    opacity: 0.4;
  }
}

/* Layer 3: flowing curve. Hidden by default -- the bloom + grid
   already carry the kit's signature on every artifact, and a
   decorative curve over body content competes more than it adds.

   Opt in by adding .hero-atmos-flourish to the host element on
   pages that have room for the gesture (start-here, leadership-
   facing landing pages). Keep it off normal artifacts.
*/
.hero-atmos-flow {
  display: none;
}
.hero-atmos-flourish .hero-atmos-flow {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0.85;
  /* Promote the SVG to its own compositor layer so the browser
     paints the animating path on the GPU rather than triggering
     main-thread paints. Cheap insurance against scroll stutter
     on high-DPI displays without changing any behavior. */
  will-change: transform;
  transform: translateZ(0);
  /* Asymmetric horizontal mask: keeps a faint trace of the curve
     visible even through the text zone so the line reads as ONE
     continuous gesture rather than two disconnected strokes. The
     middle stays at 12% opacity -- present enough to connect the
     two ends, faint enough that body copy is unaffected. */
  -webkit-mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 6%,
    rgba(0,0,0,0.12) 22%,
    rgba(0,0,0,0.12) 78%,
    rgba(0,0,0,1) 94%,
    rgba(0,0,0,1) 100%);
  mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 6%,
    rgba(0,0,0,0.12) 22%,
    rgba(0,0,0,0.12) 78%,
    rgba(0,0,0,1) 94%,
    rgba(0,0,0,1) 100%);
}
/* Presenter-mode mask: slides have wider text spans than hero
   pillars, so the middle fade extends further (15%-85%) and the
   center is softened to 8% so headlines and body copy are
   unobstructed regardless of where they land in the viewport. */
body[data-view="presenter"] .hero-atmos-flourish .hero-atmos-flow {
  -webkit-mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 4%,
    rgba(0,0,0,0.08) 15%,
    rgba(0,0,0,0.08) 85%,
    rgba(0,0,0,1) 96%,
    rgba(0,0,0,1) 100%);
  mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 4%,
    rgba(0,0,0,0.08) 15%,
    rgba(0,0,0,0.08) 85%,
    rgba(0,0,0,1) 96%,
    rgba(0,0,0,1) 100%);
}
/* The edge-thick path: paints a 2.5px stroke layered behind the
   thin 1.4px path. Its gradient is a tileable bell-shape pattern
   that translates horizontally over time (driven from app.js)
   producing the visual of ink-density flowing forward along the
   curve. Mask drops the visibility through the text zone to a
   very low value (4%) so the flowing thickness doesn't flash
   through reading copy, but enough remains to keep the curve's
   weight feeling continuous as the bell flows past. */
.hero-atmos-flow-path-thick {
  stroke-linecap: round;
  -webkit-mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 6%,
    rgba(0,0,0,0.04) 22%,
    rgba(0,0,0,0.04) 78%,
    rgba(0,0,0,1) 94%,
    rgba(0,0,0,1) 100%);
  mask-image: linear-gradient(to right,
    rgba(0,0,0,1) 0%,
    rgba(0,0,0,1) 6%,
    rgba(0,0,0,0.04) 22%,
    rgba(0,0,0,0.04) 78%,
    rgba(0,0,0,1) 94%,
    rgba(0,0,0,1) 100%);
}
.hero-atmos-flow-path {
  /* d attribute set by JS at boot. Default fallback wave so the
     page doesn't render flat if JS is blocked. */
  d: path("M -100 280 C 200 180, 500 380, 800 240 S 1100 120, 1300 200");
}

/* Theme adjustments: the bloom and flow re-theme via --accent
   automatically; we just nudge intensity so warm and dark feel
   right against their surface tones. The bloom uses opacity
   overrides per theme rather than separate keyframes so each
   theme reads with calibrated presence: dark needs more
   luminance (the accent gets absorbed by dark surfaces), warm
   takes a small lift, light is balanced as-is. */
body[data-theme="warm"] .hero-atmos-bloom {
  --bloom-opacity-low: 0.36;
  --bloom-opacity-high: 0.58;
}
body[data-theme="dark"] .hero-atmos-grid { opacity: 0.42; }
body[data-theme="dark"] .hero-atmos-bloom {
  --bloom-opacity-low: 0.38;
  --bloom-opacity-high: 0.62;
}
body[data-theme="dark"] .hero-atmos-flourish .hero-atmos-flow { opacity: 0.85; }
/* Light theme: the accent stroke against the off-white background.
   Now that the bg is meaningfully off-white (#f7f6f1, not pure
   white), the curve has surface to push against and doesn't
   need its pre-session-9 boost (1.8px / opacity 1.0). Softening
   to match dark-mode's quieter presence so light mode feels
   less foregrounded while preserving readability. */
body[data-theme="light"] .hero-atmos-flow-path,
body:not([data-theme]) .hero-atmos-flow-path {
  stroke-width: 1.5;
}
body[data-theme="light"] .hero-atmos-flourish .hero-atmos-flow,
body:not([data-theme]) .hero-atmos-flourish .hero-atmos-flow {
  opacity: 0.85;
}

/* Hide the atmosphere when it can't read or shouldn't compete. */
@media print {
  .hero-atmos { display: none; }
}
body[data-view="map"] .hero-atmos { display: none; }


/* ---------- Part 1 / Problem hero ---------- */
.problem-hero { padding-block: var(--pad-xl) var(--pad-m); }
.problem-thesis {
  font-size: 2.5rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0;
  margin: 0 0 var(--pad-m);
  max-width: 22ch;
}
.problem-thesis-accent { color: var(--accent); }

/* Diagnostic line: one-line state-of-play under the hero */
.problem-diagnostic {
  display: inline-flex;
  gap: 0.75rem;
  align-items: center;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink-soft);
  padding: 0.5rem 0.85rem;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 999px;
  margin-bottom: var(--pad-l);
}
.problem-diagnostic strong { color: var(--ink); }


/* ---------- Bet / pillar cards (used in Part 1) ----------
   Editorial pillars naming the kit's claims. Not interactive --
   no card chrome. Gutter hairlines separate columns. Same
   anti-vibe-code discipline applied across every kit artifact:
   chrome only on interactive surfaces, typography everywhere
   else. */
.bet-row {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--pad-l);
  position: relative;
}
.bet-item {
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  position: relative;
}
/* Vertical column-separator hairlines were removed: they fought
   the breathing curve in the hero atmosphere, and the pillars'
   typographic gutters carry enough separation on their own. The
   set reads as three editorial columns held by whitespace. */
.bet-item-num {
  display: inline-block;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: var(--pad-xs);
}
.bet-item-name {
  font-size: 1.15rem;
  font-weight: 600;
  margin: 0 0 0.5rem;
}
.bet-item-why {
  font-size: 0.95rem;
  color: var(--ink-soft);
  margin: 0;
}


/* ---------- Part 2 / Scale: KPI strip ---------- */
.scale-kpi-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--pad-m);
  margin: var(--pad-m) 0;
}
.scale-kpi {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.scale-kpi-num {
  font-size: 3rem;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
}
.scale-kpi-label {
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink);
}
.scale-kpi-sub {
  font-size: 0.8rem;
  color: var(--ink-muted);
}


/* ---------- Part 3 / Journey: blueprint grid ----------
   .bp-wrap is the positioning context for floating scroll arrows --
   no overflow on it. The .blueprint itself owns the horizontal scroll.
   Each .bp-row is its own independent grid (HITL/CS Help pattern,
   not subgrid) -- this is the only structure where position: sticky
   on .bp-label reliably anchors to the blueprint's scrollport. */
.bp-wrap {
  margin: var(--pad-m) 0;
  position: relative;
  overflow: visible;
}
.blueprint {
  border: 1px solid var(--rule);
  background: var(--bg-elev);
  border-radius: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  min-width: 0;
}
/* Each row defines its own column tracks identically. */
.bp-row {
  display: grid;
  grid-template-columns: var(--bp-label-col) repeat(var(--bp-cols), minmax(var(--bp-stage-min), 1fr));
  align-items: stretch;
  min-width: calc(var(--bp-label-col) + var(--bp-stage-min) * var(--bp-cols));
}
/* (Each .bp-row is its own grid -- defined above near .blueprint.
   No subgrid: position: sticky on .bp-label needs .blueprint as its
   scrollport ancestor, which subgrid would inadvertently break.) */
.bp-label {
  padding: var(--pad-s);
  background: var(--bg);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  /* Sticky-left so label column stays visible during horizontal scroll */
  position: sticky;
  left: 0;
  z-index: 5;
}
.bp-lbl-title {
  font-size: 0.85rem;
  font-weight: 600;
}
.bp-lbl-sub {
  font-size: 0.72rem;
  color: var(--ink-muted);
  line-height: 1.35;
}
/* Interactive-row hint: small badge in the row label that signals
   "the nodes in this row are clickable." Use on rows with .bp-popover
   triggers (effort, emotion, etc.). */
.bp-lbl-hint {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.4rem;
  font-size: 0.65rem;
  font-weight: 500;
  color: var(--accent);
  letter-spacing: 0.01em;
  line-height: 1.3;
}
.bp-lbl-hint-icon {
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  opacity: 0.85;
}
.bp-stage-head {
  padding: var(--pad-s);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  background: var(--bg);
}
.bp-stage-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.75rem;
  font-weight: 700;
  background: var(--bg-elev);
  border: 1.5px solid var(--rule);
  border-radius: 50%;
  color: var(--ink-soft);
}
.bp-stage-name {
  font-size: 0.9rem;
  font-weight: 600;
}
.bp-stage-actor {
  font-size: 0.72rem;
  color: var(--ink-muted);
}

/* Cells */
.bp-c {
  padding: var(--pad-s);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-height: 5rem;
  position: relative;
  overflow: hidden;
}
.bp-c-headline {
  font-size: 0.88rem;
  font-weight: 600;
}
.bp-c-sub {
  font-size: 0.78rem;
  color: var(--ink-soft);
  line-height: 1.4;
}
.bp-c-empty .bp-c-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ink-muted);
  opacity: 0.4;
  margin-top: 0.5rem;
}

/* Layer rows: subtle background tint per layer */
.bp-layer-customer .bp-c { background: color-mix(in srgb, var(--accent-soft) 35%, var(--bg-elev)); }
.bp-layer-frontstage .bp-c { background: var(--bg-elev); }
.bp-layer-backstage .bp-c { background: color-mix(in srgb, var(--bg) 70%, var(--bg-elev)); }
.bp-layer-support .bp-c { background: color-mix(in srgb, var(--bg) 90%, var(--bg-elev)); }

/* Separation lines: rendered as named designed elements, not just borders.
   The row uses the same grid as other rows, with the band cell spanning
   columns 2 through end (.bp-sep-band has grid-column: 2 / -1). */
.bp-sep-label {
  background: var(--bg-elev);
  font-size: 0.72rem;
  color: var(--ink-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 0.5rem var(--pad-s);
  border-right: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.bp-sep-band {
  grid-column: 2 / -1;
  padding: 0.5rem var(--pad-s);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--bg-elev);
  font-size: 0.78rem;
  color: var(--ink-muted);
}
/* Line of Visibility: the "designed" separator. If your artifact's
   invisibility / exposure / boundary bet lives here, this is its home. */
.bp-sep-visibility .bp-sep-label,
.bp-sep-visibility .bp-sep-band {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}


/* ---------- Part 4 / Evidence: hold / crack stress cards ---------- */
.stress-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--pad-s);
}
.stress-item {
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 8px;
  overflow: hidden;
}
.stress-head {
  display: flex;
  align-items: baseline;
  gap: var(--pad-s);
  padding: var(--pad-s) var(--pad-m);
  border-bottom: 1px solid var(--rule);
}
.stress-num {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
  text-transform: uppercase;
  font-weight: 600;
}
.stress-name { font-size: 1.15rem; font-weight: 600; margin: 0; }
.stress-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
}
.stress-col {
  padding: var(--pad-m);
  border-right: 1px solid var(--rule);
}
.stress-col:last-child { border-right: 0; }

/* State pill: dual-encoded with color + glyph + text */
.stress-state {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  margin-bottom: 0.5rem;
}
.stress-state-hold {
  background: var(--hold-bg);
  color: var(--hold-ink);
  border: 1px solid var(--hold-edge);
}
.stress-state-flex {
  background: var(--flex-bg);
  color: var(--flex-ink);
  border: 1px solid var(--flex-edge);
}
.stress-state-break {
  background: var(--break-bg);
  color: var(--break-ink);
  border: 1px solid var(--break-edge);
}
.stress-state-glyph {
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  border-radius: 50%;
}
.stress-state-hold .stress-state-glyph { background: var(--hold-edge); }
.stress-state-flex .stress-state-glyph { background: var(--flex-edge); }
.stress-state-break .stress-state-glyph { background: var(--break-edge); }

.stress-text { font-size: 0.95rem; color: var(--ink); margin: 0 0 0.75rem; }
.stress-quote {
  border-left: 2px solid var(--rule);
  padding: 0.25rem 0.75rem;
  margin: 0;
  font-style: italic;
  color: var(--ink-soft);
  font-size: 0.9rem;
}
.stress-quote cite {
  display: block;
  font-style: normal;
  font-size: 0.75rem;
  color: var(--ink-muted);
  margin-top: 0.25rem;
}


/* ---------- Part 5 / Takeaway: roadmap + recommendation ---------- */
.recommendation {
  padding: var(--pad-m);
  background: var(--accent-soft);
  border-left: 4px solid var(--accent);
  border-radius: 0 8px 8px 0;
  margin: var(--pad-m) 0 var(--pad-l);
}
.recommendation-eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
  margin: 0 0 0.5rem;
}
.recommendation-text {
  font-size: 1.25rem;
  font-weight: 500;
  margin: 0;
  color: var(--ink);
}

.roadmap {
  display: grid;
  gap: var(--pad-s);
}
.roadmap-phase {
  padding: var(--pad-s) var(--pad-m);
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 8px;
}
.roadmap-phase-eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  margin: 0 0 0.25rem;
}
.roadmap-phase-name {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 0 0 0.35rem;
}
.roadmap-phase-body {
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin: 0;
}


/* ---------- Sources sidecar with cross-linking ---------- */
.page-footer {
  padding: var(--pad-l) var(--pad-m);
  background: var(--bg-elev);
  border-top: 1px solid var(--rule);
}
.sources-details {
  max-width: var(--container-max);
  margin: 0 auto;
}
.sources-summary {
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  padding: 0.5rem 0;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.sources-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.sources-summary::before {
  content: "+";
  font-size: 1.25rem;
  font-weight: 400;
  color: var(--ink-muted);
  transition: transform 0.2s;
  display: inline-block;
  width: 1rem;
}
.sources-details[open] .sources-summary::before {
  content: "-";
}
.sources-summary::-webkit-details-marker { display: none; }
.sources-list {
  margin: var(--pad-s) 0 0;
  padding: 0;
  display: grid;
  gap: var(--pad-s);
}
.sources-list dt {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-muted);
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.sources-qualifier {
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-soft);
  font-style: italic;
}
.sources-backlink {
  font-size: 0.72rem;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px dotted var(--accent);
}
.sources-backlink:hover, .sources-backlink:focus-visible {
  border-bottom-style: solid;
}
.sources-list dd {
  margin: 0.25rem 0 0;
  padding: 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
  line-height: 1.5;
}

/* Source-flash animation for cross-linking */
.source-flash {
  animation: sourceFlash 1.5s ease-out;
}
@keyframes sourceFlash {
  0%, 100% { background: transparent; }
  20% { background: var(--accent-soft); }
}
@media (prefers-reduced-motion: reduce) {
  .source-flash {
    animation: none;
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
}


/* ---------- Map-only view toggle ----------
   Two reading modes:
     1. Narrative (default) -- all sections visible
     2. Map only -- body[data-view="map"] hides every section except
        .section-journey, plus the section nav and footer.
   Toggle button sits next to the theme pill in the top bar.
   Keyboard: m toggles, Esc exits. Persists in localStorage. */
.view-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  height: 34px;
  padding: 0 0.75rem;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 500;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
  white-space: nowrap;
}
.view-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.view-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.view-toggle[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
body[data-theme="dark"] .view-toggle[aria-pressed="true"] {
  color: var(--bg);
}
/* In map-only mode, compress to icon-only */
body[data-view="map"] .view-toggle .view-toggle-label {
  display: none;
}
body[data-view="map"] .view-toggle {
  padding: 0 0.5rem;
  width: 34px;
}
/* Hide everything except the journey section in map-only mode */
body[data-view="map"] .section:not(.section-journey),
body[data-view="map"] .top-nav,
body[data-view="map"] .page-footer,
body[data-view="map"] .btt {
  display: none !important;
}
body[data-view="map"] .section-journey {
  padding-top: 1.5rem !important;
  padding-bottom: 2rem !important;
}
/* Hide the "Part 3" eyebrow in map-only -- no other parts to anchor against */
body[data-view="map"] .section-journey .section-label {
  display: none;
}


/* ---------- Stage header gradient + splash on hover ----------
   The stages row has ONE solid gradient line across its top -- a single
   continuous bar that fades from accent (start) to red (end). Each stage
   tile is otherwise neutral; only the top stripe carries the gradient.

   On hover (or when col-highlight is applied), every cell in that column
   gets a top-anchored color splash that slides down the column. The
   splash color comes from --col-glow, set per stage column below so it
   matches the stage row gradient.

   IMPORTANT: the gradient bar is rendered as a top border on each stage
   head (not a row-level ::before) so the row stays NON-positioned --
   that lets position:sticky on .bp-label use .blueprint as its scroll
   ancestor and the label column stays pinned during horizontal scroll. */
.bp-row.bp-stages .bp-stage-head {
  position: relative;
  overflow: hidden;
  background: var(--bg);
  transition: background-color 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  border-top: 3px solid var(--col-glow, var(--accent));
}
/* Stages-row label needs to match the stage heads' border-top so the
   row baselines line up, and needs z-index above the row's splash
   ::after pseudos so the label content isn't dimmed. */
.bp-row.bp-stages > .bp-label {
  background: var(--bg);
  border-top: 3px solid transparent;
  z-index: 6;
}
.bp-stage-num {
  background: var(--bg-elev);
  border: 1.5px solid var(--rule);
  color: var(--ink-soft);
  position: relative;
  z-index: 2;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1),
              border-color 0.25s ease,
              color 0.25s ease;
}

/* Per-stage glow color is set by app.js at boot (applyStageGlows).
   For each [data-stage] element, JS samples the stages-row gradient
   at that stage's center position -- (N - 0.5) / total -- and writes
   --col-glow inline. This works for any stage count and keeps helper
   elements (effort hits, sticky-header clones) in sync without
   stage-specific CSS rules.

   Default fallback (used until JS runs, or if JS is disabled): every
   stage falls back to --accent so the column treatment still reads. */
[data-stage] { --col-glow: var(--accent); }

/* The splash itself: a top-anchored gradient that grows from height: 0
   to height: 100%. Lives ONLY on the stage header cell -- it's the
   header that gets the showy splash. Other cells in the column use the
   simpler .col-highlight background tint defined further down. */
.bp-stage-head::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 0;
  background: linear-gradient(180deg,
    var(--col-glow, transparent) 0%,
    transparent 100%);
  opacity: 0.18;
  transition: height 0.35s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
  z-index: 0;
}
.bp-stage-head:hover::after,
.bp-stage-head.col-highlight::after {
  height: 100%;
}
/* When the stage header is hovered/highlighted, the number tile pops
   forward subtly. */
.bp-stage-head:hover .bp-stage-num,
.bp-stage-head.col-highlight .bp-stage-num {
  border-color: var(--col-glow, var(--accent));
  color: var(--col-glow, var(--accent));
  transform: scale(1.06);
}

/* Cell content sits above the splash */
.bp-c > * { position: relative; z-index: 1; }
.bp-stage-head > * { position: relative; z-index: 1; }


/* ---------- Tasteful animations: reveal-on-scroll ----------
   When the journey is in view, each row fades in. We use ONLY opacity
   (not transform) because transform on a row creates a containing
   block that breaks position:sticky on the .bp-label children -- the
   left label column would stop sticking during horizontal scroll.
   Fade-only keeps the cascade elegant without that side effect.
   Single bezier (CS Help curve), short duration, no distraction.
   Runs once per page load. Respects prefers-reduced-motion via the
   global reset at the top.

   Cascade ordering: app.js writes --reveal-i (the row's index inside
   .blueprint) onto each .bp-row at boot. CSS multiplies that by 50ms
   so layers cascade top-down without enumerating row classes. Adding
   or removing rows works without touching CSS. */
.blueprint .bp-row {
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--reveal-i, 0) * 50ms);
}
.blueprint.is-revealed .bp-row { opacity: 1; }


/* ---------- Cell hover pop ----------
   Cells lift slightly when hovered. Subtle. Stops the blueprint from
   feeling static while reading. */
.bp-c {
  transition:
    background-color 0.2s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.2s ease;
}
.bp-c:hover {
  z-index: 2;
}


/* ---------- Loop pill: marching dashes (CS Help pattern) ----------
   The dashed border on .lp-pill marches around the perimeter, signaling
   continuous backtracking. Animation is slow enough to be ambient,
   not distracting. */
@keyframes lp-march {
  to { stroke-dashoffset: -72; }
}


/* ---------- Column highlight on hover ----------
   Hovering any cell lights up its entire column via .col-highlight.
   JS adds the class to all elements with matching data-stage.
   The tint uses each column's --col-glow so the highlight matches
   the stage header splash and the gradient bar above. */
.col-highlight {
  background-color: color-mix(in srgb, var(--col-glow, var(--accent)) 8%, transparent) !important;
  transition: background-color 0.15s ease;
}
.bp-stage-head.col-highlight {
  background: color-mix(in srgb, var(--col-glow, var(--accent)) 14%, var(--bg));
}


/* ---------- Sticky stage header on scroll ----------
   Appears when blueprint is in view AND user has scrolled past the real
   stage header row. JS positions left/width to match the blueprint wrap
   so columns align exactly. JS also mirrors horizontal scroll. */
/* ---------- Sticky stage header on scroll ----------
   Appears when blueprint is in view AND user has scrolled past the real
   stage header row. JS positions left/width to match the blueprint wrap
   so columns align exactly. JS also mirrors horizontal scroll position
   onto the inner cloned blueprint.

   Architecture:
     .bp-sticky-header  -- fixed-position wrapper, clipped horizontally
     .blueprint (clone) -- inside the wrapper, owns horizontal scroll
                           (so the cloned label can position:sticky to
                           its own scrollport) -- but JS hides the
                           scrollbar and writes scrollLeft to mirror
                           the real blueprint. */
.bp-sticky-header {
  position: fixed;
  top: 50px; /* below top-bar */
  z-index: 90;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--rule);
  box-shadow: 0 4px 12px -8px rgba(0,0,0,0.15);
  opacity: 0;
  /* Use opacity-only for show/hide -- transform on a fixed element
     would create a containing block and break the sticky-left on the
     cloned .bp-label inside. */
  transition: opacity 0.15s ease;
  pointer-events: none;
  /* Outer wrapper: no scrolling here. The cloned .blueprint inside
     owns the scroll (overflow-x: auto from base .blueprint rule),
     which makes it the scrollport for sticky-left on the cloned
     label cell. JS keeps that scrollLeft synced with the real
     blueprint so columns align. */
  overflow: hidden;
}
.bp-sticky-header::-webkit-scrollbar { display: none; }
.bp-sticky-header.is-visible {
  opacity: 1;
  pointer-events: auto;
}
.bp-sticky-header .blueprint {
  border: 0;
  border-radius: 0;
  background: transparent;
  /* Don't set min-width here. The clone .blueprint must size to its
     wrapper (which JS pegs to the live blueprint's bounding rect) so
     that scrollWidth - clientWidth equals the live blueprint's
     scrollable distance. The .bp-row inside still carries
     min-width: calc(...) from the base rule, so it overflows
     internally and the .blueprint scrolls horizontally just like the
     live one. With those two widths matched, absolute scrollLeft
     mirroring keeps every column lined up. */
  scrollbar-width: none;
}
.bp-sticky-header .blueprint::-webkit-scrollbar { display: none; }
/* Sticky header clone: bypass the reveal-cascade opacity:0 (the cloned
   stage row would otherwise inherit those styles and stay invisible). */
.bp-sticky-header .blueprint .bp-row,
.bp-sticky-header .blueprint .bp-row.bp-stages {
  opacity: 1 !important;
  transform: none !important;
}
.bp-sticky-header .bp-stage-head {
  padding: 0.4rem 0.6rem;
  background: var(--bg-elev);
}
.bp-sticky-header .bp-label {
  background: var(--bg-elev);
  font-size: 0.75rem;
  padding: 0.4rem 0.75rem;
}

/* Slim phases strip in the sticky header. Compresses vertical padding
   so it adds about 1.6rem of height total when phases are present --
   enough to read the chapter label without crowding the stage row
   below. The horizontal padding stays at the live blueprint value so
   the spans still align column-for-column. Cloned phases inherit the
   per-phase --col-glow and grid-column from the original (cloneNode
   copies inline styles), so positioning is automatic. */
.bp-sticky-header .bp-phase {
  padding: 0.35rem var(--pad-s);
  background: var(--bg-elev);
  /* Drop the bottom border so the strip reads as one cohesive header. */
  border-bottom: 0;
}
.bp-sticky-header .bp-phase-label {
  font-size: 0.62rem;
  letter-spacing: 0.16em;
}
/* The phases label cell in the sticky strip uses the same compressed
   padding as the stages label cell so vertical alignment matches. */
.bp-sticky-header .bp-layer-phases > .bp-label {
  padding: 0.35rem 0.75rem;
}


/* ---------- Horizontal scroll arrows ----------
   Appear when blueprint overflows. Float over the wrap. */
.bp-wrap {
  position: relative;
}
/* Scroll arrows: position: fixed so they stay anchored to the
   viewport center while the user scrolls vertically. JS sets the
   left/right values to match the live blueprint's bounds, and
   toggles visibility based on whether the blueprint is in view.
   Visual treatment: filled accent disc with white chevron -- reads
   as a deliberate control without competing with map content. */
.bp-scroll-arrow {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 80;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  border: 0;
  border-radius: 50%;
  color: var(--bg-elev);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, background-color 0.15s, transform 0.15s ease;
  box-shadow: 0 4px 14px -2px rgba(0, 0, 0, 0.25),
              0 2px 6px -1px rgba(0, 0, 0, 0.15);
}
.bp-scroll-arrow svg {
  width: 22px;
  height: 22px;
  stroke-width: 2.5;
}
.bp-scroll-arrow:hover {
  background: color-mix(in srgb, var(--accent) 88%, black);
  transform: translateY(-50%) scale(1.06);
  box-shadow: 0 6px 18px -2px rgba(0, 0, 0, 0.32),
              0 3px 8px -1px rgba(0, 0, 0, 0.18);
}
.bp-scroll-arrow:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Dark theme: keep the chevron readable when the accent token shifts */
body[data-theme="dark"] .bp-scroll-arrow {
  color: var(--bg);
  box-shadow: 0 4px 14px -2px rgba(0, 0, 0, 0.5),
              0 2px 6px -1px rgba(0, 0, 0, 0.3);
}
.bp-wrap.has-overflow.bp-in-view:not(.scrolled-start) .bp-scroll-left,
.bp-wrap.has-overflow.bp-in-view:not(.scrolled-end) .bp-scroll-right {
  opacity: 1;
  pointer-events: auto;
}


/* ---------- Time row (CS Help pattern) ---------- */
.bp-layer-time .bp-label { background: var(--bg); }
.bp-c-time {
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 4rem;
  background: var(--bg-elev);
  flex-direction: column;
  gap: 0.3rem;
}
.tm-ring {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}
.tm-bg {
  fill: none;
  stroke: var(--rule);
  stroke-width: 3;
}
.tm-fg {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke 0.2s;
}
.tm-c-ok { stroke: var(--hold-edge); }
.tm-c-mid { stroke: var(--accent); }
.tm-c-over { stroke: var(--flex-edge); }
.tm-c-bad { stroke: var(--break-edge); }
.tm-num {
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}
.tm-num-bad { color: var(--break-ink); }


/* ---------- Clicks row (CS Help pattern) ----------
   Wedge gradient ramp rises from bottom-left to top-right -- a single
   triangular shape that visibly accumulates effort across stages.
   Each cell carries a click count with a cursor icon prefix. */
.bp-layer-clicks .bp-label { background: var(--bg); }
.bp-layer-clicks { position: relative; }
.bp-layer-clicks::before {
  content: "";
  position: absolute;
  inset: 0;
  left: var(--bp-label-col);
  clip-path: polygon(0 100%, 100% 0, 100% 100%);
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 8%, transparent) 0%,
    color-mix(in srgb, var(--flex-edge) 16%, transparent) 65%,
    color-mix(in srgb, var(--break-edge) 28%, transparent) 100%);
  pointer-events: none;
  z-index: 0;
}
.bp-c-clicks {
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 3rem;
  background: transparent;
  position: relative;
  z-index: 1;
  flex-direction: row;
  gap: 0.4rem;
}
.rk-num {
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
/* Cursor icon prefix on each click count -- mirrors CS Help mask-image
   approach using an inline SVG. */
.rk-num::before {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  background: currentColor;
  opacity: 0.55;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 0l16 12.279-6.951 1.17 4.325 8.817-3.596 1.734-4.35-8.879L4 20V0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 0l16 12.279-6.951 1.17 4.325 8.817-3.596 1.734-4.35-8.879L4 20V0z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.rk-num-bad { color: var(--break-ink); }


/* ---------- Touchpoint chips (CS Help pattern) ----------
   Frontstage cells carry an .ev-desc one-liner plus .tag chips naming
   actual touchpoints. */
.bp-c-touch {
  align-items: flex-start;
  gap: 0.4rem;
}
.ev-desc {
  font-size: 0.85rem;
  color: var(--ink-soft);
  line-height: 1.4;
  margin: 0 0 0.25rem;
  font-weight: 500;
}
.tag {
  display: inline-block;
  font-size: 0.75rem;
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 500;
  margin: 0 0.25rem 0.2rem 0;
  white-space: nowrap;
}


/* ---------- Pain points row (CS Help pattern) ----------
   Severity dual-encoded:
     - .pc-critical: red filled circle with alert SVG
     - .pc-detractor: amber filled circle (no SVG)
     - .pc-ok: green tint, no severity mark
   Background gradient rises from left (calm) to right (broken) -- same
   visual logic as the clicks row, but in the red palette. */
.bp-layer-pain { position: relative; }
.bp-layer-pain::before {
  content: "";
  position: absolute;
  inset: 0;
  left: var(--bp-label-col);
  background: linear-gradient(90deg,
    transparent 0%,
    transparent 15%,
    color-mix(in srgb, var(--break-edge) 5%, transparent) 40%,
    color-mix(in srgb, var(--break-edge) 14%, transparent) 100%);
  pointer-events: none;
  z-index: 0;
}
.bp-layer-pain .bp-label { background: var(--bg); }
.bp-c-pain {
  align-items: stretch;
  flex-direction: column;
  gap: 0.4rem;
  padding: var(--pad-s);
  position: relative;
  z-index: 1;
}
.pc-item {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  padding: 0.25rem 0;
}
.pc-item + .pc-item {
  border-top: 1px solid color-mix(in srgb, var(--break-edge) 15%, transparent);
}
.pc-text {
  font-size: 0.85rem;
  color: var(--ink);
  line-height: 1.5;
}
.pc-sev-mark {
  flex-shrink: 0;
  margin-top: 3px;
  width: 9px;
  height: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.pc-detractor .pc-sev-mark {
  background: var(--flex-edge);
  border-radius: 50%;
  width: 9px;
  height: 9px;
}
.pc-critical .pc-sev-mark {
  color: var(--break-ink);
  width: 13px;
  height: 13px;
}
.pc-critical .pc-sev-mark svg {
  width: 13px;
  height: 13px;
}
/* "OK" cells -- where there's no pain at this stage */
.pc-ok {
  background: color-mix(in srgb, var(--hold-bg) 35%, transparent) !important;
}
.pc-ok .pc-text {
  color: var(--hold-ink);
  font-weight: 500;
}
.pc-bad {
  background: color-mix(in srgb, var(--break-bg) 50%, transparent) !important;
}


/* ---------- Backstage System / Gap pattern (CS Help) ----------
   Each backstage cell carries two stacked blocks:
     - .bk-sys: "System" -- what supports this stage (green dot)
     - .bk-gap: "Gap" -- where it falls short (amber dot)
   The pairing makes "what's there vs what's missing" legible per stage. */
.bp-c-bk {
  flex-direction: column;
  gap: 0.5rem;
  padding: var(--pad-s);
  align-items: stretch;
}
.bk-section {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.5rem 0.6rem;
  border-radius: 5px;
}
.bk-sys { background: color-mix(in srgb, var(--hold-bg) 60%, var(--bg)); }
.bk-gap { background: color-mix(in srgb, var(--flex-bg) 50%, var(--bg)); }
.bk-label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
}
.bk-label::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.bk-sys .bk-label { color: var(--hold-ink); }
.bk-sys .bk-label::before { background: var(--hold-ink); }
.bk-gap .bk-label { color: var(--flex-ink); }
.bk-gap .bk-label::before { background: var(--flex-ink); }
.bk-text {
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--ink);
}


/* ---------- Reading guide ----------
   Optional first-time-reader primer that sits between the journey
   lede and the journey-selector. Native <details> for free disclosure
   semantics; closed by default. Four micro-cards explain the kit's
   visual encoding using glyphs that mirror the actual encoding (rows
   = layers, columns = time, hover = column highlight, ringed nodes =
   popovers). Disable by deleting the markup.

   Markup contract:
     <details class="reading-guide">
       <summary class="reading-guide-summary">...</summary>
       <div class="reading-guide-grid" role="list">
         <div class="reading-guide-card" role="listitem">
           <span class="reading-guide-glyph">SVG</span>
           <h3 class="reading-guide-card-title">Title</h3>
           <p class="reading-guide-card-body">Body</p>
         </div>
         ... three more cards ...
       </div>
     </details>

   Discipline: if a card no longer matches the kit's visual encoding,
   fix the kit OR fix the card. The guide is a contract with the
   reader; drift between guide and artifact is worse than no guide. */
.reading-guide {
  margin: var(--pad-l) 0 var(--pad-m);
  border-top: 1px solid var(--rule);
  padding-top: var(--pad-m);
}

/* Trigger: feels native, never decorative. Three honest signals:
   info glyph (this is help), label (what it does), chevron (it
   discloses). Pill shape matches the kit's other affordance pills
   (theme pill, view toggle) without copying their visual weight. */
.reading-guide-summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.85rem 0.4rem 0.7rem;
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.reading-guide-summary::-webkit-details-marker { display: none; }
.reading-guide-summary:hover {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--rule));
  background: color-mix(in srgb, var(--accent-soft) 30%, transparent);
}
.reading-guide-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  color: var(--ink);
}
.reading-guide[open] .reading-guide-summary {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
  background: color-mix(in srgb, var(--accent-soft) 40%, transparent);
}

.reading-guide-info {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--accent);
}
.reading-guide-chevron {
  width: 10px;
  height: 10px;
  flex-shrink: 0;
  color: var(--ink-muted);
  transition: transform 0.2s ease;
  margin-left: 0.1rem;
}
.reading-guide[open] .reading-guide-chevron {
  transform: rotate(180deg);
  color: var(--accent);
}

/* Four-card grid. Auto-fits gracefully from 4-across on desktop to
   2-across on tablet to 1-across on mobile. No shadows, no borders
   thicker than 1px -- the primer must never compete with the journey
   it's introducing. */
.reading-guide-grid {
  margin-top: var(--pad-m);
  padding: var(--pad-m) var(--pad-m) calc(var(--pad-m) - 0.25rem);
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 6px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--pad-m);
}

.reading-guide-card {
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  grid-template-rows: auto auto;
  column-gap: 0.85rem;
  row-gap: 0.2rem;
  align-items: start;
}
.reading-guide-glyph {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 1.5rem;
  margin-top: 0.15rem;
  color: var(--accent);
  flex-shrink: 0;
}
.reading-guide-glyph svg {
  width: 100%;
  height: 100%;
  display: block;
}
.reading-guide-card-title {
  grid-column: 2;
  grid-row: 1;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
  margin: 0;
  line-height: 1.3;
  letter-spacing: -0.005em;
}
.reading-guide-card-body {
  grid-column: 2;
  grid-row: 2;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: 0.15rem 0 0;
  max-width: 32ch;
}

/* Reduced motion: drop the chevron rotation transition. The state
   change still happens; it just doesn't animate. */
@media (prefers-reduced-motion: reduce) {
  .reading-guide-summary,
  .reading-guide-chevron {
    transition: none;
  }
}

/* Print: always show the guide content (printed copies need the
   primer most), drop the chevron since the disclosure is moot, and
   remove the trigger frame so the page reads as continuous document. */
@media print {
  .reading-guide-summary {
    border: 0;
    padding-left: 0;
    background: transparent;
  }
  .reading-guide-chevron { display: none; }
  .reading-guide-grid {
    display: grid !important;
    background: transparent;
    border: 0;
    padding: var(--pad-s) 0 0;
  }
}


/* ---------- Journey selector (multi-journey pattern) ----------
   Tab strip that switches between multiple .bp-wrap journeys on the
   same page. Use when an artifact carries more than one journey worth
   showing (CS Help DNA: shipping + device troubleshooting). For
   single-journey artifacts (HITL, WMP, the template), skip the
   selector entirely; the .bp-wrap stands alone.

   Markup contract:
     <div class="journey-selector" role="tablist">
       <button class="jtab" data-journey="X" aria-selected="true|false">
         <span class="jtab-num">1</span>
         <span class="jtab-content">
           <span class="jtab-label">Journey name</span>
           <span class="jtab-meta">Short context</span>
         </span>
       </button>
       ...
     </div>
     <div class="bp-wrap" id="journey-X"> ... </div>
     <div class="bp-wrap" id="journey-Y"> ... </div>

   Visual treatment: numbered chips with label + meta text, sitting in
   a horizontal strip above the journey wraps. Active tab carries an
   accent underline so the connection between the selector and the
   blueprint below is unambiguous. */
.journey-selector {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 var(--pad-m);
  padding: 0.25rem 0 0;
}
.journey-selector-label {
  width: 100%;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  margin: 0 0 0.25rem;
}
.jtab {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.5rem 0.85rem 0.55rem 0.6rem;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  color: var(--ink-soft);
  font: inherit;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.jtab:hover {
  border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
  color: var(--ink);
}
.jtab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.jtab.active,
.jtab[aria-selected="true"] {
  border-color: var(--accent);
  color: var(--ink);
  background: var(--bg-elev);
  box-shadow: 0 0 0 1px var(--accent);
}
.jtab-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.72rem;
  font-weight: 700;
  border-radius: 50%;
  background: var(--bg);
  color: var(--ink-muted);
  border: 1.5px solid var(--rule);
  flex-shrink: 0;
}
.jtab.active .jtab-num,
.jtab[aria-selected="true"] .jtab-num {
  background: var(--accent);
  color: var(--bg-elev);
  border-color: var(--accent);
}
.jtab-content {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  line-height: 1.25;
}
.jtab-label {
  font-size: 0.85rem;
  font-weight: 600;
}
.jtab-meta {
  font-size: 0.72rem;
  color: var(--ink-muted);
  font-weight: 500;
}
.jtab[aria-selected="true"] .jtab-meta { color: var(--ink-soft); }


/* Slim sticky-header variant of the journey selector. JS clones the
   original .journey-selector into each .bp-sticky-header so the reader
   can switch journeys mid-scroll without jumping back to the top
   (CS Help pattern). The sticky variant compresses padding and font
   sizes to fit alongside the cloned stage row without making the
   sticky strip too tall. */
.bp-sticky-header .journey-selector-sticky {
  /* Override the base .journey-selector flex defaults: tighter gap,
     no bottom margin (the sticky strip handles vertical rhythm). */
  display: flex;
  gap: 0.4rem;
  margin: 0;
  padding: 0.4rem var(--pad-s);
  background: var(--bg-elev);
  border-bottom: 1px solid var(--rule);
  /* Hide horizontal overflow if the strip gets too wide -- selector
     stays single-line and the reader can scroll the page if it
     truncates rather than overflowing the strip vertically. */
  overflow-x: auto;
  scrollbar-width: thin;
  flex-wrap: nowrap;
}
.bp-sticky-header .journey-selector-sticky .jtab {
  /* Compact: lower padding, smaller chip number, tighter content. */
  padding: 0.3rem 0.6rem 0.3rem 0.4rem;
  gap: 0.5rem;
  flex-shrink: 0;
}
.bp-sticky-header .journey-selector-sticky .jtab-num {
  width: 1.25rem;
  height: 1.25rem;
  font-size: 0.65rem;
}
.bp-sticky-header .journey-selector-sticky .jtab-label {
  font-size: 0.78rem;
}
.bp-sticky-header .journey-selector-sticky .jtab-meta {
  /* Drop the meta line in the sticky variant -- label alone carries
     enough context, and meta would force two-line tabs that bloat
     the strip vertically. */
  display: none;
}



   Chapter-level grouping above stages. Each .bp-phase spans a range
   of stage columns via grid-column, set by JS from data-from / data-to
   so designers don't have to translate stage numbers to grid line
   numbers. The label sits centered between two horizontal rules
   (CS Help "Between the Rounds" pattern). Color samples from the
   journey's gradient automatically -- the rules use --col-glow at the
   phase's center stage so phases inherit the kit's left-to-right
   gradient without introducing a new color system. Override per phase
   by setting --phase-color inline.

   Use when the journey has natural chapters that group stages -- onboarding
   / active / churn, before / during / after, or any tri-fold story arc.
   Skip when stages don't cluster, or when the gradient bar above stages
   already does enough. */
.bp-layer-phases .bp-label { background: var(--bg); }
.bp-phase {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem var(--pad-s);
  background: var(--bg);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
/* Phases row: drop the right border on the last phase so the gradient
   bar at the top of stages still reads as a continuous line. */
.bp-phase:last-child { border-right: 0; }
.bp-phase-rule {
  height: 1.5px;
  background: color-mix(in srgb,
    var(--phase-color, var(--col-glow, var(--accent))) 65%,
    transparent);
  border-radius: 1px;
}
.bp-phase-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--phase-color, var(--col-glow, var(--accent)));
  white-space: nowrap;
}


/* ---------- Narrative row (optional) ----------
   Storytelling overlay. Each card sits CENTERED inside its stage's
   column, with a vertical accent rail along one side. The rail
   extends past the card's track-facing edge as a short leader,
   terminating in a dot on the track. The whole composition stays
   inside the stage column, so column-hover highlight remains
   coherent (the card and its dot belong to the same stage visually).

   Card body is pure typography on the canvas; the connector
   vocabulary (rail + leader + dot) carries the visual association
   to the stage in the stage's accent color so the kit's left-to-right
   gradient flows through narrative beats too.

   Authoring:
     data-stage="N"             -- which column to anchor to (required)
     data-side="above|below"    -- which side of the track (required)
     data-anchor="left|right"   -- which side of the card the rail
                                   sits on. Default is auto: alternates
                                   left/right based on stage parity so
                                   the rhythm reads as a zigzag without
                                   manual choreography. Override per
                                   card when the layout calls for it. */
.bp-layer-narrative .bp-label { background: var(--bg); }
.bp-c-narrative {
  /* Single spanning cell. Height is set by JS at boot from measured
     card heights so cards never spill into the rows above or below.
     The fallback min-height here keeps the cell from collapsing
     before JS runs. */
  grid-column: 2 / -1 !important;
  position: relative;
  min-height: 8rem;
  padding: 0;
  background: var(--bg-elev);
  overflow: visible;
}

/* Track line -- thin horizontal rule at vertical center. Sized
   stronger than the row hairlines so it reads as the journey's
   spine across all three themes; the colored dots and rails still
   sit above it visually thanks to higher contrast and z-index. */
.narr-track {
  position: absolute;
  top: 50%;
  left: 1.5rem;
  right: 1.5rem;
  height: 1px;
  background: color-mix(in srgb, var(--ink-muted) 55%, transparent);
  pointer-events: none;
}
body[data-theme="dark"] .narr-track {
  background: color-mix(in srgb, var(--ink-muted) 70%, transparent);
}
body[data-theme="warm"] .narr-track {
  background: color-mix(in srgb, var(--ink-muted) 60%, transparent);
}

.narr-card {
  position: absolute;
  /* Width and horizontal placement set by JS based on stage count
     (each card occupies its column's width minus side padding). */
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  /* Equal horizontal padding on both sides so the body isn't pinned
     to the rail-facing edge. The rail sits OUTSIDE the padding box
     via border-left/right. */
  padding: 0.6rem 0.75rem;
  /* Subtle frame: a faint tint of the stage's accent on top of the
     cell's elevated bg. Reads as a discrete panel without competing
     with the cells on either side. The mix lifts on column-hover via
     the existing .col-highlight rule (which writes its own background
     with !important, so it overrides this one cleanly). */
  background: color-mix(in srgb,
    var(--col-glow, var(--accent)) 4%, var(--bg-elev));
  /* Default rail: left edge, in the stage's accent color. */
  border-left: 2px solid var(--col-glow, var(--accent));
  z-index: 2;
}

/* Right-anchored variant: rail flips to right edge. */
.narr-card[data-anchor="right"] {
  border-left: 0;
  border-right: 2px solid var(--col-glow, var(--accent));
}


/* Per-stage hit zones inside the narrative spanning cell. The cell
   itself has aria-colindex="2" but no data-stage (it spans all stage
   columns), so without these the column-hover highlight wouldn't fire
   from empty space in the row. The hits give each column a hover
   target that participates in the kit's column-highlight system.
   Same pattern as .effort-hits.

   z-index: 1 keeps these BEHIND cards (which sit at z-index: 2) so
   cards still receive their own hover and pointer events. */
.narr-hits {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--bp-cols, 9), 1fr);
  z-index: 1;
  pointer-events: auto;
}
.narr-hit {
  transition: background-color 0.15s ease;
}
.narr-hit.col-highlight {
  background-color: color-mix(in srgb,
    var(--col-glow, var(--accent)) 8%, transparent) !important;
}

/* Leader: a 1rem extension of the rail past the card edge that faces
   the track. Same 2px width and color as the rail so they read as one
   continuous mark. */
.narr-card::before {
  content: "";
  position: absolute;
  width: 2px;
  height: 1rem;
  background: var(--col-glow, var(--accent));
  pointer-events: none;
}
.narr-card[data-anchor="left"]::before { left: -2px; }
.narr-card[data-anchor="right"]::before { right: -2px; }
.narr-card[data-side="below"]::before { top: -1rem; }
.narr-card[data-side="above"]::before { bottom: -1rem; }

/* Dot: terminates the leader on the track, at the same x-position
   as the rail. Tight halo so the dot reads cleanly against the
   track line below it. */
.narr-card::after {
  content: "";
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--col-glow, var(--accent));
  box-shadow: 0 0 0 2.5px var(--bg-elev);
  pointer-events: none;
}
.narr-card[data-anchor="left"]::after { left: -4.5px; }
.narr-card[data-anchor="right"]::after { right: -4.5px; }
.narr-card[data-side="below"]::after { top: calc(-1rem - 3.5px); }
.narr-card[data-side="above"]::after { bottom: calc(-1rem - 3.5px); }

.narr-card-eyebrow {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--col-glow, var(--accent));
}
.narr-card-body {
  font-size: 0.82rem;
  line-height: 1.5;
  margin: 0;
  color: color-mix(in srgb, var(--ink) 92%, var(--ink-soft));
}



   Mark a cell with .bp-c-bad to flag a stage where the action breaks
   down. The cell picks up a left accent strip in break-edge and a
   subtle break-bg tint -- no new color introduced; same break semantic
   used in stress cards, pain points, and time-ring overruns.
   Headline + sub structure stays the same. */
.bp-c-bad {
  background: color-mix(in srgb, var(--break-bg) 35%, var(--bg-elev));
  box-shadow: inset 2px 0 0 var(--break-edge);
}
.bp-c-bad .bp-c-headline {
  color: var(--break-ink);
}

/* ---------- Cell list (multi-step within one stage) ----------
   When a single cell genuinely contains more than one discrete action,
   replace .bp-c-sub with a .bp-c-list ordered list. Numbers come from
   <ol> automatically -- no inline indices needed. Stays compact so a
   row of multi-step cells still scans top-down without bloating. */
.bp-c-list {
  list-style: none;
  counter-reset: bpli;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.bp-c-list li {
  counter-increment: bpli;
  position: relative;
  padding-left: 1.25rem;
  font-size: 0.78rem;
  color: var(--ink-soft);
  line-height: 1.4;
}
.bp-c-list li::before {
  content: counter(bpli);
  position: absolute;
  left: 0;
  top: 0.05rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.95rem;
  height: 0.95rem;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--bg-elev);
  font-size: 0.6rem;
  font-weight: 600;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}


/* ---------- Actors rows (paired: human + digital) ----------
   Two rows distinguish HUMAN actors from DIGITAL actors. The split is
   structural (who's doing the work), not state -- so it's encoded as
   FORM (icon + border style) rather than color. The kit's color budget
   stays reserved for state, severity, and accent.
     - Human chips: solid border, person icon
     - Digital chips: dashed border, screen/system icon
   Both share the same neutral chip foreground/background.
   The Digital row's label uses .bp-label-pair to drop the title (since
   the label "Actors" already named the pair on the Human row). */
.bp-layer-actors .bp-label { background: var(--bg); }
.bp-actors-human .bp-label { padding-bottom: 0.5rem; }
.bp-actors-digital .bp-label { padding-top: 0.5rem; border-top: 0; }
.bp-label-pair {
  background: var(--bg);
}
.bp-label-pair .bp-lbl-sub {
  font-size: 0.78rem;
}
/* Human row sits flush above Digital row (no border between them) */
.bp-actors-human .bp-c { border-bottom-color: transparent; }

.bp-c-actor {
  align-items: flex-start;
  justify-content: center;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.3rem;
  min-height: 2.5rem;
  padding: 0.4rem 0.5rem;
}
.actor-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem 0.2rem 0.4rem;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--bg-elev);
  color: var(--ink-soft);
}
.actor-chip-icon {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  opacity: 0.85;
}
/* Human: solid border, person icon (already in markup) */
.actor-human {
  border: 1px solid var(--rule);
}
/* Digital: dashed border, screen/system icon (already in markup) */
.actor-digital {
  border: 1px dashed var(--ink-muted);
  background: color-mix(in srgb, var(--bg) 60%, var(--bg-elev));
}


/* ---------- Channels row (optional) ----------
   What surface the customer is on at each stage. Channel chips share
   the actor-chip vocabulary (icon + label, no per-channel color) so
   the kit's color budget stays reserved for state and severity. The
   icon distinguishes the channel: phone, chat, email, web, app, etc. */
.bp-layer-channel .bp-label { background: var(--bg); }
.bp-c-channel {
  align-items: flex-start;
  justify-content: center;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.3rem;
  min-height: 2.5rem;
  padding: 0.4rem 0.5rem;
}
.channel-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem 0.2rem 0.4rem;
  border-radius: 4px;
  white-space: nowrap;
  background: var(--bg-elev);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
}
.channel-chip-icon {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  opacity: 0.85;
}


/* ---------- Customer Thoughts row (optional) ----------
   First-person inner monologue. Each cell holds an unordered list of
   thoughts (questions, concerns, doubts). Italic, quote-style, slightly
   recessed background. Bullets are small dots in ink-muted -- enough
   structure to scan multiple thoughts without competing with the
   stronger row labels above. */
.bp-layer-thoughts .bp-label { background: var(--bg); }
.bp-c-thought {
  background: color-mix(in srgb, var(--bg) 70%, var(--bg-elev));
  padding: var(--pad-s);
  align-items: flex-start;
}
.ca-thoughts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.ca-thoughts li {
  position: relative;
  padding-left: 0.85rem;
  font-size: 0.82rem;
  font-style: italic;
  line-height: 1.45;
  color: var(--ink-soft);
}
.ca-thoughts li::before {
  content: "";
  position: absolute;
  left: 0.15rem;
  top: 0.6em;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--ink-muted);
  opacity: 0.7;
}


/* ---------- Effort arc row (optional) ----------
   A single cell that spans all stage columns. Inside: an SVG path that
   curves through effort levels (low/medium/high) with colored dots at
   each stage. Use when effort variation is part of the argument. */
.bp-layer-effort .bp-label { background: var(--bg); }
.bp-c-effort {
  /* Span all stage columns -- the arc is a single visual across the row */
  grid-column: 2 / -1 !important;
  padding: 0.5rem var(--pad-s) 0.5rem 0;
  min-height: 4.5rem;
  position: relative;
  background: color-mix(in srgb, var(--bg) 60%, var(--bg-elev));
  overflow: visible;
}
.effort-arc-wrap {
  /* Positioning context for the SVG, axis labels, ref line, AND
     the .effort-node click targets. Fixed height so the SVG below
     can absolute-fill it without stretching to its viewBox aspect.
     z-index 3 here lifts the wrap (and its node buttons) above the
     sibling .effort-hits layer at z-index 2; pointer-events: none on
     the wrap itself lets the hits below still capture mouseover for
     column highlight in the gaps between buttons. */
  position: relative;
  width: 100%;
  height: 4.5rem;
  z-index: 3;
  pointer-events: none;
}
.effort-arc {
  /* Fill the wrap exactly. Absolute positioning keeps the SVG's CSS
     box independent of its viewBox aspect ratio -- combined with
     preserveAspectRatio="none" on the SVG, the path stretches to fill
     any wrap dimensions. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 2;
  /* Visual-only -- node buttons are siblings, not children */
  pointer-events: none;
}
.effort-axis-label {
  /* Absolute positioning inside .effort-arc-wrap so the three labels
     occupy their own vertical slots (high / mid / low). Sticky-left
     wouldn't help here -- they'd stick to the cell's scroll context
     and stack at the same y-position. */
  position: absolute;
  left: 0.5rem;
  font-size: 0.65rem;
  color: var(--ink-soft);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  pointer-events: none;
  z-index: 4;
  background: color-mix(in srgb, var(--bg) 70%, var(--bg-elev));
  padding: 0.05rem 0.35rem;
  border-radius: 3px;
  width: max-content;
  display: block;
}
.effort-axis-high { top: 0.25rem; }
.effort-axis-mid {
  top: 50%;
  transform: translateY(-50%);
}
.effort-axis-low {
  bottom: 0.25rem;
  top: auto;
}
.effort-ref-line {
  position: absolute;
  top: 50%;
  left: 4rem;
  right: 1rem;
  height: 0;
  /* Higher-contrast dashed line, stronger than rule color so it reads
     across all three themes. Sits BEHIND the arc (z-index 1). */
  border-top: 1px dashed color-mix(in srgb, var(--ink-muted) 60%, transparent);
  pointer-events: none;
  z-index: 1;
}

/* Effort nodes: shared positioning + visible dot. JS positions each
   .effort-node by percentage and draws the connecting curve into the
   .effort-arc SVG below. The visible dot is a ::before pseudo so the
   interactive variant can keep a larger click hit area while the
   visual stays small.

   INTERACTIVITY DISCIPLINE: only nodes that open a popover get the
   discoverability ring + hover + cursor treatment. Static nodes render
   as flat dots -- no false affordance. Mark interactive ones by using
   <button class="effort-node" data-popover-target="..."> and static
   ones with <span class="effort-node">. Both share the same dot color
   (from data-effort), the same coordinate system, and the same
   reveal/scroll behavior; only the ring + hover differ. */
.effort-node {
  position: absolute;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  margin-top: -4.5px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  z-index: 3;
  pointer-events: none;
  transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.effort-node::before {
  /* The visible dot. Color from data-effort. */
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 9px;
  height: 9px;
  margin: -4.5px 0 0 -4.5px;
  border-radius: 50%;
  background: var(--accent);
  pointer-events: none;
  transition: transform 0.15s ease;
}
.effort-node[data-effort="low"]::before  { background: var(--hold-edge); }
.effort-node[data-effort="mid"]::before  { background: var(--flex-edge); }
.effort-node[data-effort="high"]::before { background: var(--break-edge); }

/* Interactive variant: any <button class="effort-node"> -- typically
   one with data-popover-target set. Larger hit area, discoverability
   ring, hover state, focus ring. The ring is meaningful: it tells the
   reader "this dot has more detail behind it." Static dots have none. */
button.effort-node {
  width: 22px;
  height: 22px;
  margin-left: -11px;
  margin-top: -11px;
  cursor: pointer;
  pointer-events: auto;
  /* Resting-state ring -- discoverability cue */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink-muted) 35%, transparent);
}
button.effort-node:hover {
  background-color: color-mix(in srgb, var(--accent) 18%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 50%, transparent),
              0 0 0 6px color-mix(in srgb, var(--accent) 15%, transparent);
  transform: scale(1.15);
}
button.effort-node:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  background-color: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Legacy: kept for any project still using separate .effort-dot +
   .effort-node-btn pairs. New work should use the unified .effort-node. */
.effort-dot {
  position: absolute;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  margin-top: -4.5px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 3;
}
.effort-dot[data-effort="low"]  { background: var(--hold-edge); }
.effort-dot[data-effort="mid"]  { background: var(--flex-edge); }
.effort-dot[data-effort="high"] { background: var(--break-edge); }
/* Per-stage hit zones: invisible overlay grid that gives each effort
   stage its own data-stage hit target. Without these, the spanning
   cell's data-stage="1" would highlight stage 1 across the whole row.
   Column count comes from --bp-cols so this works for any stage count;
   JS generates the per-stage .effort-hit children in initEffortRows. */
.effort-hits {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--bp-cols, 9), 1fr);
  z-index: 2;
}
.effort-hit {
  /* Transparent hit target -- catches hover and gets data-stage from
     the markup so the column highlight handler picks the right stage.
     When a stage is highlighted across the row, the matching hit zone
     paints the column-glow tint over its slice of the effort cell. */
  transition: background-color 0.15s ease;
}
.effort-hit.col-highlight {
  background-color: color-mix(in srgb, var(--col-glow, var(--accent)) 8%, transparent) !important;
}

/* Legacy: .effort-node-btn was the old name for the click target before
   it was unified with the visible dot into a single .effort-node. Kept
   as alias rules so any in-flight projects don't break on upgrade. */
.effort-node-btn {
  position: absolute;
  width: 22px;
  height: 22px;
  margin-left: -11px;
  margin-top: -11px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  z-index: 3;
  transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink-muted) 35%, transparent);
}
.effort-node-btn:hover {
  background-color: color-mix(in srgb, var(--accent) 18%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 50%, transparent),
              0 0 0 6px color-mix(in srgb, var(--accent) 15%, transparent);
  transform: scale(1.15);
}
.effort-node-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  background-color: color-mix(in srgb, var(--accent) 18%, transparent);
}


/* ---------- Emotion row (optional) ----------
   Custom face glyphs (positive / neutral / negative / anxious) paired
   with inline feeling chips. The glyph signals valence at a glance,
   the chips name the specific feelings. Both are readable WITHOUT
   clicking -- popovers are opt-in for stages where research provenance
   or longer detail is worth pulling forward.

   Glyphs are SVG symbols defined once at the top of the document and
   referenced via <use href="#emotion-N"/>. Color comes from the kit's
   hold/break tokens via .emotion-positive / .emotion-neutral /
   .emotion-negative / .emotion-anxious modifiers on the wrapper. */
.bp-layer-emotion .bp-label { background: var(--bg); }
.bp-c-emotion {
  background: color-mix(in srgb, var(--bg) 60%, var(--bg-elev));
  align-items: center;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.5rem 0.6rem;
}

/* Face glyph wrapper -- renders the SVG via <use> at a consistent size.
   Color flows through currentColor on the <use> reference. */
.emotion-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--ink-muted);
}
.emotion-glyph svg {
  width: 100%;
  height: 100%;
  display: block;
}
.emotion-positive { color: var(--hold-edge); }
.emotion-neutral  { color: var(--ink-muted); }
.emotion-negative { color: var(--break-edge); }
.emotion-anxious  { color: var(--flex-edge); }

/* Glyph as a popover trigger (only on stages with optional depth).
   Subtle resting-state hint that this one is interactive, distinct
   from the static glyphs around it. */
.emotion-glyph-btn {
  background: transparent;
  border: 0;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  /* Resting-state ring -- discoverability cue */
  box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent);
}
.emotion-glyph-btn:hover {
  transform: scale(1.12);
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 50%, transparent),
              0 0 0 6px color-mix(in srgb, currentColor 14%, transparent);
}
.emotion-glyph-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Inline feeling chips next to the glyph -- the load-bearing content,
   readable at a glance without clicking. */
.emotion-feelings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}
.emotion-feeling {
  display: inline-flex;
  align-items: center;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 0.1rem 0.45rem;
  border-radius: 3px;
  white-space: nowrap;
  background: var(--bg);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
}
.emotion-feeling-positive {
  background: color-mix(in srgb, var(--hold-bg) 60%, transparent);
  color: var(--hold-ink);
  border-color: color-mix(in srgb, var(--hold-edge) 30%, transparent);
}
.emotion-feeling-negative {
  background: color-mix(in srgb, var(--break-bg) 55%, transparent);
  color: var(--break-ink);
  border-color: color-mix(in srgb, var(--break-edge) 30%, transparent);
}
.emotion-feeling-neutral {
  background: var(--bg);
  color: var(--ink-soft);
  border-color: var(--rule);
}


/* ---------- Popover component (.bp-popover) ----------
   Reusable progressive-disclosure pattern. Triggered by any element
   with [data-popover-target="ID"] -- the ID matches the popover panel.
   JS positions the popover next to the trigger, traps focus, and
   handles Esc / click-outside dismiss.
   Used by effort dots, emotion dots, and any future node-style row. */
.bp-popover {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 12px 32px -8px rgba(0,0,0,0.18),
              0 4px 12px -4px rgba(0,0,0,0.10);
  padding: 1rem 1.25rem 1.1rem;
  min-width: 18rem;
  max-width: 22rem;
  opacity: 0;
  transform: scale(0.96);
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s cubic-bezier(0.16, 1, 0.3, 1);
}
.bp-popover.is-open {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

/* Pointer arrow: a 12px triangle that anchors the popover to its
   trigger. Two stacked pseudos -- the outer matches the popover border,
   the inner sits 1px in to mask that border line so the triangle reads
   as a continuation of the popover surface, not a separate shape.
   JS sets:
     --bp-pop-side -- which side the arrow points from (top|bottom|left|right)
     --bp-pop-arrow -- horizontal/vertical offset along that side
   so the arrow always points back at the trigger even when the popover
   is clamped to the viewport edge. */
.bp-popover::before,
.bp-popover::after {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
}
/* Default (popover above trigger) -- arrow on bottom edge pointing down */
.bp-popover {
  --bp-pop-arrow: 50%;
}
.bp-popover[data-pop-side="top"]::before,
.bp-popover[data-pop-side="top"]::after {
  left: var(--bp-pop-arrow);
  top: 100%;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
}
.bp-popover[data-pop-side="top"]::before {
  border-top: 8px solid var(--rule);
}
.bp-popover[data-pop-side="top"]::after {
  border-top: 8px solid var(--bg-elev);
  margin-top: -1px;
}
/* Popover below trigger -- arrow on top edge pointing up */
.bp-popover[data-pop-side="bottom"]::before,
.bp-popover[data-pop-side="bottom"]::after {
  left: var(--bp-pop-arrow);
  bottom: 100%;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
}
.bp-popover[data-pop-side="bottom"]::before {
  border-bottom: 8px solid var(--rule);
}
.bp-popover[data-pop-side="bottom"]::after {
  border-bottom: 8px solid var(--bg-elev);
  margin-bottom: -1px;
}
/* Popover left of trigger -- arrow on right edge pointing right */
.bp-popover[data-pop-side="left"]::before,
.bp-popover[data-pop-side="left"]::after {
  top: var(--bp-pop-arrow);
  left: 100%;
  transform: translateY(-50%);
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}
.bp-popover[data-pop-side="left"]::before {
  border-left: 8px solid var(--rule);
}
.bp-popover[data-pop-side="left"]::after {
  border-left: 8px solid var(--bg-elev);
  margin-left: -1px;
}
/* Popover right of trigger -- arrow on left edge pointing left */
.bp-popover[data-pop-side="right"]::before,
.bp-popover[data-pop-side="right"]::after {
  top: var(--bp-pop-arrow);
  right: 100%;
  transform: translateY(-50%);
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}
.bp-popover[data-pop-side="right"]::before {
  border-right: 8px solid var(--rule);
}
.bp-popover[data-pop-side="right"]::after {
  border-right: 8px solid var(--bg-elev);
  margin-right: -1px;
}
.bp-popover-close {
  position: absolute;
  top: 0.4rem;
  right: 0.5rem;
  background: transparent;
  border: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--ink-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s, color 0.15s;
}
.bp-popover-close:hover {
  background: var(--bg);
  color: var(--ink);
}
.bp-popover-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.bp-popover-eyebrow {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: 0.4rem;
}
.bp-popover-title {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 0.6rem;
  color: var(--ink);
  padding-right: 1.5rem;
}
.bp-popover-body {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: 0 0 0.85rem;
}
.bp-popover-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  align-items: center;
  margin-bottom: 0.6rem;
}
.bp-popover-chip-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  width: 100%;
  margin-bottom: 0.2rem;
}
.emotion-chip {
  display: inline-flex;
  align-items: center;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.18rem 0.55rem;
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink-soft);
  border: 1px solid var(--rule);
}
.emotion-chip-positive {
  background: color-mix(in srgb, var(--hold-bg) 70%, transparent);
  color: var(--hold-ink);
  border-color: color-mix(in srgb, var(--hold-edge) 35%, transparent);
}
.emotion-chip-negative {
  background: color-mix(in srgb, var(--break-bg) 65%, transparent);
  color: var(--break-ink);
  border-color: color-mix(in srgb, var(--break-edge) 35%, transparent);
}
.emotion-chip-neutral {
  background: var(--bg);
  color: var(--ink-soft);
  border-color: var(--rule);
}
.bp-popover-meta {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.bp-popover-meta-label {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.bp-popover-meta-value {
  font-size: 0.78rem;
  color: var(--ink-soft);
}


/* ---------- Insights row (optional) ----------
   Sits at the bottom of the blueprint. Heavier top separator + elevated
   background so it reads as a separate section that still aligns with
   stage columns. */
.bp-layer-insights {
  border-top: 2px solid var(--rule);
  background: color-mix(in srgb, var(--accent-soft) 35%, var(--bg-elev));
}
.bp-label-insights {
  background: color-mix(in srgb, var(--accent-soft) 50%, var(--bg)) !important;
  color: var(--accent);
}
.bp-label-insights .bp-lbl-title { color: var(--accent); }
.bp-c-insight {
  background: transparent;
  align-items: flex-start;
  gap: 0.4rem;
  padding: var(--pad-s);
  min-height: 5rem;
}
.insight-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
}
.insight-text {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}


/* ---------- Recommendations row (optional) ----------
   Pairs with Insights above. Insights = observed read. Recs = imperative
   action. Same structural shell so the two rows read as a paired closer
   to the journey grid; discipline says no new color, so the differentiator
   is FORM: each rec cell carries a 2px left-rail accent (echoing the Part
   5 .recommendation block) so the row reads as "do this," not "this is
   true." */
.bp-layer-recs {
  border-top: 1px solid var(--rule);
  background: var(--bg-elev);
}
.bp-label-recs {
  background: var(--bg-elev) !important;
}
.bp-label-recs .bp-lbl-title { color: var(--ink); }
.bp-c-rec {
  background: transparent;
  align-items: flex-start;
  gap: 0.4rem;
  padding: var(--pad-s);
  /* Left-rail accent: 2px stripe along the inner edge of every recs
     cell. The stripe IS the affordance signal -- "this is action." */
  border-left: 2px solid var(--accent);
  min-height: 5rem;
}
.bp-c-rec.bp-c-empty {
  /* Empty cells drop the rail so they don't masquerade as actions. */
  border-left: 2px solid transparent;
}
.rec-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
}
.rec-text {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}


/* ---------- Journey closer (optional) ----------
   Post-grid block at the end of Part 3. Pulls systemic conclusions
   out of the journey into stated form. Pairs with the Recs row at
   different altitudes: Recs = per-stage tactical, closer = whole-
   journey systemic. Mirrors HITL's part3-implications pattern.

   Visual register: pulled-quote tier. Display font + larger size +
   citation in the kit's whisper voice. No card frame, no shadow --
   the editorial weight is what makes it land, not decoration. */
.section-inner-post-bp {
  /* Wrapper that re-applies the section's container max-width to
     post-blueprint content, so the closer aligns with section copy
     even though the .bp-wrap above broke out of it. */
  max-width: var(--container-max);
  margin: 0 auto;
}
.journey-closer {
  display: flex;
  flex-direction: column;
  gap: var(--pad-m);
  margin: 4rem 0 0;
  padding: 2.5rem 0 1rem;
  border-top: 1px solid var(--rule);
}
.journey-closer-eyebrow {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0;
}
.journey-closer-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: start;
}
.journey-implication {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
/* Vertical hairline between paired anchors. The second anchor gets
   the rule on its left edge so it reads as a divider, not a frame. */
.journey-implication + .journey-implication {
  border-left: 1px solid var(--rule);
  padding-left: 3rem;
}
.journey-implication-anchor {
  font-size: clamp(1.3rem, 1.1rem + 0.5vw, 1.55rem);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0;
  margin: 0;
  color: var(--ink);
}
.journey-implication-text {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0;
  max-width: 38ch;
}
.journey-implication-citation {
  font-size: 0.75rem;
  font-style: italic;
  color: var(--ink-muted);
  margin: 0;
  max-width: 38ch;
}

/* Single-anchor layout: when only one .journey-implication is
   present, the pair grid collapses to one column at full width. */
.journey-closer-pair:has(.journey-implication:only-child) {
  grid-template-columns: 1fr;
}

/* Mobile: stack the pair, drop the vertical rule, replace with a
   horizontal one above the second anchor. */
@media (max-width: 720px) {
  .journey-closer { margin-top: 3rem; padding-top: 2rem; }
  .journey-closer-pair {
    grid-template-columns: 1fr;
    gap: 2.25rem;
  }
  .journey-implication + .journey-implication {
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-left: 0;
    padding-top: 2.25rem;
  }
}


/* ---------- Loop row (CS Help pattern) ----------
   Each .lp-pill uses left: -50% to physically straddle the boundary
   between this cell and the previous one. The pill IS the visual span
   between two columns; no SVG arc. The dashed border on the pill
   reinforces "loops back across this boundary".
   Place the pill in the cell that is the DESTINATION of the loop (where
   the customer lands after looping back). */
.bp-layer-loop .bp-label { background: var(--bg); }
.bp-c-loop {
  background: var(--bg-elev);
  min-height: 3.5rem;
  align-items: center;
  justify-content: center;
  padding: 0.6rem 0;
  position: relative;
  overflow: visible;
  border-right-color: transparent !important;
}
.lp-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--bg-elev);
  color: var(--flex-ink);
  border: 2px dashed var(--flex-edge);
  border-radius: 999px;
  padding: 0.3rem 0.85rem;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  position: relative;
  left: -50%;
  z-index: 2;
  /* Pills are visual only -- never intercept hover, so column highlight
     works whenever a pill overlays a cell. */
  pointer-events: none;
}
.lp-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--flex-edge);
}

/* LONG loop variant: the pill itself widens via --lp-span to physically
   span the loop. --lp-span = number of stage hops the loop covers
   (= cell-widths of pill width). No separate SVG arc -- the pill IS
   the visual span, same vocabulary as the short loop. */
.bp-c-loop-long {
  /* Allow the wide pill to overflow leftward across prior cells */
  overflow: visible;
}
.lp-pill-long {
  /* Anchor the pill's right edge at this cell's center; extend leftward
     by --lp-span * cell-widths so the pill spans from origin (its left
     end) to destination (this cell's center). */
  position: absolute;
  top: 50%;
  right: 50%;
  transform: translateY(-50%);
  /* Width = span hops (left of this cell's center) + 0.5 cell to center pill horizontally on this cell */
  width: calc((var(--lp-span, 2) + 0.5) * 100% - var(--pad-s) * 2);
  justify-content: center;
  z-index: 2;
  /* Reset the short-loop's left:-50% offset */
  left: auto;
}


/* ---------- Screenshots row ---------- */
/* The thumbnail is a real interactive element, not a placeholder.
   Frame styling reads as "device screen" (rounded corners, subtle
   elevation), not "image upload zone" (dashed border, generic icon).
   The .ev-thumb-mock SVG inside fills the frame edge-to-edge so each
   stage's mock reads as the actual screen at thumbnail size. The
   lightbox renders the same SVG at full canvas size on click. */
.bp-layer-screenshots .bp-label { background: var(--bg); }
.bp-c-shot {
  background: var(--bg-elev);
  align-items: stretch;
  justify-content: center;
  padding: 0.4rem;
  min-height: 5.25rem;
}
.ev-thumb-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--ink-soft);
  transition: color 0.15s ease;
}
.ev-thumb-btn:hover { color: var(--ink); }
.ev-thumb-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 8px;
}

/* Stage wrapper: holds the frame plus its badges. The wrapper is
   inline-flex sized to its content (the frame), with a small inset
   gutter that the badges live in. This keeps the badges near the
   thumbnail at any cell width without ever overlapping the mock
   itself, and without ballooning out when the cell is wide.
   The wrapper is the positioning context for both badges. */
.ev-thumb-stage {
  position: relative;
  display: inline-flex;
  align-self: center;
  /* The padding IS the badge gutter -- enough room for badges to sit
     at the corners without crowding. Tuned to read as breathing room,
     not awkward space. */
  padding: 0.85rem 0.85rem 0;
  margin: 0 auto;
}

/* The frame is the visible "device" -- a rounded rectangle with a
   1px outline and a soft shadow so it lifts off the row. Hover
   strengthens the rim and shadow to signal the affordance.
   Discipline: no transform on hover; badges sit absolutely on the
   parent stage wrapper, so any frame transform would create
   subpixel shifts on the badge corners. Stronger border + shadow
   is the safer cue. */
.ev-thumb-frame {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  max-width: 5rem;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 1px 0 color-mix(in srgb, var(--ink) 5%, transparent);
  transition: border-color 0.15s ease, box-shadow 0.2s ease;
}
.ev-thumb-btn:hover .ev-thumb-frame {
  border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
  box-shadow: 0 2px 8px color-mix(in srgb, var(--ink) 14%, transparent);
}
.ev-thumb-btn:focus-visible .ev-thumb-frame {
  border-color: var(--accent);
}
.ev-thumb-mock {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--ink);
}

/* Expand icon: top-LEFT corner of the stage wrapper. Sits OUTSIDE
   the frame in the wrapper's gutter, so the mock screen is never
   obscured by the affordance. Always visible (not hover-revealed);
   the reader knows on first glance the thumbnail is interactive.
   Pattern mirrors CS Help's expand-icon overlay. */
.ev-thumb-expand {
  position: absolute;
  top: 0;
  left: 0;
  width: 1.4rem;
  height: 1.4rem;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 2;
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
  color: var(--ink-soft);
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ev-thumb-expand svg {
  width: 60%;
  height: 60%;
  display: block;
}
.ev-thumb-btn:hover .ev-thumb-expand {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 60%, var(--rule));
  background: var(--bg-elev);
}

/* Set badge: top-RIGHT corner of the stage wrapper. Sits opposite
   the expand icon and outside the frame for the same reason -- never
   covers the mock. Pure text ("1 of N") -- the badge IS the signal. */
.ev-thumb-set-badge {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  pointer-events: none;
  z-index: 2;
  line-height: 1;
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
  white-space: nowrap;
}
.ev-thumb-btn:hover .ev-thumb-set-badge {
  color: var(--ink);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
}

.ev-thumb-btn .whisper {
  font-size: 0.7rem;
  letter-spacing: 0.02em;
}

/* Reduced motion: drop the shadow transition; keep border-color cue. */
@media (prefers-reduced-motion: reduce) {
  .ev-thumb-frame,
  .ev-thumb-btn:hover .ev-thumb-frame {
    transition: border-color 0.15s ease;
  }
  .ev-thumb-expand,
  .ev-thumb-btn:hover .ev-thumb-expand {
    transition: color 0.15s ease, border-color 0.15s ease;
  }
}

/* Legacy placeholder class -- kept for backward compatibility with
   artifacts that haven't migrated to .ev-thumb-frame yet. New artifacts
   should use the frame markup. */
.ev-thumb-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}
.ev-thumb-placeholder svg {
  opacity: 0.6;
}


/* ---------- Lightbox (HITL / CS Help pattern) ----------
   Floating pill header with prev/next/dots, separate close button,
   content area with image. Built dynamically by app.js. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 99999;
  background: rgba(0,0,0,0);
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  visibility: hidden;
  pointer-events: none;
  transition: background 0.3s ease, visibility 0s 0.35s;
}
.lightbox.open {
  visibility: visible;
  pointer-events: auto;
  background: rgba(0,0,0,0.85);
  transition: background 0.3s ease, visibility 0s 0s;
}

/* Hide top bar and sticky header when lightbox is open */
body:has(.lightbox.open) .top-bar,
body:has(.lightbox.open) .bp-sticky-header {
  opacity: 0;
  pointer-events: none;
}

/* Floating pill header. The header is `position: fixed` so it can
   sit above the dimmed lightbox content, but its visibility is tied
   to .lightbox.open via the parent state. When the lightbox closes,
   the header fades in lockstep with the dim backdrop -- no orphaned
   pill lingering after close. */
.lightbox-header {
  position: fixed;
  top: 20px;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 0 20px;
  z-index: 100001;
  pointer-events: none;
}
.lb-top-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  pointer-events: none;
}
.lb-top-row > * { pointer-events: auto; }

.lb-pill {
  display: flex;
  align-items: stretch;
  background: rgba(0,0,0,0.75);
  border-radius: 50px;
  border: 1px solid rgba(255,255,255,0.06);
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  pointer-events: auto;
  width: 460px;
  max-width: calc(100vw - 120px);
  overflow: hidden;
  position: relative;
}
.lb-header-center {
  flex: 1;
  text-align: center;
  min-width: 0;
  padding: 14px 8px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.lb-step-counter {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-bottom: 6px;
}
.lb-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: none;
  padding: 0;
  background: rgba(255,255,255,0.2);
  cursor: pointer;
  transition: background 0.2s, transform 0.2s;
}
.lb-dot:hover { background: rgba(255,255,255,0.5); transform: scale(1.3); }
.lb-dot-visited { background: rgba(255,255,255,0.45); }
.lb-dot-active { background: #fff; }
.lb-dot-active:hover { background: #fff; }
.lb-dot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.lb-step-title {
  font-size: 14px;
  font-weight: 400;
  color: #fff;
  line-height: 1.3;
}
.lb-step-num {
  font-weight: 400;
  color: rgba(255,255,255,0.75);
}
.lb-step-name { font-weight: 600; }
.lb-step-set {
  font-weight: 400;
  font-size: 0.85em;
  color: rgba(255,255,255,0.6);
  margin-left: 0.25rem;
}
.lb-step-desc {
  font-size: 11px;
  font-weight: 400;
  color: rgba(255,255,255,0.7);
  line-height: 1.3;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 320px;
  margin-left: auto;
  margin-right: auto;
}

.lb-pill-nav {
  width: 44px;
  min-width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: rgba(255,255,255,0.5);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.2s, color 0.2s;
  margin: auto 6px;
}
.lb-pill-nav:hover { background: rgba(255,255,255,0.1); color: #fff; }
.lb-pill-nav[hidden] { visibility: hidden; }
.lb-pill-nav:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.lb-close-float {
  width: 56px;
  height: 56px;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 50%;
  background: rgba(0,0,0,0.7);
  color: rgba(255,255,255,0.5);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  pointer-events: auto;
  transition: background 0.2s, color 0.2s;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 100003;
}
.lb-close-float:hover { background: rgba(0,0,0,0.8); color: #fff; }
.lb-close-float:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Parent-state-tied visibility: when the lightbox is closed, the
   floating header, close button, and keyboard hint fade out in
   lockstep with the dim backdrop. Without this, those elements
   (which are position: fixed at high z-indexes to escape the dimmed
   surface) linger at full opacity for the duration of the JS cleanup
   timeout. Tied to the same 0.3s window as the .lightbox background
   transition so the whole close animation reads as one motion. */
.lightbox-header,
.lb-close-float,
.lb-keyboard-hint {
  opacity: 1;
  transition: opacity 0.25s ease;
}
.lightbox:not(.open) .lightbox-header,
.lightbox:not(.open) .lb-close-float,
.lightbox:not(.open) .lb-keyboard-hint {
  opacity: 0;
  pointer-events: none;
}
/* On open, the keyboard hint fades in slightly later so the eye
   lands on the screenshot first, then notices the affordance. The
   delay matches HITL's pattern -- 300ms past the open transition. */
.lightbox.open .lb-keyboard-hint {
  transition: opacity 0.4s ease 0.3s;
}

/* Keyboard shortcut hint -- top-LEFT corner, opposite the floating
   close button (top-right). Pure decoration; AT users already get
   the keyboard model from the dialog's own semantics. Tokens kept
   in line with the existing dark-on-dark lightbox vocabulary so the
   hint reads as part of the modal, not a tooltip. */
.lb-keyboard-hint {
  position: fixed;
  top: 28px;
  left: 24px;
  background: rgba(0,0,0,0.75);
  border: 1px solid rgba(255,255,255,0.06);
  color: rgba(255,255,255,0.6);
  font-size: 11px;
  letter-spacing: 0.01em;
  padding: 6px 14px;
  border-radius: 20px;
  z-index: 100002;
  pointer-events: none;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  white-space: nowrap;
}
.lb-keyboard-hint kbd {
  display: inline-block;
  background: rgba(255,255,255,0.12);
  border-radius: 3px;
  padding: 1px 5px;
  font-family: inherit;
  font-size: 10px;
  color: rgba(255,255,255,0.78);
  margin: 0 1px;
}
/* Reduced motion: drop the open-fade delay so the hint appears
   immediately instead of feeling like a sluggish tooltip. */
@media (prefers-reduced-motion: reduce) {
  .lightbox.open .lb-keyboard-hint {
    transition: opacity 0.01ms;
  }
}
/* Print: the hint is purely interactive, no role on a printed page. */
@media print {
  .lb-keyboard-hint { display: none; }
}

.lightbox-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 100px 40px 40px;
  transform: scale(0.98);
  opacity: 0;
  transition: transform 0.25s ease, opacity 0.2s ease;
  margin: auto;
  min-width: min-content;
  will-change: opacity, transform;
}
.lightbox.open .lightbox-content {
  transform: scale(1);
  opacity: 1;
}
/* Lightbox content layout. Single-screen stages render one centered
   focal screen at near-fullscreen height. Multi-screen stages render
   every screen for the stage side by side, equal height, in a
   horizontally scrollable row -- the reader sees the whole set at a
   glance and can scroll if there are too many to fit. Mirrors the CS
   Help pattern. */
.lightbox-imgs {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 24px;
  max-width: 95vw;
  max-height: 86vh;
}
.lightbox-imgs.is-set {
  /* When the set overflows the viewport horizontally, allow scroll
     instead of squashing screens. Padding so the first/last frames
     get breathing room when scrolled to the edge. */
  overflow-x: auto;
  overflow-y: hidden;
  padding: 0 16px;
  justify-content: flex-start;
  scrollbar-color: rgba(255,255,255,0.3) transparent;
}
.lightbox-imgs.is-set::-webkit-scrollbar { height: 10px; }
.lightbox-imgs.is-set::-webkit-scrollbar-track { background: transparent; }
.lightbox-imgs.is-set::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.25);
  border-radius: 999px;
}
.lightbox-imgs img {
  height: 78vh;
  width: auto;
  max-width: none;
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.45);
  object-fit: contain;
  flex-shrink: 0;
}
.lightbox-imgs.is-single img {
  height: auto;
  max-height: 86vh;
  max-width: 92vw;
}
/* Legacy single-image class -- kept for backward compatibility. */
.lightbox-imgs img.lightbox-single {
  height: auto;
  max-height: 86vh;
  max-width: 92vw;
}

/* Lightbox mock frame: device-shaped container for the kit demo
   mocks. Same visual vocabulary as the thumbnail, just scaled. In a
   set, every mock gets its own frame and they sit side by side; the
   reader sees the stage's screens as a strip, not a stack. */
.lb-mock-frame {
  height: 78vh;
  aspect-ratio: 4 / 5;
  max-width: 92vw;
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0,0,0,0.45);
  color: var(--ink);
  flex-shrink: 0;
}
.lightbox-imgs.is-single .lb-mock-frame {
  height: 80vh;
  max-width: 92vw;
}
.lb-mock {
  display: block;
  width: 100%;
  height: 100%;
}
.lb-no-img {
  font-size: 15px;
  color: rgba(255,255,255,0.5);
  font-style: italic;
  padding: 80px 40px;
  text-align: center;
  background: rgba(255,255,255,0.04);
  border: 1px dashed rgba(255,255,255,0.2);
  border-radius: 8px;
  max-width: 480px;
}


/* ---------- Bridge actor lane (kept for patterns.html) ---------- */
.bp-layer-bridge .bp-c {
  background: color-mix(in srgb, var(--accent-soft) 20%, var(--bg-elev));
  border-left: 2px solid var(--accent);
}
.bp-layer-bridge .bp-label {
  border-left: 2px solid var(--accent);
}


/* ---------- Emotion arc row (kept for patterns.html) ---------- */
.bp-layer-emotion .bp-c {
  min-height: 3rem;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.bp-layer-emotion .bp-label { background: var(--bg); }
.bp-emotion-calm .bp-c-headline,
.bp-emotion-relieved .bp-c-headline { color: var(--hold-ink); }
.bp-emotion-calm,
.bp-emotion-relieved { background: color-mix(in srgb, var(--hold-bg) 50%, var(--bg-elev)); }
.bp-emotion-anxious .bp-c-headline { color: var(--flex-ink); }
.bp-emotion-anxious { background: color-mix(in srgb, var(--flex-bg) 50%, var(--bg-elev)); }
.bp-emotion-frustrated .bp-c-headline { color: var(--break-ink); }
.bp-emotion-frustrated { background: color-mix(in srgb, var(--break-bg) 50%, var(--bg-elev)); }
.bp-emotion-uncertain .bp-c-headline { color: var(--ink-soft); }
.bp-emotion-uncertain { background: var(--bg-elev); }


/* ---------- Back-to-top FAB ----------
   Appears bottom-right when scrolled past 1.5 viewport heights.
   Reads as native UI chrome, not decoration: --bg-elev surface,
   --accent stroke, layered shadow that lifts on hover. The reveal
   animation slides the FAB up 8px while fading in -- the motion
   signals "appearing because something changed," not "always here
   just invisible." Theme-tuned shadows for warm and dark so the
   button never blends into the page. */
.btt {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 90;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev);
  color: var(--accent);
  border: 1px solid var(--rule);
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  box-shadow: 0 4px 14px -6px rgba(26, 26, 26, 0.14),
              0 1px 3px rgba(26, 26, 26, 0.06);
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity 0.25s ease, transform 0.25s ease,
              background 0.2s ease, color 0.2s ease,
              border-color 0.2s ease, box-shadow 0.2s ease;
}
.btt.visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.btt:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent) 32%, transparent),
              0 1px 3px rgba(26, 26, 26, 0.08);
  transform: translateY(-2px);
}
.btt:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.btt svg {
  width: 18px;
  height: 18px;
}

/* Per-theme shadow tuning. Warm gets a warmer ambient + warmer hover
   glow; dark gets a deeper ambient + accent-blue hover glow so the
   button reads against the dark surface without going neon. */
body[data-theme="warm"] .btt {
  box-shadow: 0 4px 14px -6px rgba(42, 31, 20, 0.14),
              0 1px 3px rgba(42, 31, 20, 0.06);
}
body[data-theme="warm"] .btt:hover {
  box-shadow: 0 6px 18px -6px rgba(138, 90, 42, 0.30),
              0 1px 3px rgba(42, 31, 20, 0.08);
}
body[data-theme="dark"] .btt {
  box-shadow: 0 4px 16px -6px rgba(0, 0, 0, 0.5),
              0 1px 3px rgba(0, 0, 0, 0.3);
}
body[data-theme="dark"] .btt:hover {
  background: var(--accent-soft);
  box-shadow: 0 6px 20px -6px rgba(140, 176, 255, 0.35),
              0 1px 3px rgba(0, 0, 0, 0.3);
}

/* Reduced motion: drop the lift + slide-up reveal; keep opacity for
   the show/hide cue and color transitions for hover feedback. */
@media (prefers-reduced-motion: reduce) {
  .btt {
    transform: none !important;
    transition: opacity 0.15s ease, background 0.15s ease,
                color 0.15s ease, border-color 0.15s ease;
  }
  .btt:hover { transform: none !important; }
}

/* Mobile: tighter inset, slightly smaller target. */
@media (max-width: 720px) {
  .btt {
    right: 1rem;
    bottom: 1rem;
    width: 2.5rem;
    height: 2.5rem;
  }
  .btt svg {
    width: 16px;
    height: 16px;
  }
}


/* ---------- Whisper class: muted small text ---------- */
.whisper {
  font-size: 0.8rem;
  color: var(--ink-muted);
}


/* ---------- Mobile ---------- */
@media (max-width: 720px) {
  :root { --section-pad: 3rem; }
  .top-nav { display: none; }
  .problem-thesis { font-size: 1.75rem; }
  .section-title { font-size: 1.5rem; }
  .stress-body { grid-template-columns: 1fr; }
  .stress-col { border-right: 0; border-bottom: 1px solid var(--rule); }
  .stress-col:last-child { border-bottom: 0; }
}


/* ============================================================
   ADDITIONAL COMPONENTS (CS Help patterns)
   ------------------------------------------------------------
   Available components beyond the default 5-Part shell. Use as
   needed in your project. None are required for kit conformance.
   ============================================================ */


/* ---------- Hero with count-up stat (CS Help opener pattern) ----------
   Use as an alternative to .problem-hero when your Part 1 leads with
   a single dramatic number rather than thesis + pillars.
*/
.hero-stat {
  padding: var(--pad-xl) var(--pad-m);
  background: var(--bg-elev);
  border-bottom: 1px solid var(--rule);
}
.hero-stat-inner {
  max-width: var(--container-max);
  margin: 0 auto;
}
.hero-stat-eyebrow {
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
  margin: 0 0 var(--pad-m);
}
/* Claim is the headline. The number is the evidence inside it. */
.hero-stat-claim {
  font-size: clamp(1.8rem, 1.4rem + 1.6vw, 2.6rem);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.25;
  margin: 0 0 var(--pad-m);
  max-width: 28ch;
  color: var(--ink);
}
/* The number sits inline inside the claim as an accent span,
   so it reads as part of the headline sentence rather than
   competing with it. Inline-block keeps the line break behavior
   tight when the claim wraps. */
.hero-stat-num {
  font-family: var(--font-display);
  font-size: 1.15em;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
  letter-spacing: 0;
  margin: 0;
  display: inline-block;
}
.hero-stat-detail {
  font-size: 1rem;
  color: var(--ink-soft);
  max-width: 55ch;
  margin: 0;
  line-height: 1.6;
}


/* ---------- Ingress fan-in (CS Help Part 1 pattern) ----------
   Two columns of source items flow into a central hub. Use when your
   artifact answers "where do they come from / what feeds this?" The
   hub is the convergence point; the sides are the sources.
*/
.ingress {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--pad-l);
  align-items: stretch;
  margin: var(--pad-l) 0;
}
.ing-side {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: var(--pad-m);
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 10px;
}
.ing-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
  font-weight: 600;
  padding-bottom: 0.65rem;
  margin-bottom: 0.35rem;
  border-bottom: 1px solid var(--rule);
}
.ing-header > :last-child {
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--ink);
  text-transform: none;
}
.ing-src {
  font-size: 0.92rem;
  padding: 0.55rem 0 0.55rem 0.85rem;
  color: var(--ink-soft);
  border-left: 2px solid color-mix(in srgb, var(--rule) 70%, transparent);
  line-height: 1.4;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.ing-src:hover {
  border-left-color: var(--accent);
  color: var(--ink);
}
.ing-src-lead {
  font-weight: 600;
  color: var(--ink);
}
.ing-src-note {
  font-size: 0.78rem;
  color: var(--ink-muted);
  margin-left: 0.4rem;
}
.ing-hub {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--pad-l) var(--pad-m);
  background: color-mix(in srgb, var(--accent-soft) 70%, var(--bg-elev));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--rule));
  border-radius: 12px;
  min-width: 13rem;
}
.ing-hub-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.01em;
}
.ing-hub-num {
  font-size: 2.6rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
  letter-spacing: 0;
  margin: 0.5rem 0 0.35rem;
}
.ing-hub-stat {
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.ing-hub-note {
  font-size: 0.75rem;
  color: var(--ink-muted);
  font-style: italic;
  margin-bottom: var(--pad-s);
  max-width: 14ch;
  line-height: 1.4;
}
@media (max-width: 720px) {
  .ingress { grid-template-columns: 1fr; }
}


/* ---------- Pattern cards (CS Help Part 3 pattern) ----------
   Grid of theme cards. Use when your artifact closes by surfacing
   patterns observed across the journey -- a "this repeats" closing.
*/
.patterns-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--pad-m);
  margin: var(--pad-m) 0;
}
.pattern-card {
  padding: var(--pad-m);
  background: var(--bg-elev);
  border: 1px solid var(--rule);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Pattern category glyph (optional). Sits at the top of the card,
   above the eyebrow. Encodes the SHAPE of the pattern (mismatch /
   drift / gap / loop / cliff / compounding) via geometry; the kit
   ships six variants in the SVG defs library at the top of each
   page. Color stays --accent so it reads as a category marker, not
   a decoration. Subtle accent-soft circle behind it lifts the glyph
   off the card surface without competing with the pattern name.
   Cards without data-pattern-type render glyph-less. */
.pattern-card-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: 0.15rem;
  background: color-mix(in srgb, var(--accent-soft) 70%, var(--bg-elev));
  border-radius: 999px;
  color: var(--accent);
  flex-shrink: 0;
}
.pattern-card-glyph svg {
  width: 60%;
  height: 60%;
  display: block;
}
.pattern-card-num {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 600;
}
.pattern-card-name {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 0;
}
.pattern-card-body {
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin: 0;
  line-height: 1.5;
}
.pattern-card-example {
  font-size: 0.8rem;
  color: var(--ink-muted);
  font-style: italic;
  margin-top: auto;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
}


/* ---------- Compressed-shape Part adjustments ----------
   When folding Part 2 (Scale) into Part 1 (Problem), use .scale-inline
   on the kpi strip so it sits inside the hero rather than as its own
   section.
*/
.scale-inline {
  margin-top: var(--pad-l);
  padding-top: var(--pad-l);
  border-top: 1px solid var(--rule);
}


/* ============================================================
   PRINT STYLES
   ------------------------------------------------------------
   When a reader exports to PDF, the artifact should remain readable:
   no fixed top bar, full blueprint, no scroll affordances, sources
   sidecar always expanded.
   ============================================================ */
@media print {
  body { background: white; color: black; }
  .top-bar, .btt, .skip-link { display: none !important; }
  .section { padding: 1.5rem 0; break-inside: avoid; page-break-inside: avoid; }
  .section-title { break-after: avoid; }
  .blueprint { min-width: 0; overflow: visible; }
  .bp-wrap { overflow: visible; }
  .sources-details[open] .sources-summary::before,
  .sources-details .sources-summary::before { content: ""; }
  .sources-details summary { font-weight: 700; }
  .sources-list { display: block !important; }
  .sources-details { open: true; }
  details { display: block; }
  details > summary { list-style: none; }
  details:not([open]) > *:not(summary) { display: block !important; }
  a { color: black; text-decoration: underline; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #555; }
}
