/* ==========================================================================
   SYSTEM INTELLIGENCE  ·  THE ASK / DETECT / EXPLAIN SCROLL FLOW
   ==========================================================================
   Created 2026-08-12 for spec §5.2. One drawing, three stages, scrubbed by
   scroll. It replaced three stacked .sp-blocks that each argued separately.

   THE ONE IDEA THIS FILE EXISTS TO PROTECT.
   It is the SAME PICTURE at all three stages. Three unit signals on one time
   axis: stage 1 names a moment on them, stage 2 puts the learned envelope
   around them, stage 3 walks the investigation back across them. Nothing is ever
   removed, dimmed away or replaced. An earlier draft greyed the lanes out at
   stage 3 and the arrow was left floating on an abandoned timeline — it stopped
   being a timeline and became an arrow diagram. So: reveals only, never hides.

   AND STAGE 3 RUNS BACKWARDS (2026-09-03). The signal propagates forwards in
   time; the investigation does not. It starts at the outcome somebody noticed
   and walks back to the cause, so the arrows point up and to the LEFT and the
   red origin marker is the last thing to arrive. Reversing that would put a
   display in front of its own cause. Relatedly, all three lanes now leave
   their envelopes — one unit deviating while the two it depends on stayed
   normal was a picture that contradicted the finding printed beside it.
   If you add a rule here that lowers the opacity of a signal, you have undone
   the reason the file is shaped this way.

   HOW SCROLL REACHES THE DRAWING.
   js/page/si-flow.js writes exactly one number, --si-p, from 0 to 1 across the
   flow's scroll track. Every reveal below is derived from it with clamp() — no
   second progress value, no per-element JavaScript, and nothing in the script
   that measures layout while scrolling. Two consequences worth knowing:

     · The sub-ranges are authored here, in CSS, not in the script. Retiming a
       stage is a number in this file.
     · --si-p: 1 is the finished frame. That is the whole reduced-motion and
       no-JavaScript story: the markup already contains every stage, and the
       default value of --si-p in .si-flow is 1, so a visitor who gets no
       script at all sees stage 3 complete rather than a blank axis. The script
       lowers it to 0 only after it has confirmed it will animate.

   COLOUR IS RESTATED BY VALUE, not read from --pd-*. Those tokens are scoped
   to .pd inside css/demo-system.css, which this rework may not edit and must
   not reach into from outside the demo system. Same three values, one
   functional meaning each: teal is data, orange is the single flagged
   deviation and the answer path drawn from it, red is the origin.
   ========================================================================== */

.si-flow {
  --si-p: 1;

  /* Data teal, action orange, failure red. See the note above. */
  --si-data: #4ecdc4;
  --si-data-line: rgba(78, 205, 196, 0.26);
  --si-data-fill: rgba(78, 205, 196, 0.145);
  --si-action: #ef8354;
  --si-fail: #d64550;

  /* ---- the reveal ranges, all derived from the one progress value --------
     Read them as a script: what the reader has scrolled far enough to have
     been told. Each is 0 before its stage, 1 after it, and the drawing holds
     every value it has reached. */

  /* 1 ASK — the marker drops onto the axis almost at once, because the signals
     and the question are already there and the marker only names a moment. */
  --si-mark: clamp(0, (var(--si-p) - 0.04) * 8.5, 1);

  /* 2 DETECT — first what normal is, then the one thing outside it. The
     envelope has to land before the flag, or the flag has nothing to be
     outside of. */
  --si-band: clamp(0, (var(--si-p) - 0.30) * 8, 1);
  --si-flag: clamp(0, (var(--si-p) - 0.44) * 9, 1);

  /* 3 EXPLAIN — THE ORDER IS THE ARGUMENT, and it was reversed 2026-09-03.
     It used to reveal the origin first and then walk arrows forward to the
     symptom, which drew the physics and hid the method. The investigation
     starts where somebody noticed something, so the symptom marker lands
     first, the two hops walk back along the axis, and the red origin arrives
     LAST — it is the conclusion of the walk, not its premise. Each arrowhead
     lands only once its line has arrived. */
  --si-start: clamp(0, (var(--si-p) - 0.60) * 14, 1);
  --si-hop1: clamp(0, (var(--si-p) - 0.66) * 10, 1);
  --si-head1: clamp(0, (var(--si-p) - 0.74) * 20, 1);
  --si-hop2: clamp(0, (var(--si-p) - 0.77) * 10, 1);
  --si-head2: clamp(0, (var(--si-p) - 0.85) * 20, 1);
  --si-origin: clamp(0, (var(--si-p) - 0.88) * 14, 1);
}


