/*
  How It Works — eight stages in one row, one of them open, third on the page.

  ── What this replaced ───────────────────────────────────────────────────────

  A radial diagram of eight nodes with a detail panel beside it. Then six equal columns, all
  six open, every interface on screen at once. Then the accordion: one column open at four
  times the width of its neighbours, five collapsed to a number, a name and a glyph.

  This is that accordion with two things changed, and both of them came from the same
  discovery — that the panel could hold a photograph of the actual product instead of an
  interface drawn in divs.

  First, the count. Six was a compromise the drawn screens paid for: `Brief & Development`,
  `Materials & Factory Match` and `Freight & Delivery` were three merges made to buy each
  column a readable measure. The stages are eight, they have always been eight, and the
  merges each hid a place where the work changes hands — content/copy.js has the argument.
  Eight is what the section now shows.

  Second, the panel. A screenshot inset by 28px inside a bordered frame is a picture of
  software placed on a website; the same screenshot taken to the panel's own edges is the
  website opening into the software. So the panel's padding is zero, the frame is gone, and
  the image runs from the hairline under the header to the bottom corner of the column. The
  reader crosses a stage name and a sentence and then they are looking at the product.

  ── What eight costs ─────────────────────────────────────────────────────────

  Width, and it is worth being exact about it. Seven collapsed columns at their 108px floor
  plus seven seams take 791px of the row, so the open column measures 521px at 1440, 694px at
  1728 and 875px at 1920. The old six-column arrangement gave it 695px at 1440. Two stages
  cost the open column a third of its width, and the screenshot inside it is a picture of a
  whole workspace rather than a paragraph — it survives the loss in a way body copy would not
  have, because what a reader takes from it is a shape rather than a sentence.

  It also raises the floor on the accordion itself from 1081px to 1240. At 1240 the open column
  is 325px and the screenshot in it is 325×183 — the smallest this section will ever show, and
  the point at which the grid below starts being the better layout. See the overrides at the
  bottom of the file.

  ── The bar, and why it is at the top ────────────────────────────────────────

  It used to be under the panel. Under the panel it is a scrubber, and a scrubber is a
  control for a slideshow — the reader's eye reached it last, after the content it was
  supposed to be describing, and read it as a carousel indicator.

  At the top it is read before the content, in the place the eye already goes looking for
  `01 → 02 → 03`: eight segments, one per column, some of them filled. That is the section's
  entire claim stated in two pixels of height before a word of it has been read — a process
  with a position in it. Same pixels, opposite meaning, purely from where they sit.

  One segment per column, each exactly as wide as the column beneath it, which is what makes
  the bar and the accordion one object instead of two. The open column's segment is seven
  times its neighbours', so the bar is itself a picture of where the attention is. A single
  line across the whole component was the alternative and it says `loading`.

  And it is the only auto-advance system in here. No dot, no timeline under the panel, no
  `auto advancing` caption, no countdown, no Replay button. A component that explains its own
  mechanism twice has admitted the first attempt did not read.

  ── The two layouts ─────────────────────────────────────────────────────────

  Desktop-first, like every stylesheet here, but with one extra split: the base is equal open
  columns — four to a row, two rows — and the accordion is layered over it at
  `min-width: 1240px` for `html[data-js='true']` only.

  Which is not two designs kept side by side; it is one design and the state it enhances. A
  visitor with the script blocked gets every stage on the page, its screenshot included, and
  nothing hidden behind a button that cannot run. The one state that does not exist is an
  empty frame.

  ── The height ───────────────────────────────────────────────────────────────

  Unchanged as a constraint: the heading and all eight stages inside a laptop screen. Steps
  seven and eight below the fold was the ring's failure and it would be this one's too. The
  header zone is a fixed 170px and the panel's height is then whatever the screenshot's own
  ratio makes of the open column — 293px at 1440, 492px at 1920 — so the section measures 708px
  at a 1440 laptop and 909px at a 1920 one, heading and padding included. The screenshot is the
  only thing that grows with the viewport, which is the right thing to spend a large screen on.
  Removing the panel's 46px of vertical padding paid for most of the second row of subtext the
  longer sentences now need.
*/

/* ═══ THE SECTION ════════════════════════════════════════════════════════ */

/*
  The paper's third page, and the grey one again — the same two `--paper-*` tones the
  rail and the benefits grid use, in the rail's arrangement: near-white panels standing
  on grey. So the page runs dark, grey, near-white, grey, and this section pairs with the
  category rail two above it rather than with the section directly overhead.

  No rule at the top. There was one, on the argument that #FAFAF8 down to #ECECEA was
  too small a step to read as a boundary; it is the identical 14-level step as grey up to
  near-white above, which never had a rule and never needed one — see the note in
  advantage.css.

  Padding at the low end of the page's range, and the same rhythm as the benefits grid
  directly above — 63px over, 55 under at 1440. Two neighbouring sections with different
  padding read as two layouts, and both of them are here to be crossed in one screen
  rather than settled into. Every other section on this page is at 100–120px; these two
  give that room to their content.
*/
.process {
  padding-block: clamp(48px, 4.4vw, 64px) clamp(40px, 3.8vw, 56px);
  --bg: var(--paper-grey);
  --bg-raised: var(--paper-white);
  --line-strong: var(--paper-line-strong);
  --fg-sub: var(--paper-sub);
  /* One muted grey for both, for the reason set out on `.cats` in categories.css. */
  --fg-soft: var(--paper-sub);

  /*
    ── The one accent, and why it is blue ────────────────────────────────────

    This section is the orchestration layer: eight stages of a process the platform runs,
    with a bar showing where in that process the section currently is. Blue is what the
    page uses for system state — analysis, routing, the thing the software is doing — and
    the configurator below it now says so in the same voice.

    Orange is spoken for. On the map in `#network` it means physical execution: freight
    moving, a factory making, a container arriving. A progress bar in that colour claims
    that reaching stage 05 is a shipment, which is the one thing this strip is not
    describing — it is describing where the *system* is, not where the goods are. Two
    neighbouring sections using one colour for two unrelated ideas is how a palette stops
    carrying meaning, and the fix is on this side of the seam: freight is genuinely orange,
    a progress indicator genuinely is not.

    Scoped, because `--accent` is the page's global signal and six other places read it —
    `home.css`, `estimator.css`, `suppliers.css`. Re-pointing the token would repaint half
    the site to change one bar.

    It is also the only accent in here. No green, yellow, orange or red at any state: a
    completed stage is full ink, a future one is grey, and the current one is blue. Three
    tones for three conditions, and the reader never has to learn a fourth.
  */
  --proc-state: var(--lot-blue);

  /*
    And the structure it sits against, unified on one neutral.

    The section carried two warm greys that were nearly the same and not the same —
    `--paper-line` #D5D5D0 on the hairlines, #D7D7D1 hard-coded on the progress track. Both
    are now #D8D8D4, which is the border grey the configurator below uses. Inactive
    structure is one colour across both sections or it is noise; and the track is the one
    place on this page where a neutral sits directly beside the accent, so it is the one
    place the difference between two of them would have shown.
  */
  --line: #d8d8d4;
}

