/* Figure internals that used to live in /style.css, the previous design
   system. Split out when the site moved onto the grid (GRID.md): the old
   page layout stays in style.css, and case studies load only this — the
   parts their diagrams, replays and result lists draw with. Nothing here
   places a block on the page; editorial.css does that. */

:root {
  --placeholder: rgba(0, 0, 0, 0.07);
}

.frame {
  aspect-ratio: 3 / 2;
  background: var(--placeholder);
  transition: opacity 160ms ease;
}
.frame--wide {
  aspect-ratio: 16 / 9;
}
.frame--band {
  aspect-ratio: 21 / 9;
}
/* A real photograph in a frame's place. The grey stays underneath it as the
   loading state, so nothing reflows when the file arrives. */
.frame--photo {
  display: block;
  width: 100%;
  height: auto;
  /* .frame pins the box to 3/2, so a file that isn't 3/2 would stretch.
     Cover crops it instead — a no-op for every image that already fits. */
  object-fit: cover;
}

/* Results list — the percentage block that closes a case study. */
.results {
  display: grid;
  gap: 6px;
}
.results div {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--gutter);
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}
.results div:first-child {
  border-top: 1px solid var(--rule);
}

/* A live prototype standing where a hero image usually goes. Used once, on the
   Alakazam case study, because that page had no screen to show — the agent
   lives in Slack — and a running reenactment is the honest substitute.

   Sized in height rather than aspect-ratio on purpose. The replay scales an
   iPhone to fit whatever box it lands in, so the useful question is not what
   shape the box is, it's whether the phone ends up large enough to read. Height
   is what controls that; the width takes care of itself.

   Framing this at all needs two header exceptions on the replay's own response.
   See the /alakazam-replay rules in _headers before changing the src. */
/* The box owns the size; the poster and the iframe both fill it. Stacking them
   rather than swapping them means the still is what you get whenever the frame
   does not paint — scripting off, the embed blocked, the replay 404ing after a
   rename — with no detection code that could itself be the thing that breaks. */
