/* Research — the unified page: one stage, three visualizers, then the
   essay. Loaded after ../styles.css (untouched — it owns the shared
   furniture: corners, band, footer); this file owns the stage (tabs,
   frames, noise overlay, year slider) and the essay + its "proposed
   instrument" specimens. */

/* ---------- the stage: three views, one visible ---------- */

/* the visualizer frame sits at the page's horizontal CENTRE (713px,
   margin auto — as it always did); the copy column HANGS immediately
   left of it, out of flow, its top flush with the frame's top and one
   gutter off the frame's left edge — the composition reads
   [copy][CENTERED frame][empty balance]. The column width flexes down
   toward the page padding on narrower viewports, and below 1200px the
   column reflows above the visualizer instead. */
/* the stage claims exactly one viewport below the fixed header's
   clearance (.blog-main's padding-top, 140px / 120px<900px) — dvh first
   with a vh fallback for older browsers (mobile URL-bar chrome makes vh
   unreliable; dvh accounts for it). min-height only ever pushes the
   essay DOWN past the fold, never up — content taller than the box
   (short viewports) is handled by the max-height compressions below, so
   the tabs never get pushed out of the initial view. Flex + centered
   content keeps the existing vertical balance of view + tabs. */
.stage-zone {
  --title-col: 280px;
  --stage-gap: 32px;
  --header-clearance: 140px;
  position: relative;
  min-height: calc(100vh - var(--header-clearance));
  min-height: calc(100dvh - var(--header-clearance));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* the canvas swap is a quick fade-in of the incoming view — the text
   carries the encryption grammar (research.js), the frame furniture
   holds pixel-still */
.stage-view {
  position: relative;
  animation: stage-in 120ms linear;
}

.stage-view.is-off {
  display: none;
  animation: none;
}

@keyframes stage-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .stage-view {
    animation: none;
  }
}

/* input is blocked while a view transition runs — the encrypting text
   and the noise flood must not be re-rendered from under the scramble */
.stage-zone.is-switching {
  pointer-events: none;
}

/* the hanging copy column: right edge one gutter left of the centred
   frame's left edge (50% of the row + half the frame), top on the
   frame top. Width caps at --title-col and yields to the page padding
   first (50% - half-frame - gutter is exactly the room to the row's
   left edge), so it can never clip the viewport. */
.stage-copy {
  position: absolute;
  top: 0;
  right: calc(50% + 356.5px + var(--stage-gap));
  width: min(var(--title-col), calc(50% - 356.5px - var(--stage-gap)));
}

/* the switcher row — the home slider's [1] [2] variable-button grammar,
   active blue. It sits BELOW the stage, its left edge on the frame's
   left edge, just under the bottom brackets; [4] explore rides the same
   row as an action, never active. */
.v-tabs {
  width: 100%;
  max-width: 713px;
  margin: 12px auto 0;
  display: flex;
  flex-wrap: wrap; /* narrow screens wrap whole tabs, never inside one */
  justify-content: flex-start;
  gap: 8px 16px;
  font-size: 12px;
}

.v-tab {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-weight: inherit;
  font-size: 12px;
  color: var(--ink-dim);
  cursor: pointer;
  white-space: nowrap;
}

.v-tab:hover {
  color: var(--ink);
}

.v-tab.is-active {
  color: #2222ff;
}

/* the year slider — view [3]'s time control in the home page's
   .speed-slider grammar: dim label, 160px 1px track with ticks, 2px
   blue handle, blue value readout. It sits in the copy column's flow,
   centered below the subhead, so it appears and leaves with the view;
   where the column runs narrow the track shrinks (handle and ticks are
   %-positioned, so the five stops keep their places). */
.year-slider {
  width: max-content;
  max-width: 100%;
  margin: 24px auto 0;
  font-size: 12px;
}

.year-slider .slider-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.year-slider .slider-label {
  color: var(--ink-dim);
  min-width: 4ch;
}