/*
  Full width, as the ring was and the configurator below still is: eight columns of a
  sequence are the argument here, and on a 1728px screen a 1240px measure would leave
  460px of empty page beside them and take 400px off the open column to do it. Same left
  edge as everything else, right edge at the gutter.

  Capped at 1800, which is the number the benefits grid above takes. A 1200px screenshot
  across a 2400px monitor is a product tour rather than a diagram of a process, and the two
  sections are now neighbours running the same width — if their outer edges disagreed at
  some width the page would read as two layouts rather than one.

  Only the strip grows. The head keeps its own measure below, so a wider section is not
  a wider sentence.
*/
.process > .shell {
  max-width: 1800px;
}

/* ═══ THE HEAD ═══════════════════════════════════════════════════════════ */

/*
  A label over a heading, and nothing under it — the same head as the benefits section
  above, built out of the same two parts for the same reason. Every section on this page
  used to be a heading with a supporting paragraph beneath it, which is a shape a reader
  stops registering somewhere around the third one.

  40px to the strip, against the benefits grid's 60. The gap there separates a head from
  a block of argument a reader is meant to settle into; here it separates a head from a
  sequence they are meant to cross, and 40 is also the last place the section can find
  height without touching the crops.
*/
.process .head {
  margin-bottom: clamp(32px, 3.2vw, 40px);
}

.process .head .eyebrow {
  margin-bottom: clamp(14px, 1.2vw, 16px);
}

/*
  `--t-h2`, the page's own display size, at a measure that lets both sentences sit on one
  line at desktop. The break is set in the words: `We manage` is glued with a
  non-breaking space in content/copy.js, so the only place the heading can ever break is
  after `brief.` — which puts one whole sentence on each line when it does.

  Two sentences and not one with a comma in the middle. `You set the brief.` is what the
  buyer does and `We manage the rest.` is what we do, and a full stop between them is the
  division of labour the strip below then spends eight columns illustrating.
*/
.process .head h2 {
  max-width: 17em;
  margin-bottom: 0;
}

/* ═══ THE STRIP — BASE ═══════════════════════════════════════════════════ */

/*
  Four equal columns, two rows, 5px between, every panel open with its screenshot in it.
  This is what a printer and a browser with no JavaScript get above 1240px; the accordion at
  the bottom of this file is layered over it, and the overrides below take it to two columns
  and then one as the viewport narrows.

  Four and not eight. When each panel held a drawn screen, eight in a row would still have
  been the right answer — a sequence that wraps has stopped being a sequence, and the ring
  died of putting stages out of sight. What changed is that a panel now holds a photograph:
  at a 220px column that screenshot is 220×124, which is not a picture of an interface, it is
  a grey smudge with an orange dot in it. Two rows of four keeps every stage on the page and
  gives each one a 420px image. The numbers `01`–`08` carry the order across the wrap.

  `minmax(0, …)` rather than `1fr` because `1fr` is `minmax(auto, 1fr)` — a column whose
  longest word is wider than its share takes the difference out of its neighbours, and four
  columns that are nearly equal is worse than four that are obviously not.

  No overflow, no scroll-snap, no drag. A horizontally scrollable strip is the ring's
  failure in a different costume — stages seven and eight exist but are not visible.

  5px of seam against the benefits grid's 6, because a panel here is a third of the width
  of one there: the seam has to read as the same weight of line relative to what it
  divides, and at 200px wide 6px starts to look like a gap.
*/
.process-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 5px;
  list-style: none;
}

/*
  ── The panel ───────────────────────────────────────────────────────────────

  Near-white, flat, 3px of radius — the benefits panel's corner, for the same reason
  given there: 90° reads as a screenshot of a table, and 3px is the least softening that
  stops it without becoming a card. No shadow and no border. The seam separates them and
  the panel is not a control.

  16px of inset on the text. Tight, and it has to be: at a 200px column, 26px of padding
  either side leaves 148px for a two-word stage name.

  No padding at the bottom, because the bottom of the panel is the screenshot and the
  screenshot is flush — a 16px near-white margin under an image whose own edge is near-white
  produces a panel that appears to end 16px lower than it does. `overflow: hidden` is what
  cuts the image's square corners to the panel's radius; it is the only frame the shot gets,
  here and in the accordion both.
*/
.process-step {
  display: flex;
  flex-direction: column;
  padding: 16px 0 0;
  border-radius: 3px;
  overflow: hidden;
  background: var(--bg-raised);
}

/*
  One step per column, so the eight arrive left to right — which is the direction the strip
  is read in, and the only thing in this section that says the stages are ordered besides
  the numbers. 55ms is a touch slower than the benefits grid's 45: there the six are a
  set arriving together, here they are a sequence, and the sweep is the point.

  Scoped to `html[data-js='true']` because that is where the reveal's base state lives —
  see base.css. With the module blocked there is no transition to delay.
*/
html[data-js='true'] .process-step[data-reveal] {
  transition-delay: calc(var(--i, 0) * 55ms);
}

