/* =============================================================================
   Storefronts — the page.

   Mostly dark, with two light sections as relief before the close. The dark is
   not a mood: this page is the first thing a prospect sees of what we can
   build, so it is allowed to be the most composed thing on the site, and the
   light sections are where it stops performing and answers questions.

   Two scroll pieces carry it, and between them they use all three axes:

     the turn    a card rotating on Y, front of the business to back of it
     the needs   a track moving on X, through what a trade buyer actually asks

   Both are scrubbed and damped in storefront-page.js. Nothing here runs on a
   timer.
   ========================================================================== */

/*
 * Typed, because these are arithmetic rather than tokens.
 *
 * An unregistered custom property is a string until it is used, so dividing by
 * one that holds a calc() is invalid at computed-value time and the whole
 * declaration is dropped — which is exactly what happened
 * here: every row painted at opacity 1 and nothing staggered. Registered as
 * <number> they resolve eagerly, the maths holds, and as a bonus they become
 * animatable rather than merely substitutable.
 */
@property --flow {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

@property --flow-n {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

@property --flow-i {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

/* Inherits, because the row computes it but its children are what react to it
   — the tag, the sentence, the label. Registered `inherits: false` the sentence
   read the initial 0 and masked itself out of existence while the row it sits
   in was fully lit. */
@property --lit {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

/* ------------------------------------------------------------------ hero */

.sf-hero {
  position: relative;
  padding-block: 0;
}

/* The runway. Height here rather than on the section, so the section's own
   content is not pushed down by the pin's scroll distance. */
.sf-hero__runway {
  block-size: 320vh;
  position: relative;
}

.sf-hero__pin {
  position: sticky;
  inset-block-start: 0;
  block-size: 100vh;
  display: grid;
  align-items: center;
  overflow: clip;
}

.sf-hero__stage {
  inline-size: min(100% - (var(--grid-gutter) * 2), var(--grid-max));
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(var(--s-6), 6vw, var(--s-9));
}

.sf-hero__say {
  min-inline-size: 0;
}

.sf-hero__say h1 {
  margin: 0;
  max-inline-size: 15ch;
}

.sf-hero__lead {
  margin-block-start: var(--s-5);
  max-inline-size: 42ch;
  color: var(--c-ink-soft);
}

.sf-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  margin-block-start: var(--s-6);
}

.sf-hero__card {
  position: relative;
  min-inline-size: 0;
}

/*
 * The caption under the card names which side you are looking at.
 *
 * Two labels in one slot, cross-faded by --turn: the front's label leaves as
 * the card passes edge-on and the back's arrives, so the words change at the
 * same instant the object does.
 */
.sf-hero__caption {
  display: grid;
  margin-block-start: var(--s-6);
  min-block-size: 1.4em;
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
  text-align: center;
}

.sf-hero__caption span {
  grid-area: 1 / 1;
}

.sf-hero__caption span:first-child {
  opacity: clamp(0, calc(1 - var(--turn, 0) * 2.4), 1);
}

.sf-hero__caption span:last-child {
  opacity: clamp(0, calc((var(--turn, 0) - 0.62) * 2.8), 1);
  color: var(--c-accent);
}

/* --------------------------------------------------------------- the needs */

.sf-needs {
  position: relative;
  padding-block: 0;
}

.sf-needs__runway {
  /* Written by storefront-page.js from the number of stops. */
  block-size: var(--needs-length, 400vh);
  position: relative;
}

.sf-needs__pin {
  position: sticky;
  inset-block-start: 0;
  block-size: 100vh;
  display: grid;
  grid-template-rows: auto auto;
  align-content: center;
  gap: var(--s-6);
  overflow: clip;
}

.sf-needs__head {
  inline-size: min(100% - (var(--grid-gutter) * 2), var(--grid-max));
  margin-inline: auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-5);
  flex-wrap: wrap;
}

.sf-needs__count {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-micro);
  color: var(--c-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.sf-needs__count b {
  color: var(--c-accent);
  font-weight: 500;
}

.sf-needs__viewport {
  display: flex;
  align-items: stretch;
  min-inline-size: 0;
}

/*
 * The track.
 *
 * Padded by the gutter at both ends so the first stop starts where the page's
 * text starts and the last can come to rest there too. Translated rather than
 * scrolled: this is the only thing moving in the section, and it should move on
 * the compositor.
 */
.sf-needs__track {
  display: flex;
  align-items: stretch;
  gap: clamp(var(--s-5), 3vw, var(--s-7));
  padding-inline: var(--grid-gutter);
  min-inline-size: max-content;
  translate: calc(var(--needs-progress, 0) * var(--needs-travel, 0px) * -1) 0;
  will-change: transform;
}

.need {
  inline-size: clamp(17rem, 26vw, 23rem);
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6);
  border: var(--border);
  border-radius: var(--r-md);
  background: var(--c-canvas-sunk);
  /* The one not being read recedes rather than disappearing, so the row still
     reads as a sequence you are moving along. */
  opacity: 0.34;
  scale: 0.965;
  transition:
    opacity var(--d-slow) var(--e-out),
    scale var(--d-slow) var(--e-out),
    border-color var(--d-slow) var(--e-out);
}

.need[data-current='true'] {
  opacity: 1;
  scale: 1;
  border-color: var(--c-accent);
}

.need__index {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  color: var(--c-accent);
}

/* The buyer's question, in their words, and the loudest thing in the card:
   knowing which questions these are is the whole demonstration. */
.need__ask {
  font-size: var(--t-lead);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.25;
  margin: 0;
}

.need__pair {
  margin: 0;
  padding-block-start: var(--s-4);
  border-block-start: var(--border);
}

.need__pair dt {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
  margin-block-end: var(--s-2);
}

.need__pair dd {
  margin: 0 0 var(--s-4);
  color: var(--c-ink-soft);
}

.need__pair dd:last-of-type {
  margin-block-end: 0;
  color: var(--c-ink);
}

/* ------------------------------------------------------------- the doors */

.sf-doors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(var(--s-6), 4vw, var(--s-8));
  margin-block-start: var(--s-7);
}

.sf-door {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding-block-start: var(--s-4);
  border-block-start: var(--border-strong);
}

.sf-door__title {
  font-size: var(--t-lead);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0;
}

.sf-door__list {
  list-style: none;
  margin: var(--s-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  color: var(--c-ink-soft);
}

.sf-door__list li {
  display: flex;
  gap: var(--s-3);
}

.sf-door__list li::before {
  content: '';
  flex: none;
  inline-size: 4px;
  block-size: 4px;
  border-radius: 50%;
  background: var(--c-accent);
  margin-block-start: 0.62em;
}

/* -------------------------------------------------------------------- say */

/*
 * One sentence, four languages, resolving in place.
 *
 * The flaps this replaced were the wrong instinct: boxes made the words look
 * like a component, and the words are the whole subject. Set at reading scale
 * with nothing around them, the line is the object and the change of language
 * happens to it rather than inside a grid of cells.
 */
.say {
  margin-block-start: clamp(var(--s-7), 7vw, 5rem);
  min-block-size: 9rem;
}

.say__lang {
  margin: 0 0 var(--s-5);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

.say__lang span {
  color: var(--c-accent);
}

/*
 * Large, and set in the site's own display treatment rather than a new one.
 * The sentence is short enough to hold at this size on one or two lines, and
 * at anything smaller the resolving characters are too fine to read as an
 * effect at all.
 */
.say__line {
  margin: 0;
  /* Two lines' worth, always.
   *
   * The sentences are different lengths, so Dutch wrapped to two lines while
   * English fitted on one — and the block changed height on every switch,
   * taking everything below it with it. Reserving the taller state means the
   * language changes and nothing else moves. */
  min-block-size: 2.3em;
  font-size: clamp(1.5rem, 4.4vw, 3.2rem);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.035em;
  /* Each glyph is its own inline box, so a settling character can carry colour
     without the line reflowing around it. */
  display: block;
  max-inline-size: 22ch;
}

/* A character that has not landed yet. Accent rather than grey, so the eye
   follows the front of the change across the line. */
.say__char {
  transition: color var(--d-fast) linear;
}

.say__char[data-settling='true'] {
  color: var(--c-accent);
}

/* The list the line is built from — kept in the document, hidden visually. */
.say__source {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------- web */

/*
 * The field of connections behind each door. Sits under the content and above
 * the cursor light, at an alpha low enough to be furniture.
 */
.sf-door__web {
  position: absolute;
  inset: 0;
  z-index: -1;
  inline-size: 100%;
  block-size: 100%;
  color: var(--c-accent);
  opacity: 0.5;
  pointer-events: none;
  transition: opacity var(--d-slow) var(--e-out);
}

.sf-door[data-active='true'] .sf-door__web {
  opacity: 0.9;
}

/* ------------------------------------------------------------------- term */

/*
 * The training log.
 *
 * A panel, not a card: it is the one place on the page that is meant to look
 * like a machine rather than like a document, so it keeps its border and its
 * fill and everything around it stays typographic.
 */
.term {
  /* Movement is a translate on a custom property, so the panel's layout
     position never changes and nothing below it shifts while it is carried. */
  translate: var(--drag-x, 0) var(--drag-y, 0);
  touch-action: none;
  margin-block-start: clamp(var(--s-6), 4vw, var(--s-7));
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--c-dark) 55%, transparent);
  overflow: hidden;
  position: relative;
  max-inline-size: 46rem;
}

/* Scanlines. Faint, and at 3px so they read as a surface rather than as
   stripes; the whole panel is only a few hundred pixels tall. */
.term::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 2px,
    color-mix(in srgb, var(--c-dark-ink) 4%, transparent) 2px 3px
  );
}

