/* Mid-page diagrams — the mapping proposal, the channel fan-out and the case
   study's combinatorial wall.

   Geometry partner of webpage/mapping_diagram.py, webpage/fanout_diagram.py and
   webpage/matrix_diagram.py,
   the way flow.css is the partner of flow_diagram.py. Same division of labour:
   those modules emit every animation-delay and animation-duration as an inline
   style from their T_* choreography constants, and the rules here own only the
   names, the easings and the finished look. Do not add delays in this file.

   Hidden states live in the keyframes, never in the base rules, and every
   animation runs with animation-fill-mode: both. That inverts the usual trap —
   `animation: none` (reduced motion, the stacked phone layout) leaves the
   *finished* diagram on screen instead of an invisible one.

   Three things are deliberately unlike the hero, and both diagrams share the
   reasoning:

   1. No aspect-ratio lock and no cqw type. .pim-flow-stage is pinned to its
      viewBox's ratio and scales every label with the stage, which is right for a
      full-bleed picture. These two sit mid-page beside body copy at the site's
      rem scale, where shrinking type to preserve a rectangle is exactly the
      complaint the page is answering. So their height follows their content and
      only the wire layer is stretched: one SVG per diagram spans the gutter
      column with preserveAspectRatio="none", and because the body rows are all
      equal, a row's centre is the same fraction of that box at every width.

      Which is why the gutter widths and row heights below are in rem, and are
      the two numbers each diagram module also holds (GUTTER_W_REM, ROW_H_REM).
      The viewBox is built from them, so it carries the gutter's *real* aspect
      ratio and the stretch is a uniform scale. Get that ratio wrong and the
      scale is anisotropic, which distorts the stroke as well as the curve — a
      flat run of wire renders several times thicker than a steep one. Change a
      gutter width or a row height here and change it in the module too.

      It is also why the grids have row-gap: 0 and the cells carry their own
      margin instead. A row gap would put a row's centre somewhere other than
      (i + 0.5) / n of the gutter's height, and every wire endpoint would drift
      off its row.

   2. No ambient loop. The hero's sparks and halo run forever because the hero is
      the screen you are looking at. Perpetual motion two inches from a paragraph
      someone is reading is noise, so everything here is one-shot.

   3. The one shot is expected to be missed. There is no JavaScript on this site
      and therefore no scroll observer: both sequences play on load, and a
      visitor who scrolls down later arrives after they finished. So the settled
      state is the deliverable — crossing wires drawn, fields named and typed,
      one record and four visibly different shapes — and the animation is a bonus
      for whoever happens to be looking, never the only way to read the picture.

   No --pim-flow-* class is touched or reused: the hero has to be unaffected.
   Colours come only from the tokens site.css already defines. */

/* —— The mapping proposal ————————————————————————————————————————————— */

/* Three columns — the file's headers, the wire gutter, the fields they land in —
   and one row per mapping, all equal. The row count arrives as --pim-map-rows
   from mapping_diagram.py, as a custom property rather than a whole inline
   grid-template-rows, so the phone block below can restate the rule instead of
   having to out-!important an inline style. */
.pim-map,
.pim-fan {
  display: grid;
  width: 100%;
  margin-block: var(--pim-space-4);
  /* See note 1 at the top: a row gap would desynchronise the wires. */
  row-gap: 0;
  column-gap: 0;
}

/* 6.5rem / 4.5rem: mapping_diagram.GUTTER_W_REM and ROW_H_REM. The row floor
   is a minimum rather than a fixed height, so a long translated chip set grows
   the rows instead of spilling out of them -- the wire endpoints survive that
   (they are fractions of the box), only the stroke's evenness degrades, and in
   proportion. */
.pim-map {
  /* The reader's own monospace face. Nothing is fetched for it, so the site
     keeps its no-external-request property, and it is the right face twice over:
     both the column letters and the headers below are literal workbook content,
     not writing. --pim-font-brand was the first attempt and was wrong -- a
     geometric display face turns "A" and "B" into shapes you have to decode. */
  --pim-map-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  grid-template-columns: minmax(0, 1fr) 6.5rem minmax(0, 1.04fr);
  grid-template-rows: auto repeat(var(--pim-map-rows), minmax(4.5rem, 1fr));
}

.pim-map-head,
.pim-fan-head {
  grid-row: 1;
  align-self: end;
  padding-bottom: var(--pim-space-2);
  font-size: var(--pim-fs-meta);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--pim-text-dim);
  animation-name: pim-map-fade-up;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