/*
  The track is drawn by the accordion and nowhere else, and so is the glyph. Equal open
  columns have no active step, nothing is advancing, and a progress bar over a column that
  is not progressing is a lie in two pixels. The glyph is the collapsed state's substitute
  for an interface; where every interface is on screen there is nothing to substitute for.
*/
.process-track,
.process-glyph {
  display: none;
}

/*
  ── The step icon's box and the image in it ─────────────────────────────────

  Geometry, and it sits out here rather than in the accordion block on purpose. Whether the
  icon is *shown* depends on the width and on the column's state, and that lives in the
  accordion; how big it is and where in its own box it sits does not depend on either, and a
  rule that only exists inside a media query is a rule that stops existing the moment
  something else decides to show the element.

  64px of box holding at most 52px of image. The box is fixed for all eight and the image is
  not, which is the arrangement that keeps the footprint still: a 34px-tall container mark and
  a 49px-tall document sit in the same 64 units, so the column's layout does not change when
  the strip advances from one to the other. Sizing the box to the image instead would move
  every collapsed column's contents by up to 15px as the accordion runs.

  `max-width` and `max-height` rather than `width` and `height`, with both dimensions `auto`
  and `object-fit: contain` — so nothing is ever stretched. The files are square 208px
  canvases with the mark already at its own optical size inside (see icons.py), so all eight
  resolve to exactly 52x52 here and the mark inside each one is the size it should be. That is
  what makes this one rule and not eight: the optical correction is in the raster, where it
  survives, instead of in eight `max-width` exceptions, where the next person to touch this
  file would have to guess which of them was deliberate.
*/
.process-icon {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
}

.process-mark {
  display: block;
  max-width: 52px;
  max-height: 52px;
  width: auto;
  height: auto;
  object-fit: contain;
}

/*
  ── The header, in the base layout ──────────────────────────────────────────

  `display: contents` so the four things a panel is made of — number row, name, clause and
  screenshot — are flex items of the step itself, exactly as they were before the header
  existed as an element. Up in the accordion the same three text elements are a fixed-height
  block with a hairline under it and the screenshot fills what is left; here they are simply
  four stacked children of a column. One DOM, two readings, no duplicated markup.

  The clause used to take `order: 1` and sit under the drawn screen at this width, so that
  eight screens of unequal height still lined their bottom edges up. Screenshots are all one
  ratio in columns that are all one width, so their heights already agree — the clause goes
  back where the markup puts it, above the image it introduces.
*/
.process-head {
  display: contents;
}

/*
  The button is not a control at this width — there is nothing to open — so it is styled
  down to the text it contains and nothing else. It stays a button in the markup because
  the accordion above needs one, and a button that does nothing is cheaper than a second
  version of this section's markup.
*/
.process-tab {
  display: block;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}

.process-title {
  margin: 0 16px 12px;
}

/*
  ── The number row ──────────────────────────────────────────────────────────

  `01` through `08` in the mono voice every index on this site uses, at the muted grey,
  with a hairline under it that continues across every panel in a row at the same height.

  That rule is the strip's spine. It is drawn per panel — there is no way to draw one line
  across a grid row and have it break at the seams — but every panel's number row is the
  same height, so the segments land on one line with 5px interruptions in it. Which is
  precisely the reading the section wants: one band, divided, not eight cards.

  1.6 of line height, stated rather than inherited. The number used to sit in a paragraph
  and now sits inside the heading, where `h3`'s 1.12 would have taken 6px out of the row and
  moved the spine — and everything hanging off it — up by that much in every panel.

  Not the accent. Every panel is open in this fallback, so every stage is equally current
  and none of them is the one the process is at — eight blue numerals would say the opposite.
  In the accordion below, exactly one of them is blue, which is a claim this layout cannot
  make.
*/
.process-num {
  display: block;
  margin-bottom: 16px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  line-height: 1.6;
  letter-spacing: 0.06em;
  color: var(--fg-sub);
}

/*
  ── The stage name ──────────────────────────────────────────────────────────

  17px, and the size is the constraint rather than a choice. Six of the eight names are three
  or four words — `Factory Matching & Compliance` is the worst of them — so the name has to
  survive breaking onto three lines without the panel growing to suit it. Tight leading for
  the same reason: a name on three lines should read as one thing.

  `min-height` holds two lines' worth so that the screenshots below line up across a row
  whether a name takes one line or two. Without it the strip's band goes ragged, and the
  band is half of what makes this a strip.
*/
.process-name {
  display: block;
  min-height: calc(2 * 1.2em);
  font-size: clamp(16px, 1.2vw, 17px);
  line-height: 1.2;
}

/*
  ── The rhythm above the screenshot ─────────────────────────────────────────

  Number, 10px, rule, 16px, stage name, 12px, clause, 22px, screenshot.

  The gap under the clause is the largest of the four, and that is the mechanism: the number
  belongs to the rule, the rule belongs to the name, the name belongs to the clause, and the
  block of text as a whole sits over the screenshot as its caption rather than floating
  between two things. Even gaps would make five elements one undifferentiated stack.
*/