/* Picked up here — but only once the module has said so. */
.term[data-drag='true'] .term__bar {
  cursor: grab;
}

.term__bar {
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-block-end: 1px solid var(--c-line);
  background: color-mix(in srgb, var(--c-dark-ink) 4%, transparent);
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

.term__path {
  /* Truncates rather than wrapping the bar onto two lines on a phone. */
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.term__state {
  color: var(--c-accent);
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.6em;
}

/* The spinner. Written by the module as a custom property holding a braille
   frame, so the turning costs one property update rather than a DOM write. */
.term__state::before {
  content: var(--spin, '\2802');
  color: var(--c-accent);
}

.term[data-held='true'] {
  cursor: grabbing;
  box-shadow: 0 18px 50px rgb(0 0 0 / 45%);
}

.term[data-held='true'] .term__bar {
  cursor: grabbing;
}

.term__body {
  margin: 0;
  padding: var(--s-4);
  /* Fixed height, so a log that keeps producing does not keep growing the page.
     It scrolls itself and follows its own output. */
  block-size: 15.5rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.35em;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  line-height: 1.5;
  color: var(--c-dark-muted);
  /* A log is a log: it does not reflow into a paragraph on a narrow screen, it
     scrolls, and this is the one element on the page allowed to. */
  overflow-x: auto;
}

.term__line {
  white-space: pre;
}

.term__line--cmd { color: var(--c-dark-ink); }
.term__line--ok { color: var(--c-accent-on-dark); }
.term__line--work { color: var(--c-ochre); }
.term__line--wait { color: var(--c-muted); }
.term__line--note { color: var(--c-dark-muted); font-style: italic; }

/* The block cursor, on the last line, until the log finishes. */
.term__body > .term__line:last-child::after {
  content: '';
  display: inline-block;
  inline-size: 0.55em;
  block-size: 1em;
  margin-inline-start: 0.15em;
  vertical-align: -0.15em;
  background: var(--c-accent-on-dark);
  animation: term-blink 1.05s steps(1) infinite;
}

.term[data-done='true'] .term__body > .term__line:last-child::after {
  display: none;
}

@keyframes term-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* ---------------------------------------------------------- the assistant */

/*
 * What it learns, listed as the business's own things rather than as features.
 * "Your lead times" is a claim about fitting a company; "natural language
 * understanding" is a claim about a library, and every competitor has one.
 */
.sf-learns {
  display: grid;
  /* Three, explicitly, not auto-fit. Six items across an auto-fit track came
     out five and one, which reads as a mistake in the content rather than a
     consequence of the viewport. Three gives two even rows at any width wide
     enough to hold them. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-6) var(--s-7);
  margin-block-start: var(--s-7);
  padding-block-start: var(--s-6);
  border-block-start: var(--border);
}

.sf-learn {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.sf-learn__what {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-accent);
}

.sf-learn__why {
  margin: 0;
  color: var(--c-ink-soft);
}

/* Said once, in the section head, and not softened anywhere else. */
.sf-wip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 0.25em 0.7em;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

.sf-wip::before {
  content: '';
  inline-size: 5px;
  block-size: 5px;
  border-radius: 50%;
  background: var(--c-accent);
}

/* ------------------------------------------------------------- the terms */

.sf-terms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--s-6);
  margin-block-start: var(--s-7);
}

.sf-term {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding-block-start: var(--s-4);
  border-block-start: var(--border-strong);
}

.sf-term__head {
  font-size: var(--t-lead);
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0;
}

.sf-term__note {
  margin: 0;
  color: var(--c-ink-soft);
}

/* Set apart from the terms above it: left-aligned directly under the first
   column, it read as a fifth line of "a fixed price". */
.sf-first {
  margin-block-start: var(--s-7);
  padding-block-start: var(--s-5);
  border-block-start: var(--border);
  color: var(--c-muted);
}


/*
 * Choosing between the two.
 *
 * Both doors sit slightly back until the cursor picks one, at which point it
 * resolves and the other softens. The blur is small — 3px on a heading is
 * plenty to read as out of focus and cheap enough to run on two elements — and
 * it is only ever applied to ONE of them, so there is never more than a single
 * blurred layer being composited.
 */
.sf-door {
  position: relative;
  isolation: isolate;
  transition:
    filter var(--d-slow) var(--e-out),
    opacity var(--d-slow) var(--e-out),
    border-color var(--d-slow) var(--e-out);
}

.sf-doors[data-choosing='true'] .sf-door[data-active='false'] {
  filter: blur(3px);
  opacity: 0.42;
}

.sf-doors[data-choosing='true'] .sf-door[data-active='true'] {
  border-block-start-color: var(--c-accent);
}

/*
 * The light under the cursor.
 *
 * Behind the content rather than over it (z-index: -1 inside the door's own
 * isolation), so it warms the panel instead of washing the text. Position comes
 * from the pointer as a percentage; the transition is what makes it trail
 * rather than teleport between pointermove events, which fire at the input
 * device's rate and not the display's.
 */
.sf-door__light {
  position: absolute;
  /* Bleeds vertically only. The horizontal -6% it had was 6% of a door that is
     the full column once these stack, which put it past the edge of the page
     and gave /storefronts/ a scrollbar at 390px. The gradient fades out well
     before its own box anyway, so the softness came from the falloff rather
     than from the bleed. */
  inset: -8% 0;
  z-index: -1;
  border-radius: var(--r-lg);
  background: radial-gradient(
    22rem 16rem at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--c-accent) 16%, transparent),
    transparent 68%
  );
  opacity: 0;
  transition:
    opacity var(--d-slow) var(--e-out),
    background-position var(--d-base) linear;
  pointer-events: none;
}

