/* =========================================================================
   OPERO365 — Design tokens
   Tokens encode approved brand decisions only. No new brand decisions here.
   ========================================================================= */

/* Inter is the approved product / operational UI typeface (Brand Manual p15).
   Self-hosted from the official distribution. */
@font-face {
  font-family: "InterVariable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/InterVariable.woff2") format("woff2");
}

:root {
  /* --- Approved palette. These five values are the entire colour system. --- */
  --carbon:   #0A0A0A;
  --white:    #FAFAF9;
  --graphite: #1E1E1E;
  --silver:   #BDBDBD;
  --blue:     #2457FF;

  /* --- Typefaces ---
     Brand: Söhne. Licensed; font files not supplied with the brand package.
     The remaining entries are a neutral technical fallback for rendering only,
     NOT a brand substitution. Drop Söhne web files into /assets/fonts and add
     an @font-face named "Söhne" to activate the approved face site-wide. */
  --font-brand: "Söhne", "Soehne", "Söhne Web", "InterVariable", "Inter", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-ui: "InterVariable", "Inter", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* --- Text roles ---
     Silver measures 1.80:1 on White and therefore never carries text on light
     backgrounds. Quiet text on light is Carbon at reduced alpha, which stays
     inside the monochrome system and clears WCAG AA. */
  --text-loud:   var(--carbon);
  --text-body:   rgba(10, 10, 10, 0.86);
  --text-quiet:  rgba(10, 10, 10, 0.62);
  --text-meta:   rgba(10, 10, 10, 0.58);
  --rule:        rgba(10, 10, 10, 0.14);
  --rule-strong: rgba(10, 10, 10, 0.28);
  --control:     rgba(10, 10, 10, 0.46); /* form control borders: 3:1 on White */
  --surface:     var(--white);

  /* --- Type scale --- */
  /* Display is used on the home hero only. Capped so the headline, the lede
     and what Opero365 does all resolve in the first screen rather than the
     headline alone filling it. */
  --step-display: clamp(2.50rem, 1.20rem + 5.00vw, 5.00rem);
  --step-h1:      clamp(2.25rem, 1.30rem + 4.10vw, 4.50rem);
  --step-h2:      clamp(1.75rem, 1.20rem + 2.30vw, 3.00rem);
  --step-h3:      clamp(1.30rem, 1.05rem + 1.05vw, 1.90rem);
  --step-lede:    clamp(1.10rem, 0.98rem + 0.55vw, 1.45rem);
  --step-body:    1.0625rem;
  --step-small:   0.9375rem;
  --step-meta:    0.75rem;

  /* --- Spacing --- */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;
  --s-5: 24px;  --s-6: 32px;  --s-7: 48px;  --s-8: 64px;
  --s-9: 96px;  --s-10: 128px; --s-11: 176px; --s-12: 224px;

  --chapter-y: clamp(88px, 11vw, 176px);
  --container: 1280px;
  --gutter: clamp(20px, 5vw, 80px);
  --measure: 64ch;
  --measure-tight: 46ch;

  /* --- Motion --- */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-fast: 140ms;
  --dur: 320ms;
  --dur-resolve: 900ms;

  --radius: 2px; /* engineered, not bubbly */
}

/* Dark chapters re-map the same role tokens. No new colours are introduced. */
.chapter--dark {
  --text-loud:   var(--white);
  --text-body:   rgba(250, 250, 249, 0.88);
  --text-quiet:  var(--silver);
  --text-meta:   var(--silver);
  --rule:        rgba(250, 250, 249, 0.18);
  --rule-strong: rgba(250, 250, 249, 0.34);
  --surface:     var(--graphite);
}
.chapter--carbon {
  --text-loud:   var(--white);
  --text-body:   rgba(250, 250, 249, 0.88);
  --text-quiet:  var(--silver);
  --text-meta:   var(--silver);
  --rule:        rgba(250, 250, 249, 0.18);
  --rule-strong: rgba(250, 250, 249, 0.34);
  --surface:     var(--carbon);
}

/* =========================================================================
   Base
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--white);
  color: var(--text-body);
  font-family: var(--font-brand);
  font-size: var(--step-body);
  line-height: 1.62;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--text-loud);
  font-weight: 500;
  letter-spacing: -0.022em;
  line-height: 1.08;
  text-wrap: balance;
}

p { margin: 0; }

a { color: inherit; }

img, svg { display: block; max-width: 100%; }

ul, ol { margin: 0; padding: 0; list-style: none; }

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 1px;
}

::selection { background: var(--blue); color: var(--white); }

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -100px;
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--carbon);
  color: var(--white);
  font-family: var(--font-ui);
  font-size: var(--step-small);
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: var(--s-4); }

/* Review builds only (build.py --review). Emitted so a reviewer can never
   mistake a preview URL for the live site. Carbon on White, approved palette,
   no new colour. Absent from production builds. */
.review-banner {
  margin: 0;
  padding: var(--s-3) var(--gutter);
  background: var(--carbon);
  color: var(--white);
  font-family: var(--font-ui);
  font-size: var(--step-small);
  line-height: 1.4;
  text-align: center;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* =========================================================================
   Layout
   ========================================================================= */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.chapter {
  padding-block: var(--chapter-y);
  background: var(--surface);
  color: var(--text-body);
  position: relative;
}
.chapter--dark { background: var(--graphite); }
.chapter--carbon { background: var(--carbon); }
.chapter--tight { padding-block: clamp(64px, 7vw, 104px); }

/* A hairline is the default separator. Containers are used only when
   containment means something. */
.chapter + .chapter:not(.chapter--dark):not(.chapter--carbon)::before {
  content: "";
  position: absolute;
  inset-inline: var(--gutter);
  top: 0;
  height: 1px;
  background: var(--rule);
}

/* Two-column editorial split with no rule and no padding. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s-5) var(--s-8);
  align-items: start;
}

/* =========================================================================
   Typographic volumes: LOUD / CLEAR / QUIET / FUNCTIONAL
   ========================================================================= */

.eyebrow {
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-meta);
  line-height: 1.4;
  margin: 0;
}