/*
  ── The portal screenshot ───────────────────────────────────────────────────

  One image of the real product, flush to the panel's own edges, and nothing else in the box:
  no caption, no title over it, no second progress bar under it, no frame. The text above
  says which stage this is and the image says what the stage looks like.

  The frame is the thing that went. It used to be a hairline at 10% ink with 2px of radius and
  16px of near-white either side, and the argument for it was real — a near-white interface on
  a near-white panel has no visible edge, so the reader cannot see where the software stops and
  the page resumes. What that argument missed is that stating the edge is what makes the image
  read as *a picture of software placed on a website*. Removing it makes the panel open into
  the software instead, which is worth the ambiguity at the top bar; and the panel's own 3px
  radius plus the 5px seam still tell the eye where one stage ends. A drop shadow would be the
  same mistake with more confidence, and nothing else on this page is elevated.

  `background: #fff` so the box is the screenshot's own white while it is still decoding
  rather than a grey hole the width of the panel.

  The picture's own ratio does the sizing. `width: 100%` with `height: auto` on an `<img>`
  that states its intrinsic 1672×941 is a box that cannot crop, cannot stretch and cannot
  letterbox, and the space is reserved before the file arrives. The brief asks for
  `aspect-ratio: 16 / 9; object-fit: cover`, which this is to within half a pixel of height at
  the largest size it is ever shown at — and `cover` would spend that half pixel cropping the
  screenshot, against the same brief's instruction never to crop the sidebar, the top bar or
  the bottom of the interface. The true ratio keeps the promise underneath the number.
*/
.process-shot {
  margin: 0;
  background: #fff;
}

.process-shot img {
  display: block;
  width: 100%;
  height: auto;
}

/*
  One clause, 15px, at the muted grey. `--fg-sub` is #686864 on the panel's #FAFAF8 —
  5.4:1 — and the sentence is the only text in the panel a reader has to work for.

  `pretty` and not `balance`: these are eleven-and-twelve-word clauses and balance will spend
  a whole extra line evening two of them out — which up in the accordion, where the header is
  a fixed height, is a line there may not be room for. Pretty leaves the count alone and only
  refuses to end on a single word, which is the actual failure in a 300px column.
*/
.process-copy {
  margin: 0 16px 22px;
  font-size: 15px;
  line-height: 1.4;
  color: var(--fg-sub);
  text-wrap: pretty;
}

/* ═══ THE ACCORDION ══════════════════════════════════════════════════════ */