.sf-door[data-active='true'] .sf-door__light {
  opacity: 1;
}

/*
 * No pointer, no choosing. A touch reader gets both doors sharp and lit, which
 * is the resolved state rather than a degraded one.
 */
@media (hover: none), (pointer: coarse) {
  .sf-doors[data-choosing='true'] .sf-door[data-active='false'] {
    filter: none;
    opacity: 1;
  }

  .sf-door__light {
    display: none;
  }
}

/*
 * The sentence arriving in each language in turn.
 *
 * --flow is the section's own read progress, 0 to 1, damped in
 * storefront-page.js. Each row derives its share of it from the --i in its
 * markup, so the stagger is a property of the element and adding a fifth
 * language needs no new rule and no new number here.
 *
 * The sentence is masked in from the left rather than faded: it should read as
 * being written, which is what the section is about. The rows behind stay
 * written, because the point is that all four exist at once.
 */
/* flow × n walks 0→n across the section; subtracting i staggers each row by
   its own place in the list. Multiplication only — no division by a variable,
   which is what made the first version of this invalid. */
.sf-lang {
  --lit: clamp(0, calc(var(--flow) * var(--flow-n) - var(--flow-i) * 0.82), 1);
}

.sf-lang__tag {
  opacity: calc(0.25 + var(--lit) * 0.75);
  transition: opacity var(--d-base) var(--e-out);
}