.display {
  font-size: var(--step-display);
  letter-spacing: -0.032em;
  line-height: 1.02;
}

.h1 { font-size: var(--step-h1); }
.h2 { font-size: var(--step-h2); letter-spacing: -0.026em; }
.h3 { font-size: var(--step-h3); letter-spacing: -0.018em; line-height: 1.22; }

.lede {
  font-size: var(--step-lede);
  line-height: 1.5;
  color: var(--text-body);
  max-width: var(--measure);
  letter-spacing: -0.008em;
}

.body { max-width: var(--measure); color: var(--text-body); }
.quiet { color: var(--text-quiet); }
.small { font-size: var(--step-small); }
.measure-tight { max-width: var(--measure-tight); }

.stack > * + * { margin-top: var(--s-5); }
.stack-sm > * + * { margin-top: var(--s-3); }
.stack-lg > * + * { margin-top: var(--s-7); }

/* Blue is meaning, never decoration: active, selected, important, progress. */
.active-mark { color: var(--blue); }

/* =========================================================================
   Header / navigation — quiet until needed
   ========================================================================= */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--white);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease);
}
.site-header[data-scrolled="true"] { border-bottom-color: var(--rule); }

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  min-height: 92px;
}

.brand-lockup { display: inline-flex; align-items: center; text-decoration: none; }
/* The logo is 1800x480 (3.75:1), so height drives the lockup. It has to hold
   its own against the nav rather than sit in the corner as a small mark. */
.brand-lockup img { height: clamp(30px, 3.1vw, 42px); width: auto; }

.nav { display: flex; align-items: center; gap: clamp(18px, 2.2vw, 36px); }

.nav__link {
  font-family: var(--font-ui);
  font-size: var(--step-small);
  font-weight: 450;
  letter-spacing: -0.004em;
  color: var(--text-quiet);
  text-decoration: none;
  padding-block: var(--s-2);
  position: relative;
  transition: color var(--dur-fast) var(--ease);
}
.nav__link:hover { color: var(--text-loud); }

/* Current page is a state, so it earns blue. */
.nav__link[aria-current="page"] { color: var(--text-loud); }
.nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--blue);
}

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--s-2);
  background: none;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  padding: 10px 14px;
  font-family: var(--font-ui);
  font-size: var(--step-small);
  color: var(--text-loud);
  cursor: pointer;
}

/* =========================================================================
   Buttons — precise, limited, not pill-shaped
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 14px 26px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-ui);
  font-size: var(--step-small);
  font-weight: 500;
  letter-spacing: -0.002em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.btn--primary { background: var(--carbon); color: var(--white); }
.btn--primary:hover { background: var(--blue); }

.chapter--dark .btn--primary,
.chapter--carbon .btn--primary { background: var(--white); color: var(--carbon); }
.chapter--dark .btn--primary:hover,
.chapter--carbon .btn--primary:hover { background: var(--blue); color: var(--white); }

.btn--secondary {
  background: transparent;
  color: var(--text-loud);
  border-color: var(--rule-strong);
}
.btn--secondary:hover { border-color: var(--text-loud); }

/* Restrained text link — no decorative arrows. */
.link {
  font-family: var(--font-ui);
  font-size: var(--step-small);
  font-weight: 500;
  color: var(--text-loud);
  text-decoration: none;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--rule-strong);
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.link:hover { color: var(--blue); border-bottom-color: var(--blue); }

.actions { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4); align-items: center; }

/* =========================================================================
   O visual system
   Every instance is the unmodified approved SVG placed via <img>.
   Only scale, position and opacity vary. Geometry and colour are never altered.
   ========================================================================= */

/* The O is never distorted. Each <img> carries intrinsic width/height
   attributes (480x480); if CSS sets only a width, the 480px height attribute
   still applies and the mark renders as a stretched ellipse. Every O rule
   below sets a width, so height must be released here for all of them. */
.o-field img,
.o-flow img,
.o-reduction img,
.o-mark { height: auto; }

.o-mark { width: 100%; }

/* Giant O — the principal brand moment. Shown whole, never cropped. */
.o-giant { width: min(100%, 460px); margin-inline: auto; }

/* O Field — many operations, one needing attention. */
.o-field {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(12px, 1.6vw, 22px);
  max-width: 520px;
}
.o-field img { opacity: 0.10; }
.o-field img.is-active { opacity: 1; }

/* O Flow — sequential operational progression. No connectors are drawn. */
.o-flow {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
  flex-wrap: wrap;
}
.o-flow img { width: clamp(34px, 5vw, 56px); }
.o-flow img:nth-child(1) { opacity: 0.22; }
.o-flow img:nth-child(2) { opacity: 0.45; }
.o-flow img:nth-child(3) { opacity: 0.70; }
.o-flow img:nth-child(4) { opacity: 1; }


/* O Reduction — complexity resolving into a simpler state. */
.o-reduction { display: flex; align-items: center; gap: clamp(20px, 4vw, 56px); flex-wrap: wrap; }
.o-reduction__many { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; width: clamp(120px, 18vw, 190px); }
.o-reduction__many img { opacity: 0.16; }
.o-reduction__one { width: clamp(74px, 11vw, 124px); }

/* =========================================================================
   Approved motion — Scatter, Align, Connect, Resolve.
   Used once per page at most, to explain complexity resolving into order.
   No rotation is applied: the active segment position must never change.
   ========================================================================= */

[data-resolve] > * {
  opacity: 0;
  transform: translate(var(--dx, 0), var(--dy, 0));
  transition: opacity var(--dur-resolve) var(--ease),
              transform var(--dur-resolve) var(--ease);
  transition-delay: var(--delay, 0ms);
}
[data-resolve].is-resolved > * {
  opacity: 1;
  transform: translate(0, 0);
}
/* The field keeps its quiet/active opacity relationship after resolving. */
[data-resolve].is-resolved .o-field-inner img { opacity: 0.10; }
[data-resolve].is-resolved .o-field-inner img.is-active { opacity: 1; }