/*
  Everything from here to the narrow overrides is the enhanced state: one column open, seven
  collapsed, a segmented bar across the top, advancing on its own.

  Gated twice. `html[data-js='true']` because the whole thing is operated by js/process.js —
  the attribute is written by an inline script in <head>, so first paint is already the
  accordion and there is no equal-columns flash.

  And `min-width: 1240px`, which was 1081 when there were six stages. Seven collapsed columns
  and their seams take 791px of the row; at 1240 that leaves the open column 399px, and 399px
  of 16:9 screenshot is 225px tall — small, but the smallest thing in it is still a legible
  row of a table. Below 1240 it stops being a screenshot and becomes a swatch, so the stages
  go to the grid instead. Both numbers are the same calculation with a different count; the
  gate is a consequence of the stage list, not a taste.
*/
@media (min-width: 1240px) {
  /*
    Flex and not grid, and the reason is the animation. `grid-template-columns` is
    animatable in current browsers and it animates the *container*, which means every
    column's width is interpolated by one property on one element — elegant, and it puts the
    open column's width in the same declaration as the seven closed ones, so the two cannot
    be transitioned independently. Widths on the items themselves are what let the outgoing
    column's contraction and the incoming column's expansion be one gesture with one curve.
  */
  html[data-js='true'] .process-strip {
    display: flex;
    align-items: stretch;
    --process-rail: clamp(108px, 7.4vw, 126px);
  }

  /*
    A fixed rail for the seven closed columns and everything left over for the open one. It
    was 11.2% each and 44% for the sixth, which shared the width out in the same proportion at
    every viewport — so the open column got 44% of 973px at 1081 and 44% of 1792px at 1920,
    which is a rule that gives the least room to the viewport with the least to spare. A closed
    column does not need to grow: it holds a number, a name of at most three lines and a 48px
    glyph. Everything above the rail belongs to the column that is actually being read.

    `clamp(108px, 7.4vw, 126px)`, and every number in it is set by something.

    The floor is set by one word. `Development` is the longest unbreakable string in the eight
    names, and at the closed size — `clamp(13px, 0.9vw, 14px)` — it measures 6.25px per pixel of
    font size, so 81px at the floor of that clamp and 87px at the ceiling. A column leaves
    rail-less-20 of content, so 108 gives it 88.

    The two clamps have to be read together, and the case that decides them is neither the
    widest viewport nor the narrowest. Both clamps have a floor, a slope and a ceiling, and they
    reach them at different widths — so the tight point is wherever the font has started growing
    and the rail has not. A build with the font at `0.95vw` to a 15px ceiling and the rail at
    `7.2vw` put that point at 1500px, where the word came out one pixel wider than its column.
    A pixel, found by the sweep, at a width nobody would have thought to check. At `0.9vw` to 14
    and `7.4vw` the worst point in the whole range is about 1460px with 6px of slack.

    Which is why the rail slope is not the 6.6 that would have hit the brief's 900px in the open
    column at 1920: the rail has to outrun the font, and the font is what keeps the seven closed
    names readable.

    126 is the ceiling because past it the rail is buying whitespace, and 126 is also what
    lands the open column at 873px at 1920. The brief asks for 900–1050 there. It is not
    reachable with eight stages: 900 plus seven columns at the brief's own 120px minimum plus
    seven seams is 1775px of content inside a 1790px row, and that is before the closed columns
    are wide enough for `Development`. 873 is what the arithmetic allows.

    The open column then takes the row less seven rails and less the seven seams, which sums to
    100% exactly — this row must not be able to want more width than it has, or the whole strip
    reflows mid-animation.

    Any stage name added later has to be checked against that 88px at 15px, not against this
    comment.

    The 7 in that calculation is the number of *closed* columns and is the one place in this
    file that knows the strip has eight. A ninth stage means an 8 here — and a fresh look at
    whether the open column has anything left to give.

    `min-width: 0` is load-bearing and invisible. A flex item's automatic minimum size is its
    min-content width, and a collapsed column contains a full-width screenshot — set to
    `display: none`, but the moment it is not, the column refuses to go below the widest
    unbreakable thing inside it and the seven closed columns silently take room from the open
    one.

    640ms on the width, on the brief's curve. The whole of `cubic-bezier(0.22, 1, 0.36, 1)`
    happens in the first third of that: it is a decelerating ease that arrives, which is what
    stops a 400px expansion reading as a drawer sliding. Not a spring — the brief rules out
    bouncy easing, and a screenshot that overshoots its column and settles back reads as a
    panel that was thrown rather than opened.

    The 180ms of delay is the screenshot fading out. Both directions carry it — the two
    columns have to move together or the row overflows for the length of the animation — so
    the sequence is: screenshot goes, then the widths travel, then the new screenshot arrives.

    The full `transition` is restated rather than added to, because base.css sets a two-item
    list here for the reveal and a later `transition` shorthand replaces the list rather than
    extending it. Losing the reveal's fade is a subtle bug; losing the width is a loud one.
  */
  html[data-js='true'] .process-step {
    width: var(--process-rail);
    min-width: 0;
    overflow: hidden;
    padding: 0;
    transition:
      opacity var(--dur-slow) var(--ease),
      transform var(--dur-slow) var(--ease),
      width 640ms cubic-bezier(0.22, 1, 0.36, 1) 180ms;
  }

  html[data-js='true'] .process-step[data-state='active'] {
    width: calc(100% - 7 * (var(--process-rail) + 5px));
  }

  /*
    And the same trap in reverse: the reveal's stagger is set as a bare `transition-delay`
    on a more specific selector, so it overrides all three delays in the shorthand above and
    would make step 08 wait 385ms before it starts opening. Three values, in the order the
    shorthand lists them.
  */
  html[data-js='true'] .process-step[data-reveal] {
    transition-delay: calc(var(--i, 0) * 55ms), calc(var(--i, 0) * 55ms), 0s;
  }

  /*
    A closed column is a control and says so with the cursor and with two small shifts on
    hover: the panel lifts off the section's grey by a third of the step between them, and
    the name goes from muted charcoal to the full ink. The track is not touched. A progress
    bar that fills under the pointer is claiming the stage advanced because it was hovered.
  */
  html[data-js='true'] .process-step:not([data-state='active']) {
    cursor: pointer;
  }

  /* Including over the number and the name, which is where the pointer actually goes: a
     `<button>` carries `cursor: default` of its own, so the one part of the column that looks
     most like a control was the one part not showing that it is one. */
  html[data-js='true'] .process-step:not([data-state='active']) .process-tab {
    cursor: inherit;
  }

  html[data-js='true'] .process-step:not([data-state='active']):hover {
    background: color-mix(in srgb, var(--bg-raised) 62%, var(--bg));
  }

  /* ── The bar ──────────────────────────────────────────────────────────── */

  /*
    2px, flush to the top of the panel and the full width of it. Not inset to the header's
    padding: inset, eight segments read as eight underlines belonging to eight numbers, and the
    thing they have to read as is one bar across the component that happens to be divided
    where the columns are.

    The unfilled segment is `--line`, which in this section is #D8D8D4 — see the note on
    `.process` for why that is now one number and not two. It sits between the panel's
    #FAFAF8 and the section's #ECECEA: dark enough that an unfilled segment is visibly a
    segment, light enough that eight of them do not draw a line across the section on their
    own. The eight of them are the future of the process, and grey is what a stage that has
    not happened yet looks like.
  */
  html[data-js='true'] .process-track {
    display: block;
    height: 2px;
    background: var(--line);
  }

  /*
    The fill, and there is no JavaScript in it. A CSS animation on the active step's fill
    means the bar is driven by the same clock the browser uses for everything else: it
    pauses with the tab, it cannot drift from a `setTimeout`, and its end event is what
    advances the section — so what the reader sees reaching 100% *is* what triggers the next
    stage rather than a timer that hopes to agree with it.

    `scaleX` and not `width`, so the whole 6.5 seconds is composited off the main thread.
    The origin is the left edge because the bar fills in reading order.

    860ms of delay is the transition above it — 180 of fade plus 640 of travel. The bar must
    not move while the column is still opening, and this is where that is enforced: the
    stage's clock starts when the stage has finished arriving.

    Gated on `[data-run='true']`, which js/process.js writes while the section is on screen
    and removes when it is not. A component that advances through all eight stages twice
    before the reader has scrolled to it has spent its whole argument on an empty viewport.

    6.5 seconds each, so a full pass is now 52 seconds rather than 39. Nobody watches a lap;
    what matters is that a stage stays long enough to be read, and a screenshot needs more of
    that than a paragraph does, not less.
  */
  html[data-js='true'] .process-fill {
    display: block;
    height: 100%;
    background: var(--proc-state);
    transform: scaleX(0);
    transform-origin: left center;
  }

  /*
    A finished stage keeps its segment filled, and keeps it the same blue. The bar is one
    reading left to right — solid blue behind what has happened, blue arriving across the
    stage happening now, grey ahead — so a completed segment in a second colour would turn
    a progress bar into a legend.
  */
  html[data-js='true'] .process-step[data-state='done'] .process-fill {
    transform: scaleX(1);
  }

  html[data-js='true'] .process-strip[data-run='true'] .process-step[data-state='active'] .process-fill {
    animation: process-fill 6500ms linear 860ms forwards;
  }

  /*
    Held while one of the eight buttons has focus — somebody reading a column on a keyboard
    should not have it collapse under them. Paused rather than cancelled, so the bar keeps the
    position it had and picks it up again on blur.

    Focus and not hover. The pointer rests over the middle of the page while a reader reads, so
    a strip that pauses on hover is a strip that has stopped, which is the exact mistake the
    category rail was corrected for.

    The selector repeats the whole chain above it and adds `[data-hold]` deliberately, and this
    is load-bearing: `animation` is a shorthand, so the rule that starts the bar is also setting
    `animation-play-state: running`. A shorter selector here loses to it on specificity and the
    pause silently does nothing — which is what it did, until a keyboard test caught the bar
    running on through a focused column.
  */
  html[data-js='true']
    .process-strip[data-run='true'][data-hold='true']
    .process-step[data-state='active']
    .process-fill {
    animation-play-state: paused;
  }

  /* ── The header zone ──────────────────────────────────────────────────── */

  /*
    170px in all eight columns, and the hairline under it lands at the same height across the
    section — which is the one thing carried over intact from the layout this replaced. Above
    the line is navigation, below it is content, and the line is what says the eight columns
    are one component rather than one panel and seven labels.

    Fixed rather than automatic because the eight headers hold different amounts: one line of
    name and two of subtext in the open column, up to three lines of name in the closed ones.
    Left to itself the tallest would set the height for all eight and the spine would move
    every time the selection did. The brief asks for `min-height: 170px`, and a minimum is the
    one thing that cannot do this job — eight columns each free to exceed it is eight hairlines
    at eight heights. The number is the brief's; the property is not.

    It is also what makes the open column read as two zones rather than one column of stuff.
    A fixed header with a rule under it and a picture beneath the rule is a title and an
    exhibit; the same two things with an automatic gap between them is a caption that has
    drifted. Now that the picture goes to the panel's edges, this line is the only frame the
    screenshot has — so it is the whole boundary between the page and the software.

    The height grows as the viewport shrinks, which looks backwards written down and is the
    only formula that works. What the header has to hold is 28 of padding, the number row, one
    line of name at the open size and however many lines the subtext takes — and the subtext
    wraps *more* as the column gets narrower. The longest of the eight is stage 02's at 95
    characters: one line where the open column is over 700px, two below that, three under about
    340. So: 170 from 1440 up, rising on a straight line to 176 at the 1240 gate, where the
    open column is 399px and the third line has to be somewhere. The alternative was cutting
    the sentences down until they fitted the narrowest column, which is letting a 1240px laptop
    write the copy — and these eight sentences are the brief's, to the word.

    170 also clears the worst closed column: three lines of `Factory Matching & Compliance`
    plus the number row come to 113.

    28px over. 28 at the sides of the open column, which is now the one inset in the whole
    panel — the screenshot below has none, so the stage name is indented and the software is
    not. That is deliberate: it is what makes the image read as the panel's floor rather than
    as a figure placed inside a margin. 10 at the sides of a closed one, down from 14, and the
    four pixels went to the names, which need every one of the 88 the rail leaves them.
  */
  html[data-js='true'] .process-head {
    display: block;
    height: clamp(170px, 213px - 3vw, 176px);
    padding: 28px 10px 0;
    border-bottom: 1px solid var(--line);
  }

  html[data-js='true'] .process-step[data-state='active'] .process-head {
    padding: 28px 28px 0;
  }

  html[data-js='true'] .process-title {
    margin: 0;
  }

  /*
    The rule under the number goes — there is one under the whole header now, and two
    hairlines 40px apart in a column this narrow is a form field.

    One number is blue: the one the section is currently at. Completed stages take the full
    ink and the ones ahead stay muted, so the row of eight numbers is itself a progress
    reading — read left to right it goes dark, blue, grey. Only the number, never the
    title: a blue stage name is a heading shouting, and the same information is already
    in the segment directly above it.

    One, and only one. Eight blue numerals would say the process is at eight places, which
    is the failure of every progress indicator that colours its whole scale.
  */
  html[data-js='true'] .process-num {
    margin-bottom: 8px;
    padding-bottom: 0;
    border-bottom: 0;
  }

  html[data-js='true'] .process-step[data-state='active'] .process-num {
    color: var(--proc-state);
  }

  html[data-js='true'] .process-step[data-state='done'] .process-num {
    color: var(--fg);
  }

  /*
    The open column's name is the section's local headline and is sized like one; the seven
    closed ones are navigation and are sized like that. No floor on either — the header's
    own height is the floor now.

    `clamp(13px, 0.9vw, 14px)` at the closed size, down from `clamp(15px, 1.05vw, 16px)` when
    there were six. The whole of that reduction is `Development`, which arrived with stage 02
    and is 100px wide at 16px. Eight stages mean a narrower rail, a narrower rail means a
    smaller ceiling, and the closed names are navigation: they are not the size a reader is
    being asked to notice. 14px is still a label somebody reads at a glance, which is the floor
    under the floor — a rail that fitted the word by setting it at 11px would have solved the
    wrong problem. See the rail's own note for why these three numbers and not others; the two
    clamps are one calculation.

    And no fallback break. It used to carry `overflow-wrap: break-word` as a last resort, on
    the argument that an ugly wrap beats a column silently widening. It does not: `Productio`
    over `n` is the single worst thing this section has ever rendered, and it happened, at 1440,
    in the build before this one. `normal` on all three properties means a name that outgrows
    its rail overflows into the panel's `overflow: hidden` instead — which is a bug that shows
    up in the width sweep rather than one that ships looking intentional. The rail floor is what
    guarantees it does not happen; a wrap rule is not a substitute for the right width.

    The size is transitioned. It is the one property in here that costs a reflow per frame,
    and it is worth it: without it the name jumps from 13px to 25px at the instant the click
    lands, 300ms before the column that holds it has finished arriving.
  */
  /*
    Graphite, stated, rather than 82% of the ink mixed into the panel.

    The mix computed to #343434 and the stage names are content — the one thing in a closed
    column a reader is actually meant to read. #212426 is the page's graphite and is what
    content in a secondary position wears everywhere else on the site, so a closed name now
    matches a closed name in the configurator below instead of landing on a value that only
    existed because it was a percentage of something.

    It does not blur the open column, because size is what separates the two: 13px against
    21–26px, a ratio of nearly two. Colour was never doing that work — the number above and
    the segment above that are what mark the current stage, and the open column is the one
    with a screenshot in it.
  */
  html[data-js='true'] .process-name {
    min-height: 0;
    font-size: clamp(13px, 0.9vw, 14px);
    line-height: 1.25;
    color: #212426;
    word-break: normal;
    overflow-wrap: normal;
    hyphens: none;
    transition: font-size 320ms var(--ease);
  }

  html[data-js='true'] .process-step:not([data-state='active']):hover .process-name {
    color: var(--fg);
  }

  html[data-js='true'] .process-step[data-state='active'] .process-name {
    font-size: clamp(21px, 1.7vw, 26px);
    line-height: 1.1;
    color: var(--fg);
  }

  /*
    The subtext, in the open column only. `display` is transitioned discretely so it can fade
    with the screenshot rather than vanishing a third of a second before it — see the note on
    the panel below, which does the same thing on the way in.

    A closed column has no body copy at all, and that is the collapsed state's whole
    discipline: a number, a name, a glyph and a great deal of nothing. Eight sentences of
    supporting copy in seven 110px columns is the wall of text this section was built to stop
    being.
  */
  html[data-js='true'] .process-copy {
    display: none;
    margin: 10px 0 0;
    opacity: 0;
    transition:
      opacity 180ms var(--ease),
      display 180ms allow-discrete;
  }

  /*
    No measure cap on it, which is a reversal. It was `34ch`, on the ordinary rule that a
    line of body copy past about 70 characters is harder to read than a shorter one — a rule
    written for paragraphs. This is one sentence in a fixed 170px header, and the thing that
    matters about it is whether it fits in the space measured for it: capped at 34ch these
    sentences run to four lines in a 700px column and overflow the header.
  */
  html[data-js='true'] .process-step[data-state='active'] .process-copy {
    display: block;
    max-width: none;
    opacity: 1;
    transition:
      opacity 300ms var(--ease) 620ms,
      display 300ms allow-discrete 620ms;
  }

  /* ── The screenshot zone ──────────────────────────────────────────────── */

  /*
    `padding: 0`, and this one declaration is half of the brief.

    It was `22px 28px 24px`, with the image inside a 1px frame — and every argument for that
    inset was sound in isolation. The panel matched the header's side padding, the top gap
    stopped the picture hanging off the hairline, the bottom was optically weighted. What all
    of it added up to was a screenshot *mounted* in a panel: 28px of near-white mat, a hairline
    around the edge, and the reader looking at a framed picture of an interface on a website.

    At zero the same image is the panel. It runs from the hairline under the header to the two
    bottom corners of the column, the column's own 3px radius and `overflow: hidden` cut it,
    and there is nothing between the page and the software but that one line. The reading the
    brief asked for — the accordion opens directly into the operating system — is entirely a
    function of this number being 0 and not 22.

    Which is why there is no frame, no shadow, no browser chrome, no laptop mock-up and no
    larger radius. Each of those is a way of saying *this is a picture of a screen*, and the
    point is to stop saying it.

    `display: none` when closed rather than `opacity: 0`, and this is not a nicety. Seven
    invisible full-bleed screenshots still laid out in seven 110px columns is seven copies of a
    layout nobody can see, recalculated on every resize, and each of them would push its
    column's glyph down the page by its own height.

    Which normally means it cannot fade, because a transition cannot start on a box that has
    just stopped existing. `transition-behavior: allow-discrete` is what makes both true at
    once: the box appears at the start of the incoming transition and disappears at the end
    of the outgoing one, and the opacity animates in between. In a browser that ignores the
    keyword the fades are simply instant and the accordion still works — which is the right
    failure for a decoration.

    620ms of delay on the way in: the screenshot arrives after the column has finished
    expanding, not during, so nothing is being read at a width it is not laid out for.
  */
  html[data-js='true'] .process-panel {
    display: none;
    padding: 0;
    opacity: 0;
    transition:
      opacity 180ms var(--ease),
      display 180ms allow-discrete;
  }

  html[data-js='true'] .process-step[data-state='active'] .process-panel {
    display: block;
    opacity: 1;
    transition:
      opacity 340ms var(--ease) 620ms,
      display 340ms allow-discrete 620ms;
  }

  /* ── The glyph ────────────────────────────────────────────────────────── */

  /*
    What a closed column shows instead of an interface: one 52px mark in a great deal of
    space, centred in everything the header left over.

    The 48px SVG this replaced sat in the same place and was centred the same way, so the
    marks land where the drawings did — `align-items: center` centres whatever height the child
    is, and a 64px child and a 48px child centred in one region share a centre. The
    `padding-bottom: 48px` is what holds them off the panel's lower edge and is unchanged.

    Muted on purpose, and there is no accent in it. In this section blue means one thing —
    where the process currently is, on the bar and on one number — so a quiet mark under a
    graphite name is the closed state saying accurately that it is not the thing being looked
    at. A blue icon in a closed column would be eight current stages. It darkens slightly on
    hover, which is the whole of its interactivity.

    The marks themselves are supplied artwork and stay exactly as supplied: no `filter`, no
    `mask`, no `currentColor`, nothing that could tint one. Opacity is the only control
    touched, for the reason set out on `.process-mark` below.

    The files and the reasoning behind each of them are in build.mjs and icons.py.
  */
  html[data-js='true'] .process-glyph {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    padding-bottom: 48px;
    opacity: 0;
    transition:
      opacity 300ms var(--ease) 620ms,
      display 300ms allow-discrete 620ms;
  }

  html[data-js='true'] .process-step:not([data-state='active']) .process-glyph {
    opacity: 1;
  }

  html[data-js='true'] .process-step[data-state='active'] .process-glyph {
    display: none;
    opacity: 0;
    transition:
      opacity 180ms var(--ease),
      display 180ms allow-discrete;
  }

  /*
    ── Muted with opacity, and only with opacity ─────────────────────────────

    The marks are near-black artwork (a measured median ink of #080808) on a near-white column,
    and black at full strength in a collapsed column would out-weigh the stage name above it.
    0.42 over `--bg-raised` #fafaf8 composites to #949493, which is 2.91:1 against the panel.

    Slightly darker than the set it replaces, and worth knowing rather than smoothing over: the
    drawn glyphs were stroked #a5a59f at 2.37:1, and matching that exactly would be an opacity
    of 0.353. The supplied artwork also carries a visibly heavier stroke than a 1.6-unit SVG
    line, so at 0.42 these read as more present in the column than the drawings did — which is
    defensible, since the mark is the only object in a collapsed column that is not text, but it
    is a change in weight and not only in artwork. 0.42 is the briefed value and is what ships;
    the number to move if it wants to sit further back is this one, and it is one number.

    For scale: the stage name and body are `--paper-sub` #686864 at 5.35:1, so the mark stays
    the quietest thing in the column at either opacity.

    Opacity, and nothing else, is the whole adjustment. `filter`, `mask`, `mix-blend-mode` and
    a recoloured copy of the file were all available and all rejected: opacity is the one
    control that cannot change a hue, and the artwork was supplied to be used rather than
    processed. The old set was an inline SVG and therefore colourable by any rule that reached
    it; a PNG cannot be, which removes the failure mode instead of merely avoiding it.

    Two opacities multiply here and they are deliberately separate. `.process-glyph`'s is the
    reveal — 0 to 1 as the section scrolls in, on the accordion's own 620ms handover — and
    `.process-mark`'s is how dark the ink is. Putting the ink value on the wrapper would have
    made one property mean two things, and the fade-in would then be a fade from nothing to
    'collapsed', which is correct today and wrong the first time either number changes.

    0.75 on hover, not on the active step. An expanded column hides its mark entirely — that
    is the existing behaviour and this change does not touch it, so an `[data-state='active']`
    opacity would style something with `display: none`. Hover on a collapsed column is where
    the briefed darker value has a surface to land on, and it is the interaction the drawn
    glyphs already had.

    It is a larger step than the one it replaces. 0.75 composites to #444444 at 9.32:1, where
    the drawn glyphs went #a5a59f to #8b8b85 — 2.37:1 to 3.28:1. So the hover is now a real
    darkening rather than a hint of one. Both numbers are the brief's; if the hover should stay
    as quiet as it was, 0.46 is the value that reproduces the old step, at #8b8b8a and 3.26:1.
  */
  html[data-js='true'] .process-mark {
    opacity: 0.42;
    transition: opacity 200ms var(--ease);
  }

  html[data-js='true'] .process-step:not([data-state='active']):hover .process-mark {
    opacity: 0.75;
  }
}