.sf-lang__say {
  /* A soft edge on the mask, so the line resolves rather than being uncovered
     by a hard vertical line travelling across it. */
  -webkit-mask-image: linear-gradient(
    to right,
    #000 calc(var(--lit) * 118% - 18%),
    transparent calc(var(--lit) * 118%)
  );
  mask-image: linear-gradient(
    to right,
    #000 calc(var(--lit) * 118% - 18%),
    transparent calc(var(--lit) * 118%)
  );
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  translate: calc((1 - var(--lit)) * -0.4rem) 0;
}

/* The rule down the left, drawn as the rows arrive: four separate lines, one
   continuous thing. */
.sf-langs {
  position: relative;
}

.sf-langs::before {
  content: '';
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 1px;
  block-size: calc(var(--flow, 0) * 100%);
  background: var(--c-accent);
  opacity: 0.5;
}

/*
 * What it has been taught, arriving one at a time.
 *
 * These carry no .reveal class. The flow IS their entrance, and two systems
 * animating one element's opacity is a fight the more specific one wins at
 * random — .reveal.is-revealed was pinning the first three at 1 while the
 * flow held them at 0.22.
 *
 * Same mechanism as the languages, same reason: an assistant that has learnt
 * six things about a business has learnt all six, so nothing goes out again
 * behind the current.
 */