/* =========================================================================
   Editorial components
   ========================================================================= */

/* Numbered operational sequence — type-led, separated by hairlines. */
.sequence { border-top: 1px solid var(--rule); }
.sequence__item {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: var(--s-5) var(--s-6);
  padding-block: var(--s-6);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.sequence__num {
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--text-meta);
  padding-top: 0.55em;
  font-variant-numeric: tabular-nums;
}
.sequence__item:hover .sequence__num { color: var(--blue); }
.sequence__body > * + * { margin-top: var(--s-3); }

/* Two-column editorial rows separated by rules, not cards. */
.rows { border-top: 1px solid var(--rule); }
.row {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s-4) var(--s-8);
  padding-block: var(--s-7);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.row__label { display: grid; gap: var(--s-3); }
.row__body > * + * { margin-top: var(--s-3); }

/* Plain list of operational friction points. */
.list-rule { border-top: 1px solid var(--rule); }
.list-rule li {
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule);
  color: var(--text-body);
  max-width: var(--measure);
}

/* Compact bullets. Used inside a row where a full-width ruled list would be
   too heavy. Markers inherit the monochrome text roles: blue stays reserved
   for meaning and is never used as list decoration. */
.bullets {
  margin: var(--s-4) 0 0;
  padding-left: 1.15em;
  display: grid;
  gap: var(--s-2);
  max-width: var(--measure);
}
.bullets li { color: var(--text-quiet); }
.bullets li::marker { color: var(--text-meta); }

/* Outcomes: label + explanation, no containers. */
.outcomes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7) var(--s-8); }
.outcome > * + * { margin-top: var(--s-2); }

/* =========================================================================
   Operational diagrams

   Built from rules, nodes and words — no icon set, no invented geometry, no
   fictional product screens. Every line here carries a named stage or a named,
   approved operational problem; remove one and the diagram says something
   different. They inherit the chapter's light/dark role tokens, so the same
   markup works on Carbon, Graphite and White without a second version.
   ========================================================================= */

.diagram__caption { margin-bottom: var(--s-6); }

/* --- Staged flow ---------------------------------------------------------
   The rule is the operation. The nodes are the stages. The axis is the
   distance from the top of a stage down to the line that runs through it, so
   the number sits above the rule and everything else below it. */

.flow {
  --flow-axis: 22px;
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--flow-cols, 4), minmax(0, 1fr));
  gap: var(--s-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* One continuous rule through every stage. */
.flow::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: var(--flow-axis);
  height: 1px;
  background: var(--rule-strong);
}

.flow__stage {
  position: relative;
  padding-top: calc(var(--flow-axis) + var(--s-4));
  padding-right: var(--s-4);
}

/* The node. Hollow by default: a stage that is simply passed through. */
.flow__stage::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--flow-axis);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  transform: translateY(-50%);
}

.flow__num {
  position: absolute;
  top: 0;
  left: 0;
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--text-meta);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* Deliberately smaller than a heading: a stage label has to sit in a narrow
   column beside five others without wrapping into a paragraph. */
.flow__label {
  display: block;
  font-family: var(--font-brand);
  font-size: clamp(1.0625rem, 0.85rem + 0.55vw, 1.25rem);
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--text-loud);
}

.flow__note {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--step-small);
  color: var(--text-quiet);
}

/* Friction: a named problem that happens AT this stage. The node fills in and
   the rule is cut just before it, because the operation stops being
   continuous here. That break is the entire point of the diagram. */
.flow__stage--friction::before {
  background: var(--text-loud);
  border-color: var(--text-loud);
}
/* The break is drawn only while the flow is horizontal. Once it rotates to
   vertical the stages are already separated by their own stacking, and a gap
   in a vertical rule reads as a rendering fault rather than as meaning. The
   filled node and the named friction line carry it there instead. */
@media (min-width: 1151px) {
  .flow__stage--friction::after {
    content: "";
    position: absolute;
    top: var(--flow-axis);
    left: calc(-1 * var(--s-5));
    width: var(--s-5);
    height: 3px;
    background: var(--surface);
    transform: translateY(-50%);
  }
}
.flow__friction {
  display: block;
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  letter-spacing: 0.04em;
  color: var(--text-meta);
}

/* Active: where Opero365 is working. Blue is the state, not the decoration. */
.flow__stage--active::before {
  background: var(--blue);
  border-color: var(--blue);
}
.flow__stage--active .flow__num { color: var(--blue); }

/* --- Compact stage strip -------------------------------------------------
   flow() reduced to what fits inside a page row: labels on one rule, no
   numbers, no notes, no friction. Holds its horizontal form much further down
   than .flow does, because four short labels stay readable in a narrow column
   long after six stages with notes have stopped being readable. */

.strip {
  --strip-axis: 5px;
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--strip-cols, 4), minmax(0, 1fr));
  gap: var(--s-4);
  margin: var(--s-5) 0 0;
  padding: 0;
  list-style: none;
}
.strip::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: var(--strip-axis);
  height: 1px;
  background: var(--rule-strong);
}
.strip__stage {
  position: relative;
  padding-top: calc(var(--strip-axis) + var(--s-4));
  padding-right: var(--s-3);
  font-size: var(--step-small);
  line-height: 1.35;
  color: var(--text-body);
}
.strip__stage::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--strip-axis);
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  transform: translateY(-50%);
}

/* --- The solutions index -------------------------------------------------
   Eight areas, each with a large numeral, what it does, and then either its
   stages or what it covers. The numeral carries the count and the position, so
   it is information rather than ornament — which is why it is allowed to be
   the largest thing in the row. */