/*
  0 to 100 in one linear sweep, and linear is the only honest curve for it: an eased
  progress bar is a stage that appears to run out of time faster at one end than the other.
*/
@keyframes process-fill {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}

/* ═══ NARROW ═════════════════════════════════════════════════════════════ */

/*
  Two across, four down, and no accordion — the media query above stops at 1240.

  Two, not the three the six-stage version used here and not the four the base layout keeps
  above 1240, and the reason is the same one that set the accordion's gate: a panel now holds a
  photograph, and a photograph has a width below which it stops carrying information. At 1024
  two columns give each stage a 495px screenshot; three would give 325px and four 240px, which
  is a grey rectangle with an orange dot in it. The column count follows the smallest legible
  size of the image, not the number of stages.

  The cost is a four-row grid, and this section's whole history is about not letting stages
  fall out of sight — but a tablet scrolls, and eight legible stages down a page beat eight
  illegible ones across it. The numbers `01`–`08` carry the order the layout can no longer
  show at a glance.

  The sequence still reads: left to right, then down, which is how the benefits grid above
  already works and how a reader takes a wrapped grid without being told. The spine breaks
  into one line per row, and that is correct — each row is a band.
*/
@media (max-width: 1239px) {
  .process-strip {
    grid-template-columns: repeat(2, 1fr);
  }
}

