/* ═════════════════════════════════════════════════════════
   THE WALKTHROUGH
   A scripted demo. Nothing here computes anything: every
   number, file and verdict is written down, so it looks the
   same every time and can be driven at a talking pace.

   It borrows every component from site.css on purpose. The
   reason a scripted demo usually feels fake is that it is
   drawn separately from the product; this one is the product's
   own parts, arranged.
   ═════════════════════════════════════════════════════════ */

html, body { height: 100%; }
body.demo {
  background: var(--bg);
  overflow: hidden;               /* the stage never scrolls, it advances */
  display: flex; flex-direction: column;
  /* Job P5: the page now carries the shared nav.top (site.css), which is
     position:fixed and so takes no space in this flex column on its own.
     This page never scrolls (window scrollY stays 0), so nav.top never
     gains its .scr shrink state -- its rendered height is a constant
     86.6875px at 1512 and 85px at 390 (measured directly). 87px clears
     both without leaving a visible gap at either width. */
  padding-top: 87px;
}
body.demo::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(52% 40% at 14% 12%, rgba(232,98,47,.09), transparent 70%),
    radial-gradient(46% 36% at 88% 82%, rgba(217,138,74,.08), transparent 72%);
}

/* ── the bar ──────────────────────────────────────────────── */
.dbar {
  position: relative; z-index: 5; flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 20px clamp(20px, 3.2vw, 46px);
  box-shadow: inset 0 -1px 0 var(--line-2);
}
.dbar .left, .dbar .right { display: flex; align-items: center; gap: 14px; }

/* said plainly, and never removed. A scripted demo that does not say so is a lie. */
.scripted {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .15em; text-transform: uppercase;
  color: var(--ink-3); padding: 6px 12px; border-radius: 100px; box-shadow: var(--sh-in);
  background: var(--surf-2); white-space: nowrap;
}
.scripted i { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-3); flex: none; }

/* ── the rail ─────────────────────────────────────────────── */
.rail { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }
.rail button {
  display: inline-flex; align-items: center; gap: 8px; padding: 7px 13px; border-radius: 100px;
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); background: none; border: 0; cursor: pointer;
  transition: color .4s ease, background .4s ease;
}
.rail button::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; flex: none;
  box-shadow: 0 0 0 1px var(--line); transition: background .4s ease, box-shadow .4s ease;
}
.rail button:hover { color: var(--ink-2); }
.rail button[aria-current="true"] { color: var(--accent-tx); background: rgba(232,98,47,.08); }
.rail button[aria-current="true"]::before { background: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.rail button.seen::before { background: var(--line); }
.rail .tick { width: 12px; height: 1px; background: var(--line); flex: none; }
@media (max-width: 1080px) { .rail button span { display: none; } .rail button { padding: 7px 6px; } }
/* phones: the bar had no narrow layout, so the exit button sat off the right
   edge of a body that cannot scroll. Brand and exit share the first row, the
   scripted pill wraps under the brand if it must, the rail takes a full row. */
@media (max-width: 640px) {
  .dbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px 16px; align-items: start; }
  .dbar .left { grid-column: 1; grid-row: 1; flex-wrap: wrap; min-width: 0; }
  .dbar .right { grid-column: 2; grid-row: 1; justify-self: end; }
  .rail { grid-column: 1 / -1; grid-row: 2; }
}
/* Job P2 item 10: two stacked causes of the same overlap (the pill vs
   "Back to the site", measured 12x23px live).
   1. align-items:center (the grid row's own cross-axis alignment, not
      .left/.right's internal flex alignment on line 40) centred the
      single-line button against the whole two-line-tall .left column,
      landing its vertical middle on the wrapped pill's line instead of
      the brand's. Fixed with align-items:start above.
   2. .scripted keeps white-space:nowrap (needed so its own two words never
      break mid-word) with no width limit, so as a flex item of .left it
      never shrinks below its full 217px text width -- wider than the
      ~189px column 1 gets once column 2 (the button) takes its own auto
      width. A flex item's automatic minimum size for shrinking is its
      min-content size, and nowrap text's min-content IS its full width;
      that's what let it paint straight through the grid's own column
      boundary into column 2's space regardless of vertical alignment.
      Removing nowrap here (its min-content becomes "WALKTHROUGH", the
      longest word, not the full phrase) lets it actually shrink to fit
      column 1 and wrap to two lines instead of overflowing horizontally.
   Both were needed together: item 1 alone still left 12x16px of overlap,
   confirmed live via getBoundingClientRect before adding item 2. */