.replay-stage {
  position: relative;
  width: 100%;
  height: min(78vh, 720px);
  min-height: 480px;
  /* The replay's own backdrop, so the box doesn't flash white before anything
     paints. Hard-coded because it belongs to the prototype's palette, not to
     this site's. */
  background: #0b0c0e;
}
.replay-poster,
.replay-embed {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* contain, not cover: this is a phone on a stage, and cropping the stage to
   fill a wider box would cut the caption and the controls off it. */
.replay-poster {
  object-fit: contain;
}
.replay-embed {
  border: 0;
  /* Opaque, so the live replay covers the still the moment it paints. */
  background: #0b0c0e;
}

/* ── Diagrams ────────────────────────────────────────────
   A diagram sits in its figure row like any other figure (GRID.md §6): a
   --frame surface at band width. What follows is the drawing inside it. */
.diagram {
  background: var(--frame);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding: clamp(1.5rem, 4vw, 3rem);
}

/* The drawing, on the page line. 68rem rather than the old 46rem column:
   the band bought the room, and these figures were being scaled down into a
   width chosen for prose.

   Two depths, and both are real. A hand-drawn figure is an <svg> written into
   the page, so it is a direct child. An engine-drawn one is rendered into a
   host <div> the engine tags and owns, so it is a grandchild. What both have
   in common is that they are THE DRAWING — never a glyph inside one — which is
   the distinction this selector exists to make and the one the repo has twice
   got wrong by reaching for `.diagram svg`. */
.diagram > svg,
.diagram > div > svg {
  /* Its own width, so one user unit is one CSS pixel and the type comes out
     the size each style wrote it at. This was 68rem, which scaled a 640-unit
     drawing by 1.70 and turned its 13px labels into 22px ones next to 16px
     prose — and scaled the 700- and 760-unit figures beside it by different
     amounts again, so three captions meant to match came out three sizes.
     diagram-boot.js sets --dg-figure-width from the drawing; the two
     hand-drawn figures wider than 640 carry .diagram--wide, below. The long
     version of this argument is in /figures/diagram-system.css. */
  max-width: var(--dg-figure-width, 640px);
  /* Start on the content line, and let whatever is left fall to the
     right. Was `auto`, which centred it — and a centred figure cannot
     be aligned to a left-aligned paragraph, because its left edge is a
     function of the viewport width and the paragraph's is not. */
  margin-inline: 0 auto;
}

/* levels and the stage maps are HTML, so their type is already real CSS
   pixels and only their column needs capping. */
.diagram > .lv-heading,
.diagram > .lv-grid,
.diagram > .stage-map,
.diagram > div > .lv-heading,
.diagram > div > .lv-grid,
.diagram > div > .stage-map {
  max-width: 68rem;
  margin-inline: 0 auto;
}

/* A figure that brings its own canvas gets a white band instead. Two greys
   this close read as a printing error rather than as two surfaces — the same
   call .spec-figure--map makes for the wireless map. */
.diagram--flow,
.diagram--stages {
  background: #fff;
}

.diagram svg {
  display: block;
  width: 100%;
  height: auto;
  /* Label text is 13 user units, so it shrinks with the viewBox. Below this
     width it drops under ~11px, so the figure scrolls instead of shrinking. */
  min-width: 34rem;
  overflow: visible;
  color: var(--ink);
}
/* The 34rem floor above is calibrated for a 640-unit viewBox, which is what
   every figure here used until one needed six columns. A wider viewBox scaled
   into the same 34rem puts that same 13-unit text below 11px, so the floor has
   to widen with it: 40rem is 34 × 760/640. The class goes on the figure, which
   is the element that scrolls; the floor goes on the svg, as above.

   It also carries the width for the two hand-drawn figures at 760 units. An
   engine-drawn figure does not need this — boot reads the viewBox off the
   drawing — but a hand-written <svg> has nobody to do that for it, and the
   class marking it as the wider kind already exists. */
.diagram--wide {
  --dg-figure-width: 760px;
}
.diagram--wide svg {
  min-width: 40rem;
}

@media (max-width: 45rem) {
  .diagram:has(> svg)::before,
  .diagram:has(> div > svg)::before {
    color: var(--ink-50, var(--muted));
    content: "Swipe to see the full diagram \2192";
    display: block;
    font-size: 0.75rem;
    line-height: 1.4;
    margin-block-end: 1rem;
    position: sticky;
    inset-inline-start: 0;
    width: max-content;
  }
}

/* Stage maps. These borrow the pill-and-spine language of the product flow
   diagrams, but remain semantic HTML so they reflow into one readable column
   instead of shrinking or scrolling on a phone. Colour is redundant with the
   explicit "friction" and "proof" labels in the markup. */
.diagram--stages {
  max-width: 60rem;
}
.stage-map {
  --stage-accent: #60a000;
  --stage-canvas: #f1f1f1;
  --stage-dot: #e4e4e4;
  padding: calc(var(--u) * 1.5);
  background-color: var(--stage-canvas);
  background-image: radial-gradient(var(--stage-dot) 1.5px, transparent 1.5px);
  background-size: 24px 24px;
  color: #111;
}
.stage-map--friction {
  --stage-accent: #d93025;
}
.stage-map + .stage-map {
  margin-top: var(--u);
}
.stage-map__label {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: calc(var(--u) * 1.5);
  font-weight: 600;
}
.stage-map__label span {
  color: #666;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}
.stage-map__stages {
  display: grid;
  grid-template-columns: repeat(var(--stage-count), minmax(0, 1fr));
  gap: calc(var(--u) * 1.5);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: stage;
}
.stage-map__stages--4 {
  --stage-count: 4;
}
.stage-map__stages--5 {
  --stage-count: 5;
}
.stage-map__stage {
  position: relative;
  min-width: 0;
  counter-increment: stage;
}
.stage-map__stage:not(:last-child)::after {
  content: "\2192";
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-end: calc(var(--u) * -1.125);
  color: #777;
  font-size: 1.25rem;
  line-height: 1;
}
.stage-map__title {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  min-height: 2.25rem;
  padding: 0.45rem 0.75rem;
  border: 1px solid #aaa;
  border-radius: 999px;
  background: #fff;
  font-weight: 600;
  box-shadow:
    0 1px 3px rgb(0 0 0 / 10%),
    0 1px 2px rgb(0 0 0 / 6%);
}
.stage-map__title::before {
  content: counter(stage, decimal-leading-zero);
  color: #777;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}
.stage-map__steps {
  position: relative;
  display: grid;
  gap: var(--u);
  margin: var(--u) 0 0 0.75rem;
  padding: 0 0 0 1.25rem;
  list-style: none;
}
.stage-map__steps::before {
  content: "";
  position: absolute;
  inset-block: 0.35rem;
  inset-inline-start: 0;
  width: 1px;
  background: #999;
}
.stage-map__steps li {
  position: relative;
  line-height: 1.35;
}
.stage-map__steps li::before {
  content: "";
  position: absolute;
  inset-block-start: 0.42rem;
  inset-inline-start: -1.45rem;
  width: 0.45rem;
  height: 0.45rem;
  border: 2px solid var(--stage-canvas);
  border-radius: 50%;
  background: #777;
  box-shadow: 0 0 0 1px #777;
}
.stage-map__steps .stage-map__signal {
  color: var(--stage-accent);
  font-weight: 600;
}
.stage-map__steps .stage-map__signal::before {
  background: var(--stage-accent);
  box-shadow: 0 0 0 1px var(--stage-accent);
}
.stage-map__tag {
  display: block;
  margin-bottom: 0.15rem;
  color: #777;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

@media (max-width: 51rem) {
  .stage-map__stages {
    grid-template-columns: 1fr;
  }
  .stage-map__stage:not(:last-child) {
    padding-bottom: var(--u);
  }
  .stage-map__stage:not(:last-child)::after {
    content: "\2193";
    inset-block-start: auto;
    inset-block-end: -0.5rem;
    inset-inline-start: 1rem;
    inset-inline-end: auto;
  }
}

@media (max-width: 40rem) {
  .stage-map {
    padding: var(--u);
  }
}

/* Parts of a diagram, so the SVG markup stays readable. */
.d-box {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}
.d-fill {
  fill: var(--placeholder);
  stroke: none;
}
.d-line {
  stroke: currentColor;
  stroke-width: 1;
  fill: none;
}
.d-dim {
  stroke: var(--ink-50);
  fill: none;
  stroke-width: 1;
}
.d-dash {
  stroke: var(--ink-50);
  stroke-width: 1;
  fill: none;
  stroke-dasharray: 3 3;
}
.d-t {
  fill: currentColor;
  font-size: 13px;
}
.d-t--dim {
  fill: var(--ink-50);
  font-size: 13px;
}
.d-rule {
  stroke: var(--rule);
  stroke-width: 1;
}
.diagram-number {
  fill: currentColor;
  font-size: 42px;
  font-weight: 600;
  letter-spacing: -1.5px;
}
.diagram-takeaway {
  fill: currentColor;
  font-size: 16px;
  font-weight: 600;
}
.d-circle {
  vector-effect: non-scaling-stroke;
}
.migration-live {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}
.migration-dot,
.device-on {
  fill: currentColor;
}
.device-off {
  fill: var(--placeholder);
  stroke: currentColor;
  stroke-width: 1;
}
.comparison-arrow,
.story-path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}
.story-path {
  stroke-dasharray: 5 4;
}
.story-node {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}
.story-node--filled {
  fill: var(--placeholder);
}
.story-icon {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.speech {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}
.speech--answer {
  fill: var(--placeholder);
}
.building,
.room,
.ui-frame {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}
.building-floor,
.pipe,
.signal,
.signal-wave,
.person,
.callout,
.duplicate-mark,
.fragile-path,
.break-mark,
.journey-line,
.chair,
.cluster-path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.signal-wave {
  stroke-width: 1;
}
.callout {
  stroke: var(--ink-50);
}
.callout-dot,
.ui-dot,
.step-dot {
  fill: currentColor;
}
.journey-node {
  fill: var(--bg);
  stroke: currentColor;
  stroke-width: 1.5;
}
.journey-node--filled {
  fill: currentColor;
}
.journey-scope {
  fill: none;
  stroke: var(--ink-50);
  stroke-width: 1;
  stroke-dasharray: 4 3;
}
.question-chip {
  fill: var(--placeholder);
  stroke: currentColor;
  stroke-width: 1;
}
.diagram-number--small {
  font-size: 36px;
}

/* The per-style paint used to be copied in here — the .fn-*, .bp-*, .st-*,
   .bar-*, .ag-*, .sa-*, .dl-*, .cl-*, .lv-* and .gt-* families, each one a
   hand-port of a stylesheet in /diagrams, plus a restatement of the shared
   visual language underneath them.

   All of it is gone, and none of it was replaced: every case study now links
   /figures/<style>.css and /figures/diagram-system.css directly, which are the
   same files the workshop pages link. There is one copy of each, so the
   workshop and the site cannot disagree — a drift that had already cost this
   repo four commits of levels fixes that never reached the live pages.

   What stays here is what is genuinely the site's: the .diagram band, the
   .d-* hand-drawn family, the stage maps, and the two container decisions
   (.diagram--ladder, .diagram--wide) that only make sense inside a
   full-bleed band. */

/* ── Inline code ─────────────────────────────────────────
   A filename or an identifier in prose. The site has one
   typeface, so this is not a monospace face — weight does
   the work a different family would, the same way emphasis
   does everywhere else here. Without this rule the browser
   default applies and a filename in a caption arrives in
   whatever monospace the reader happens to have, which is
   the one thing the type system on this site rules out.
   ───────────────────────────────────────────────────────── */

code {
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
}

/* ── Flow diagrams: one stale export ─────────────────────
   This is the only baked engine figure left on the site, and these rules
   exist to keep it drawn. The markup on /technician-tooling was exported from
   a node-flow that no longer exists: it speaks .nf-plot, .nf-pill and
   .nf-label, where the engine in /figures/node-flow.js now emits .nf-canvas
   and .nf-node. Three names DO collide — .nf-wire, .nf-port-disc and
   .nf-port-ring mean the same thing in both — and they collide badly: the
   engine's versions read tokens off a .nf host class this markup does not
   have, so loading node-flow.css beside this markup strips the wires and the
   ports off the figure. Verified by doing it. So /technician-tooling does not
   link node-flow.css, and it is the one case study that does not link the
   stylesheet for a style that appears on it.

   That sentence is the plainest evidence for why the other fifty-three
   figures stopped being exported.

   Removing this means declaring that one figure the way the rest are. It is
   the only one whose workshop page renders through a wrapper (pair.js draws a
   tree in both orientations from one declaration), so it needs a decision
   about where the transpose lives rather than a transcription, and it was
   left rather than rushed.
   ───────────────────────────────────────────────────────── */

.diagram--flow {
  --nf-bg: #f1f1f1;
  --nf-dot: #ebebeb;
  --nf-go: #60a000;
  --nf-stop: #f71b07;
  /* Two Gaussians, from the source design's exported filter. */
  --nf-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.1))
    drop-shadow(0 1px 2px rgba(0, 0, 0, 0.06));
}

/* The canvas. A background rather than a <pattern> in the SVG, so the dots
   stay a constant size while the diagram scales with the column — a texture
   that grew and shrank with the content would read as part of the drawing. */
.diagram--flow .nf-plot {
  /* The widest of these is 878 units. Below 40rem its 16-unit labels drop
     under 12px, so the figure scrolls rather than shrinking past legible. */
  min-width: 40rem;
  background-color: var(--nf-bg);
  background-image: radial-gradient(var(--nf-dot) 2px, transparent 2px);
  background-size: 24px 24px;
  background-position: 12px 12px;
}

/* This one is a grandchild too — .nf-plot sits between the figure and the svg
   — so it takes the width rule above and needs its own number rather than an
   exception to it. 878 is its viewBox, which puts its 16-unit node labels at
   16px, the same size as the prose around it. It is written here rather than
   read off the drawing because this is the one figure on the site that is
   still baked markup, so there is no boot to read it. */
.diagram--flow {
  --dg-figure-width: 878px;
}
.diagram--flow svg {
  display: block;
  width: 100%;
  height: auto;
  /* The plot carries the floor; the svg fills whatever the plot is. */
  min-width: 0;
  overflow: visible;
}

.nf-wire {
  fill: none;
  stroke: #000;
  stroke-width: 2;
  filter: var(--nf-shadow);
}

.nf-pill {
  fill: #fff;
  filter: var(--nf-shadow);
}
.nf-pill--go {
  fill: var(--nf-go);
}
.nf-pill--stop {
  fill: var(--nf-stop);
}

.nf-label {
  fill: #000;
  font-size: 16px;
  font-weight: 600;
}
/* On a coloured pill. */
.nf-label--on {
  fill: #fff;
}

/* A white disc with a ring stroked inside its edge, drawn as two circles so
   the shadow falls from the outer edge rather than the ring. */
.nf-port-disc {
  fill: #fff;
  filter: var(--nf-shadow);
}
.nf-port-ring {
  fill: none;
  stroke: #000;
  stroke-width: 2;
}

/* ─────────────────────────────────────────────────────────
   ladder wants four columns and .diagram tops out at 46rem. 60rem matches
   .diagram--stages, the other HTML figure on these pages. The style's own
   breakpoint collapses it to a single column below 60rem. */
/* The ladder wants four columns of prose and stops being readable much past
   this, so it caps tighter than the 68rem default. Its own breakpoint
   collapses it to one column below 60rem. */
.diagram--ladder > .lv-heading,
.diagram--ladder > .lv-grid,
.diagram--ladder > div > .lv-heading,
.diagram--ladder > div > .lv-grid {
  max-width: 60rem;
}

/* `.diagram svg` above is written for a figure that fills its column, and it
   outranks .lv-icon (0,1,1 against 0,1,0). The ladder is HTML, so its icons
   are <svg> elements inside .diagram — and inheriting that rule turns a 34px
   glyph into a 34rem one. Scope it off anything inside the ladder, then give
   the icons their size back at a specificity that holds. */
.diagram--ladder svg {
  width: auto;
  height: auto;
  min-width: 0;
}
.diagram--ladder .lv-icon {
  width: 34px;
  height: 34px;
}

/* ── The hand-drawn figures ──────────────────────────────
   The .d-* family is not generated by any engine — these are the bespoke
   inline SVGs written per case study — but they are on the same pages as
   everything above, so they speak the same language or they are the odd ones
   out. The mapping is one to one: a box is an object, a line is a connector,
   a dash is a guide, a rule is structure. */
.d-box {
  fill: var(--dg-surface);
  stroke: none;
  rx: 8;
  filter: drop-shadow(0 1px 3px rgba(22, 21, 15, 0.1))
    drop-shadow(0 1px 2px rgba(22, 21, 15, 0.06));
}
.d-fill {
  fill: var(--dg-accent-wash);
  stroke: none;
}
.d-line {
  stroke: var(--dg-ink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.d-dash,
.d-dim {
  stroke: var(--dg-ink-40);
  stroke-width: 1;
  stroke-dasharray: var(--dg-guide-dash);
  stroke-linecap: round;
  opacity: var(--dg-guide-opacity);
  fill: none;
}
.d-rule {
  stroke: var(--dg-rule-strong);
  stroke-width: 1;
  fill: none;
}
.d-t {
  fill: var(--dg-ink);
}
.d-t--dim {
  fill: var(--dg-ink-60);
}
.diagram-takeaway {
  fill: var(--dg-ink);
  font-weight: var(--dg-label-weight);
}
.diagram-number {
  fill: var(--dg-ink);
  font-size: var(--dg-num-size);
  font-weight: var(--dg-num-weight);
  letter-spacing: var(--dg-num-track);
  font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
  /* The replay starts animating the moment it loads, and nobody gets to consent
     to that first. Now that a still of it exists, the honest answer is to show
     the still and not the frame — the animation is the whole point of the
     embed, so shrinking it was never the fix. The full-screen link below is
     still there for anyone who wants the motion deliberately. */
  .replay-embed {
    display: none;
  }
}