.pim-map-head--src { grid-column: 1; }
.pim-map-head--tgt { grid-column: 3; }

@keyframes pim-map-fade-up {
  from { opacity: 0; translate: 0 6px; }
  to { opacity: 1; translate: 0 0; }
}

/* The gutter, spanning every mapping row and nothing else. `-1`, not `-2`: the
   grid used to carry a trailing caption row, so the wire box stopped one line
   short of the real end on purpose. The caption is gone and its row with it,
   so the grid's last line is now the last mapping row's own end -- `-2` was
   left pointing at the second-to-last line by name, which after that removal
   is one full row short of the actual bottom, and the wire box rendered a row
   shorter than the cells it was supposed to connect. That is what shrank the
   gutter and threw off every wire's endpoint -- the box `view_h()` assumes it
   fills no longer matched the box the grid actually gave it. */
.pim-map-wires,
.pim-fan-wires {
  grid-column: 2;
  grid-row: 2 / -1;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Each wire is tinted like the field it lands on, which is what makes six
   crossing curves traceable by eye. pathLength="100" (set in Python) normalises
   them, so one set of keyframes draws the long crossings and the short hops at
   the same apparent speed. */
.pim-map-wire {
  fill: none;
  stroke: color-mix(in srgb, var(--pim-map-tone) 60%, transparent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 100 100;
  animation-name: pim-map-draw;
  animation-timing-function: ease-in-out;
  animation-fill-mode: both;
}

@keyframes pim-map-draw {
  from { stroke-dashoffset: 100; }
  to { stroke-dashoffset: 0; }
}

/* One tone hook per row, from the nine pastels site.css already publishes. Wire
   and field cell share it so they cannot drift apart. */
.pim-map-wire--sky,
.pim-map-target--sky { --pim-map-tone: var(--pim-tone-sky); }
.pim-map-wire--lilac,
.pim-map-target--lilac { --pim-map-tone: var(--pim-tone-lilac); }
.pim-map-wire--mint,
.pim-map-target--mint { --pim-map-tone: var(--pim-tone-mint); }
.pim-map-wire--gold,
.pim-map-target--gold { --pim-map-tone: var(--pim-tone-gold); }
.pim-map-wire--peach,
.pim-map-target--peach { --pim-map-tone: var(--pim-tone-peach); }
.pim-map-wire--aqua,
.pim-map-target--aqua { --pim-map-tone: var(--pim-tone-aqua); }

.pim-map-source {
  /* The cells' own margin, not a row gap — see note 1. */
  margin-block: 4px;
  margin-right: 0;
  display: flex;
  align-items: center;
  gap: var(--pim-space-2);
  min-width: 0;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--pim-edge);
  border-radius: var(--pim-radius);
  background: var(--pim-surface);
  animation-name: pim-map-slide-in;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  animation-fill-mode: both;
}

@keyframes pim-map-slide-in {
  from { opacity: 0; translate: -14px 0; }
  to { opacity: 1; translate: 0 0; }
}

/* The workbook's column letter. Generated in Python, not translated: A, B, C is
   Excel's language everywhere, and it is what makes the left column read as a
   file rather than as a list of words. */
.pim-map-col {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.45em;
  height: 1.45em;
  border-radius: 5px;
  background: rgba(9, 24, 56, 0.72);
  border: 1px solid var(--pim-edge);
  font-family: var(--pim-map-mono);
  font-size: var(--pim-fs-meta);
  color: var(--pim-text-dim);
}

/* The monospace is what makes a reader accept "bild_1;bild_2;bild_3" as
   something quoted rather than something written. `anywhere` because a header
   like that one has no space to break at and must not push the page sideways at
   390px. */
.pim-map-header {
  min-width: 0;
  font-family: var(--pim-map-mono);
  font-size: var(--pim-fs-desc);
  line-height: 1.35;
  color: var(--pim-text);
  overflow-wrap: anywhere;
}

/* Only drawn where there are no wires: on a phone the pairing has to be said in
   the markup instead. Rotated to point at the fields below it. */
.pim-map-arrow { display: none; }

/* The fields on one line and the verdict under them, at every width that has
   wires -- not side by side, which is roomier at 1100px and is the reason the
   whole band was unstable: the two wrapped onto a second line somewhere around
   900px, the rows grew by half, and the gutter's aspect ratio no longer matched
   the viewBox built from ROW_H_REM. A cell whose height does not depend on how
   much room it got keeps the wire endpoints and the stroke honest across the
   entire range. */
.pim-map-target {
  margin-block: 4px;
  display: grid;
  justify-items: start;
  align-content: center;
  gap: 0.1rem;
  min-width: 0;
  padding: 0.45rem 0.8rem;
  border-radius: var(--pim-radius);
  /* The settled look. The keyframes below flash *past* it as the wire lands and
     return here, so `animation: none` is already the finished cell. */
  border: 1px solid color-mix(in srgb, var(--pim-map-tone) 34%, transparent);
  background: color-mix(in srgb, var(--pim-map-tone) 9%, var(--pim-surface-flat));
  animation-name: pim-map-recognise;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

/* Recognition, as one pulse of light in the border rather than as movement: the
   cell is already where it belongs, what changed is that something now knows
   what it is. */
@keyframes pim-map-recognise {
  0% {
    opacity: 0;
    border-color: color-mix(in srgb, var(--pim-map-tone) 34%, transparent);
    box-shadow: 0 0 0 color-mix(in srgb, var(--pim-map-tone) 0%, transparent);
  }
  35% {
    opacity: 1;
    border-color: color-mix(in srgb, var(--pim-map-tone) 85%, transparent);
    box-shadow: 0 0 22px color-mix(in srgb, var(--pim-map-tone) 32%, transparent);
  }
  100% {
    opacity: 1;
    border-color: color-mix(in srgb, var(--pim-map-tone) 34%, transparent);
    box-shadow: 0 0 0 color-mix(in srgb, var(--pim-map-tone) 0%, transparent);
  }
}

.pim-map-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  min-width: 0;
}

.pim-map-chip {
  padding: 0.12rem 0.6rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--pim-map-tone) 52%, transparent);
  background: color-mix(in srgb, var(--pim-map-tone) 14%, transparent);
  font-size: var(--pim-fs-desc);
  line-height: 1.5;
  color: var(--pim-map-tone);
  animation-name: pim-map-chip-in;
  animation-timing-function: cubic-bezier(0.2, 0.9, 0.3, 1.35);
  animation-fill-mode: both;
}