/* ==========================================================================
   § THE TRACK AND THE PANE
   ==========================================================================
   Two parts. The TRACK is scroll distance and nothing else — it has a height
   and no content of its own, and its only job is to give the three stages room
   to arrive one at a time. The PANE is what the reader looks at while they
   travel it: pinned, quiet, unchanging in layout, changing only in what the
   drawing has been told and which stage's copy is showing.

   THE DRAWING AND THE COPY SIT SIDE BY SIDE, not stacked. Stacked was the
   first build and it failed for a specific reason worth recording: a pinned
   figure above a scrolling copy column eats the top of every block that passes
   under it, so each stage lost its eyebrow and its aphorism exactly while it
   was the active stage. Side by side, each stage is a whole still frame.

   THE THREE STAGES OCCUPY THE SAME GRID CELL and cross-fade. Only one is
   readable at a time, which is what "legible as a still frame" means. All
   three stay in the document and in reading order, so a screen reader gets the
   complete section and the opacity is a visual state rather than a gate.
   -------------------------------------------------------------------------- */

.si-flow__track {
  position: relative;
  /* Three stages, a bit under a screen each. Long enough that a stage is not
     skipped by one flick of a trackpad, short enough that nobody scrolls
     through emptiness waiting for the next one. */
  min-height: 260vh;
}

/* No min-height. The three stages share one grid cell, so the pane is already
   as tall as the tallest of them whichever one is showing — forcing it to a
   full viewport only added dead space under the drawing. */
.si-flow__pane {
  position: sticky;
  /* Clear of the site header, which is sticky and would otherwise sit over the
     top of the drawing. */
  top: calc(var(--nav-h) + 16px);
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--sp-7);
  align-items: center;
}

.si-flow__fig {
  margin: 0;
  min-width: 0;
}

.si-flow__svg {
  display: block;
  width: 100%;
  height: auto;
}

.si-flow__stages {
  display: grid;
  min-width: 0;
}

.si-flow__stage {
  /* All three in one cell, so the pane's height is the tallest stage and does
     not change as the reader moves between them. A pane that resized per stage
     would move the drawing, which is the one thing that has to hold still. */
  grid-area: 1 / 1;
  align-self: start;
  opacity: 0;
  pointer-events: none;
  /* Leaving is quick and unconditional. */
  transition: opacity 150ms ease;
}

/* Arriving waits for leaving to finish. Without the delay both stages sit at
   half opacity for a moment and the reader gets two headlines printed over each
   other, which is worse than either a cut or a fade. */
.si-flow__stage.is-active {
  opacity: 1;
  pointer-events: auto;
  transition: opacity 200ms ease 150ms;
}

/* Before the script has decided which stage is active, the first one is. */
.si-flow__stages:not(:has(.is-active)) > .si-flow__stage:first-child {
  opacity: 1;
  pointer-events: auto;
}

.si-flow__stage .sp-block {
  max-width: none;
}

/* The chip groups the stages used to carry — "Compared against", "What is
   compared", "What comes back" — were removed on 2026-09-15: grey lists of
   nouns under a headline that had already made the point, and unreadable at
   the size they were set. The rule that stacked two of them goes with them.
   This sheet is linked by one page, so there is nothing else relying on it. */


/* ==========================================================================
   § THE DRAWING
   ==========================================================================
   Every class here is a role in the picture, not a look. Nothing is coloured
   because it needed colour.
   -------------------------------------------------------------------------- */

.si-flow__svg text {
  font-family: var(--mono);
  letter-spacing: 0.05em;
}