/* 14px-tall hit area; the visible 1px line runs across its center */
.year-slider .slider-track {
  position: relative;
  flex: 0 1 160px;
  width: 160px;
  min-width: 84px;
  height: 14px;
  touch-action: none;
  cursor: pointer;
}

.year-slider .slider-track::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--ink);
}

.year-slider .tick {
  position: absolute;
  left: var(--t);
  top: 50%;
  width: 1px;
  height: 5px;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.5);
}

.year-slider .slider-handle {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 14px;
  transform: translate(-50%, -50%);
  background: #2222ff;
  cursor: grab;
}

.year-slider.dragging .slider-track,
.year-slider.dragging .slider-handle {
  cursor: grabbing;
}

.year-slider .slider-readout {
  color: #2222ff;
  min-width: 6ch; /* "2030 f" — the readout never shifts the row */
}

/* the dim forecast marker beside the 2030 value */
.year-slider .slider-readout .fc {
  color: var(--ink-dim);
}

/* shared view-heading grammar: title in ink, dim subhead */
.stage-title {
  font-size: 16px;
  font-weight: 300;
  margin-bottom: 12px;
}

.stage-sub {
  font-size: 12px;
  color: var(--ink-dim);
}

/* ---------- the shared instrument frame — ALL THREE views ---------- */
/* Identical furniture: the four corner brackets, the readout inside the
   frame's top-left, the caption at its bottom-left. Switching views
   keeps every one of these lines perfectly still. */

/* top inset clears the readout row, bottom inset the caption — both live
   inside the corner brackets, like the home instrument */
.v-frame {
  position: relative;
  width: 100%;
  max-width: 713px;
  margin: 0 auto;
  padding: 40px 14px 36px;
}

.v-canvas-wrap {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 52vh;
}

.v-canvas-wrap canvas {
  display: block;
}

/* readout + caption — home .globe-readout grammar: dim labels, blue values */
.v-readout {
  position: absolute;
  left: 14px;
  top: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  font-size: 12px;
  color: var(--ink-dim);
  pointer-events: none;
  z-index: 1;
}

.v-readout .val {
  color: #2222ff;
}

/* HOVER STABILITY — each readout field is a fixed-width slot sized in ch
   to its longest possible content (monospace: ch is exact), so swapping
   hover targets changes glyphs but never shifts positions or wraps */
.v-readout > span {
  white-space: nowrap;
}

/* [1] "line Property & casualty" · "size unwritten" · "cells 942" */
#v-readout > span:nth-child(1) { min-width: 24ch; }
#v-readout > span:nth-child(2) { min-width: 14ch; }
#v-readout > span:nth-child(3) { min-width: 9ch; }

/* [3] "domain Customer Support" · "metric AI share" · "share 96% forecast" */
#prev-readout > span:nth-child(1) { min-width: 23ch; }
#prev-readout > span:nth-child(2) { min-width: 15ch; }
#prev-readout > span:nth-child(3) { min-width: 18ch; }

.v-caption {
  position: absolute;
  left: 14px;
  bottom: 12px;
  font-size: 12px;
  color: var(--ink-dim);
  pointer-events: none;
}

/* view [3]'s proof line — the hovered domain's evidence sentence, one
   dim line under the readout, truncating rather than wrapping */
.v-proof {
  position: absolute;
  left: 14px;
  right: 14px;
  top: 30px;
  font-size: 12px;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  z-index: 1;
}

/* ---------- view [2]: Materiality Terrain specifics ---------- */

/* the terrain canvas fills the shared wrap; pan-y keeps the page
   scrollable over it on touch — horizontal drags still rotate it */
#terrain {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: pan-y;
}

#terrain.dragging {
  cursor: grabbing;
}

/* the readout-grammar card: title in ink, dim labels, blue values.
   FIXED geometry — the card is a constant width and the labels a
   constant column, so hovering across bars re-inks the values without
   moving a single glyph box (values left-aligned, never right-ragged) */