@keyframes pim-map-chip-in {
  from { opacity: 0; scale: 0.6; }
  to { opacity: 1; scale: 1; }
}

/* The verdict: the type, and where relevant the unit or locale that came with
   it. Lands after the field names it applies to. */
.pim-map-type {
  font-family: var(--pim-font-brand);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--pim-text-dim);
  animation-name: pim-map-fade-up;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

/* —— The channel fan-out ——————————————————————————————————————————————— */

/* One record on the left spanning every output row, the gutter, and the outputs.
   Deliberately not the hero's right half: no hub, no icon tiles in a ring, and
   the payload — a document's shape — is the content rather than a label. */
/* 5.5rem / 5.6rem: fanout_diagram.GUTTER_W_REM and ROW_H_REM, same contract. */
.pim-fan {
  grid-template-columns: minmax(0, 0.82fr) 5.5rem minmax(0, 1.18fr);
  grid-template-rows: auto repeat(var(--pim-fan-rows), minmax(5.6rem, 1fr));
}

.pim-fan-head--record { grid-column: 1; }
.pim-fan-head--out { grid-column: 3; }

.pim-fan-wire {
  fill: none;
  stroke: color-mix(in srgb, var(--pim-fan-tone) 60%, transparent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 100 100;
  animation-name: pim-map-draw;
  animation-timing-function: ease-in-out;
  animation-fill-mode: both;
}

/* One pulse of the record travelling out, one pass only. animation-fill-mode is
   `none` on purpose: the base rule is invisible, so the dash does not sit parked
   at the record's edge during its delay, and it is gone again once it has
   landed. That is also why reduced motion can simply hide it. */
.pim-fan-spark {
  fill: none;
  stroke: var(--pim-spark);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 5 95;
  filter: drop-shadow(0 0 5px var(--pim-halo));
  opacity: 0;
  animation-name: pim-fan-pulse;
  animation-timing-function: ease-in;
  animation-iteration-count: 1;
  animation-fill-mode: none;
}

@keyframes pim-fan-pulse {
  from { opacity: 1; stroke-dashoffset: 0; }
  to { opacity: 1; stroke-dashoffset: -100; }
}

.pim-fan-out--mint { --pim-fan-tone: var(--pim-tone-mint); }
.pim-fan-out--sky { --pim-fan-tone: var(--pim-tone-sky); }
.pim-fan-out--gold { --pim-fan-tone: var(--pim-tone-gold); }
.pim-fan-out--plum { --pim-fan-tone: var(--pim-tone-plum); }
.pim-fan-wire--mint,
.pim-fan-spark--mint { --pim-fan-tone: var(--pim-tone-mint); }
.pim-fan-wire--sky,
.pim-fan-spark--sky { --pim-fan-tone: var(--pim-tone-sky); }
.pim-fan-wire--gold,
.pim-fan-spark--gold { --pim-fan-tone: var(--pim-tone-gold); }
.pim-fan-wire--plum,
.pim-fan-spark--plum { --pim-fan-tone: var(--pim-tone-plum); }

/* The record. Centred in its span, because that centre is where every wire
   starts (y=50 of the gutter) — the two agree by construction, not by a number
   copied between files. */
.pim-fan-record {
  position: relative;
  /* Stretched over every output row rather than centred in them, which is both
     the better picture -- one tall panel facing four outputs, instead of a small
     card with a hole above it where its heading is -- and the same geometry: the
     centre of the span is the centre of the gutter, which is where every wire
     starts. The rows inside are centred, so that is still where they meet. */
  align-self: stretch;
  margin-block: 4px;
  display: grid;
  align-content: center;
  gap: var(--pim-space-4);
  min-width: 0;
  padding: var(--pim-space-3);
  border: 1px solid var(--pim-edge);
  border-radius: var(--pim-radius);
  background: var(--pim-surface);
  box-shadow: 0 0 30px rgba(20, 60, 140, 0.35);
  animation-name: pim-fan-record-in;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  animation-fill-mode: both;
}

@keyframes pim-fan-record-in {
  from { opacity: 0; scale: 0.92; }
  to { opacity: 1; scale: 1; }
}

.pim-fan-record-icon {
  position: absolute;
  top: var(--pim-space-2);
  right: var(--pim-space-2);
  color: var(--pim-text-icon);
  opacity: 0.35;
}

.pim-fan-record-glyph {
  width: 1.5rem;
  height: 1.5rem;
}

.pim-fan-field {
  display: grid;
  grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
  align-items: center;
  gap: var(--pim-space-2);
  animation-name: pim-map-fade-up;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

.pim-fan-field-label {
  font-size: var(--pim-fs-desc);
  line-height: 1.4;
  color: var(--pim-text-dim);
  overflow-wrap: anywhere;
}

/* The values are drawn, not written: any string here would be invented product
   data on a marketing page, and the picture does not need it. The width of each
   bar is set inline from fanout_diagram.FIELDS. */
.pim-fan-field-bar {
  height: 8px;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--pim-text-icon) 42%, transparent),
    color-mix(in srgb, var(--pim-text-icon) 14%, transparent)
  );
}

