/* who.css — GENERATED by js/gen_who.py. Do not edit by hand; edit the generator.

   Three isometric scenes for "Who this is for", built to Anchor's measured
   motion language. Scoped entirely to .wf so it can sit next to site.css,
   bento.css and how.css without any of them reaching into each other.

   Duplicated, not imported: how.css already defines this ramp as --hw-b0..b2
   and this easing as --hw-ease. Pointing at it would make a preview file depend
   on a section it has nothing to do with. Same numbers, own scope. */

.wf {
  /* The Quint family, and nothing else. Anything that responds to a cause uses
     outQuint; a move between two states that already exist uses inOutQuint;
     only something leaving the frame uses inQuint. linear is for ambient loops
     and is never allowed to carry a reaction. */
  --wf-out:      cubic-bezier(.22, 1, .36, 1);
  --wf-inout:    cubic-bezier(.83, 0, .17, 1);
  --wf-in:       cubic-bezier(.64, 0, .78, 0);
  --wf-dur:      .5s;    /* the dominant duration on the live site */
  --wf-hold:     1s;     /* move for a second, rest for a second */
  --wf-stagger:  .13s;   /* measured gap between sibling starts */
  --wf-wash-dur: 2s;     /* the light sweep. linear, and always running. */

}

/* The section. Deliberately the same box as .sp-bento in bento.css — same
   padding, same ground colour — so "Who this is for" sits in the page at the
   same rhythm as "What you get" directly above it. */
.wf-band {
  padding: 4.5rem 3.75rem;
  background-color: var(--primitive--c-neutral-100);
  color: var(--semantic--c-foreground);
  font-family: var(--font--saans);
}

/* 2rem, the same heading-to-content gap .sp-bento-head and .hw-title hand out. */
.wf-head { margin-bottom: 2rem; }

/* Type lives in css/section-title.css — the one copy shared with
   .sp-bento-title, .hw-title and .sp-title. Only spacing is ours. */
.wf-title { margin: 0; }

/* Three across, spaced like .home-fleet-grid on anchorbrowser.io — the section
   with the three animations. Every number below is lifted from there rather
   than invented: 3.75rem between the boxes, 1rem between a box and its text,
   .125rem between a title and its paragraph, 60ch and 50ch as the caps.
   Two columns at 991 and one at 767 is that grid's own behaviour too, orphan
   third card included.

   All three scenes share one viewBox, so at equal column widths they come out
   the same height BY CONSTRUCTION — no equal-height trick. That holds only
   because the text sits outside .wf-stage: text that wraps to another line
   grows its card, never its stage. align-items: start stops the short cards
   stretching to match the long one. */
.wf-row {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a plain 1fr track carries an automatic minimum of
     min-content, so the longest word in a column could widen that column and
     break the one thing this layout promises — three stages of equal width.
     With minmax(0, ...) the tracks are exact thirds at every width. Measured on
     the page: 400 / 400 / 400 at the real desktop row width. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3.75rem;                     /* .home-fleet-grid's own 3.75rem */
  align-items: start;
}

.wf {
  display: flex;
  flex-flow: column;
  gap: 2.25rem;                     /* .home-fleet-grid's 1rem, plus 20px */
  max-width: 60ch;
  /* The item's half of the same guard: minmax fixes the TRACK, this stops the
     card's own automatic minimum from binding.
     Worth knowing what neither of them fixes, because it looked like a bug
     while measuring. A card still overflows its track if a single unbreakable
     WORD is wider than the column — the text's min-content propagates up and
     nothing short of overflow-wrap changes that. At an artificially narrow
     83px column the three cards came out 99 / 85 / 83, tracking
     "infrastructure" / "automating" / "automation". It is not reachable here:
     the narrowest three-up column on the page is 400px, the layout drops to two
     columns at 991 and one at 767, and no word in the copy is close. Left
     unhandled deliberately rather than papered over with overflow-wrap, which
     would hyphenate real words to solve a case that does not occur. */
  min-width: 0;
}

