/* ==========================================================================
   PLATFORM · A TWIN PER SOFTWARE STATE  ·  #platform-states
   ==========================================================================
   The last band before Get started, and the only one on /platform that puts
   a photograph on the page. A vehicle with the twin drawn over it, and a
   small deck of software states next to it: same car, different system, and
   a model re-derived for each.

   WHAT CHANGED ON 2026-09-15, and why the old rules are not here any more.

   ONE SCREEN. The founder's requirement for this section, and the whole
   shape of it. What stood here was FIVE cards, each a full 5-domain x
   4-supplier matrix — twenty cells per card, most of them em dashes — over
   the same scene. That is a dataset: you had to hold five tables in your
   head to see the diff, and the band ran to three screens. It is now THREE
   states and THREE components, one line each, and the line that changed is
   the only bright thing on the card. The car is untouched, because the car
   is the anchor: it is what says "one vehicle" while the cards change.
   Everything the matrix needed is gone with it — .sws-card__table, the
   supplier columns, .sws-cell--none/--new/--dropped, .sws-sup--new, the
   delta badge variants. Dead rules in a stylesheet get copied forward.

   A LIGHT BAND. It used to be .band--dark. The page it now lives on runs
   dark from the hero to the roadmap and then turns for Get started, and
   this band turns with it. Everything OUTSIDE the photograph was re-keyed
   for a light ground — the caption, the step buttons, the heading — and
   everything INSIDE it was not, because the inside of the frame is a
   photograph and still dark. The one thing to watch when editing: teal at
   .42 alpha reads on a photograph and disappears on white, so no rule below
   uses a scene colour outside .sws-scene.

   THE PLATE IS STILL A PLACEHOLDER. /assets/hero/sw-state-plate.* does not
   exist; until it does we reuse the twin-hero plate MIRRORED, which puts the
   car in the right third and clears the left for the deck. When the real
   render lands, swap the <picture> sources in platform/index.html, delete
   .sws-scene__plate--mirrored below, and re-tune the node coordinates in the
   markup. Nothing else changes.

   THE MARKUP IS AUTHORED ASSEMBLED — card 1 in front, the twin drawn, the
   status line settled. js/blocks/platform-states.js adds .is-anim, and the
   animated half of this file hangs off that one class. No JavaScript, a
   thrown script, an old browser or prefers-reduced-motion therefore all land
   on a finished, readable picture rather than on an empty box.

   Tokens are main.css's own: --teal --teal-ink --text --muted --line
   --accent (an alias of --teal) --accent-soft --card-hover-border --mono
   --font. design-tokens.css is NOT linked by any page.
   ========================================================================== */

/* A LIGHTER GROUND THAN GET STARTED, which sits at #fbfcfd directly below.
   Two identical light bands in a row read as one long band with a heading in
   the middle; a half-step apart, they read as two sections. */
.sws { background: #fff; }

.sws__grid {
  display: grid;
  /* the figure carries the argument, so it takes the larger share */
  grid-template-columns: minmax(0, .6fr) minmax(0, 1.4fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
}

.sws__note {
  margin: 18px 0 0;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1.7;
}

.sws__figure { margin: 0; min-width: 0; }

/* ---- the aspect-locked box -------------------------------------------------
   941/1672 = 56.28%. Locked with padding-top rather than aspect-ratio so the
   percentage-positioned children have a resolved box to measure against in
   every browser that reaches this page. */
.sws-scene__frame {
  position: relative;
  width: 100%;
  padding-top: 56.28%;
  overflow: hidden;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: #07090d;
  /* The deck's type scales with the plate, not with the viewport. */
  container-type: inline-size;
}

/* The plate is authored AFTER the deck so that the narrow layout can stack
   deck-then-plate in reading order without reordering anything. Which means
   the paint order has to be stated explicitly here, or the photograph covers
   the cards. */
.sws-scene__plate-wrap { position: absolute; inset: 0; z-index: 1; }

.sws-scene__plate,
.sws-scene__plate img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

/* Placeholder only — see the file header. */
.sws-scene__plate--mirrored img { transform: scaleX(-1); }

/* A scrim over the left of the plate: it gives the deck a ground to sit on,
   so the cards read as paper on a dark surface rather than as panels
   floating in a photograph, and it sinks whatever the plate holds on that
   side — on the placeholder, the twin-hero monitor the mirror leaves there. */
.sws-scene__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    rgba(4, 6, 10, 0.97) 0%,
    rgba(4, 6, 10, 0.93) 42%,
    rgba(4, 6, 10, 0.4) 56%,
    rgba(4, 6, 10, 0) 70%
  );
}