.pim-fan-out {
  margin-block: 4px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(104px, 30%, 168px);
  align-items: center;
  gap: var(--pim-space-3);
  min-width: 0;
  padding: 0.6rem 0.85rem;
  border-radius: var(--pim-radius);
  border: 1px solid color-mix(in srgb, var(--pim-fan-tone) 34%, transparent);
  background: color-mix(in srgb, var(--pim-fan-tone) 9%, var(--pim-surface-flat));
  animation-name: pim-fan-out-in;
  animation-timing-function: cubic-bezier(0.2, 0.85, 0.3, 1.1);
  animation-fill-mode: both;
}

@keyframes pim-fan-out-in {
  from { opacity: 0; scale: 0.94; translate: -8px 0; }
  to { opacity: 1; scale: 1; translate: 0 0; }
}

.pim-fan-out-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.2rem var(--pim-space-2);
  min-width: 0;
}

.pim-fan-out-icon {
  grid-row: 1 / span 2;
  width: 1.4rem;
  height: 1.4rem;
  color: var(--pim-fan-tone);
}

.pim-fan-out-label {
  font-size: var(--pim-fs-body);
  font-weight: 600;
  line-height: 1.3;
  color: var(--pim-text);
}

.pim-fan-out-note {
  font-size: var(--pim-fs-meta);
  line-height: 1.35;
  color: var(--pim-text-dim);
}

/* —— The four shapes ——
   The whole argument of this diagram is that the outputs are not the same object
   with different labels, so each one gets its own grid: a page is an image with
   lines under it, a flat file is a field of identical cells, a price list is two
   columns, a datasheet is a title over two columns of specifications. */