.solutions-index { border-top: 1px solid var(--rule); }
.solution {
  display: grid;
  grid-template-columns: minmax(0, 108px) minmax(0, 1fr);
  gap: var(--s-4) var(--s-7);
  padding-block: var(--s-8);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.solution__num {
  font-family: var(--font-brand);
  font-size: clamp(2.25rem, 1.5rem + 2.8vw, 3.5rem);
  line-height: 0.85;
  letter-spacing: -0.045em;
  color: var(--text-meta);
  font-variant-numeric: tabular-nums;
}
.solution__body > * + * { margin-top: var(--s-3); }
.solution__body .body { max-width: var(--measure); }

/* What a solution covers, read as a specification rather than as prose. */
.covers {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--s-7);
  border-top: 1px solid var(--rule);
}
.covers li {
  padding-block: var(--s-3);
  border-bottom: 1px solid var(--rule);
  font-size: var(--step-small);
  color: var(--text-body);
}

/* --- Two terms, and which way the adapting runs ---------------------------
   The same two terms on both rows, with only their positions swapped. The
   relation sits on the rule between them, so the direction of adaptation is
   the one thing the eye reads. Only the Opero row carries blue. */

.reversal { border-top: 1px solid var(--rule); }
.reversal__row {
  padding-block: var(--s-6);
  border-bottom: 1px solid var(--rule);
}
.reversal__line {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s-4);
  margin: var(--s-4) 0 0;
}
/* One continuous rule from term to term, the same device .flow uses. The
   terms and the relation sit on it in the surface colour, so the rule reads
   as running between them rather than stopping at each label. */
.reversal__line::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: 1px;
  background: var(--rule-strong);
}
.reversal__row--default .reversal__line::before { background: var(--blue); }
.reversal__term {
  position: relative;
  justify-self: start;
  background: var(--surface);
  padding-right: var(--s-4);
  font-size: clamp(1.0625rem, 0.85rem + 0.55vw, 1.375rem);
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--text-loud);
}
.reversal__term:last-child {
  justify-self: end;
  padding-right: 0;
  padding-left: var(--s-4);
}
.reversal__relation {
  position: relative;
  background: var(--surface);
  font-size: var(--step-small);
  color: var(--text-meta);
  padding-inline: var(--s-4);
  white-space: nowrap;
}
.reversal__row--default .eyebrow { color: var(--blue); }

/* --- Fragmented state resolving into one flow ----------------------------
   Left: each item on a rule that starts and stops somewhere different, and
   text indented to match its own rule — the process does not line up, so the
   diagram does not line up. The offsets are fixed, not random: they are the
   content. Right: the same items on one flush, continuous ruled stack. */

.shift {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-7) var(--s-8);
  align-items: start;
}
.shift__set {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
}
.shift__set li {
  position: relative;
  padding-block: var(--s-3);
  color: var(--text-body);
}

.shift__side--broken .shift__set li { padding-left: var(--offset, 0); }
.shift__side--broken .shift__set li::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: var(--offset, 0);
  right: var(--short, 0);
  height: 1px;
  background: var(--rule);
}
.shift__side--broken .shift__set li:nth-child(1) { --offset: 0;   --short: 38%; }
.shift__side--broken .shift__set li:nth-child(2) { --offset: 13%; --short: 22%; }
.shift__side--broken .shift__set li:nth-child(3) { --offset: 5%;  --short: 47%; }
.shift__side--broken .shift__set li:nth-child(4) { --offset: 21%; --short: 9%;  }
.shift__side--broken .shift__set li:nth-child(5) { --offset: 9%;  --short: 33%; }
.shift__side--broken .shift__set li:nth-child(6) { --offset: 17%; --short: 29%; }

.shift__side--aligned .shift__set { border-top: 1px solid var(--rule-strong); }
.shift__side--aligned .shift__set li {
  border-bottom: 1px solid var(--rule-strong);
  color: var(--text-loud);
}

.shift__result {
  margin: var(--s-6) 0 0;
  font-family: var(--font-brand);
  font-size: var(--step-h3);
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--text-meta);
  max-width: var(--measure-tight);
}
.shift__side--aligned .shift__result { color: var(--text-loud); }
/* The one mark of blue: the resolved state is the point being made. */
.shift__side--aligned .shift__result::before {
  content: "";
  display: block;
  width: 32px;
  height: 2px;
  background: var(--blue);
  margin-bottom: var(--s-4);
}

/* --- The decision point --------------------------------------------------
   The two routes are drawn as alternatives at the same stage of the same
   sequence, and only the recommended one carries blue. Custom is not the
   default, and the colour is what says so. */

.branch {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5) var(--s-6);
  margin-top: var(--s-5);
}
.branch__option {
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
.branch__option > * + * { margin-top: var(--s-2); }
/* The branches are alternatives WITHIN step 05, not steps in their own right,
   so they are set below the stage title rather than level with it. */
.branch__option .h3 {
  font-size: clamp(1.0625rem, 0.85rem + 0.55vw, 1.25rem);
  line-height: 1.25;
}
.branch__option--default {
  border-top: 2px solid var(--blue);
  padding-top: calc(var(--s-4) - 1px);
}
.branch__option--default .eyebrow { color: var(--blue); }
.sequence__item--fork > .sequence__num { color: var(--blue); }
/* On Graphite, blue text is 3.1:1 and fails AA. The rule keeps the meaning;
   the words go back to the loud text role. */
.chapter--dark .branch__option--default .eyebrow,
.chapter--dark .sequence__item--fork > .sequence__num { color: var(--text-loud); }

/* --- Two routes, one of them the default --------------------------------- */

.compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-6) var(--s-8);
  align-items: start;
}
.compare__side {
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}
.compare__side > * + * { margin-top: var(--s-3); }
.compare__side--default {
  border-top: 2px solid var(--blue);
  padding-top: calc(var(--s-5) - 1px);
}
.compare__side--default .eyebrow { color: var(--blue); }
.chapter--dark .compare__side--default .eyebrow { color: var(--text-loud); }