.terrain-tip {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 30;
  width: 300px;
  padding: 10px 12px;
  background: #fff;
  border: 1px solid var(--ink);
  font-size: 12px;
  pointer-events: none;
}

.terrain-tip .tt-title {
  color: var(--ink);
  margin-bottom: 6px;
  white-space: nowrap;
}

.terrain-tip p {
  display: flex;
  justify-content: flex-start;
}

.terrain-tip .tt-l {
  color: var(--ink-dim);
  flex: 0 0 13ch; /* "consequence" + breathing room */
}

.terrain-tip .tt-v {
  color: #2222ff;
  text-align: left;
  white-space: nowrap;
}

/* ---------- the essay below the stage ---------- */
/* ~520px measure at the page centre. Sections 01–04 on white; 05–07 and
   the closing action row ride the black band (.blog-band, styles.css). */

/* the essay runs at a ~520px measure, but graphic-bearing sections open
   up to a wider two-column band so text and graphic can sit side by side.
   The container widens to hold the widest row; text-only content (kickers,
   paragraphs, the closing action row) is re-narrowed back to the measure
   by .es-text / the plain .essay-sec rules below. */
.essay {
  --measure: 520px;
  --essay-wide: 940px;
  --es-gutter: 72px;
  max-width: var(--essay-wide);
  margin: 160px auto 0;
}

.essay-band .essay-col {
  --measure: 520px;
  --essay-wide: 940px;
  --es-gutter: 72px;
  max-width: var(--essay-wide);
  margin: 0 auto;
}

.essay-sec {
  margin-bottom: 96px;
}

.essay-sec:last-of-type {
  margin-bottom: 0;
}

/* text-only sections (e.g. 07) keep the plain narrow measure, centred */
.essay-sec:not(.has-graphic) {
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
}

/* the closing action row rides the narrow measure too */
.essay-actions {
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
}

/* ---------- alternating side-by-side rows (essay sections) ---------- */
/* graphic sections lay text and graphic in two columns, ~55/45 with a
   generous gutter; the graphic is vertically centred against its text.
   Alternation is by row modifier — .row-text-left / .row-text-right set
   the source-order column and the grid track order. */
.essay-sec.has-graphic {
  display: grid;
  grid-template-columns: 55fr 45fr;
  column-gap: var(--es-gutter);
  align-items: center;
}

/* text LEFT / graphic RIGHT: .es-text is first in source, sits left */
.row-text-left .es-text { grid-column: 1; }
.row-text-left .es-graphic { grid-column: 2; }

/* text RIGHT / graphic LEFT: keep source order (text first) but flip the
   grid so text lands in the right track, graphic in the left */
.row-text-right { grid-template-columns: 45fr 55fr; }
.row-text-right .es-text { grid-column: 2; }
.row-text-right .es-graphic { grid-column: 1; }