.pim-fan-shape {
  display: grid;
  gap: 3px;
  align-content: center;
  justify-items: start;
  height: 66px;
  padding: 5px 6px;
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--pim-fan-tone) 22%, transparent);
  background: rgba(5, 14, 36, 0.45);
}

.pim-fan-shape--page { grid-template-columns: minmax(0, 1fr); }
.pim-fan-shape--table { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pim-fan-shape--list { grid-template-columns: minmax(0, 1fr) 30%; }
.pim-fan-shape--doc { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.pim-fan-cell {
  display: block;
  width: 100%;
  background: color-mix(in srgb, var(--pim-fan-tone) 40%, transparent);
  animation-name: pim-fan-cell-in;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

@keyframes pim-fan-cell-in {
  from { opacity: 0; scale: 0.7; }
  to { opacity: 1; scale: 1; }
}

/* A hero image: the one block big enough to read as a picture. */
.pim-fan-cell--img {
  height: 26px;
  border-radius: 4px;
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--pim-fan-tone) 46%, transparent),
    color-mix(in srgb, var(--pim-fan-tone) 12%, transparent)
  );
}

.pim-fan-cell--line {
  height: 5px;
  border-radius: 3px;
}

.pim-fan-cell--head {
  height: 7px;
  border-radius: 3px;
  grid-column: 1 / -1;
  background: color-mix(in srgb, var(--pim-fan-tone) 62%, transparent);
}

.pim-fan-cell--pill {
  height: 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--pim-fan-tone) 62%, transparent);
}

/* Four of them in a column would read as one slab at the page's price-list
   size, and the point of that shape is that it is a list. */
.pim-fan-shape--list .pim-fan-cell--pill { height: 6px; }

.pim-fan-cell--tile {
  height: 9px;
  border-radius: 2px;
}

/* —— The combinatorial wall ————————————————————————————————————————————— */

/* Geometry partner of webpage/matrix_diagram.py. Four columns — the decision,
   its options, the multiplier, the running product — and one row per decision,
   with the total as a sibling below them rather than as an eleventh row.

   Subgrid, not a repeated track list. Every row carries padding and a left rule,
   so a row laying out its own copy of the same `grid-template-columns` would
   resolve its tracks inside a content box narrower than the heading row's by
   exactly that padding, and the four columns would drift apart by a few pixels
   each. `subgrid` makes the rows share the parent's tracks instead, which is the
   one arrangement where the "×8" under the "Options" heading is actually under
   it. The @supports fallback restates the tracks for anything that lacks it —
   a hair out of alignment beats a single stacked column.

   Unlike its two siblings this diagram has no wire layer, so there is no gutter
   column, no stretched viewBox and no row-gap ban: rows here are free to be
   different heights, and a decision whose options wrap onto two lines simply is
   taller than the one below it. */
.pim-matrix {
  display: grid;
  grid-template-columns:
    minmax(7rem, 11.5rem) /* the decision */
    minmax(0, 1fr) /* its options */
    4.25rem /* the multiplier -- sized for its own heading, not for "x4" */
    minmax(5.5rem, 7.5rem); /* the running product */
  /* Matched to the rows' own gap. Without it the last two headings, which are
     right-aligned words wider than the numbers under them, run together. */
  column-gap: var(--pim-space-3);
  /* Same reasoning as --pim-map-mono: the running product is a figure being
     read digit by digit against the one above it, and tabular monospace is what
     makes a column of them a column. Nothing is fetched for it. */
  --pim-matrix-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  align-items: center;
  width: 100%;
  margin-block: var(--pim-space-4);
}

.pim-matrix-head {
  font-family: var(--pim-font-sans);
  font-size: var(--pim-fs-meta);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--pim-text-dim);
  padding-bottom: var(--pim-space-2);
  animation-name: pim-matrix-fade-up;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

.pim-matrix-head--count,
.pim-matrix-head--run { text-align: right; }

/* The rows carry padding and a left rule; the heading row does not, so without
   these a heading sits a few pixels outside the column it names. The 2px is the
   row's left border. */
.pim-matrix-head--name { padding-left: calc(var(--pim-space-3) + 2px); }
.pim-matrix-head--count,
.pim-matrix-head--run { padding-right: var(--pim-space-3); }

@keyframes pim-matrix-fade-up {
  from { opacity: 0; translate: 0 6px; }
  to { opacity: 1; translate: 0 0; }
}