/* --- O Window ------------------------------------------------------------
   Photography revealed through the approved O geometry, applied as a CSS
   mask. The SVG is referenced, never edited, redrawn or reconstructed.
   Currently unused: no photography has been supplied. See HANDOVER.md. */

.o-window { margin: 0; }
.o-window img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  -webkit-mask: url("/assets/brand/Opero365_Standalone_O.svg") center / contain no-repeat;
  mask: url("/assets/brand/Opero365_Standalone_O.svg") center / contain no-repeat;
}

/* =========================================================================
   Hero
   ========================================================================= */

.hero { padding-block: clamp(64px, 9vw, 136px) clamp(72px, 10vw, 152px); }
.hero__grid { display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: var(--s-8); align-items: center; }
.hero__copy > * + * { margin-top: var(--s-6); }
.hero__headline { max-width: 16ch; }

/* What Opero365 does, stated in the first screen. Hairlines and type only:
   no cards, no borders on three sides, no fills. */
.hero__what {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-6);
  border-top: 1px solid var(--rule);
  padding-top: var(--s-5);
}
.hero__what li { display: block; }
.hero__what-label {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-loud);
  line-height: 1.4;
}
.hero__what-body {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--step-small);
  line-height: 1.5;
  color: var(--text-quiet);
}

.page-hero__copy > * + * { margin-top: var(--s-5); }

/* =========================================================================
   Knowledge
   ========================================================================= */

.knowledge-index { border-top: 1px solid var(--rule); }
.knowledge-category {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s-4) var(--s-8);
  padding-block: var(--s-8);
  border-bottom: 1px solid var(--rule);
  align-items: start;
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-fast) var(--ease);
}
.knowledge-category:hover .knowledge-category__title { color: var(--blue); }
.knowledge-category__title { transition: color var(--dur-fast) var(--ease); }
/* Same justification as .solution__num: the numeral is the count and the
   position, and it gives four equal-weight text blocks an entry point. */
.knowledge-category__num {
  display: block;
  font-family: var(--font-brand);
  font-size: clamp(2rem, 1.4rem + 2.2vw, 3rem);
  line-height: 0.85;
  letter-spacing: -0.045em;
  color: var(--text-meta);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--s-5);
}
.knowledge-category__body > * + * { margin-top: var(--s-3); }

.article-list { border-top: 1px solid var(--rule); }
.article-item { padding-block: var(--s-6); border-bottom: 1px solid var(--rule); }
.article-item a { text-decoration: none; }
.article-item a:hover .article-item__title { color: var(--blue); }
.article-item__title { transition: color var(--dur-fast) var(--ease); margin-top: var(--s-3); }
.article-item__meta { display: flex; gap: var(--s-4); flex-wrap: wrap; }

.empty-state {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding-block: var(--s-9);
  display: grid;
  gap: var(--s-4);
  justify-items: start;
}

/* Article reading experience */
.prose { max-width: 68ch; }
.prose > * + * { margin-top: var(--s-5); }
.prose h2 { font-size: var(--step-h2); margin-top: var(--s-8); }
.prose h3 { font-size: var(--step-h3); margin-top: var(--s-7); }
.prose ul, .prose ol { display: grid; gap: var(--s-3); padding-left: var(--s-5); }
.prose ul li { list-style: disc; }
.prose ol li { list-style: decimal; }
.prose a { color: var(--blue); text-decoration: underline; text-underline-offset: 3px; }
.prose blockquote {
  border-left: 2px solid var(--blue);
  padding-left: var(--s-5);
  font-size: var(--step-lede);
  color: var(--text-loud);
}
.prose code {
  font-family: var(--font-ui);
  font-size: 0.92em;
  background: rgba(10, 10, 10, 0.06);
  padding: 2px 5px;
  border-radius: var(--radius);
}

/* Search */
.search { display: flex; gap: var(--s-3); max-width: 520px; }
.search input { flex: 1; }

/* =========================================================================
   Form
   ========================================================================= */

.form { max-width: 640px; }
.form__row { display: grid; gap: var(--s-2); margin-bottom: var(--s-6); }
.form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--s-5); }

.label {
  font-family: var(--font-ui);
  font-size: var(--step-small);
  font-weight: 500;
  color: var(--text-loud);
}
.label .req { color: var(--blue); margin-left: 2px; }

.help { font-family: var(--font-ui); font-size: var(--step-small); color: var(--text-quiet); }

.input, .textarea, .select {
  width: 100%;
  font-family: var(--font-ui);
  font-size: var(--step-body);
  color: var(--text-loud);
  background: transparent;
  border: 1px solid var(--control);
  border-radius: var(--radius);
  padding: 13px 14px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.textarea { min-height: 168px; resize: vertical; line-height: 1.6; }

.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: inset 0 0 0 1px var(--blue);
}

.input[aria-invalid="true"], .textarea[aria-invalid="true"], .select[aria-invalid="true"] {
  border-color: var(--carbon);
  box-shadow: inset 0 0 0 1px var(--carbon);
}

.error {
  font-family: var(--font-ui);
  font-size: var(--step-small);
  color: var(--carbon);
  font-weight: 500;
}
.error:empty { display: none; }

.form__status { margin-top: var(--s-5); }
.form__status:empty { display: none; }

/* Grouped enquiry sections. A fieldset carries the grouping for screen
   readers; the hairline carries it visually. No cards, no panels. */
.form__section {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 0 0 var(--s-7);
  padding: var(--s-6) 0 0;
  min-width: 0; /* fieldsets refuse to shrink in grid without this */
}
.form__section:first-of-type { border-top: 0; padding-top: 0; }
.form__section > .form__row:last-child { margin-bottom: 0; }

.form__legend {
  padding: 0;
  margin-bottom: var(--s-5);
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-meta);
  line-height: 1.4;
}

.textarea--short { min-height: 96px; }

.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.success {
  border-left: 2px solid var(--blue);
  padding: var(--s-5) var(--s-6);
  display: grid;
  gap: var(--s-3);
}