/* Lane and axis furniture. */
.si-f-lbl {
  font-size: 11px;
  fill: var(--dim);
  text-transform: uppercase;
}

.si-f-unit {
  font-family: var(--font);
  font-size: 12px;
  letter-spacing: 0;
  fill: var(--text);
}

.si-f-val {
  font-size: 11.5px;
  fill: var(--muted);
  letter-spacing: 0.03em;
}

.si-f-rule {
  fill: none;
  stroke: var(--line);
  stroke-width: 1;
}

.si-f-tick {
  stroke: var(--line);
  stroke-width: 1;
}

/* The question, as the thing that entered. */
.si-f-chip {
  fill: none;
  stroke: var(--line);
}

.si-f-chiptxt {
  font-size: 11.5px;
  fill: var(--muted);
  letter-spacing: 0.02em;
}

/* ---- stage 1 · the signals and the moment ------------------------------- */

/* The run's own trace. Teal from the first frame: this is measured data, and
   the reader has to be looking at a real signal before anything is claimed
   about it. */
.si-f-trace {
  fill: none;
  stroke: var(--si-data);
  stroke-width: 1.6;
  stroke-linejoin: round;
}

.si-f-mark {
  stroke: var(--si-action);
  stroke-width: 1.2;
  stroke-dasharray: 5 4;
  opacity: var(--si-mark);
}

.si-f-marktxt {
  font-size: 11.5px;
  fill: var(--si-action);
  opacity: var(--si-mark);
}

/* ---- stage 2 · what normal is, and the one thing outside it ------------- */

/* A SHADED REGION, not a second line. The fill carries the meaning — "anywhere
   in here is normal" — and the hairline only closes it so that "outside" has an
   edge to be outside of. It was a dotted outline hugging the trace, and at that
   width the dashes read as texture ON the signal: the flagged stretch did not
   visibly leave anything and the label was left asserting it alone. So: wide,
   soft, quiet, and behind the trace, which stays the brightest thing in the
   lane. The envelope's WIDTH is a drawing choice like the 41 ms marker — the
   geometry note in solutions/system-intelligence.html says how much depends on
   it. Do not add a dash back, and do not raise the fill until it competes with
   the trace. */
.si-f-band {
  fill: var(--si-data-fill);
  stroke: var(--si-data-line);
  stroke-width: 1;
  opacity: var(--si-band);
}

.si-f-bandkey {
  opacity: var(--si-band);
}

.si-f-bandkey text {
  font-size: 11px;
  fill: var(--si-data);
  text-transform: uppercase;
}

/* The key swatch is the envelope in miniature, so it carries exactly the same
   treatment — a filled patch, no dash. */
.si-f-bandkey rect {
  fill: var(--si-data-fill);
  stroke: var(--si-data-line);
  stroke-width: 1;
}

/* The one flagged deviation: the stretch of trace that sits outside its own
   envelope, drawn over the teal so the reader sees the same line leaving. */
.si-f-out {
  fill: none;
  stroke: var(--si-action);
  stroke-width: 2.8;
  stroke-linejoin: round;
  opacity: var(--si-flag);
}

.si-f-flagdot {
  fill: var(--si-action);
  opacity: var(--si-flag);
}

.si-f-flagtxt {
  font-size: 11.5px;
  fill: var(--si-action);
  opacity: var(--si-flag);
}

/* ---- stage 3 · the investigation, walked back along the same axis ------- */

/* Where the walk starts: the symptom, on the display's lane. Hollow and
   orange, not red — this is the thing that was observed, not the thing that
   caused it, and the drawing must not let the two share a treatment. */
.si-f-start {
  fill: none;
  stroke: var(--si-action);
  stroke-width: 1.8;
  opacity: var(--si-start);
}

.si-f-starttxt {
  font-size: 11.5px;
  fill: var(--si-action);
  opacity: var(--si-start);
}

/* Origin is the only red in the section. */
.si-f-origin {
  fill: var(--si-fail);
  opacity: var(--si-origin);
}

.si-f-origintxt {
  font-size: 11.5px;
  fill: var(--si-fail);
  opacity: var(--si-origin);
}