/* One tone hook per row, from the nine pastels site.css already publishes. The
   left rule and the multiplier pill are the only two places it is spent — a
   fully tinted row ten times over would read as a rainbow, and the argument
   this picture makes is arithmetic, not colour. */
.pim-matrix-row--lilac { --pim-matrix-tone: var(--pim-tone-lilac); }
.pim-matrix-row--plum { --pim-matrix-tone: var(--pim-tone-plum); }
.pim-matrix-row--mint { --pim-matrix-tone: var(--pim-tone-mint); }
.pim-matrix-row--sky { --pim-matrix-tone: var(--pim-tone-sky); }
.pim-matrix-row--peach { --pim-matrix-tone: var(--pim-tone-peach); }
.pim-matrix-row--gold { --pim-matrix-tone: var(--pim-tone-gold); }
.pim-matrix-row--aqua { --pim-matrix-tone: var(--pim-tone-aqua); }
.pim-matrix-row--sage { --pim-matrix-tone: var(--pim-tone-sage); }
.pim-matrix-row--rose { --pim-matrix-tone: var(--pim-tone-rose); }

.pim-matrix-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  gap: var(--pim-space-3);
  padding: var(--pim-space-2) var(--pim-space-3);
  border-top: 1px solid var(--pim-line);
  border-left: 2px solid color-mix(in srgb, var(--pim-matrix-tone) 55%, transparent);
}

@supports not (grid-template-columns: subgrid) {
  .pim-matrix-row {
    grid-template-columns: minmax(7rem, 11.5rem) minmax(0, 1fr) 4.25rem minmax(5.5rem, 7.5rem);
  }
}

.pim-matrix-row:last-of-type { border-bottom: 1px solid var(--pim-line); }

.pim-matrix-name,
.pim-matrix-opts,
.pim-matrix-count {
  animation-name: pim-matrix-fade-up;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  animation-fill-mode: both;
}

.pim-matrix-name {
  font-family: var(--pim-font-display);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--pim-text);
}

.pim-matrix-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  min-width: 0;
}

.pim-matrix-chip {
  padding: 0.14rem 0.5rem;
  border: 1px solid var(--pim-edge);
  border-radius: 999px;
  background: var(--pim-surface-flat);
  font-size: 0.76rem;
  color: var(--pim-text-dim);
  white-space: nowrap;
}

/* The overflow pill says how many options the row has that are not drawn. It is
   not one of them, so it does not get their chrome. */
.pim-matrix-chip--more {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--pim-matrix-tone) 40%, transparent);
  background: none;
  color: color-mix(in srgb, var(--pim-matrix-tone) 80%, var(--pim-text-dim));
}

.pim-matrix-count {
  justify-self: end;
  padding: 0.1rem 0.45rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--pim-matrix-tone) 16%, transparent);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  font-weight: 600;
  color: color-mix(in srgb, var(--pim-matrix-tone) 88%, var(--pim-text));
}

/* The running product. Tabular figures so the column is a column: the whole
   effect is 4 / 20 / 160 / 800 lining up under each other and then running out
   of room. */
.pim-matrix-run {
  justify-self: end;
  font-family: var(--pim-matrix-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.92rem;
  color: var(--pim-spark);
  animation-name: pim-matrix-count-in;
  animation-timing-function: cubic-bezier(0.2, 0.9, 0.3, 1.35);
  animation-fill-mode: both;
}

@keyframes pim-matrix-count-in {
  from { opacity: 0; translate: 0 -4px; scale: 0.94; }
  to { opacity: 1; translate: 0 0; scale: 1; }
}

.pim-matrix-total {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  margin-top: var(--pim-space-4);
  padding: var(--pim-space-4) var(--pim-space-3);
  border: 1px solid var(--pim-edge);
  border-radius: var(--pim-radius);
  background:
    radial-gradient(120% 160% at 50% 0%, var(--pim-glow-core), transparent 70%),
    var(--pim-surface-flat);
  text-align: center;
  animation-name: pim-matrix-total-in;
  animation-timing-function: cubic-bezier(0.2, 0.85, 0.3, 1.1);
  animation-fill-mode: both;
}

@keyframes pim-matrix-total-in {
  from { opacity: 0; translate: 0 10px; scale: 0.97; }
  to { opacity: 1; translate: 0 0; scale: 1; }
}

.pim-matrix-total-figure {
  font-family: var(--pim-font-display);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.1rem, 6vw, 3.4rem);
  font-weight: 700;
  line-height: 1.05;
  color: var(--pim-text);
  text-shadow: 0 0 28px var(--pim-halo);
}