/* =========================================================================
   FAQ
   ========================================================================= */

.faq { border-top: 1px solid var(--rule); }
.faq__item { border-bottom: 1px solid var(--rule); }
.faq__q {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-5);
  background: none;
  border: 0;
  padding: var(--s-6) 0;
  text-align: left;
  font-family: var(--font-brand);
  font-size: var(--step-h3);
  font-weight: 500;
  letter-spacing: -0.018em;
  color: var(--text-loud);
  cursor: pointer;
}
.faq__sign {
  flex: none;
  font-family: var(--font-ui);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-meta);
  transition: color var(--dur-fast) var(--ease), transform var(--dur) var(--ease);
}
.faq__q[aria-expanded="true"] .faq__sign { color: var(--blue); transform: rotate(45deg); }
.faq__a { padding-bottom: var(--s-6); max-width: var(--measure); }
.faq__a[hidden] { display: none; }

/* =========================================================================
   Footer
   ========================================================================= */

.site-footer { background: var(--carbon); color: rgba(250, 250, 249, 0.88); padding-block: var(--s-9) var(--s-7); }
.site-footer a { color: inherit; text-decoration: none; }
.site-footer a:hover { color: var(--white); }

.footer__top { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s-8); padding-bottom: var(--s-8); }
.footer__brand img { height: 24px; width: auto; }
.footer__brand > * + * { margin-top: var(--s-5); }
.footer__descriptor { color: var(--silver); max-width: 34ch; font-size: var(--step-small); }

.footer__nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }
.footer__nav h2 {
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--silver);
  margin-bottom: var(--s-4);
}
.footer__nav li + li { margin-top: var(--s-3); }
.footer__nav a { font-size: var(--step-small); }

.footer__bottom {
  border-top: 1px solid rgba(250, 250, 249, 0.16);
  padding-top: var(--s-5);
  display: flex;
  justify-content: space-between;
  gap: var(--s-5);
  flex-wrap: wrap;
  font-family: var(--font-ui);
  font-size: var(--step-small);
  color: var(--silver);
}
.footer__legal { display: flex; gap: var(--s-5); flex-wrap: wrap; }

/* =========================================================================
   Responsive — mobile is designed, not compressed
   ========================================================================= */

/* The flow rotates rather than shrinks: the rule becomes vertical, the stages
   stack and the numbers move above their labels. Same information, drawn for
   the screen it is actually on.

   This turns earlier than the site's general 900px breakpoint because it is
   governed by column count, not by device. The six-stage flow needs roughly
   160px per stage before the friction lines start breaking into four-word
   ribbons; below about 1150px it no longer has that, so it rotates while it
   is still on a laptop. A narrower flow would tolerate less width, but only
   one flow exists and it is sized for the one that is actually built. */
@media (max-width: 1150px) {
  .flow {
    --flow-axis: 6px;
    grid-template-columns: 1fr;
    gap: 0;
    padding-left: var(--s-6);
  }
  .flow::before {
    left: 4px;
    right: auto;
    top: var(--flow-axis);
    bottom: 0;
    width: 1px;
    height: auto;
  }
  .flow__stage {
    padding-top: 0;
    padding-right: 0;
    padding-bottom: var(--s-6);
  }
  .flow__stage:last-child { padding-bottom: 0; }
  .flow__stage::before { left: calc(-1 * var(--s-6)); }
  .flow__num {
    position: static;
    display: block;
    margin-bottom: var(--s-3);
  }
  .flow__note,
  .flow__friction { max-width: var(--measure); }
  /* The rule ends at the last node, not at the bottom of the last label.
     Starts just clear of the node's lower edge (the node is 9px, so its
     bottom sits 4.5px past the axis) so the cover never clips the dot. */
  .flow__stage:last-child::after {
    content: "";
    position: absolute;
    left: calc(-1 * var(--s-6) + 3px);
    top: calc(var(--flow-axis) + 5px);
    bottom: 0;
    width: 3px;
    background: var(--surface);
  }
}

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .hero__headline { max-width: 100%; }
  .hero__visual { order: -1; }
  /* Stacked, still hairline-separated so the three stay one list. */
  .hero__what { grid-template-columns: 1fr; gap: 0; }
  .hero__what li { padding-block: var(--s-4); border-bottom: 1px solid var(--rule); }
  .hero__what li:last-child { border-bottom: 0; padding-bottom: 0; }
  .o-giant { width: min(58%, 260px); margin-inline: 0; }

  .row,
  .split,
  .knowledge-category { grid-template-columns: 1fr; gap: var(--s-4); }
  .outcomes { grid-template-columns: 1fr; gap: var(--s-6); }

  .shift,
  .branch,
  .compare { grid-template-columns: 1fr; }
  /* Stacked, the two halves need a rule between them to stay two halves. */
  .shift__side--aligned {
    padding-top: var(--s-7);
    border-top: 1px solid var(--rule);
  }
  .footer__top { grid-template-columns: 1fr; gap: var(--s-7); }
  .form__grid { grid-template-columns: 1fr; }

  .nav-toggle { display: inline-flex; }

  /* Without JavaScript the navigation simply stacks below the header. */
  .nav {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding-bottom: var(--s-5);
  }

  /* With JavaScript it becomes a panel, closed by default so nothing
     flashes on screen before the script runs. */
  .js .site-header__inner { flex-wrap: nowrap; }
  .js .nav { display: none; }
  .js .nav.is-open {
    display: flex;
    position: fixed;
    inset: 92px 0 0 0;
    background: var(--white);
    padding: var(--s-5) var(--gutter) var(--s-8);
    overflow-y: auto;
    border-top: 1px solid var(--rule);
  }
  .nav__link {
    font-size: 1.35rem;
    font-family: var(--font-brand);
    letter-spacing: -0.02em;
    color: var(--text-loud);
    padding-block: var(--s-4);
    border-bottom: 1px solid var(--rule);
  }
  .nav__link[aria-current="page"]::after { display: none; }
  .nav__link[aria-current="page"] { color: var(--blue); }
  .nav .btn { margin-top: var(--s-6); width: 100%; }
}