.si-f-logtxt {
  font-size: 11.5px;
  fill: var(--muted);
  opacity: var(--si-origin);
}

/* The hops. Drawn, not faded in: a line that grows is the only way the picture
   says the walk has a direction — and here that direction is backwards along
   the axis, because each path is authored starting at the later moment. The
   dash length is a fixed over-estimate of the path length, so no code ever
   measures the geometry. */
.si-f-hop {
  fill: none;
  stroke: var(--si-action);
  stroke-width: 1.6;
  stroke-dasharray: 200px;
}

.si-f-hop--1 {
  stroke-dashoffset: calc(200px * (1 - var(--si-hop1)));
}

.si-f-hop--2 {
  stroke-dashoffset: calc(200px * (1 - var(--si-hop2)));
}

.si-f-head {
  fill: var(--si-action);
}

.si-f-head--1 { opacity: var(--si-head1); }
.si-f-head--2 { opacity: var(--si-head2); }

/* .si-f-way — the mid-chain waypoint — was removed with the forward version of
   this stage. The two hops now meet at the Comfort ECU's own out-of-envelope
   stretch, which is a better waypoint than a drawn dot: it is evidence. */


/* ==========================================================================
   § SMALL SCREENS
   ==========================================================================
   Below this width the pane cannot hold both halves. Two columns become one,
   and a 900-unit viewBox squeezed into a phone puts the axis labels under 6px.

   So the narrow layout gets the honest version: the finished frame, scrollable
   sideways at a width where the labels are still labels, with all three stage
   blocks at full strength beneath it. Nothing is withheld — the difference is
   that the three stages arrive together rather than in sequence, which is the
   same compromise reduced motion makes below. Sideways scrolling on a time axis
   is the one place it reads as intended rather than as a layout failure.

   1024px, not a token: it is the width at which the pane's copy column drops
   under about 34 characters. js/page/si-flow.js has to agree on the number,
   because below it there is no scrub for it to drive.
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  .si-flow__track {
    min-height: 0;
  }

  .si-flow__pane {
    position: static;
    min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
  }

  /* The drawing is one continuous timeline and cannot be reflowed: scaled to
     366px its own labels fall to about 6px. So it scrolls sideways — but it
     has to say so. Cut flat at the frame edge it reads as a broken layout,
     which is what "the scrolling does not work" looks like from the outside.

     The snap declaration here was inert: scroll-snap-type needs children
     carrying scroll-snap-align and this scroller has exactly one child, the
     <svg>. Removed rather than left looking like behaviour that exists. */
  .si-flow__fig {
    position: relative;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Both edges: the script opens this scroller at 62% on purpose, on the
       moment the section is about, so the drawing genuinely continues to the
       left as well as to the right. */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
  }

  /* Sticky, because a plain block here is as wide as the 640px drawing and its
     text sits at x=0 — scrolled out of sight the moment the figure opens. */
  .si-flow__fig::after {
    content: "swipe the timeline \2194";
    position: sticky;
    left: 0;
    display: block;
    width: max-content;
    margin-top: 10px;
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 11.5px;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--color-metal, rgba(120, 134, 153, .8));
  }

  .si-flow__svg {
    min-width: 640px;
  }

  .si-flow__stages {
    display: block;
  }

  .si-flow__stage {
    opacity: 1;
    pointer-events: auto;
  }

  .si-flow__stage + .si-flow__stage {
    margin-top: var(--sp-7);
  }
}


/* ==========================================================================
   § REDUCED MOTION
   ==========================================================================
   Reduced motion is none, not less (js/demo/README.md). --si-p stays at 1, so
   the drawing settles on stage 3 with every stage's content present, and the
   pane stops pinning because there is nothing left to scrub. The script checks
   the same query and never binds a listener.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .si-flow {
    --si-p: 1;
  }

  .si-flow__track {
    min-height: 0;
  }

  .si-flow__pane {
    position: static;
    min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
  }

  .si-flow__stages {
    display: block;
  }

  .si-flow__stage {
    opacity: 1;
    pointer-events: auto;
    transition: none;
  }

  .si-flow__stage + .si-flow__stage {
    margin-top: var(--sp-7);
  }
}