.wf-stage {
  border: 1px solid var(--semantic--c-stroke);
  border-radius: 4px;
  overflow: hidden;
}

.wf-svg { display: block; width: 100%; height: auto; }

.wf-text {
  display: flex;
  flex-flow: column;
  gap: 0.125rem;
  max-width: 50ch;
}

/* Size, weight and colour all come from site.css's utilities on the element
   itself. These two rules only undo the browser's default margins. */
.wf-name { margin: 0; }
.wf-copy { margin: 0; }

/* ---------------------------------------------------------------- primitives */

/* The lattice. Real lines on the real tile pitch, so a plate placed on an
   intersection lands on one. */
.wf-grid path { fill: none; stroke: rgba(255,255,255,.92); stroke-width: 1.4; }

/* 1.41 with round cap and join, in every one of the nine source files. Never a
   half-transparent line — softness comes from colour, not from opacity. */
.wf-edge {
  fill: none;
  stroke-width: 1.41;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.wf-ghost path {
  fill: none;
  stroke: #7E9BCE;
  stroke-width: 1.7;
  stroke-dasharray: 5 5.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The travelling highlight, on the 30 degree axis. This is the one place the
   spec is absolute: the geometry underneath does not move, only the light. */
.wf-sheen {
  animation: wf-wash var(--wf-wash-dur) linear infinite;
  animation-delay: var(--sd, 0s);
}
@keyframes wf-wash {
  from { transform: translate(var(--wx), var(--wy)); }
  to   { transform: translate(calc(var(--wx) * -1), calc(var(--wy) * -1)); }
}

/* The cursor floats; it never lands and never casts a shadow. */
.wf-bob { animation: wf-bob 3.2s var(--wf-inout) infinite; }
@keyframes wf-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}


@keyframes s1-fly {
  0% { transform: translate(770px, 480px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  12.5% { transform: translate(419.5px, 179px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  18.8% { transform: translate(400.5px, 168px); }
  31.2% { transform: translate(400.5px, 168px); animation-timing-function: cubic-bezier(.83, 0, .17, 1); }
  41.2% { transform: translate(411.7px, 282.5px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  47.5% { transform: translate(400.5px, 276px); }
  67.5% { transform: translate(400.5px, 276px); animation-timing-function: cubic-bezier(.64, 0, .78, 0); }
  80% { transform: translate(-160px, -100px); }
  100% { transform: translate(-160px, -100px); }
}
@keyframes s1-wings {
  0% { opacity: 0; }
  67.5% { opacity: 0; }
  69.4% { opacity: 1; }
  82.5% { opacity: 1; }
  86.2% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes s1-push-3 {
  0% { transform: translate(19.1px, 11px); }
  12.5% { transform: translate(19.1px, 11px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  18.8% { transform: translate(0px, 0px); }
  80% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.64, 0, .78, 0); }
  92.5% { transform: translate(19.1px, 11px); }
  100% { transform: translate(19.1px, 11px); }
}
@keyframes s1-push-1 {
  0% { transform: translate(11.3px, 6.5px); }
  41.2% { transform: translate(11.3px, 6.5px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  47.5% { transform: translate(0px, 0px); }
  80% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.64, 0, .78, 0); }
  92.5% { transform: translate(11.3px, 6.5px); }
  100% { transform: translate(11.3px, 6.5px); }
}
#s1-cursor { transform: translate(-160px, -100px); animation: s1-fly 8s linear infinite; }
#s1-cursor .wf-wings { animation: s1-wings 8s linear infinite; }
.s1-lean-3 { transform: translate(19.1px, 11px); animation: s1-push-3 8s linear infinite; }
.s1-lean-1 { transform: translate(11.3px, 6.5px); animation: s1-push-1 8s linear infinite; }

@keyframes s2-fly {
  0% { transform: translate(770px, -120px); animation-timing-function: cubic-bezier(.83, 0, .17, 1); }
  28.6% { transform: translate(320px, 30.2px); }
  39.3% { transform: translate(301.3px, 23.9px); }
  40.6% { transform: translate(299px, 23.1px); }
  41.8% { transform: translate(296.8px, 22.4px); }
  43.1% { transform: translate(294.5px, 21.6px); }
  44.3% { transform: translate(292px, 20.7px); }
  45.6% { transform: translate(288.9px, 19.6px); }
  46.9% { transform: translate(284.7px, 18.2px); }
  48.1% { transform: translate(278.6px, 16.2px); }
  49.4% { transform: translate(269.6px, 13.1px); }
  50.7% { transform: translate(256.2px, 8.5px); }
  51.9% { transform: translate(236.6px, 1.9px); }
  53.2% { transform: translate(208.4px, -7.7px); }
  54.5% { transform: translate(168.9px, -21.2px); }
  55.7% { transform: translate(114.6px, -39.7px); }
  57% { transform: translate(41.6px, -64.5px); }
  58.3% { transform: translate(-54.8px, -97.4px); }
  59.5% { transform: translate(-180px, -140px); }
  100% { transform: translate(-180px, -140px); }
}
@keyframes s2-wings {
  0% { opacity: 0; }
  45.2% { opacity: 0; }
  48.8% { opacity: 1; }
  56% { opacity: 1; }
  59.5% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes s2-drop {
  0% { transform: translate(770px, 12.8px); animation-timing-function: cubic-bezier(.83, 0, .17, 1); }
  28.6% { transform: translate(320px, 163px); animation-timing-function: cubic-bezier(.83, 0, .17, 1); }
  40.5% { transform: translate(320px, 225px); }
  100% { transform: translate(320px, 225px); }
}
@keyframes s2-drop-o {
  0% { opacity: 0; }
  3.6% { opacity: 0; }
  6% { opacity: 1; }
  65.5% { opacity: 1; }
  72.6% { opacity: 0; }
  100% { opacity: 0; }
}
@keyframes s2-ghost-o {
  0% { opacity: 1; }
  36.7% { opacity: 1; }
  40.5% { opacity: 0; }
  65.5% { opacity: 0; }
  72.6% { opacity: 1; }
  100% { opacity: 1; }
}
@keyframes s2-pop1 {
  0% { transform: translate(0px, 0px); }
  61.9% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  65% { transform: translate(0px, -12.2px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  75% { transform: translate(0px, 0px); }
  100% { transform: translate(0px, 0px); }
}
.s2-r1 { animation: s2-pop1 4.2s linear infinite; }
@keyframes s2-pop2 {
  0% { transform: translate(0px, 0px); }
  65% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  68.1% { transform: translate(0px, -9.5px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  78.1% { transform: translate(0px, 0px); }
  100% { transform: translate(0px, 0px); }
}
.s2-r2 { animation: s2-pop2 4.2s linear infinite; }
@keyframes s2-pop3 {
  0% { transform: translate(0px, 0px); }
  68.1% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  71.2% { transform: translate(0px, -7.4px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  81.2% { transform: translate(0px, 0px); }
  100% { transform: translate(0px, 0px); }
}
.s2-r3 { animation: s2-pop3 4.2s linear infinite; }
@keyframes s2-pop4 {
  0% { transform: translate(0px, 0px); }
  71.2% { transform: translate(0px, 0px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  74.3% { transform: translate(0px, -5.8px); animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  84.3% { transform: translate(0px, 0px); }
  100% { transform: translate(0px, 0px); }
}
.s2-r4 { animation: s2-pop4 4.2s linear infinite; }
#s2-cursor { transform: translate(-180px, -140px); animation: s2-fly 4.2s linear infinite; }
#s2-cursor .wf-wings { animation: s2-wings 4.2s linear infinite; }
#s2-drop { transform: translate(var(--rx), var(--ry)); opacity: 0; animation: s2-drop 4.2s linear infinite, s2-drop-o 4.2s linear infinite; }
.s2-ghost { animation: s2-ghost-o 4.2s linear infinite; }

/* ------------------------------------------------------------------ bindings */
/* Every animation runs on a linear master curve because the easing lives on the
   individual keyframe stops — putting a curve here too would apply it twice. */

.wf-1 .wf-edge { stroke: url(#s1-line); }
.wf-2 .wf-edge { stroke: url(#s2-line); }
.wf-3 .wf-edge { stroke: url(#s3-line); }

/* Scenes 1 and 2 bind their own animations from the generator: the loop length
   and the ring count are data, and a duration written by hand here goes stale
   without any error the moment a scene is retimed. */

/* The carried plate wears the mark. Black is legal here: the nine source files
   use exactly two flat fills, #ffffff and #000000, and take every other colour
   from a gradient. */
.s2-mark { opacity: 1; }

/* scene 3 -------------------------------------------------- the elastic line */
/* No @keyframes: a rope that sags under drag and snaps taut is a spring, and
   CSS cannot interpolate a path's d in Firefox. js/who.js drives it.

   The markup is the SETTLED state — four ropes closed, every node lit, cursor
   parked off-frame. who.js un-settles it on init. So with JS off, or reduced
   motion on, the scene is already finished rather than half-drawn. */
.s3-rope  { fill: none; stroke-width: 2.8; stroke-linecap: round; }
.s3-pulse { fill: none; stroke-width: 3.6; stroke-linecap: round; opacity: 0; }
.s3-node  { fill: #007EEF; stroke: #FFFFFF; stroke-width: 1.6; }

/* ---------------------------------------------------------------- the gating */
/* Pause by play-state, never by removing the animation: removing it restarts
   the sweep from its first frame, so an observer flickering near its threshold
   makes the light visibly jump.

   Note the polarity. Running is the DEFAULT and js/who.js only ever adds
   .is-idle. Gating the other way round — paused by default, opted in by script —
   means that if the script fails to load, every scene freezes on frame 0: an
   empty socket, a leaning stack, no cursor anywhere. That is not a still of the
   animation, it is the animation broken. This way a missing script costs a
   little battery and nothing else.

   The universal selector is deliberate: it cannot miss an animation the way an
   explicit list can.

   ⚠️ The !important is load-bearing, and the reason is specificity. The cursors
   are bound by id — `#s1-cursor`, `#s2-cursor`, `#s2-drop` — and an id beats
   this rule's three classes. Worse, they bind through the `animation`
   shorthand, which resets animation-play-state to its initial `running`. So
   without !important the gate paused every scene EXCEPT its cursor: the cursor
   flew on out of view while the plates it pushes stood still, and came back N
   seconds ahead of them. Nothing resynced it short of a reload, because a
   paused animation keeps its own clock. Measured on a cold load: cursor at
   9008ms, its own leans at 57ms. */
.wf.is-idle .wf-svg * { animation-play-state: paused !important; }

/* -------------------------------------------------------------- reduced motion */
/* Not "the same thing, slower" — the finished state, held. The stack is square,
   the plate is seated, the circuit is closed. The cursor is gone because a
   frozen cursor mid-push reads as a bug rather than as a still. */
@media (prefers-reduced-motion: reduce) {
  .wf-svg * { animation: none !important; }
  #s1-cursor, #s2-cursor, #s3-cursor,
  .s2-ghost { display: none; }
  #s2-drop { transform: translate(var(--rx), var(--ry)); opacity: 1; }
}

/* Breakpoints last, always. A rule appended after them wins over them silently
   — bento.css carries the same warning at the top of the file. */
@media screen and (max-width: 991px) {
  .wf-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media screen and (max-width: 767px) {
  .wf-row { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
  .wf-band { padding: 1.875rem 1.25rem; }
  /* No cap at all in one column. A 40rem cap capped from the left, which kept
     the heading and the scene on one left edge but left the RIGHT edge short of
     the band's padding: at 760px that was 20px of padding on the left against
     100px on the right. Nothing here needs the cap — the stage is a picture,
     and the prose keeps its own 50ch on .wf-text — so the card spans the band
     and both edges land on the same padding. */
  .wf { max-width: none; }
}