@media (max-width: 620px) {
  .sequence__item { grid-template-columns: 1fr; gap: var(--s-2); }
  .sequence__num { padding-top: 0; }

  /* The numeral moves above the area name rather than shrinking beside it. */
  .solution { grid-template-columns: 1fr; gap: var(--s-3); padding-block: var(--s-7); }
  .covers { grid-template-columns: 1fr; }

  /* The strip rotates the same way the flow does, for the same reason. */
  .strip {
    grid-template-columns: 1fr;
    gap: 0;
    padding-left: var(--s-5);
  }
  .strip::before {
    left: 3px;
    right: auto;
    top: var(--strip-axis);
    bottom: 0;
    width: 1px;
    height: auto;
  }
  .strip__stage {
    padding-top: 0;
    padding-right: 0;
    padding-bottom: var(--s-4);
    --strip-axis: 0.68em;
  }
  .strip__stage:last-child { padding-bottom: 0; }
  .strip__stage::before { left: calc(-1 * var(--s-5)); }
  .strip__stage:last-child::after {
    content: "";
    position: absolute;
    left: calc(-1 * var(--s-5) + 2px);
    top: calc(var(--strip-axis) + 4px);
    bottom: 0;
    width: 3px;
    background: var(--surface);
  }
  /* The reversal rotates the same way the flow and the strip do: the rule
     becomes vertical and the two terms stack, so the direction still reads. */
  .reversal__line {
    grid-template-columns: 1fr;
    gap: var(--s-3);
    padding-left: var(--s-6);
  }
  .reversal__line::before {
    inset-inline: auto;
    left: 3px;
    top: 0.6em;
    bottom: 0.6em;
    height: auto;
    width: 1px;
  }
  .reversal__term,
  .reversal__term:last-child {
    justify-self: start;
    padding-inline: 0;
    background: none;
  }
  .reversal__relation { padding-inline: 0; background: none; }

  .o-field { grid-template-columns: repeat(5, 1fr); max-width: 300px; }
  .footer__nav { grid-template-columns: 1fr; }
  .search { flex-direction: column; }
  .search .btn { width: 100%; }
  .actions .btn { width: 100%; }
}

/* =========================================================================
   Reduced motion and print
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  [data-resolve] > * { opacity: 1; transform: none; }
}

@media print {
  .site-header, .site-footer, .nav-toggle, .o-mark { display: none; }
  body { color: var(--carbon); background: var(--white); }
}


/* =========================================================================
   Process scenes — monoline drawings of the physical operation
   One stroke weight, no fills, currentColor so they inherit each chapter's
   text role. Blue marks the one thing in each scene that is moving, being
   checked or being confirmed. See src/scenes.py.
   ========================================================================= */

.scene {
  display: block;
  width: 100%;
  height: auto;
  color: var(--text-loud);
  overflow: visible;
}
.scene__ghost { stroke: var(--text-meta); }

/* --- Illustrated process flow --------------------------------------------
   strip() with the operation made visible. The rule is the operation, the
   nodes are the stages, and above each stage is what happens there. */

.pflow {
  --pflow-axis: 6px;
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--pflow-cols, 4), minmax(0, 1fr));
  gap: var(--s-5);
  margin: var(--s-6) 0 0;
  padding: 0;
  list-style: none;
}
.pflow::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: var(--pflow-axis);
  height: 1px;
  background: var(--rule-strong);
  transform-origin: left center;
}
.pflow__stage {
  position: relative;
  padding-top: calc(var(--pflow-axis) + var(--s-5));
  padding-right: var(--s-4);
}
.pflow__stage::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--pflow-axis);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  transform: translateY(-50%);
}
.pflow__scene { display: block; max-width: 176px; }
.pflow__label {
  display: block;
  margin-top: var(--s-4);
  font-family: var(--font-brand);
  font-size: clamp(1rem, 0.85rem + 0.4vw, 1.125rem);
  letter-spacing: -0.015em;
  line-height: 1.25;
  color: var(--text-loud);
}
/* The route marker: blue travelling the operation once, then resting. Sits
   outside the grid so it takes no cell. */
.pflow__runner {
  position: absolute;
  top: var(--pflow-axis);
  left: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--blue);
  transform: translate(-50%, -50%);
  opacity: 0;
  pointer-events: none;
}
/* Where the marker stops: the left edge of the last column. */
.pflow {
  --pflow-end: calc(
    (100% - (var(--pflow-cols) - 1) * var(--s-5)) * (var(--pflow-cols) - 1) / var(--pflow-cols)
    + (var(--pflow-cols) - 1) * var(--s-5)
  );
}

/* Inside the solutions index the flow sits under the description. */
.solution__body .pflow { margin-top: var(--s-5); }
.solution__body .pflow__scene { max-width: 150px; }

/* --- Figure ---------------------------------------------------------------- */
.figure { margin: 0; }
.figure .scene { max-width: 560px; }
.figure__caption {
  margin-top: var(--s-4);
  font-family: var(--font-ui);
  font-size: var(--step-meta);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-meta);
}

/* The operational problems beside the one being drawn. */
.problems {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: var(--s-7) var(--s-9);
  align-items: start;
}
.problems .figure { position: sticky; top: 120px; }

/* --- Route rule on the home sequence ------------------------------------- */
.sequence--progress { position: relative; }
.sequence--progress::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--blue);
  transform-origin: top center;
}
.sequence--progress .sequence__item { padding-left: var(--s-5); }

/* =========================================================================
   Entry motion — Scatter → Align → Connect → Resolve
   Every rule below explains a change in state: a rule extending is the
   operation becoming continuous; a drawing resolving is a stage becoming
   clear; the marker travelling is progression. Each runs once, on entry,
   and only when JavaScript is present to trigger it — without it the
   resolved state is simply shown.
   ========================================================================= */