/* the graphic column centres its instrument within the track */
.es-graphic {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* dim 12px overline: `01 · thesis` */
.essay-kicker {
  font-size: 12px;
  color: var(--ink-dim);
  margin-bottom: 16px;
}

.essay-title {
  font-size: 16px;
  font-weight: 300;
  margin-bottom: 16px;
}

.essay-sec p:not(.essay-kicker) {
  font-size: 12px;
  line-height: 1.8;
}

.essay-sec p + p {
  margin-top: 16px;
}

.essay .star,
.essay-col .star {
  color: #2222ff;
}

/* band recolors: body goes white, kickers half-white (values stay blue) */
.essay-band .essay-kicker {
  color: rgba(255, 255, 255, 0.5);
}

/* the closing action row — bracket verbs, home-nav grammar */
.essay-actions {
  margin-top: 128px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 32px;
  font-size: 12px;
}

.essay-actions a:hover {
  color: #2222ff;
}

/* ---------- the shared essay-graphic shell (.spec-block) ---------- */
/* Every essay section's graphic lives in one structurally STABLE shell:
   a corner-bracketed block whose three parts — a fixed-geometry canvas,
   a dim tag, and an always-present one-line readout — never change size
   on interaction. HARD RULE: hover changes pixels, never geometry. The
   canvas is a fixed aspect-ratio box (aspect set per section in s0N.css)
   with overflow:hidden, so any hover-revealed element that overflows the
   SVG (satellite labels, widened strokes) is CLIPPED and can never grow
   the box; the readout line is reserved at all times (idle &nbsp;), so a
   rolling readout swaps glyphs inside a held slot. Per-section rendering
   lives in sections/s0N.js, per-section styling in sections/s0N.css. */

.spec-block {
  position: relative;
  margin: 0 auto;
  padding: 18px;
  display: flex;
  flex-direction: column;
  /* DEFINITE width: fill the graphic column (grid/flex track) and NEVER
     size to content. This is the crux of the geometry fix — without it,
     the readout's fixed-width nowrap slots (min-width in ch) would push
     the block's max-content wider on hover (idle text short → block
     narrow; hovered text long → block wide). box-sizing keeps the 100%
     inclusive of padding; min-width:0 lets the nowrap readout be clipped
     (overflow:hidden + ellipsis) instead of expanding the block. */
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

/* the graphic column stretches the block to the full track width so the
   100% above has a definite basis (align-items defaults to stretch, but
   be explicit — the block must fill, not centre-shrink, its column) */
.es-graphic {
  align-items: stretch;
}

/* the tag rides on TOP of the graphic (its original position), the
   canvas in the middle, the readout at the bottom — source order is
   canvas → tag → readout (the shell contract), reordered here in flow */
.spec-tag {
  order: -1;
  font-size: 11px;
  color: var(--ink-dim);
  margin-bottom: 16px;
}

/* the fixed-geometry canvas: an aspect-ratio box whose height is a pure
   function of its (track-determined) width — NEVER of its content. The
   SVG is ABSOLUTELY positioned to fill it, so the SVG is out of flow and
   contributes NOTHING to the grid track's content sizing; hover-revealed
   geometry (satellite labels, widened strokes) therefore cannot feed back
   into layout and grow the block. overflow:hidden clips any spill past
   the box. Each section sets .spec-canvas's aspect-ratio: the static
   specimens in s0N.css (their fixed viewBox ratio); the JS-rendered ones
   (s01/s02) set host.style.aspectRatio after computing their viewBox. */
.spec-canvas {
  position: relative;
  width: 100%;
  overflow: hidden;
}

.spec-canvas svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* the reserved one-line readout — a fixed slot beneath every graphic,
   present even when idle (&nbsp;), so a rolling readout (NSRoll) swaps
   glyphs without ever moving the block's geometry. Truncates, never
   wraps. Per-section slot widths / colors live in s0N.css. */
.spec-readout {
  margin-top: 14px;
  min-height: 1.4em; /* one line held even when idle */
  font-size: 12px;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-dim);
}

/* the band recolors the tag + corner brackets to white */
.essay-band .spec-tag {
  color: rgba(255, 255, 255, 0.5);
}

/* styles.css draws the brackets in ink; the band needs them white */
.essay-band .corner {
  border-color: #fff;
}

/* ---------- motion + mobile ---------- */

/* under 1200px the hanging column would pinch to a sliver — it reflows
   above the visualizer instead, sharing the frame's centred 713px
   column so the title's left edge meets the frame's left edge */
@media (max-width: 1200px) {
  /* padding (not a child margin) closes the column, so research.js can
     equalize the three columns' heights — the frame furniture then sits
     at the same y in every view, stacked or hanging */
  .stage-copy {
    position: static;
    width: 100%;
    max-width: 713px;
    margin: 0 auto;
    padding-bottom: 16px;
  }
}

@media (max-width: 900px) {
  /* .blog-main's padding-top drops to 120px here — match it so the
     stage still claims exactly one viewport, no more, no less */
  .stage-zone {
    --header-clearance: 120px;
  }

  /* stage ~45vh: shorter canvases, framing intact, tabs stay reachable */
  .v-canvas-wrap {
    height: 34vh;
  }

  /* the proof line has no clean slot above the frame on small screens —
     the readout carries the values (the year slider simply follows the
     stacked copy column, still centered) */
  .v-proof {
    display: none;
  }

  /* readout moves above the frame; anchored to the frame top so it can
     wrap without drifting over the brackets (fixed slots per field keep
     the wrap points constant across hover targets) */
  .v-frame {
    margin-top: 40px;
    padding-top: 14px;
  }

  .v-readout {
    top: auto;
    bottom: calc(100% + 8px);
    left: 0;
  }

  .essay {
    margin-top: 112px;
  }

  .essay-sec {
    margin-bottom: 72px;
  }

  .essay-actions {
    margin-top: 96px;
  }

  /* the graphic shell tightens its padding on smaller screens */
  .spec-block {
    padding: 14px;
  }
}

/* short viewports — a squat desktop window or a landscape phone. Keyed
   on HEIGHT, independent of the width breakpoints above (a landscape
   phone is wide enough to miss the 900px width cutoff entirely). The
   stage's min-height (100dvh - header-clearance) only ever pushes the
   essay down, never up, so with content this tall it would simply grow
   the box and shove the tabs — the site's navigation — below the fold.
   Compress the canvas and tighten the vertical rhythm instead, so the
   whole composition (copy + frame + tabs) fits inside a short box and
   the tabs stay put. */
@media (max-height: 700px) {
  .v-canvas-wrap {
    height: 30vh;
  }

  .stage-copy {
    padding-bottom: 8px;
  }

  .stage-title {
    margin-bottom: 6px;
  }

  .v-frame {
    margin-top: 16px;
    padding: 14px 14px 28px;
  }

  .year-slider {
    margin-top: 12px;
  }

  .v-tabs {
    margin-top: 8px;
  }
}

/* landscape phones are short AND narrow enough to also hit the <900px
   column-stack rules above — recompress the readout-clearing margin the
   900px block adds back in, so the two compressions don't double up */
@media (max-height: 700px) and (max-width: 900px) {
  .v-frame {
    margin-top: 28px;
    padding-top: 10px;
  }
}

/* very short + narrow at once (landscape phone, ~375px tall) — the
   copy column is stacked ABOVE the frame here (<1200px rule), so its
   subhead paragraph is real height in the box, not a hanging aside.
   Drop it, same move as .v-proof above: the tabs are the navigation
   and must stay inside the fold, the subhead is expendable. */
@media (max-height: 480px) and (max-width: 900px) {
  .stage-sub {
    display: none;
  }

  .stage-title {
    margin-bottom: 0;
  }

  .v-canvas-wrap {
    height: 22vh;
  }

  .v-frame {
    margin-top: 20px;
    padding: 8px 14px 20px;
  }

  .year-slider {
    margin-top: 8px;
  }
}

/* ---------- the alternating rows collapse to a single column ---------- */
/* below the point where 55/45 pinches the graphic, each graphic section
   stacks: text first, graphic after (source order already has text first,
   so a single 1fr track and a forced grid-row ordering does it). A gap of
   air separates the two. Runs on white and on the band alike. */
@media (max-width: 860px) {
  .essay,
  .essay-band .essay-col {
    max-width: var(--measure);
  }

  .essay-sec.has-graphic {
    display: block;
  }

  .es-graphic {
    margin-top: 40px;
  }

  /* neutralize the flipped-track ordering so graphic always follows text */
  .row-text-right .es-text,
  .row-text-left .es-text,
  .row-text-right .es-graphic,
  .row-text-left .es-graphic {
    grid-column: auto;
  }

  /* every graphic shell centres in the single stacked column */
  .spec-block {
    margin: 0 auto;
  }
}