/* ==========================================================================
   THE TWIN OVER THE VEHICLE
   ==========================================================================
   Five domain nodes sitting on the body zones of the car, and the edges
   between them. NODE COORDINATES ARE TUNED TO THE PLACEHOLDER PLATE and are
   authored in the markup in one clearly-marked block for that reason.
   ========================================================================== */

.sws-tw {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.sws-tw__edge {
  fill: none;
  stroke: rgba(78, 205, 196, 0.42);
  stroke-width: 2;
  stroke-linecap: round;
}

.sws-tw__node {
  fill: rgba(7, 9, 13, 0.86);
  stroke: rgba(78, 205, 196, 0.85);
  stroke-width: 2.4;
}

.sws-tw__halo { fill: rgba(78, 205, 196, 0.14); }

.sws-tw__tag {
  fill: rgba(244, 245, 247, 0.9);
  font: 600 19px/1 var(--font);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The status line under the car. Authored settled. */
.sws-status {
  position: absolute;
  left: 62%;
  bottom: 5.5%;
  width: 34%;
  margin: 0;
  font-size: clamp(10px, 1.05cqw, 14px);
  line-height: 1.5;
  color: rgba(244, 245, 247, 0.72);
  letter-spacing: 0.02em;
}

.sws-status__dot {
  display: inline-block;
  width: 0.55em;
  height: 0.55em;
  margin-right: 0.5em;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0.25em rgba(78, 205, 196, 0.18);
  vertical-align: baseline;
}

.sws-status__state { color: rgba(244, 245, 247, 0.94); font-weight: 600; }

/* ==========================================================================
   THE DECK  ·  three software states, three components each
   ==========================================================================
   The cards do not reflow: each sits in the same absolutely-positioned box
   and is placed by a depth class, so advancing the deck is a class swap and
   nothing is measured.
   ========================================================================== */

/* THE DECK BOX. Height was 52%, which made the card shorter than its own
   content: the header, three rows and the delta badge added up to more than
   the box, so the footer pushed through the bottom padding and the badge sat
   2px off the border while the other three sides had 18. The card is
   absolutely positioned to inset:0, so nothing was going to reflow it — the
   box had to grow. 56% leaves the badge the same room the header has.

   Top comes down from 10% to 12% because the fan is nine sheets deep now and
   runs down-right; at 10% the mass sat high in the frame with a third of the
   plate empty beneath it. */
.sws-deck {
  position: absolute;
  left: 3.5%;
  top: 12%;
  width: 56%;
  height: 56%;
  z-index: 3;
  perspective: 1200px;
}

.sws-card {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 1.05rem 1.15rem 1.05rem;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background:
    linear-gradient(168deg, rgba(24, 30, 42, 0.96) 0%, rgba(13, 17, 25, 0.97) 100%);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  color: rgba(244, 245, 247, 0.92);
  font-size: clamp(12px, 1.5cqw, 19px);
  transform-origin: 12% 92%;
}

/* ---- depth ---------------------------------------------------------------
   Three cards, so three positions. The rotations alternate in sign so the
   stack reads as paper someone put down in a hurry, not as a fanned deck. */
/* AS DEEP AS THE MARKUP GOES. The depth used to be four hand-written classes,
   which capped the pile at five cards however many were authored. It is a
   custom property now: the script writes --d (how many states landed after
   this one) and --rot, and the transform is computed from them, so adding a
   tenth card is a markup change and nothing else — which is what the file
   claimed all along.

   Offsets are bigger than the old ones. At 2.4% the pile read as one card with
   a soft edge; the point of this deck is that states ACCUMULATE, so the ones
   underneath have to be countable.

   The d1-d4 classes stay for the no-JS fallback, where the markup authors a
   static stack and nothing is setting custom properties. */
.sws-card--d1 { transform: translate(3.2%, 3.4%) rotate(1.8deg) scale(0.982); }
.sws-card--d2 { transform: translate(6.4%, 6.6%) rotate(-2.8deg) scale(0.962); }
.sws-card--d3 { transform: translate(9.4%, 9.6%) rotate(3.4deg) scale(0.943); }
.sws-card--d4 { transform: translate(12.2%, 12.4%) rotate(-3.9deg) scale(0.925); }

.sws-scene.is-anim .sws-card {
  transform:
    translate(calc(var(--d, 0) * 3.1%), calc(var(--d, 0) * 3.2%))
    rotate(var(--rot, 0deg))
    scale(calc(1 - var(--d, 0) * 0.019));
}

/* A pile has to have countable edges. Cards below the front one get a lighter
   rim and keep their drop shadow, so each one reads as a separate sheet
   rather than as a blur behind the top card. */
.sws-scene.is-anim .sws-card:not(.is-front) {
  border-color: rgba(255, 255, 255, 0.22);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.62), 0 0 0 1px rgba(0, 0, 0, 0.35);
}

.sws-scene.is-anim .sws-card.is-waiting {
  opacity: 0;
  transform: translate(-2%, -12%) scale(0.94);
  pointer-events: none;
}

.sws-scene.is-anim .sws-card.is-front,
.sws-card.is-front {
  transform: none;
  opacity: 1;
  z-index: 5;
  border-color: rgba(78, 205, 196, 0.4);
  box-shadow:
    0 22px 60px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(78, 205, 196, 0.14);
}

/* Cards behind the front one are legible as objects, not as text. */
.sws-card:not(.is-front) .sws-card__head,
.sws-card:not(.is-front) .sws-rows,
.sws-card:not(.is-front) .sws-card__foot {
  opacity: 0.22;
  filter: blur(1.6px);
}

.sws-card__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* THE WEEK IS THE SUBJECT. A software state is identified by WHEN it ran —
   that is what a release lead holds in their head and what the deck is
   counting — so the calendar week is the largest thing on the card and the
   state number is a reference under it, not the headline it used to be.
   Dates were 2026-01-14; nobody schedules a programme in days. */
.sws-card__wk { display: flex; align-items: baseline; gap: 0.4em;
  font-size: clamp(1.15rem, 1.5vw, 1.45rem); font-weight: 700;
  letter-spacing: -0.02em; line-height: 1; color: #f4f5f7; }
.sws-card__wk i { font-style: normal; font-size: 0.55em; font-weight: 500;
  letter-spacing: 0.06em; color: rgba(244, 245, 247, 0.45); }
.sws-card__id { font-family: var(--mono); font-size: 0.62rem; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: rgba(244, 245, 247, 0.5); }
.sws-card__date { color: rgba(244, 245, 247, 0.6); font-variant-numeric: tabular-nums;
  margin-left: auto; }

/* ---- the three lines -------------------------------------------------------
   One line per component: what it is, who delivers it, what version is in
   this state. A supplier is four words under a name here rather than a
   column of its own — the page argues supplier fragmentation in the
   automotive band and must not argue it twice. */
.sws-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.1em; }

.sws-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.5em;
  padding: 0.42em 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.sws-row:last-child { border-bottom: 0; }

.sws-row__n { font-weight: 600; color: rgba(244, 245, 247, 0.9); }
.sws-row__s {
  display: block;
  font-family: var(--mono);
  font-size: 0.74em;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(244, 245, 247, 0.48);
}
.sws-row__v {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: rgba(244, 245, 247, 0.62);
  white-space: nowrap;
}

/* THE ONLY BRIGHT THING ON THE CARD. A component that arrived with this
   state; everything carried over is dim. The reader catches which lines
   moved without reading a single number. */
.sws-row--changed .sws-row__v { color: var(--accent); font-weight: 700; }
.sws-row--changed .sws-row__v::before { content: "\0394\00a0"; opacity: .7; }

.sws-card__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 0.6em;
  flex-wrap: wrap;
  color: rgba(244, 245, 247, 0.62);
  font-family: var(--mono);
  font-size: 0.82em;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* THE DELTA IS THE POINT OF THE CARD. It was an outline pill in the same
   weight as everything else in the footer, so the one fact the deck exists to
   show — this state moved against the one before it — read as a caption. It is
   filled now, and it names the state it moved from: "2 of 3 moved" is a number
   about nothing until you can see what it is counted against. */
.sws-delta {
  padding: 0.24em 0.72em;
  border-radius: 999px;
  border: 1px solid rgba(78, 205, 196, 0.55);
  background: rgba(78, 205, 196, 0.16);
  color: var(--accent);
  font-weight: 700;
}
.sws-delta b { font-weight: 600; opacity: .78; }
.sws-delta--none { border-color: rgba(255, 255, 255, 0.18); color: rgba(244, 245, 247, 0.6); }

/* ==========================================================================
   THE ANIMATED HALF  ·  everything below needs .is-anim
   ========================================================================== */

.sws-scene.is-anim .sws-card {
  transition:
    transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 380ms ease,
    border-color 380ms ease,
    box-shadow 380ms ease;
}

/* The card being retired throws itself off to the left before it drops to
   the back of the stack. The class is removed when the transition ends. */
.sws-scene.is-anim .sws-card.is-leaving {
  transform: translate(-26%, -7%) rotate(-9deg) scale(0.97);
  opacity: 0;
  z-index: 6;
}

/* Changed lines arrive when their card comes to the front. Keyed to
   .is-front, so stepping back to a card replays it. */
.sws-scene.is-anim .sws-card.is-front .sws-row--changed .sws-row__v {
  animation: sws-arrive 700ms cubic-bezier(0.22, 0.61, 0.36, 1) 420ms 1 both;
}

@keyframes sws-arrive {
  0%   { opacity: 0; transform: translateX(34%); }
  100% { opacity: 1; transform: translateX(0); }
}

/* ---- the twin re-deriving ------------------------------------------------
   Not a recolor: the edges are re-drawn from zero length, so you watch the
   model be rebuilt from that state's own runtime rather than watch a light
   change. The dash length is longer than any edge in the graph, so one value
   covers all of them. */
.sws-scene.is-anim .sws-tw__edge {
  stroke-dasharray: 900;
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 620ms cubic-bezier(0.33, 0.8, 0.4, 1) 140ms;
}

.sws-scene.is-anim.is-rebuilding .sws-tw__edge {
  stroke-dashoffset: 900;
  transition: stroke-dashoffset 0ms;
}

.sws-scene.is-anim.is-rebuilding .sws-tw__node,
.sws-scene.is-anim.is-rebuilding .sws-tw__tag,
.sws-scene.is-anim.is-rebuilding .sws-tw__halo { opacity: 0.18; }

.sws-scene.is-anim .sws-tw__node,
.sws-scene.is-anim .sws-tw__tag,
.sws-scene.is-anim .sws-tw__halo { transition: opacity 420ms ease 380ms; }

/* The domains a card actually touched settle last and brighter. */
.sws-scene.is-anim .sws-tw__unit.is-hot .sws-tw__node {
  stroke: #ffd9a8;
  stroke-width: 3;
}
.sws-scene.is-anim .sws-tw__unit.is-hot .sws-tw__halo { fill: rgba(255, 217, 168, 0.2); }

.sws-scene.is-anim .sws-tw__unit .sws-tw__node,
.sws-scene.is-anim .sws-tw__unit .sws-tw__halo {
  transition: stroke 420ms ease, fill 420ms ease, opacity 420ms ease 380ms;
}

.sws-scene.is-anim.is-rebuilding .sws-status__state { opacity: 0.35; }
.sws-scene.is-anim .sws-status__state { transition: opacity 300ms ease; }

/* ==========================================================================
   CONTROLS AND CAPTION  ·  outside the frame, so keyed for the LIGHT band
   ========================================================================== */




/* On the light band the selected pill cannot be white-on-teal-tint: it has
   to be the darkest text in the row, not the lightest. */


.sws__caption {
  max-width: 52rem;
  margin: 0.9rem 0 0;
  color: var(--muted);
  font-size: 0.86rem;
  line-height: 1.6;
}

/* ==========================================================================
   NARROW
   ==========================================================================
   Below 900px the copy and the figure stop sitting side by side, and the
   deck stops overlaying the plate: it sits above it at full width, and the
   plate keeps the car and the twin. Reading order in the markup is already
   deck-then-plate, so nothing is reordered here.
   ========================================================================== */

@media (max-width: 900px) {
  /* ONE SCREEN HAS TO SURVIVE THE STACK. Side by side, the copy and the
     figure share the viewport's height; stacked, they add up, and the band
     went half a screen over. Every number below is that overshoot being paid
     back — the band's own padding, the heading, the deck, and the crop on the
     photograph. Nothing is hidden to make it fit: the section says the same
     things at 400px as it does at 1440. */
  .sws { padding-block: clamp(22px, 3.4vh, 36px); }
  .sws__grid { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .sws__grid .iap-eyebrow { margin-bottom: 8px; }
  .sws__grid .iap-h2 { margin-bottom: 10px; font-size: 23px; line-height: 1.14; }
  .sws__note { margin-top: 12px; }

  /* THE CARD HAS TO BE A CARD. The deck spanned the frame edge to edge, so
     the card's right-hand border, its rounded corners and its shadow were all
     sliced off by the frame's overflow:hidden — it read as a panel that had
     outgrown its box. It is inset instead, and the photograph keeps the wide
     strip below it, where the whole machine is visible; cropping the plate
     square to use it as a backdrop showed only wheels and ground, because the
     card covers exactly the band the car occupies.

     The two sit flush, no gap between them: the card is a thing lying ON the
     photograph, and a 9px seam between the two made them read as two pieces
     that had come apart. */
  .sws-scene__frame {
    padding-top: 0;
  }

  .sws-deck {
    position: relative;
    left: auto;
    top: auto;
    width: auto;
    /* Tall enough for the card's own content — head, three lines and the
       delta pill — AT THIS TYPE SIZE. The card is set at 13px here and needs
       243px for it; the deck was fixed at 12.4rem/198px, which is the height
       the SMALLER card below 560px needs, so every width from 561 to 900 cut
       the delta pill and the last row off. The narrow block sets its own. */
    height: 15.6rem;
    margin: 0.7rem 0.7rem 0;
    font-size: 13px;
  }

  /* the plate is cropped rather than shrunk: object-fit keeps the car, and
     the empty sky above it is what the band could least afford */
  .sws-scene__plate-wrap {
    position: relative;
    width: 100%;
    padding-top: 34%;
    margin-top: 0.7rem;
  }

  .sws-card { font-size: 13px; }

  /* THE FAN GETS SHALLOWER. The stacked cards behind the front one fan out to
     12.2% of the card's width — 43px at this size — which put the back of the
     deck 25px past the frame, where overflow:hidden sliced it. The fan is
     depth, not information, so it loses what it has to; the front card, which
     is the one being read, is untouched. */
  .sws-card--d1 { transform: translate(1.4%, 1.5%) rotate(1.2deg) scale(0.986); }
  .sws-card--d2 { transform: translate(2.8%, 2.9%) rotate(-1.9deg) scale(0.972); }
  .sws-card--d3 { transform: translate(4.1%, 4.2%) rotate(2.3deg) scale(0.958); }
  .sws-card--d4 { transform: translate(5.3%, 5.4%) rotate(-2.6deg) scale(0.945); }

  /* The waiting pile is offset up and to the LEFT, which puts it past the
     frame's left edge once the deck is inset. It is transparent at rest, so
     nothing shows — but it fades up as it becomes the front card, and for
     those frames it was a card with its left edge sliced off. Straight up
     instead: scale(0.94) already insets it evenly on both sides. */
  .sws-scene.is-anim .sws-card.is-waiting {
    transform: translate(0, -8%) scale(0.94);
  }
  /* the cropped plate leaves the status line over the darkest part of the
     photograph, where it was barely legible. It gets its own ground here
     rather than a brighter colour, so it still reads as something printed on
     the picture. */
  .sws-status {
    left: 4%;
    right: auto;
    width: auto;
    bottom: 4%;
    display: inline-flex;
    align-items: baseline;
    font-size: 11px;
    padding: 0.3em 0.75em;
    border-radius: 999px;
    background: rgba(4, 6, 10, 0.66);
    color: rgba(244, 245, 247, 0.85);
  }
  .sws__caption { margin-top: 0.7rem; }
}

@media (max-width: 560px) {
  /* THE CARD WAS BEING CUT. Its content measured 206px inside a deck fixed at
     11.4rem/182px, so the delta pill and the last row were clipped by the
     frame's overflow:hidden — the reader saw a card with its bottom sliced
     off. Paid back from both sides: the rows and the padding give up what
     they can, and the deck takes the small remainder.

     THE WEEK STAYS FULL SIZE. It is the one thing this card is for — which
     software state you are looking at — so the tightening is taken from the
     rows around it, not from the heading. */
  .sws-deck { height: 12.4rem; }
  .sws-card { font-size: 11.5px; padding: 0.62rem 0.72rem 0.55rem; gap: 0.24rem; }
  .sws-row { padding: 0.2em 0; }
  .sws__caption { font-size: 0.78rem; line-height: 1.5; }
}