/* Hero: the copy settles into place as the page opens. Pure CSS, so it does
   not depend on the script. */
.hero__copy > * {
  animation: rise 640ms var(--ease) both;
  animation-delay: calc(var(--n, 0) * 90ms);
}
.hero__copy > :nth-child(1) { --n: 0; }
.hero__copy > :nth-child(2) { --n: 1; }
.hero__copy > :nth-child(3) { --n: 2; }
.hero__copy > :nth-child(4) { --n: 3; }
.hero__copy > :nth-child(5) { --n: 4; }
.hero__visual { animation: settle 900ms var(--ease) 120ms both; }
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
@keyframes settle {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: none; }
}

/* Scenes draw in stroke by stroke, in stage order. */
.js [data-animate] .scene path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1100ms var(--ease), opacity 600ms var(--ease);
  transition-delay: calc(var(--i, 0) * 180ms);
}
.js [data-animate] .scene .scene__acc { transition-delay: calc(var(--i, 0) * 180ms + 700ms); }
.js [data-animate] .scene .scene__ghost {
  stroke-dasharray: 0.05 0.045;
  stroke-dashoffset: 0;
  opacity: 0;
  transition-delay: calc(var(--i, 0) * 180ms + 500ms);
}
.js [data-animate].is-in .scene path { stroke-dashoffset: 0; }
.js [data-animate].is-in .scene .scene__ghost { opacity: 1; }
.scene__ghost { stroke-dasharray: 0.05 0.045; }

/* The rule extends; the nodes are reached in order. */
.js .pflow::before,
.js .flow::before { transform: scaleX(0); transition: transform 1400ms var(--ease); }
.js .pflow.is-in::before,
.js .flow.is-in::before { transform: scaleX(1); }
.js .pflow__stage::before,
.js .flow__stage::before {
  opacity: 0;
  transition: opacity 300ms var(--ease), background-color 300ms var(--ease), border-color 300ms var(--ease);
  transition-delay: calc(var(--i, 0) * 180ms + 200ms);
}
.js .pflow.is-in .pflow__stage::before,
.js .flow.is-in .flow__stage::before { opacity: 1; }
.js .pflow__label,
.js .flow__label, .js .flow__num, .js .flow__friction {
  opacity: 0;
  transition: opacity 500ms var(--ease);
  transition-delay: calc(var(--i, 0) * 180ms + 300ms);
}
.js .pflow.is-in .pflow__label,
.js .flow.is-in .flow__label, .js .flow.is-in .flow__num, .js .flow.is-in .flow__friction { opacity: 1; }

/* The marker travels the operation once and rests on the final stage. */
.js .pflow.is-in .pflow__runner { animation: run 2200ms var(--ease) 500ms both; }
.js .pflow.is-in .pflow__stage:last-child::before {
  background: var(--blue);
  border-color: var(--blue);
  transition-delay: 2600ms;
}
@keyframes run {
  0%   { left: 0; opacity: 0; }
  6%   { opacity: 1; }
  92%  { opacity: 1; }
  100% { left: var(--pflow-end); opacity: 0; }
}

/* The fragmented state settles; the aligned state connects. */
.js .shift__set li {
  opacity: 0;
  transform: translateX(-10px);
  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
  transition-delay: calc(var(--i, 0) * 110ms);
}
.js .shift__side--aligned .shift__set li { transition-delay: calc(var(--i, 0) * 110ms + 500ms); }
.js .shift.is-in .shift__set li { opacity: 1; transform: none; }
.js .shift__side--aligned .shift__result::before {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 500ms var(--ease) 1300ms;
}
.js .shift.is-in .shift__side--aligned .shift__result::before { transform: scaleX(1); }

/* The route rule draws down the home sequence; each stage lights as it is
   reached. */
.js .sequence--progress::before { transform: scaleY(0); transition: transform 1800ms var(--ease) 200ms; }
.js .sequence--progress.is-in::before { transform: scaleY(1); }
.js .sequence--progress .sequence__num {
  transition: color 300ms var(--ease);
  transition-delay: calc(var(--i, 0) * 340ms + 300ms);
}
.js .sequence--progress.is-in .sequence__num { color: var(--text-loud); }

/* The rack figure resolves like the other scenes; its caption follows. */
.js .figure .figure__caption { opacity: 0; transition: opacity 500ms var(--ease) 1200ms; }
.js .figure.is-in .figure__caption { opacity: 1; }

/* --- Responsive: the illustrated flow rotates like the strip ------------- */
@media (max-width: 900px) {
  .pflow {
    grid-template-columns: 1fr;
    gap: 0;
    padding-left: var(--s-6);
  }
  .pflow::before {
    left: 3px;
    right: auto;
    top: var(--pflow-axis);
    bottom: 0;
    width: 1px;
    height: auto;
    transform-origin: center top;
  }
  .js .pflow::before { transform: scaleY(0); }
  .js .pflow.is-in::before { transform: scaleY(1); }
  .pflow__stage {
    display: grid;
    grid-template-columns: minmax(0, 120px) minmax(0, 1fr);
    gap: var(--s-4) var(--s-5);
    align-items: center;
    padding: 0 0 var(--s-6);
  }
  .pflow__stage:last-child { padding-bottom: 0; }
  .pflow__stage::before { left: calc(-1 * var(--s-6)); top: 50%; }
  .pflow__scene { max-width: 120px; }
  .pflow__label { margin-top: 0; }
  .pflow__stage:last-child::after {
    content: "";
    position: absolute;
    left: calc(-1 * var(--s-6) + 2px);
    top: calc(50% + 5px);
    bottom: 0;
    width: 3px;
    background: var(--surface);
  }
  .pflow__runner { display: none; }
  .problems { grid-template-columns: 1fr; }
  .problems .figure { position: static; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__copy > *, .hero__visual { animation: none; }
  .js [data-animate] .scene path { stroke-dashoffset: 0; }
  .js [data-animate] .scene .scene__ghost { opacity: 1; }
}