.pim-matrix-total-label {
  font-family: var(--pim-font-sans);
  font-size: 0.82rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--pim-spark);
}

.pim-matrix-total-note {
  max-width: 44ch;
  font-size: 0.86rem;
  color: var(--pim-text-dim);
}

/* —— Phones and small tablets ——————————————————————————————————————————
   Below this the gutter is too narrow for six crossing curves to be anything but
   mush, so the wires go and the two columns collapse into stacked pairs. With no
   wires there is no sequence left to follow either — flow.css reaches the same
   conclusion at its own breakpoint — so the choreography is off and every part
   is simply present. That also avoids a pair card standing half-drawn for two
   seconds while it waits for a wire that will never arrive.

   The per-cell grid placement is an inline style from Python, so only
   `!important` can undo it. One `grid-area: auto` covers row and column at once. */
@media (max-width: 760px) {
  .pim-map,
  .pim-fan {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
  }

  .pim-map-head,
  .pim-map-source,
  .pim-map-target,
  .pim-fan-head,
  .pim-fan-record,
  .pim-fan-out {
    grid-area: auto !important;
  }

  .pim-map-wires,
  .pim-fan-wires { display: none; }

  .pim-map *,
  .pim-map,
  .pim-fan *,
  .pim-fan { animation: none; }

  /* Both column headings label columns that no longer exist here, and the
     source/target direction is carried by the arrow inside each card instead.
     The section's own heading, which the page template owns, is what titles the
     diagram at this width. */
  .pim-map-head { display: none; }

  /* A pair reads as one card: the header on top, the fields it became below. */
  .pim-map-source {
    margin: var(--pim-space-3) 0 0;
    border-radius: var(--pim-radius) var(--pim-radius) 0 0;
    border-bottom: 0;
  }

  .pim-map-target {
    margin: 0;
    border-radius: 0 0 var(--pim-radius) var(--pim-radius);
    border-color: color-mix(in srgb, var(--pim-map-tone) 34%, transparent);
    border-top-color: var(--pim-edge);
  }

  .pim-map-arrow {
    display: block;
    flex: none;
    width: 1.1rem;
    height: 1.1rem;
    margin-left: auto;
    rotate: 90deg;
    color: var(--pim-text-dim);
  }

  /* The fan-out keeps both headings: its document order is record, then
     outputs, so each heading still sits directly above what it names. */
  .pim-fan-head--out { padding-top: var(--pim-space-4); }

  .pim-fan-record { margin-block: 0; }

  .pim-fan-out { margin-block: var(--pim-space-2) 0; }

  /* The thumbnail stays beside its label rather than going full width under it:
     a shape stretched across a phone screen stops looking like a page or a flat
     file and starts looking like a progress bar, which is the one thing this
     diagram cannot afford to lose. It only gets narrower. */
  .pim-fan-out {
    grid-template-columns: minmax(0, 1fr) clamp(92px, 30%, 132px);
    gap: var(--pim-space-2);
  }

  .pim-fan-shape { height: 58px; }

  /* Four columns will not fit, and the two that matter are the decision and what
     the count has reached. So the options wrap under their own decision name and
     the multiplier joins the running product on one line: "×8" and "160" read as
     a single statement anyway, and putting them together is what frees the width
     for the chips. */
  .pim-matrix { grid-template-columns: minmax(0, 1fr) auto; }

  .pim-matrix-head--opts,
  .pim-matrix-head--count { display: none; }

  /* Placed explicitly rather than left to auto-flow. With the options spanning
     both columns, auto-placement pushes the multiplier onto a third row under
     the chips and leaves the running product beside it -- which reads as two
     unrelated numbers at the bottom of a card. The order that survives the
     narrow layout is: the decision and how many options it has, then the
     options, then what the running product has reached. */
  .pim-matrix-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--pim-space-2);
  }

  .pim-matrix-name { grid-area: 1 / 1; }
  .pim-matrix-count { grid-area: 1 / 2; justify-self: end; }
  .pim-matrix-opts { grid-area: 2 / 1 / 3 / -1; }
  .pim-matrix-run { grid-area: 3 / 1 / 4 / -1; justify-self: end; }

  .pim-matrix,
  .pim-matrix * { animation: none; }
}