/*
  One across, eight down, in order. A phone reads a sequence vertically or not at all.

  The panel becomes a row: number, name and clause stacked over a screenshot the full width of
  the phone. Eight of those is a long scroll and the alternative is worse — two columns at 390px
  is a 190px picture of an interface, which is a picture of software rather than software.

  The seams do the separating here as they do at every other width: the 5px of grey between
  two near-white panels is the separator, and a border as well would be the same line drawn
  twice.
*/
@media (max-width: 620px) {
  .process-strip {
    grid-template-columns: 1fr;
  }

  .process-name {
    /* Full measure, one line, no floor to hold. */
    min-height: 0;
    font-size: 17px;
  }
}

/* ═══ REDUCED MOTION ═════════════════════════════════════════════════════ */

/*
  The sweep goes, and so does the advance — js/process.js never starts the clock, so the
  accordion becomes eight columns with one open and seven that open when they are pressed.

  Which is the reason the fill is left with nothing in it under this query. base.css already
  collapses every animation to a single 1ms iteration, so the bar would fill instantly and
  sit there permanently: a stage claiming to be finished the moment it is looked at. An empty
  track on the open column is the truthful state when nothing is timing anything, and the
  blue on the number still says where the reader is.

  The delay on the reveal is set here and would survive base.css's reset, leaving eight panels
  each holding a transition they are not using.
*/
@media (prefers-reduced-motion: reduce) {
  html[data-js='true'] .process-step[data-reveal] {
    transition-delay: 0ms;
  }

  html[data-js='true'] .process-strip[data-run='true'] .process-step[data-state='active'] .process-fill {
    animation: none;
  }
}

/* ═══ PRINT ══════════════════════════════════════════════════════════════ */

/*
  Four columns, no screenshots, no accordion — the media query is width-gated and a printed
  page is narrower than 1240px, so what prints is the base layout with its backgrounds
  dropped. On paper the white space separates the stages and the numbers order them; eight
  grey rectangles print as eight grey rectangles.

  The screenshots go, all eight of them. They are one file printed eight times at 240px on a
  laser printer, which is eight grey smudges, and a page about the software rather than a page
  about the eight stages. Eight names and eight clauses is what this section prints as.
*/
@media print {
  .process-strip {
    grid-template-columns: repeat(4, 1fr);
    gap: 22px 26px;
  }

  .process-shot {
    display: none;
  }

  .process-step {
    padding: 0;
    background: none;
  }

  .process-title,
  .process-copy {
    margin-inline: 0;
  }

  .process-name {
    min-height: 0;
  }
}