.sf-learn {
  --lit: clamp(0, calc(var(--flow) * var(--flow-n) - var(--flow-i) * 0.78), 1);
  position: relative;
  padding-inline-start: var(--s-4);
  translate: 0 calc((1 - var(--lit)) * 0.5rem);
  opacity: calc(0.22 + var(--lit) * 0.78);
  transition: translate var(--d-slow) var(--e-out);
}

/* The mark that fills as the current reaches this one. */
.sf-learn::before {
  content: '';
  position: absolute;
  inset-block: 0.15em auto;
  inset-inline-start: 0;
  inline-size: 2px;
  block-size: calc(var(--lit) * 100%);
  min-block-size: 2px;
  background: var(--c-accent);
  border-radius: 1px;
}

.sf-learn__what {
  color: color-mix(in srgb, var(--c-accent) calc(40% + var(--lit) * 60%), var(--c-muted));
}

/* ------------------------------------------------------------------ chain */

/*
 * How many people stand between a client and the work.
 *
 * The honest version of the old "there are three of us": that claim stops being
 * true the day anyone is hired, and this one does not, because it is about the
 * path rather than the payroll. The agency lane fades as the section is read
 * and the direct lane draws — the middle links stay behind as ghosts, because
 * the point is that the coordination still exists somewhere, you are simply not
 * paying for it here.
 */
.chain {
  --lit: clamp(0, calc(var(--flow) * 1.35), 1);
  display: grid;
  gap: clamp(var(--s-6), 5vw, var(--s-8));
  margin-block-start: clamp(var(--s-7), 6vw, 4.5rem);
}

.chain__lane {
  display: grid;
  gap: var(--s-4);
}

.chain__tag {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

/*
 * The gap between two nodes IS the connector's width, so the line each node
 * draws exactly fills the space to the next. The first version pulled the line
 * back over the gap with a negative margin and it rendered past the end of the
 * last pill — this way the two numbers cannot disagree, because there is one.
 */
.chain__links {
  --link-gap: clamp(var(--s-4), 2.5vw, var(--s-6));
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--link-gap);
}

.chain__lane--direct .chain__links {
  --link-gap: clamp(3rem, 14vw, 13rem);
}

/*
 * A node and the line that leaves it. The connector is drawn as a pseudo on
 * each node rather than as its own element, so a lane is a list of names and
 * nothing else — the drawing is entirely in the stylesheet.
 */
.chain__node {
  position: relative;
  padding: var(--s-2) var(--s-4);
  border: var(--border);
  border-radius: var(--r-pill);
  background: var(--c-canvas);
  font-size: var(--t-small);
  color: var(--c-ink-soft);
  white-space: nowrap;
}

.chain__node:not(:last-child)::after {
  content: '';
  position: absolute;
  inset-inline-start: 100%;
  inset-block-start: 50%;
  inline-size: var(--link-gap);
  block-size: 1px;
  background: currentColor;
  opacity: 0.4;
}

.chain__node--end {
  border-color: var(--c-line-strong);
  color: var(--c-ink);
}