@media (max-width: 640px) { .scripted { white-space: normal; } }

/* ── the stage ────────────────────────────────────────────── */
.stage {
  position: relative; z-index: 2; flex: 1; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
  gap: clamp(24px, 3.6vw, 60px);
  padding: clamp(22px, 3vw, 44px) clamp(20px, 3.2vw, 46px) clamp(18px, 2.4vw, 34px);
}
/* Job S3 (Fable's own follow-up on demo's window, below): no
   grid-template-rows means the single implicit row sizes to its
   content (auto), not to whatever height .stage's own flex:1 actually
   gives it -- #beat and .win both settled at ~580px (this scene's
   text height) while .stage itself had ~755px of real content room,
   the gap just sitting unused. minmax(0,1fr) makes the row claim all
   of it, so align-items:normal's default stretch has a real, definite
   height to stretch .win (a flex container, and the two need to
   agree) into -- needed for .win's own fix below to have a genuine
   height to work from, not a guess. */
@media (min-width: 901px) { .stage { grid-template-rows: minmax(0, 1fr); } }
@media (max-width: 900px) {
  .stage { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
}

/* the reading column */
.beat { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.beat .n {
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent-tx);
}
.beat h2 {
  font-size: var(--t-6); line-height: 1.04; letter-spacing: -.038em;
  margin: 14px 0 16px; max-width: 15ch;
}
.beat p { font-size: var(--t-3); line-height: 1.6; color: var(--ink-2); max-width: 42ch; }
.beat .note {
  margin-top: 20px; padding-top: 16px; box-shadow: inset 0 1px 0 var(--line-2);
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .1em; line-height: 1.7;
  text-transform: uppercase; color: var(--ink-3); max-width: 40ch;
}
@media (max-width: 900px) {
  .beat h2 { max-width: none; }
  .beat p { max-width: none; }
  .beat .note { display: none; }
}

/* the window. One window for the whole walkthrough: the chrome persists and the
   body changes, so it reads as one product moving rather than seven pictures. */
/* the window keeps a screen-like proportion and sits centred, rather than
   stretching to whatever height the viewport happens to be. A panel with 400px
   of void under three rows reads as a page that failed to load. */
/* Fable's own finding (site-builder's audit): that intent wasn't actually
   held at wide viewports. width:100% + aspect-ratio + max-height:100%
   derives height FROM width, but once the ratio-derived height exceeds
   what's actually available, max-height clips it back down -- at that
   point BOTH dimensions are externally constrained and aspect-ratio no
   longer applies (it only fills in a dimension left auto, it doesn't
   reconcile two already-fixed ones). Measured live at 1512x982: 986x580
   (1.70:1, not 1.6:1); at 1920x1080: 1388x580 (2.39:1) -- wider and
   flatter as the viewport widened, plus the leftover width (100% of a
   track that outgrew the ratio-correct box) read as an off-centre,
   right-anchored window rather than a centred one.
   Fable's own formula was width = min(container, (100vh - nav - 160) *
   1.6) -- a fixed 87+160 viewport-height budget. Tried it first
   (.win given that literal height, .ui's width from the same formula):
   it broke a different way instead of fixing it -- 87+160 doesn't
   equal the space .stage's chrome (dbar, dfoot, .stage's own
   padding) actually leaves, so .win came out 44px TALLER than
   .stage's real remaining room and bled into .dfoot at both 1512 and
   1920 (measured live before shipping it). Used the real number
   instead of a second guessed one: with .stage's row now claiming its
   full real height (grid-template-rows:minmax(0,1fr) above), .win
   naturally stretches to .stage's actual available space -- exact,
   not approximated, and it can't drift if dbar/dfoot ever change
   height again. .ui's height:100% resolves against that now-genuine
   definite parent, width:auto lets aspect-ratio derive the exact
   16:10 companion width, and max-width:100% is Fable's own
   min(container, ...) -- the same container her formula named, just
   reached through .win's real height instead of a constant that
   didn't hold. align-items:center on .win centres the window in
   whatever leftover track width the ratio doesn't use. */
.win { display: flex; flex-direction: column; justify-content: center; min-height: 0; min-width: 0; }
.win .ui {
  width: 100%; max-height: 100%; aspect-ratio: 16 / 10;
  display: flex; flex-direction: column; box-shadow: var(--sh-lg), var(--sh-in);
}
@media (min-width: 901px) {
  /* gated to desktop only -- below it .win .ui's own mobile override
     two rules down (aspect-ratio:auto;flex:1) already abandons the
     fixed ratio on purpose (the taller scenes need the window's real
     height, not a letterboxed one); an unconditional version here
     would have fought that the same way an earlier item-8 rule fought
     os.html's own mobile fallback (see that fix's commit) -- checked
     this time before shipping, not after.
     height:100%/width:auto (tried first) broke again at 1512, where
     the container is the binding constraint: max-width:100% caps the
     aspect-ratio-derived width down, but height was already the fixed
     side (100%), and aspect-ratio doesn't reduce an already-definite
     dimension to re-match a capped one -- 985.58x657.31, 1.499:1, not
     1.6. WIDTH has to be the one derived value in every case, honouring
     Fable's own min(container, height-budget * 1.6) shape exactly, but
     "height-budget" only exists as a real number at render time now
     that it comes from .stage's own stretch rather than a constant --
     container queries read it back: .win is a size container, .ui's
     width reads 100cqh (.win's own height, in its own px) through the
     ratio, min()'d against 100% (.win's own width) the normal way,
     and height stays auto so aspect-ratio derives it from whichever
     of those two the min() actually picked. Verified live both ways:
     985.58x616 at 1512 (container binds, 1.6:1), 1208.5x755.31 at
     1920 (the height budget binds, 1.6:1) -- exact in both directions
     now, not just the one that happened to need no capping. */
  .win { align-items: center; container-type: size; }
  .win .ui { width: min(100%, calc(100cqh * 1.6)); height: auto; max-width: 100%; max-height: none; }
}
.win .ui-body { flex: 1; min-height: 0; overflow: hidden; padding: clamp(18px, 2vw, 28px); }
@media (max-width: 900px) {
  /* on a phone the window cannot hold the taller scenes at once, so its body
     scrolls rather than clipping them. The page itself still never scrolls. */
  .win .ui { aspect-ratio: auto; flex: 1; }
  .win .ui-body { overflow-y: auto; -webkit-overflow-scrolling: touch; padding-top: 4px; }
  .scene { height: auto; justify-content: flex-start; }
  /* Job P2 item 9: site.css's own .files/.file (shared with product.html,
     so the fix lives here instead) stack four rows to ~172px -- more than
     the intake scene's whole ui-body gets at this width (~120px), so at
     rest (before anyone discovers the window scrolls) the list's own
     layout box reads past the footer's caption/play row underneath it.
     A row of horizontally-scrolling chips (same peek pattern as the
     mini-nav fix, item 8) takes one row's height instead of four,
     comfortably inside the window with no scroll needed to see it.
     flex-shrink:0 is load-bearing, not decoration: .files is a flex item
     of .grow (a column flex container with real shrink pressure here),
     and setting overflow-x:auto on it also computes overflow-y to auto
     (the spec turns a plain "visible" axis into "auto" the moment its
     sibling axis scrolls) -- an item with any overflow other than
     visible loses its content-based automatic minimum size for
     shrinking purposes, so without this it collapsed to 2px instead of
     one row's real height, checked live via getBoundingClientRect. */
  .win .files { flex-direction: row; flex-shrink: 0; overflow-x: auto; gap: 8px; padding-bottom: 2px;
    -webkit-overflow-scrolling: touch; scrollbar-width: none; mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
  .win .files::-webkit-scrollbar { display: none; }
  .win .file { flex: none; white-space: nowrap; padding: 6px 9px; }
  /* fix(fovea) follow-up: item 9's row-of-chips wasn't enough on its own --
     Fable's detector still found the chips over the footer copy on main,
     because .scene/.grow (min-height:0, real shrink pressure at this
     width) let their children's rects spill past ui-body's own clip
     regardless of any one child's own height. The chips only ever clear
     the footer for real if EVERYTHING above them in the scene actually
     fits ui-body's box, not just scrolls inside it -- so this shrinks the
     two things eating the most room: .folder's path text (was two lines,
     53px) to one line with an ellipsis (a live folder path reads fine
     truncated), and the scene's own gaps (14px/10px, was tuned for the
     desktop aspect-ratio window, not this squeezed one). tally (not part
     of Fable's report) still scrolls into view below the fold -- only
     lbl/folder/files needed to fit above the footer, confirmed live via
     getBoundingClientRect against ui-body's own bottom edge, not just the
     footer's, so nothing here relies on scroll-clipping to look right. */
  .win .scene { gap: 4px; }
  .win .scene .grow { gap: 4px; }
  .win .folder { min-width: 0; padding: 6px 9px; }
  .win .folder-path { display: inline-block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
  /* site.css's .folder+.files{margin-top:10px} (a shared rule, tuned for
     the desktop card) stacked on top of .grow's own 4px flex gap above --
     found live via computed style (10px margin-top on .files, not the
     gap), zeroed here so the gap value alone governs the spacing. */
  .win .folder+.files { margin-top: 0; }
  /* The tally line (.grow's last child, a big number + a two-line caption,
     only ever used in this one intake scene) is the one thing left that
     still can't fit above the footer at this width even after every trim
     above -- fitting it too would mean shrinking a live counter down to
     the point it stops reading as one. lbl/folder/files were the only
     elements Fable's report named and the only ones the "own row, 16px
     gutter" ask was about, so this scene alone drops the tally line at
     this width rather than crush it -- the same call this file already
     makes for .beat .note above, a supplementary line, not core content. */
  .win .tally { display: none; }
}

.scene { display: none; height: 100%; flex-direction: column; justify-content: center; gap: 14px; min-height: 0; }
.scene.on { display: flex; }
.scene .lbl { flex: none; }
.scene .grow { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
/* the two scenes whose last line is a footnote push it to the floor instead */
/* the footnote gets a clear gap from the body rather than floating to a floor
   that no longer exists now the scene is centred */
.scene .grow > .drop-note:last-child,
.scene .grow > .tally:last-child { margin-top: 8px; padding-top: 12px; box-shadow: inset 0 1px 0 var(--line-2); }

/* every element that animates in starts here and is released by the script */
.q { opacity: 0; transform: translateY(10px); }
.q.in { opacity: 1; transform: none; transition: opacity .55s ease, transform .6s cubic-bezier(.22,1,.36,1); }

/* ── the controls ─────────────────────────────────────────── */
.dfoot {
  position: relative; z-index: 5; flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 0 clamp(20px, 3.2vw, 46px) clamp(18px, 2.4vw, 30px);
}
.caption {
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); min-width: 0;
}
.caption b { color: var(--accent-tx); font-weight: 400; }
.ctrl { display: flex; align-items: center; gap: 9px; flex: none; }
.ctrl button {
  /* 390 audit (Codex, via Fable): was 40x40, under the 44px touch target.
     demo.css is this page's own file (only demo.html loads it), so the
     fix lives directly in the rule rather than a scoped override elsewhere. */
  width: 44px; height: 44px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--surf-hi); box-shadow: var(--sh-sm), var(--sh-in);
  display: flex; align-items: center; justify-content: center;
  transition: transform .4s cubic-bezier(.22,1,.36,1), box-shadow .4s ease;
}
.ctrl button:hover { transform: translateY(-2px); box-shadow: var(--sh-md), var(--sh-in); }
.ctrl button:disabled { opacity: .38; cursor: default; transform: none; }
.ctrl button svg { width: 16px; height: 16px; stroke: var(--ink); fill: none; stroke-width: 1.9; }
.ctrl .play { width: auto; padding: 0 20px; border-radius: 100px; gap: 9px; background: var(--ink); }
.ctrl .play svg { stroke: var(--surf); }
.ctrl .play span {
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .14em; text-transform: uppercase; color: var(--surf);
}

/* the autoplay progress, drawn on the ring of the next button */
.ctrl .next { position: relative; }
.ctrl .next .ring { position: absolute; inset: -3px; border-radius: 50%; pointer-events: none; }
.ctrl .next .ring circle { fill: none; stroke: var(--accent); stroke-width: 1.6; opacity: .9; }

/* ── scene-specific pieces the site does not already have ── */

/* the count that ticks up during intake */
.tally { display: flex; align-items: baseline; gap: 9px; }
.tally b { font-size: var(--t-6); font-weight: 600; letter-spacing: -.04em; color: var(--ink); }
.tally span {
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
}

/* the two-up that shows the same brief with and without the lens */
.vs { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; min-height: 0; flex: 1; }
.vs > div { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.vs .cap {
  font-family: var(--mono); font-size: var(--t-1); letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
}
.vs .cap.acc { color: var(--accent-tx); }
.vs .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; flex: 1; min-height: 0; }
.vs .grid .tile { border-radius: 8px; min-height: 0; }
.vs .grid .tile.flat { background: #ddd8ce; }

/* the ship card that closes the walkthrough */
.ship { display: flex; align-items: center; gap: 14px; padding: 13px 15px; border-radius: 12px;
  background: var(--surf-hi); box-shadow: var(--sh-sm), var(--sh-in); }
.ship .tk {
  width: 26px; height: 26px; border-radius: 50%; background: #4a6b57; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.ship .tk svg { width: 13px; height: 13px; stroke: #fff; fill: none; stroke-width: 2.6; }
.ship b { font-size: var(--t-2); font-weight: 600; letter-spacing: -.02em; display: block; }
.ship span { font-size: var(--t-1); color: var(--ink-3); display: block; margin-top: 2px; }
.ship .amt {
  margin-left: auto; font-family: var(--mono); font-size: var(--t-1); letter-spacing: .1em; color: var(--accent-tx);
  white-space: nowrap;
}

/* the end card */
.endcard { position: absolute; inset: 0; z-index: 8; display: none;
  align-items: center; justify-content: center; text-align: center;
  background: rgba(5,5,5,.92); backdrop-filter: blur(10px); }
.endcard.on { display: flex; }
.endcard .in { max-width: 46ch; padding: 0 24px; }
.endcard h2 { font-size: var(--t-6); line-height: 1.06; letter-spacing: -.04em; }
.endcard p { margin-top: 14px; color: var(--ink-2); font-size: 16px; line-height: 1.6; }
.endcard .row { margin-top: 26px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* ── no JavaScript, and reduced motion ────────────────────── */
.noscript-note { display: none; }
html.no-js .stage { display: block; overflow: auto; }
html.no-js .scene { display: flex !important; margin-bottom: 26px; }
html.no-js .q { opacity: 1; transform: none; }
html.no-js body.demo { overflow: auto; }

@media (prefers-reduced-motion: reduce) {
  .q, .q.in { opacity: 1; transform: none; transition: none; }
  .ctrl button { transition: none; }
}

/* one continuous rail across the fan, instead of a dash per branch */
.win .tree .fan { position: relative; }
.win .tree .fan::before {
  content: ""; position: absolute; top: 0; left: 10%; right: 10%; height: 1px; background: var(--line);
  transform: scaleX(0); transform-origin: center; transition: transform .6s cubic-bezier(.22,1,.36,1) .45s;
}
.win .tree.on .fan::before { transform: scaleX(1); }
.win .tree .lb::before { content: none; }