/* Same approach flow.css takes: everything settles into its finished state
   immediately, which works because the hidden states live in the keyframes and
   not in the base rules. The travelling pulse is the one thing with nothing to
   settle into, so it is hidden rather than stopped. */
@media (prefers-reduced-motion: reduce) {
  .pim-fan-spark { display: none; }

  .pim-map-head,
  .pim-map-wire,
  .pim-map-source,
  .pim-map-target,
  .pim-map-chip,
  .pim-map-type,
  .pim-fan-head,
  .pim-fan-wire,
  .pim-fan-record,
  .pim-fan-field,
  .pim-fan-out,
  .pim-fan-cell,
  .pim-matrix-head,
  .pim-matrix-name,
  .pim-matrix-opts,
  .pim-matrix-count,
  .pim-matrix-run,
  .pim-matrix-total {
    animation: none;
  }
}

/* Lighting case matrix: inherit the same steel accent as its surrounding
   records. This is kept here because diagrams.css loads after site.css. */
.case-detail--lighting .pim-matrix {
  --pim-matrix-tone: var(--lighting-accent);
}

.case-detail--lighting .pim-matrix-row,
.case-detail--lighting .pim-matrix-row--lilac,
.case-detail--lighting .pim-matrix-row--plum,
.case-detail--lighting .pim-matrix-row--mint,
.case-detail--lighting .pim-matrix-row--sky,
.case-detail--lighting .pim-matrix-row--peach,
.case-detail--lighting .pim-matrix-row--gold,
.case-detail--lighting .pim-matrix-row--aqua,
.case-detail--lighting .pim-matrix-row--sage,
.case-detail--lighting .pim-matrix-row--rose {
  --pim-matrix-tone: var(--lighting-accent);
  border-color: var(--lighting-line);
  border-left-color: var(--lighting-accent);
}

.case-detail--lighting .pim-matrix-head {
  color: var(--lighting-muted);
}

.case-detail--lighting .pim-matrix-name {
  color: var(--lighting-ink);
}

.case-detail--lighting .pim-matrix-chip {
  border-color: var(--lighting-line);
  background: var(--lighting-surface-alt);
  color: var(--lighting-muted);
}

.case-detail--lighting .pim-matrix-chip--more {
  border-color: var(--lighting-line-strong);
  background: transparent;
  color: var(--lighting-accent-dark);
}

.case-detail--lighting .pim-matrix-count {
  background: #dce6ed;
  color: var(--lighting-accent-dark);
}

.case-detail--lighting .pim-matrix-run {
  color: var(--lighting-accent-dark);
}

.case-detail--lighting .pim-matrix-total {
  border-color: var(--lighting-line-strong);
  background: var(--lighting-surface-alt);
  box-shadow: 0 8px 18px rgba(27, 42, 59, .05);
}

.case-detail--lighting .pim-matrix-total-figure {
  color: var(--lighting-ink);
  text-shadow: none;
}

.case-detail--lighting .pim-matrix-total-label {
  color: var(--lighting-accent-dark);
}

.case-detail--lighting .pim-matrix-total-note {
  color: var(--lighting-muted);
}

/* Keep the row mapping, but spend it only on each row's steel rule and count
   badge. Chips, running products and the total remain neutral. */
.case-detail--lighting .pim-matrix-row--lilac { --lighting-group: #52677e; }
.case-detail--lighting .pim-matrix-row--plum { --lighting-group: #6d6179; }
.case-detail--lighting .pim-matrix-row--mint { --lighting-group: #3e6b64; }
.case-detail--lighting .pim-matrix-row--sky { --lighting-group: #356b90; }
.case-detail--lighting .pim-matrix-row--peach { --lighting-group: #7c604e; }
.case-detail--lighting .pim-matrix-row--gold { --lighting-group: #76622f; }
.case-detail--lighting .pim-matrix-row--aqua { --lighting-group: #356c72; }
.case-detail--lighting .pim-matrix-row--sage { --lighting-group: #576f58; }
.case-detail--lighting .pim-matrix-row--rose { --lighting-group: #805e6b; }

.case-detail--lighting .pim-matrix-row {
  border-left-color: var(--lighting-group, var(--lighting-accent));
}

.case-detail--lighting .pim-matrix-row .pim-matrix-count {
  color: var(--lighting-group, var(--lighting-accent-dark));
  background: color-mix(in srgb, var(--lighting-group, var(--lighting-accent)) 11%, var(--lighting-surface-alt));
}