/* The middle of the agency lane recedes as the section is read: still there,
   still real, no longer between you and the work. */
.chain__lane--via .chain__node:not(.chain__node--end) {
  opacity: calc(1 - var(--lit) * 0.72);
  transition: opacity var(--d-slow) var(--e-out);
}

.chain__lane--via {
  opacity: calc(1 - var(--lit) * 0.45);
}

/* And the direct lane arrives. */
.chain__lane--direct {
  opacity: var(--lit);
  translate: 0 calc((1 - var(--lit)) * 0.75rem);
}

.chain__lane--direct .chain__node {
  border-color: var(--c-accent);
  color: var(--c-accent-deep);
  background: var(--c-accent-soft);
}

/* The one long line, drawn rather than fading in — it is the whole claim. */
.chain__lane--direct .chain__node:not(:last-child)::after {
  background: var(--c-accent);
  opacity: 1;
  scale: var(--lit) 1;
  transform-origin: left center;
}

.chain__cost {
  margin: 0;
  color: var(--c-muted);
  font-size: var(--t-small);
}

@media (min-width: 900px) {
  .chain {
    gap: clamp(var(--s-7), 6vw, 5rem);
  }

}

/* Six pixels short at 320px, which is not worth an ellipsis — the bar simply
   tightens instead, and the path stays whole. */
@media (max-width: 400px) {
  .term__bar {
    gap: var(--s-3);
    padding-inline: var(--s-3);
    font-size: 0.625rem;
  }
}

@media (max-width: 620px) {
  /* Stacked, because six pills on one line at 320px is not a chain, it is a
     paragraph of boxes. The connector turns to run down instead. */
  .chain__links {
    display: grid;
    justify-items: start;
    gap: var(--s-5);
  }

  /* Stacked, so the connector turns and runs down the gap instead. */
  .chain__links,
  .chain__lane--direct .chain__links {
    --link-gap: var(--s-5);
  }

  .chain__node:not(:last-child)::after {
    inline-size: 1px;
    block-size: var(--link-gap);
    inset-inline-start: var(--s-5);
    inset-block-start: 100%;
  }

  .chain__lane--direct .chain__node:not(:last-child)::after {
    scale: 1 var(--lit);
    transform-origin: top center;
  }
}

/* ------------------------------------------------------------------ narrow */

@media (max-width: 960px) {
  .sf-learns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .sf-hero__runway {
    block-size: 280vh;
  }

  .sf-hero__stage {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-7);
    align-content: center;
  }

  /* The card keeps a readable size rather than filling the column. */
  .sf-hero__card {
    max-inline-size: 22rem;
    margin-inline: auto;
    inline-size: 100%;
  }

  .sf-doors {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-7);
  }
}

@media (max-width: 620px) {
  .sf-learns {
    grid-template-columns: minmax(0, 1fr);
  }

  .need {
    inline-size: min(78vw, 20rem);
    padding: var(--s-5);
  }

  .sf-lang {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-2);
  }
}

/* ----------------------------------------------------------- reduced motion */

/*
 * Both runways collapse and both pieces become ordinary blocks. The needs track
 * stops being a track and wraps into a grid, because a horizontal scroller that
 * cannot be scrubbed is content the reader cannot reach.
 */
@media (prefers-reduced-motion: reduce) {
  .sf-hero__runway,
  .sf-needs__runway {
    block-size: auto;
  }

  .sf-hero__pin,
  .sf-needs__pin {
    position: static;
    block-size: auto;
    padding-block: var(--section-y);
    overflow: visible;
  }

  .sf-needs__viewport {
    display: block;
  }

  .sf-needs__track {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    min-inline-size: 0;
    padding-inline: 0;
    translate: none;
    will-change: auto;
  }

  .need {
    inline-size: auto;
    opacity: 1;
    scale: 1;
  }

  /* Every current is delivered at 1 by the module, so these are already in
     their finished state — but the mask and the offsets are removed outright
     rather than relied on to resolve, since a mask that never repaints is a
     line that never appears. */
  .sf-lang__say {
    -webkit-mask-image: none;
    mask-image: none;
    translate: none;
  }

  .sf-learn {
    translate: none;
    opacity: 1;
  }

  .sf-door,
  .sf-door__light {
    transition: none;
  }
}
