/* The faces are linked from the document head, not imported here. Behind an
   @import the browser has to fetch and parse this file before it even learns
   the face file exists — three round trips deep before a glyph is asked for,
   which is the flash of fallback text on a cold load. */

/* ==========================================================================
   Tokens — values taken from the Figma source
   ========================================================================== */
:root {
  --ink: #090c31;
  --indigo: #5962be;
  --sky: #53bdff;
  --blue: #1d98f6;

  --card-01: #3cc591;
  --card-02: #9cbb50;
  --card-03: #8b4a7c;
  --card-04: #1d9ef5;
  --card-05: #b9643c;

  /* Opening a card is a slow, soft move; button hovers are quick and crisp. */
  --card-speed: 0.85s;
  --card-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Shared so every button hover stays in step */
  --hover-speed: 0.2s;
  --hover-ease: cubic-bezier(0.65, 0, 0.35, 1);

  --serif: "Gelasio", Georgia, serif;
  --sans: "Geist", -apple-system, "Helvetica Neue", Arial, sans-serif;
  --display: "Cal Sans", var(--sans);
  --pixel: "Geist Pixel", ui-monospace, "SFMono-Regular", Menlo, monospace;

  /* Design canvas */
  --page: 1440px;
  --gutter: 48px;

  /* Ruled stripe pattern: 1.5px lines on a 6px pitch.
     Both are divided by the page zoom so a rule lands on a whole number of
     device pixels. At a fractional zoom, 1.5px covers 2.98 device pixels and
     the hairlines render soft and appear to crawl as they move. */
  --stripe-pitch: calc(6px / var(--page-zoom));
  --stripe-weight: calc(1.5px / var(--page-zoom));
}

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

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  background: #fff;
  color: var(--ink);
  font-family: var(--sans);
}

p,
h1,
h2,
h3 {
  margin: 0;
}

img {
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

/* The design is a 1440px canvas, and it is laid out fluidly: the coordinates
   that carry the composition are percentages of that canvas, so a wider or
   narrower desktop stretches rather than scales and the type holds its size.
   At exactly 1440 every one of them lands back on its design pixel.

   Nothing is ever scaled: the type holds its size at every desktop width and
   the sections stretch around it. The one exception is the Win95 dialog, which
   is a picture of an application and cannot reflow — `initHeroFit` shrinks it
   to whatever room the headline leaves, rather than the page shrinking the
   headline to make room for it.

   Below 1200 the stacked layout takes over; see the foot of this file. */
:root {
  --design-width: 1440;
  --page-zoom: 1;
  --page: 100%;
}

.page {
  position: relative;
  width: var(--page);
  /* The composition has nothing more to say past this, so it stops growing
     and centres. */
  max-width: 1920px;
  margin: 0 auto;
  overflow-x: clip;
  zoom: var(--page-zoom);
}


/* Below the clamp the canvas no longer fits, so allow it to scroll. */
@media (max-width: 1036px) {
  body {
    overflow-x: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* Vertical rule pattern used at several section edges.
   Column widths are whole multiples of the pitch and sit flush against the
   block they border, so the gap to the block equals the gap between rules. */
.stripes {
  --stripe-offset: 0px;
  position: relative;
  overflow: hidden;
}

/* The pattern lives on a layer that is translated, rather than being redrawn
   at a new background-position each frame. Animating background-position
   re-rasterises the gradient on a different sub-pixel phase every frame, which
   makes the hairlines shimmer and change weight as they move; translating a
   layer that was rasterised once keeps them identical throughout.
   The layer overhangs a full pitch on both sides so a ±1 pitch shift never
   uncovers an edge, and the pitch repeat keeps the phase continuous. */
.stripes::before {
  content: "";
  position: absolute;
  /* Overhangs vertically as well as sideways. The layer is promoted — it has
     `will-change: transform` and it drifts — so it rasterises to whole device
     pixels, while the box beside it is laid out at whatever fraction the
     content gives it. On a screen where that height is not a whole number of
     device pixels the layer rounds down and the rules stop a pixel or two
     short of the block; the parent clips, so the overhang costs nothing. */
  inset: -2px calc(var(--stripe-pitch) * -1);
  background-image: repeating-linear-gradient(
    to right,
    currentColor 0,
    currentColor var(--stripe-weight),
    transparent var(--stripe-weight),
    transparent var(--stripe-pitch)
  );
  background-position-x: var(--stripe-offset);
  animation: stripe-drift-left var(--stripe-speed, 2s) linear infinite;
  will-change: transform;
}

/* Drifting one full pitch loops seamlessly. */
@keyframes stripe-drift-left {
  to { transform: translateX(calc(var(--stripe-pitch) * -1)); }
}

@keyframes stripe-drift-right {
  to { transform: translateX(var(--stripe-pitch)); }
}

.stripes--rightward::before {
  animation-name: stripe-drift-right;
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  font-family: var(--sans);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}

/* Only the pills need clipping; on the outlined button it would eat the
   1px stroke. */
.btn-primary,
.btn-invert,
.btn-pill-sm {
  overflow: hidden;
}

.btn .caret {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  flex: none;
}

/* The filled buttons square off on hover — the outline button's move run in
   reverse, on the same 0.2s ease-out. Their resting radius is written as half
   their height rather than the design's nominal 152/88/184px: radius is capped
   at half the box, so animating from a value ten times larger would spend most
   of the transition invisibly clamped and read as a snap. */
.btn-primary,
.btn-invert,
.btn-pill-sm {
  transition: border-radius var(--hover-speed) ease-out;
}

.btn-primary:hover,
.btn-invert:hover,
.btn-pill-sm:hover {
  border-radius: 0;
}

/* Primary pill — indigo fill, white label */
.btn-primary {
  height: 44px;
  padding: 8px 24px;
  border-radius: 22px;
  background: var(--indigo);
  color: #fff;
  font-size: 16px;
}

/* Same pill inverted, used on the indigo deploy panel */
.btn-invert {
  height: 44px;
  padding: 8px 24px;
  border-radius: 22px;
  background: #fff;
  color: var(--ink);
  font-size: 16px;
}

/* Compact header variants */
.btn-sm {
  height: 36px;
  font-size: 14px;
}

/* `box-sizing: border-box` keeps this rule inside the 36px box. A real border
   rather than an inset shadow, because the shadow's corners re-raster on every
   frame and the radius transition reads as steppy.
   The width is divided by the page zoom: at a fractional zoom a flat 1px lands
   on 1.98 device pixels, which the browser floors to 1 — a visible hairline
   instead of a 1px stroke. Dividing it back out lands on a full 2. */
.btn-outline {
  height: 36px;
  padding: 8px 12px;
  border: calc(1px / var(--page-zoom)) solid var(--ink);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font-size: 14px;
  font-weight: 500;
  transition: border-radius var(--hover-speed) ease-out;
}

/* Rounds to a pill on hover. The target is half the height, not the pill's
   nominal 184px: radius is capped at half the box, so animating to 184px
   reaches the final shape about a tenth of the way in and the remaining
   0.22s is visually dead — it reads as a snap rather than an ease. */
.btn-outline:hover {
  border-radius: 18px;
}

.btn-pill-sm {
  height: 36px;
  padding: 8px 16px;
  border-radius: 18px;
  background: var(--indigo);
  color: #fff;
  font-size: 14px;
}

/* ==========================================================================
   Menu bar
   ========================================================================== */
/* Pinned to the viewport, and pulled out of the way once the footer arrives —
   the footer carries its own copy of the nav actions, so keeping this one on
   screen would just duplicate them. */
.menubar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: transform 0.35s var(--card-ease);
  display: flex;
  align-items: center;
  gap: 48px;
  height: 62px;
  padding: 0 var(--gutter);
  background: #fff;
}

/* Dims what is under the open panel. Absolute against the fixed bar and
   starting at its foot, so the bar itself stays clean without having to know
   its own height. */
.menu-scrim {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 100vh;
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.menubar.is-open .menu-scrim {
  opacity: 0.4;
  pointer-events: auto;
}

/* The three-bar button, which only exists once the links come off the bar. */
.menu-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  /* The glyph is drawn at 0.7 of its first size — 1.4 + 4.2 + 1.4 across
     16.8 — while the button keeps its 36px target, so the icon shrank and the
     thing you press did not. The bars' centres land 2.8px either side of the
     middle, which is the translation that closes them into an X. */
  gap: 4.2px;
  width: 36px;
  height: 36px;
  padding: 0 9.6px;
  border: 0;
  background: none;
  cursor: pointer;
}

.menu-toggle span {
  display: block;
  height: 1.4px;
  background: var(--ink);
  transition: transform 0.2s ease, opacity 0.2s ease;
}

/* Open, the two bars cross. */
.menubar.is-open .menu-toggle span:nth-child(1) { transform: translateY(2.8px) rotate(45deg); }
.menubar.is-open .menu-toggle span:nth-child(2) { transform: translateY(-2.8px) rotate(-45deg); }

.menubar.is-hidden {
  transform: translateY(-100%);
  pointer-events: none;
}

/* The bar no longer takes part in flow, so the hero reclaims its height. */
.hero {
  padding-top: 0;
}

.brand {
  display: flex;
  align-items: center;
  gap: 5.154px;
  flex: none;
}

.brand img {
  width: 29.333px;
  height: 29.333px;
}

.brand span {
  font-family: var(--display);
  font-size: 23.994px;
  letter-spacing: 0.2399px;
  color: var(--ink);
}

.menubar nav {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: flex-end;
  gap: 24px;
  height: 100%;
}

.menubar nav a {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: 14px;
  letter-spacing: 0.14px;
  text-align: center;
}

.menubar nav a.is-active {
  color: var(--indigo);
  font-weight: 500;
}

.menubar nav a:hover {
  color: var(--indigo);
}

/* Caret marking the current page. It slides to whichever item is hovered and
   returns to the active one on leave — see nav indicator in main.js. */
.nav-arrow {
  position: absolute;
  top: 0;
  left: 0;
  width: 12px;
  height: 6px;
  background: var(--indigo);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform: translateX(-50%);
  transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

/* Its first placement is where it belongs, not a journey from x=0. */
.nav-arrow:not(.is-ready) {
  transition: none;
}

/* Until measured, keep it out of sight rather than parked at the left edge. */
.nav-arrow:not(.is-ready) {
  opacity: 0;
}

.menubar .actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  position: relative;
  height: 860px;
}

/* While the agent is dealing with the interruption the backdrop reacts: the
   sky turns and the rules pick up speed.

   The colour eases over 0.3s — short enough that the masked rules repaint for
   only a few frames, which is what the earlier 0.6s cross-fade got wrong.
   The speed is NOT changed here: rewriting `--stripe-speed` restarts the
   animation and jumps the rules. hero.js ramps each animation's playbackRate
   instead, which the compositor takes seamlessly. */
.hero.is-alarmed {
  --sky: #8b4a7c;
}

.hero-art .band .fill,
.hero-art .band .stripes,
.band-c {
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* The rules are a flat fill behind a static mask rather than a painted
   gradient, so the mask is rasterised once and only the colour underneath
   ever changes. */
.hero-art .stripes::before {
  background-image: none;
  background-color: currentColor;
  -webkit-mask-image: repeating-linear-gradient(
    to right,
    #000 0,
    #000 var(--stripe-weight),
    transparent var(--stripe-weight),
    transparent var(--stripe-pitch)
  );
  mask-image: repeating-linear-gradient(
    to right,
    #000 0,
    #000 var(--stripe-weight),
    transparent var(--stripe-weight),
    transparent var(--stripe-pitch)
  );
  -webkit-mask-position-x: var(--stripe-offset);
  mask-position-x: var(--stripe-offset);
}

/* --- Animated blocks (static for now) ---
   Three equal bands stepping left as they descend, each flush to the right
   edge of the canvas. */
.hero-art {
  position: absolute;
  top: 65px;
  left: 0;
  width: var(--page);
  height: 655px;
  border-bottom: 1px solid rgb(9 12 49 / 0.1);
}

/* Each step reaches 1px into the one below it. The bands are 218.333px tall
   and sit at fractional offsets, so on some widths a seam rounds to a hairline
   of paper between two blocks of colour. The step below is always the wider of
   the two and is painted after it, so the extra pixel is covered and only the
   seam disappears. The last one keeps its height — nothing sits under it. */
.hero-art .band {
  position: absolute;
  display: flex;
  align-items: center;
  height: 219.333px;
}

.hero-art .band-c { height: 218.333px; }

/* The fill takes whatever the rule column leaves, so the band keeps its
   design width while the rules stay flush. */
.hero-art .band .fill {
  height: 100%;
  flex: 1;
  background: var(--sky);
}

.hero-art .band .stripes {
  height: 100%;
  flex: none;
  color: var(--sky);
}

.band-a {
  top: 0;
  right: 0;
  width: 55.2778%;   /* 796 / 1440 */
}

.band-a .stripes {
  width: calc(var(--stripe-pitch) * 8);
}

.band-b {
  top: 218.333px;
  right: 0;
  width: 58.4028%;   /* 841 / 1440 */
}

.band-b .stripes {
  width: calc(var(--stripe-pitch) * 5);
}

.band-c {
  top: 436.667px;
  right: 0;
  width: 64.3056%;   /* 926 / 1440 */
  background: var(--sky);
}

/* ==========================================================================
   Legacy app in the hero
   A Win95-era dialog, authored at 100% in the design and scaled to 84.3%.
   Every measurement below is `--u` multiplied by its round 100% value, so the
   numbers stay readable and the output stays pixel-exact.
   ========================================================================== */
.legacy {
  position: absolute;
  top: 65px;
  /* A fixed composition that cannot stretch, so it holds its width and its
     distance from the right edge, and scales as a whole when the desktop is
     too narrow to seat it beside the headline. main.js sets the scale. */
  /* Centred on the ladder's top step. That step is band-a: right-anchored and
     55.2778% wide, so its centre sits at 72.3611% across and the box's own 330
     comes off that — which leaves 68px at 1440 and keeps the two centres
     together at any width. Pinned at a flat 49px the dialog held its distance
     from the paper's edge while the step kept growing with the page, and the
     two drifted apart: 96px adrift by 1720. */
  right: calc(27.6389% - 330px);
  width: 660px;
  /* Scaled about its top right corner, then nudged so the windows sit on the
     band's centre line — otherwise shrinking leaves all the slack underneath. */
  transform: translateY(var(--legacy-shift, 0px)) scale(var(--legacy-scale, 1));
  transform-origin: top right;
  height: 655px;
  z-index: 1;
  /* Not clipped: the windows all sit inside the box anyway, and the agent's
     cursor has to be free to reach their edges without losing half of itself.
     Scaled down on a phone the box is tight, and it showed there first. */
  overflow: visible;

  --u: 0.84313725px;
  --face: #d3d0c9;
  --shadow: #808080;
  --light: #dfdfdf;
  --dark: #0a0a0a;

  /* Classic two-step bevels */
  --raised:
    inset calc(var(--u) * -1) calc(var(--u) * -1) 0 0 var(--dark),
    inset var(--u) var(--u) 0 0 var(--light),
    inset calc(var(--u) * -2) calc(var(--u) * -2) 0 0 var(--shadow),
    inset calc(var(--u) * 2) calc(var(--u) * 2) 0 0 #fff;
  --raised-btn:
    inset calc(var(--u) * -1) calc(var(--u) * -1) 0 0 var(--dark),
    inset var(--u) var(--u) 0 0 #fff,
    inset calc(var(--u) * -2) calc(var(--u) * -2) 0 0 var(--shadow),
    inset calc(var(--u) * 2) calc(var(--u) * 2) 0 0 var(--light);
  --sunken:
    inset calc(var(--u) * -1) calc(var(--u) * -1) 0 0 #fff,
    inset var(--u) var(--u) 0 0 var(--shadow),
    inset calc(var(--u) * -2) calc(var(--u) * -2) 0 0 var(--light),
    inset calc(var(--u) * 2) calc(var(--u) * 2) 0 0 var(--dark);

  font-family: Tahoma, Verdana, Geneva, "DejaVu Sans", sans-serif;
  font-size: calc(var(--u) * 14);
  letter-spacing: calc(var(--u) * 0.14);
  color: var(--ink);
}

.legacy-win {
  position: absolute;
  background: var(--face);
  border: var(--u) solid rgb(0 0 0 / 0.1);
  box-shadow: var(--raised);
}

.legacy-win--behind {
  left: 35px;
  top: 70px;
  width: calc(var(--u) * 636);
  height: calc(var(--u) * 492);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* --- The records list behind the form (Figma 32:21) --- */
.legacy-menubar {
  display: flex;
  gap: calc(var(--u) * 12);
  width: 100%;
  padding: 0 calc(var(--u) * 12);
}

.legacy-menubar span {
  display: flex;
  align-items: center;
  height: calc(var(--u) * 28);
  padding: 0 calc(var(--u) * 4);
  font-size: calc(var(--u) * 15);
  letter-spacing: normal;
  color: #222;
}

.legacy-listwrap {
  display: flex;
  flex: 1;
  min-height: 0;
  width: 100%;
  padding: calc(var(--u) * 4) calc(var(--u) * 12);
}

.legacy-list {
  display: flex;
  flex: 1;
  min-height: 0;
  align-items: flex-start;
  justify-content: space-between;
  padding: 2px;
  overflow: hidden;
  background: #fff;
  box-shadow: var(--sunken);
}

.legacy-list-col {
  width: calc(var(--u) * 592);
  flex: none;
}

.legacy-list-head {
  display: flex;
  width: 100%;
}

/* Column headers are bevelled buttons, as they were. */
.legacy-th {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  height: calc(var(--u) * 24);
  padding: 0 calc(var(--u) * 8);
  background: var(--face);
  box-shadow: var(--raised-btn);
  font-size: calc(var(--u) * 11.8);
  letter-spacing: normal;
  color: #000;
}

/* Tighter than the design's 12px, which only had eight names to show. The
   list is meant to read as a real caseload. */
.legacy-tr {
  display: flex;
  padding: calc(var(--u) * 5) calc(var(--u) * 8);
}

/* Rows take the same selection blue as the dropdown lists. */
.legacy-tr:hover {
  background: #000080;
}

.legacy-tr:hover .legacy-td {
  color: #fff;
}

.legacy-td {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  color: #000;
}

.legacy-td--name,
.legacy-th.legacy-td--name { width: calc(var(--u) * 244); flex: none; }
.legacy-td--id,
.legacy-th.legacy-td--id   { width: calc(var(--u) * 96);  flex: none; }

/* The header sits inside the 2px inset, so the rows are what scroll under it. */
.legacy-list-body {
  width: 100%;
}

.legacy-list-scroll {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  flex: none;
  width: calc(var(--u) * 16);
}

.legacy-list-scroll .legacy-menu-arrow {
  height: calc(var(--u) * 16);
  background-size: calc(var(--u) * 13);
}

.legacy-list-thumb {
  height: calc(var(--u) * 9);
  flex: none;
  background: var(--face);
  box-shadow: var(--raised-btn);
}

.legacy-foot {
  display: flex;
  width: 100%;
  padding: calc(var(--u) * 10.12);
}

.legacy-btn--small {
  height: calc(var(--u) * 30.35);
  min-width: 0;
  padding: 0 calc(var(--u) * 15.18);
}

.legacy-win--front {
  left: 89.7px;
  top: 104px;
  width: calc(var(--u) * 636);
  height: calc(var(--u) * 572);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.legacy-titlewrap {
  padding: calc(var(--u) * 4);
  width: 100%;
}

.legacy-titlebar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: calc(var(--u) * 4) calc(var(--u) * 4) calc(var(--u) * 4) calc(var(--u) * 8);
  background: #7d7d7b;
  font-weight: 700;
  font-size: calc(var(--u) * 15);
  letter-spacing: calc(var(--u) * 0.15);
  color: #fff;
}

/* The focused window gets the classic navy bar */
.legacy-titlebar--active {
  background: #000080;
}

.legacy-close {
  width: calc(var(--u) * 20);
  height: calc(var(--u) * 20);
  flex: none;
  background: var(--face) url("./assets/win-close.svg") no-repeat center / calc(var(--u) * 10);
  box-shadow: var(--raised);
}

.legacy-body {
  width: 100%;
  padding: calc(var(--u) * 4) calc(var(--u) * 12) 0;
}

.legacy-fields {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 16);
  padding: calc(var(--u) * 20);
  background: var(--face);
  box-shadow: var(--raised);
}

.legacy-row {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 16);
  width: 100%;
}

.legacy-field {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--u) * 8);
  flex: 1;
  min-width: 0;
}

.legacy-field--id     { flex: none; width: calc(var(--u) * 180); }
.legacy-field--policy { flex: none; width: calc(var(--u) * 208); }

.legacy-label {
  width: 100%;
}

.legacy-group {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 6);
  width: 100%;
}

/* --- Controls --- */
.legacy-input,
.legacy-combo,
.legacy-spin,
.legacy-notes {
  position: relative;
  display: flex;
  align-items: center;
  height: calc(var(--u) * 28);
  background: #fff;
  box-shadow: var(--sunken);
}

/* Controls fill their field; the field owns the width. */
.legacy-input,
.legacy-notes {
  width: 100%;
}

.legacy-input {
  padding: 0 calc(var(--u) * 8);
}

.legacy-combo,
.legacy-spin {
  justify-content: space-between;
  padding-left: calc(var(--u) * 8);
  padding-right: calc(var(--u) * 4);
}

.legacy-combo         { width: 100%; }
.legacy-combo--grow   { flex: 1; min-width: 0; width: auto; }
.legacy-spin--day     { width: calc(var(--u) * 64); flex: none; }
/* The four-digit years need the extra room the design leaves them. */
.legacy-spin--year    { width: calc(var(--u) * 63 + 30px); flex: none; }

/* Values are clipped, never sized: filling a field must not move the layout. */
.legacy-input i,
.legacy-notes i,
.legacy-combo em,
.legacy-spin em {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.legacy-caret {
  width: calc(var(--u) * 20);
  height: calc(var(--u) * 20);
  flex: none;
  background: var(--face) url("./assets/win-caret.svg") no-repeat center / calc(var(--u) * 16);
  box-shadow: var(--raised-btn);
}

/* Stacked up/down pair inside a number field */
.legacy-spin-btns {
  display: flex;
  flex-direction: column;
  gap: var(--u);
  height: 100%;
  padding: calc(var(--u) * 4) 0;
  flex: none;
}

.legacy-spin-btns i {
  position: relative;
  flex: 1;
  width: calc(var(--u) * 20);
  background: var(--face);
  box-shadow: var(--raised-btn);
}

/* The glyph lives on a pseudo-element so the up arrow can be flipped without
   flipping the button's bevel with it. */
.legacy-spin-btns i::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("./assets/win-spin.svg") no-repeat center / calc(var(--u) * 10);
}

.legacy-spin-btns i.is-up::before {
  transform: scaleY(-1);
}

.legacy-notes {
  align-items: flex-start;
  height: calc(var(--u) * 60.82);
  /* The 12.45u line-height crops the first line's leading, which would start
     the text ~2px higher than a single-line field. The top padding gives that
     back so every control's first line sits at the same height. */
  padding: calc(var(--u) * 7.8) calc(var(--u) * 4) calc(var(--u) * 5.5) calc(var(--u) * 8);
  line-height: calc(var(--u) * 12.45);
}

/* Placeholder-style text */
.legacy-input i,
.legacy-notes i,
.legacy-combo em,
.legacy-spin em {
  font-style: normal;
}

.is-empty {
  opacity: 0.4;
}

/* --- Footer buttons --- */
.legacy-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(var(--u) * 9);
  width: 100%;
  padding: calc(var(--u) * 12);
}

.legacy-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--u) * 36);
  min-width: calc(var(--u) * 91);
  padding: calc(var(--u) * 5.5) calc(var(--u) * 18);
  background: var(--face);
  box-shadow: var(--raised-btn);
  /* Figma reports 9.95px on the nested button component, but that is in the
     component's own scale — the rendered label matches the dialog's 14u body
     size, which is what reproduces the 123.35px "Submit Encounter" width. */
  font-size: calc(var(--u) * 14);
  letter-spacing: normal;
  color: #000;
  white-space: nowrap;
}

/* Held-down button: the bevel inverts and the label nudges, exactly as it did. */
.legacy-btn.is-pressed {
  box-shadow: var(--sunken);
  padding-top: calc(var(--u) * 7.5);
  padding-left: calc(var(--u) * 20);
  padding-bottom: calc(var(--u) * 3.5);
  padding-right: calc(var(--u) * 16);
}

.legacy-btn.is-disabled {
  opacity: 0.43;
}

/* A control the agent has clicked into shows the classic caret. */
.legacy-input.is-focused::after,
.legacy-notes.is-focused::after,
.legacy-combo.is-focused::after,
.legacy-spin.is-focused::after {
  content: "";
  position: absolute;
  top: calc(var(--u) * 5);
  bottom: calc(var(--u) * 5);
  left: var(--caret-x, 0);
  width: 1.5px;
  background: var(--ink);
  animation: win-caret 1.06s steps(1, end) infinite;
}

/* The notes box is four lines tall; its caret is still one line. */
.legacy-notes.is-focused::after {
  top: calc(var(--u) * 7.8);
  bottom: auto;
  height: calc(var(--u) * 12.45);
}

@keyframes win-caret {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* --- Dropdown list (Figma 26:6225) ---------------------------------------
 * Built by hero.js and hung under the combo it belongs to. Its bevels are
 * whole 1px steps rather than `--u` fractions, so the outline stays crisp
 * instead of landing on half a device pixel.
 * ---------------------------------------------------------------------- */
.legacy-combo.is-open {
  z-index: 5;
}

.legacy-menu {
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 2px;
  z-index: 5;
  display: none;
  width: 100%;
  padding: 2px;
  background: #fff;
  border: 1px solid rgb(0 0 0 / 0.1);
  box-shadow:
    inset -1px -1px 0 0 var(--dark),
    inset 1px 1px 0 0 var(--light),
    inset -2px -2px 0 0 var(--shadow),
    inset 2px 2px 0 0 #fff;
}

.legacy-menu.is-open {
  display: flex;
  align-items: stretch;
}

/* Five rows at most, as the design shows — the rest is what the bar is for. */
.legacy-menu-list {
  flex: 1;
  min-width: 0;
  max-height: calc(var(--u) * 22 * 5);
  overflow: hidden;
}

.legacy-menu-item {
  display: flex;
  align-items: center;
  height: calc(var(--u) * 22);
  padding-left: calc(var(--u) * 4);
  white-space: nowrap;
  overflow: hidden;
  color: var(--ink);
}

.legacy-menu-item.is-hover {
  background: #000080;
  color: #fff;
}

/* The scroll column: two bevelled arrows with a grey track between them. */
.legacy-menu-scroll {
  display: flex;
  flex-direction: column;
  flex: none;
  width: calc(var(--u) * 20);
}

.legacy-menu-arrow {
  height: calc(var(--u) * 20);
  flex: none;
  background: var(--face) url("./assets/win-arrow-down.svg") no-repeat center / calc(var(--u) * 16);
  box-shadow: var(--raised-btn);
}

.legacy-menu-arrow.is-up {
  background-image: url("./assets/win-arrow-up.svg");
}

.legacy-menu-track {
  flex: 1;
  background: var(--light);
}

/* A combo whose list is open shows its caret held down. */
.legacy-combo.is-open .legacy-caret {
  box-shadow: var(--sunken);
}

/* ---- The form window opening and closing ---------------------------------
 * Windows 2000 zoomed an outline between the window's bounds and the thing it
 * came from. The same four-step outline runs in both directions here.
 * ---------------------------------------------------------------------- */
.legacy-formzoom {
  position: absolute;
  left: 89.7px;
  top: 104px;
  width: calc(var(--u) * 636);
  height: calc(var(--u) * 572);
  z-index: 2;
  border: 1px solid #4c4c4c;
  opacity: 0;
  pointer-events: none;
  transform-origin: 30% 90%;
}

.legacy.is-form-opening .legacy-formzoom {
  animation: win-zoom 0.13s steps(4, end) 1;
}

.legacy.is-form-closing .legacy-formzoom {
  animation: win-zoom-out 0.13s steps(4, end) 1;
}

@keyframes win-zoom-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 1; transform: scale(0.12); }
}

.legacy-win--front.is-gone {
  display: none;
}

/* ---- The session timeout dialog the agent runs into ---- */
.legacy-modal-layer {
  /* Sits over the front window, so the dialog centres on its parent the way
     a real modal child window would. */
  position: absolute;
  left: 89.7px;
  top: 104px;
  width: calc(var(--u) * 636);
  height: calc(var(--u) * 572);
  display: none;
  z-index: 4;
}

.legacy-modal-layer.is-open,
.legacy-modal-layer.is-zooming {
  display: block;
}

/* Windows 2000 opened a dialog by racing an outline out to its bounds. */
.legacy-zoom {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--u) * 336);
  height: calc(var(--u) * 140);
  margin: calc(var(--u) * -70) 0 0 calc(var(--u) * -168);
  border: 1px solid #4c4c4c;
  opacity: 0;
  transform: scale(0.12);
}

.legacy-modal-layer.is-zooming .legacy-zoom {
  animation: win-zoom 0.13s steps(4, end) 1;
}

@keyframes win-zoom {
  from { opacity: 1; transform: scale(0.12); }
  to   { opacity: 1; transform: scale(1); }
}

.legacy-modal {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--u) * 336);
  margin-left: calc(var(--u) * -168);
  transform: translateY(-50%);
  display: none;
  flex-direction: column;
  align-items: flex-start;
}

.legacy-modal-layer.is-open .legacy-modal {
  display: flex;
}

/* Alert dialogs took a red caption bar. */
.legacy-titlebar--alert {
  background: #d8325f;
}

/* The parent window gives up focus while the dialog is up, so its caption
   drops to the inactive grey. */
.legacy-win--front.is-blurred .legacy-titlebar--active {
  background: #7d7d7b;
}

.legacy-modal-body {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 8);
  width: 100%;
  padding: calc(var(--u) * 12);
}

.legacy-modal-icon {
  width: calc(var(--u) * 40);
  height: calc(var(--u) * 40);
  flex: none;
  margin: 0 calc(var(--u) * 12);
  background: url("./assets/win-question.svg") no-repeat center / contain;
}

.legacy-modal-body p {
  flex: 1;
  min-width: 0;
}

.legacy-modal-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--u) * 9);
  width: 100%;
  padding: 0 calc(var(--u) * 12) calc(var(--u) * 12);
}

/* The default button — the one Enter would press — carried a black frame
   around its bevel. A whole 1px, so it stays crisp at any zoom. */
.legacy-btn--default,
.legacy-btn[data-submit]:not(.is-disabled) {
  box-shadow: var(--raised-btn), 0 0 0 1px #0a0a0a;
}

/* …and it keeps that frame while it is held down. */
.legacy-btn--default.is-pressed,
.legacy-btn[data-submit]:not(.is-disabled).is-pressed {
  box-shadow: var(--sunken), 0 0 0 1px #0a0a0a;
}

/* Hovered — or, for the agent, targeted — buttons show the focus rect Windows
   drew just inside the bevel. `border-style: dotted` gives no control over the
   dash, so the four edges are drawn as repeating gradients instead. */
.legacy-btn:not(.is-disabled):hover::after,
.legacy-btn.is-hover::after {
  --dash: 1.5px;
  --dash-gap: 2.5px;
  --dash-step: calc(var(--dash) + var(--dash-gap));

  content: "";
  position: absolute;
  inset: calc(var(--u) * 4);
  background-image:
    repeating-linear-gradient(90deg, #0a0a0a 0 var(--dash), transparent var(--dash) var(--dash-step)),
    repeating-linear-gradient(90deg, #0a0a0a 0 var(--dash), transparent var(--dash) var(--dash-step)),
    repeating-linear-gradient(0deg,  #0a0a0a 0 var(--dash), transparent var(--dash) var(--dash-step)),
    repeating-linear-gradient(0deg,  #0a0a0a 0 var(--dash), transparent var(--dash) var(--dash-step));
  background-size: 100% 1px, 100% 1px, 1px 100%, 1px 100%;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  background-repeat: no-repeat;
}

/* Pointer and its label, over the dialog */
.hero-pointer {
  /* Both shapes are drawn from their 24/28px source at this scale, offsets
     included, so the hotspot stays put whatever size they are. Sized to sit
     alongside the real system cursor without looking small. */
  --cursor-scale: 1.25;
  /* Inside `.legacy`, so the cursor and its targets share one coordinate
     space however the dialog is scaled to fit a narrow window. */
  position: absolute;
  top: 200px;
  left: 210px;
  /* Above every window in the stack, including the modal layer's 4 — a real
     cursor is never behind a dialog. */
  z-index: 10;
  /* The cursor must not shadow the dialog from `elementFromPoint`, which is how
     it works out what it is hovering. */
  pointer-events: none;
  transition: left 0.55s cubic-bezier(0.5, 0, 0.2, 1),
              top 0.55s cubic-bezier(0.5, 0, 0.2, 1);
}

/* Running down a dropdown is a short hop between rows, not a journey. */
.hero-pointer.is-scanning {
  transition: left 0.14s ease-out, top 0.14s ease-out;
}

/* Both cursor shapes are stacked, and each is offset so its own hotspot — the
   arrow's point, the I-beam's centre — sits on the pointer's origin. That way
   `.hero-pointer` is positioned by the hotspot alone, and swapping shapes
   mid-move cannot nudge the cursor or the pill. */
.hero-cursor {
  position: absolute;
}

.hero-cursor--arrow {
  width:  calc(24px * var(--cursor-scale));
  height: calc(24px * var(--cursor-scale));
  left:   calc(-8px * var(--cursor-scale));
  top:    calc(-5px * var(--cursor-scale));
}

.hero-cursor--text {
  width:  calc(28px * var(--cursor-scale));
  height: calc(28px * var(--cursor-scale));
  left:   calc(-14px * var(--cursor-scale));
  top:    calc(-14px * var(--cursor-scale));
}

.hero-pointer.is-text .hero-pointer-label { top: 17px; left: 5px; }

.hero-pointer .hero-cursor--text  { display: none; }
.hero-pointer.is-text .hero-cursor--arrow { display: none; }
.hero-pointer.is-text .hero-cursor--text  { display: block; }

/* The click itself: a quick nudge of the cursor plus a ring leaving it. */
.hero-pointer.is-clicking .hero-cursor {
  transform: translate(1px, 1px) scale(0.88);
}

.hero-cursor {
  transform-origin: calc(4px * var(--cursor-scale)) calc(4px * var(--cursor-scale));
  transition: transform 0.09s ease-out;
}

.hero-pointer::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border: 1.5px solid var(--indigo);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}

.hero-pointer.is-clicking::after {
  animation: agent-click 0.4s ease-out 1;
}

@keyframes agent-click {
  from { opacity: 0.8; transform: scale(0.4); }
  to   { opacity: 0;   transform: scale(2.6); }
}

/* The agent's state, read out under the cursor. */
/* The agent's pill (Figma 35:25). A dot ahead of the label: a 6px square in a
   10px box — a dot while its corners are round, a diamond once it turns. */
.hero-pointer-label {
  position: absolute;
  top: 18px;
  left: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* The dot sits the same 9px from the left, the top and the bottom. That
     falls out of 4px of vertical padding against a 16px line box: the 6px dot
     is centred in it, so 4 + (16 - 6) / 2 lands exactly on the 9px inset. */
  padding: 4px 9px;
  border-radius: 354px;
  background: var(--indigo);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.12px;
  color: #fff;
  white-space: nowrap;
  transition: background-color 0.3s ease;
}

.pill-dot {
  display: flex;
  width: 6px;
  height: 6px;
  flex: none;
  /* No clipping: turned on its corner the tile is 8.49px across, and it has
     room for that inside the pill. */
}

/* At rest they are simply dots, and they stay that way. */
.pill-dot::before {
  content: "";
  width: 6px;
  height: 6px;
  background: #fff;
  border-radius: 8px;
}

/* Adapting is the only state that disturbs it: the corners square off and the
   tile turns. */
.hero-pointer.is-alert .pill-dot::before {
  border-radius: 0;
  animation: pill-spin 1.1s linear infinite;
}

@keyframes pill-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}

.hero-pointer.is-alert .hero-pointer-label { background: #d8325f; }
.hero-pointer.is-recovered .hero-pointer-label { background: #1e9e6a; }

/* --- Headline --- */
.hero-copy {
  position: absolute;
  top: 238px;
  left: var(--gutter);
  /* The box has to be held as well as the text inside it. Absolutely
     positioned with only a `left`, it shrink-to-fits against the headline's
     *unwrapped* width — which made it a full-page-wide invisible box sitting
     over the dialog and swallowing every hover in it.

     The measure is the stripes: they begin at band-b's left edge, 41.5972%
     across, and the headline stops a hair short of them. At 1440 that comes
     out at exactly the canvas's 537px — the drawn measure IS this constraint —
     and below it the headline keeps its clearance instead of running into the
     rules. The min holds it at the canvas width on wider screens. */
  max-width: min(537px, calc(41.5972% - var(--gutter) - 14px));
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 32px;
}

.hero-copy .lede {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  max-width: 100%;
}

.backed-by {
  display: flex;
  align-items: center;
  gap: 6px;
}

.backed-by span {
  font-size: 12px;
  letter-spacing: 0.06px;
}

.backed-by img {
  width: 100.833px;
  height: 20px;
}

.hero-copy h1 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 56px;
  line-height: 1;
  letter-spacing: -1.68px;
}

/* --- Client logos --- */
.trusted {
  position: absolute;
  top: 747px;
  left: var(--gutter);
  right: var(--gutter);
  max-width: 766px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.trusted p {
  font-size: 14px;
  letter-spacing: 0.14px;
}

.trusted .logos {
  display: flex;
  align-items: center;
  gap: 32px;
  height: 32px;
  overflow: hidden;
}

.trusted .logos img {
  flex: none;
}

/* The strip is doubled by main.js and slid by exactly one set's width, so it
   comes back to where it started and the loop does not show a seam. Only the
   narrow layouts run it. */
@keyframes logo-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(var(--logo-span, 0px) * -1)); }
}

/* ==========================================================================
   Positioning statement
   ========================================================================== */
.intro {
  margin-top: 160px;
  padding: 0 var(--gutter);
}

.intro p {
  max-width: 889px;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -1.32px;
}

.intro .muted {
  color: rgb(9 12 49 / 0.5);
}

/* ==========================================================================
   Systems panel
   ========================================================================== */
.systems {
  margin-top: 160px;
  display: flex;
  align-items: center;
  padding-left: var(--gutter);
}

.systems .panel {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  height: 516px;
  padding: 24px;
  background: var(--ink);
  flex: 1;
  min-width: 0;
}

.systems h2 {
  flex: none;
  /* The break after "connection" is written into the markup, so the two lines
     hold at every width rather than depending on where the measure happens to
     put them. */
  max-width: 544px;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -1.32px;
  color: #fff;
}

/* Two bottom-aligned columns of system types */
.systems .grid {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 468px;
}

.systems .col {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.systems .item {
  display: flex;
  gap: 12px;
  width: 280px;
}

.systems .item .badge {
  position: relative;
  width: 32px;
  height: 32px;
  flex: none;
  background: var(--sky);
  overflow: hidden;
}

/* Glyph exports are cropped to their own ink bounds, so fit rather than
   stretch them into the 20px cell. */
.systems .item .badge img {
  position: absolute;
  top: 6px;
  left: 6px;
  width: 20px;
  height: 20px;
  object-fit: contain;
}

/* The DMS glyph is drawn on a 24px grid rather than 20px */
.systems .item .badge img.wide {
  top: 4px;
  left: 4px;
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.systems .item .label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 32px;
  color: #fff;
  white-space: nowrap;
}

.systems .item .label b {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 24px;
  line-height: 1;
  letter-spacing: -0.72px;
}

.systems .item .label small {
  font-size: 14px;
  letter-spacing: 0.14px;
}

/* Rules sit to the right of the panel here, so the pattern is inset by one
   gap to keep the first rule the same distance from the panel edge. */
.systems > .stripes {
  --stripe-offset: calc(var(--stripe-pitch) * 0.75);
  width: calc(var(--stripe-pitch) * 8);
  height: 516px;
  flex: none;
  color: var(--ink);
}

/* ==========================================================================
   Execution counter
   ========================================================================== */
.executions {
  position: relative;
  margin-top: 156px;
  height: 202px;
}

/* Ruled lines with data bars sitting on them */
.exec-rows {
  position: absolute;
  top: 5px;
  left: 0;
  width: var(--page);
}

/* The rule is drawn as a border on a zero-height box, so it is nudged up by
   half its weight to centre on the same baseline as the bars. */
/* Each row carries its own colour, inherited by both the rule and its bars.
   The row box sits exactly on its design y so the bars line up with the panel
   edges; the rule is drawn by a pseudo-element nudged up half its weight so it
   still centres on that same line. */
.exec-row {
  position: absolute;
  left: 0;
  width: 100%;
  height: 0;
}

.exec-row::before {
  content: "";
  position: absolute;
  top: -0.75px;
  left: 0;
  width: 100%;
  border-top: 1.5px solid currentColor;
}

/* Bars ride a track that is two copies wide, so translating it by exactly one
   copy loops seamlessly. Speed varies per row. */
.exec-track {
  position: absolute;
  top: -5px;
  left: 0;
  height: 10px;
  will-change: transform;
  animation: exec-scroll var(--exec-speed, 24s) linear infinite;
}

@keyframes exec-scroll {
  from { transform: translateX(calc(var(--exec-span) * -1)); }
  to   { transform: translateX(0); }
}

.exec-track i {
  position: absolute;
  top: 0;
  height: 10px;
  display: block;
  background: currentColor;
}

.exec-panel {
  position: absolute;
  top: 0;
  /* Overhangs the canvas by 5px, as it does in the design. */
  right: -5px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 508px;
  height: 202px;
  padding: 24px 0 16px 24px;
  background: var(--indigo);
  color: #fff;
}

.exec-panel h2 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 32px;
  line-height: 1;
  letter-spacing: -0.96px;
}

/* Odometer: each column is a strip of digits shifted by transform and clipped
   to one digit.
   The clip window is padded well beyond the 108px line box, because a glyph's
   ink overflows its line box and would otherwise be sliced. The same padding
   is removed again with a negative margin, so the row still lays out at the
   design's 108px, and the digits keep the same baseline as the "." and "M"
   beside them. Step and window are equal, so exactly one digit is ever
   visible. */
.exec-panel .value {
  --odo-line: 108px;
  --odo-pad: 26px;
  --odo-cell: calc(var(--odo-line) + var(--odo-pad) * 2);
  display: flex;
  font-size: 108px;
  line-height: var(--odo-line);
  /* Relative, not the design's flat -2.16px: that is -2% of 108px, and left
     absolute it crushes the digits together at any smaller size. */
  letter-spacing: -0.02em;
}

/* Every character — rolling digit or fixed "." and "M" — is the same box, so
   they all share one baseline no matter what is mid-roll. The box is padded
   well past the 108px line box because a glyph's ink overflows it and would
   otherwise be sliced; the padding is taken back off with a negative margin so
   the row still lays out at the design's 108px. */
.odo-cell {
  position: relative;
  height: var(--odo-cell);
  margin-block: calc(var(--odo-pad) * -1);
  flex: none;
  /* Digits are proportionally spaced, so a column is only as wide as the digit
     in it. initCounter sets the width and transitions it in step with the roll. */
  transition: width 0.9s cubic-bezier(0.65, 0, 0.35, 1);
}

.odo-cell--rolling {
  overflow: hidden;
}

.odo-strip {
  display: flex;
  flex-direction: column;
  transition: transform 0.9s cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform;
}

/* The strip must be excluded here, or it picks up the cell padding itself and
   pushes every digit off the baseline the fixed characters sit on. */
.odo-cell > span:not(.odo-strip),
.odo-strip > span {
  display: block;
  box-sizing: border-box;
  height: var(--odo-cell);
  padding-block: var(--odo-pad);
  line-height: var(--odo-line);
  text-align: center;
  white-space: pre;
}

/* ==========================================================================
   Capability cards
   ========================================================================== */
.capabilities {
  margin-top: 156px;
  display: flex;
  flex-direction: column;
  gap: 64px;
  padding: 0 var(--gutter);
}

/* The block is held while the page scrolls past it, and each card gets a share
   of that travel. Tall enough that stepping through all five neither races on
   tall windows nor feels gated on short ones. */
.cards-scroll {
  position: relative;
  height: calc(692px + 5 * clamp(240px, 52vh, 460px));
}

.cards-pin {
  position: sticky;
  top: 24px;
}

/* The heading lives inside the pinned block so it stays with the cards. */
.cards-pin h2 {
  margin-bottom: 64px;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -1.32px;
  color: var(--ink);
}

/* The card on the left, its copy on the right. On the canvas that is
   890 + 20 + 434 = 1344; here the card column takes what is left. */
/* Every measurement in the block is a multiple of the open card's height, and
   that is set from the column's width by main.js — so the whole thing keeps
   the canvas's proportions at any desktop width rather than holding 890x500
   and cropping. 500 / 890 is the card; the rest follow from it. */
.cards-stage {
  --card-open: 500px;
  display: flex;
  align-items: flex-start;
  gap: 20px;
  height: calc(var(--card-open) + 40px);
}

/* A column, not a row: the open card stands at full height and the rest are
   rules beneath it. 500 + 6 + 4x4 + 3x6 = 540, the design's own sum — and it
   is set as a fixed height so the block never breathes while a card opens.
   Two cards are mid-transition at once and their easing does not cancel out;
   left to the content, the copy beside it and everything below would drift. */
.cards {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* 890 of the design's 1324 (the row less its gap), so the split between the
     cards and their copy holds at any width. */
  flex: 0 0 calc((100% - 20px) * 890 / 1324);
  min-width: 0;
  /* 6 + 4x4 + 3x6 = 40 below the open card. Fixed, and clipped: the block's
     height is then the block's height, whatever the cards are doing inside it
     at any given moment. */
  height: calc(var(--card-open) + 40px);
  overflow: hidden;
}

.card {
  position: relative;
  height: 4px;
  /* Never flexed, and never transitioned: main.js writes every card's height
     each frame from one shared eased `t`, which is the only way their sum can
     be guaranteed to stay the column's height at every instant. See the note
     there. A flexible item would also let the browser resize all five to make
     up any difference, which is the block distorting as it steps. */
  flex: none;
  cursor: pointer;
}

/* The open card's height is written inline by main.js. It cannot come from
   `--card-open` like everything else here: a `transition` never fires when the
   property's value changes only because a custom property it references did —
   registered with `@property` or not — and the card simply held its old
   height. Everything unanimated still derives from the variable. */
.card.is-active {
  cursor: default;
}

/* A closed card lifts under the pointer, as the tabs used to. The 6px it gains
   comes off the open card, so the column's contents still sum to its height
   and the card at the foot cannot be pushed out of the bottom of it. main.js
   does both, in `targets`. */

.card-visual {
  position: relative;
  height: 100%;
  overflow: hidden;
  background: var(--card-fill);
  /* Confines the height animation's repaint to this box. */
  contain: paint;
}

.card-visual:has(.card-shader.is-ready) {
  background: var(--card-base);
}

/* The product shot, at the inset the design gives it. It is held at the open
   card's size and simply clipped as the card closes, so it never reflows. */
/* 44 / 802 / 456 against the design's 500-tall card, so the inset and the shot
   scale with it. Held at the open size and clipped as the card closes, so it
   never reflows mid-animation. */
.card-visual .shot {
  position: absolute;
  top: calc(var(--card-open) * 0.088);
  left: calc(var(--card-open) * 0.088);
  width: calc(var(--card-open) * 1.604);
  height: calc(var(--card-open) * 0.912);
  /* Only the top corners: the shot runs off the foot of the card. */
  border-radius: 4px 4px 0 0;
  /* Promoted to its own layer. These SVGs are thousands of paths, and without
     this the browser re-rasterises them on every frame of the card's height
     animation, which is what made opening a card stutter. */
  will-change: transform;
  transform: translateZ(0);
}

/* Card 02's live replay: the real session recording, positioned exactly over
   the player region baked into the shot (video at 955,86 / 625x470 in the
   1604x912 asset), so the screenshot's player genuinely plays as you scroll
   by. Same coordinate system as .shot: fractions of the open card height. */
.card-visual .shot-replay {
  position: absolute;
  left: calc(var(--card-open) * 1.043);
  top: calc(var(--card-open) * 0.174);
  width: calc(var(--card-open) * 0.625);
  height: calc(var(--card-open) * 0.47);
  object-fit: contain;
  background: #fff;
  border-radius: 2px;
  will-change: transform;
  transform: translateZ(0);
}

/* The pixel-dissolve field, drawn by shader.js over the card colour. Sized to
   the open card and left there, so closing a card reveals less of the same
   pattern rather than reflowing it mid-animation. */
.card-shader {
  position: absolute;
  top: 0;
  left: 0;
  height: var(--card-open);
  pointer-events: none;
  /* Drawn at 1x and upscaled: nearest-neighbour keeps the cells' edges hard
     instead of interpolating them into a blur. */
  image-rendering: pixelated;
}

/* Without WebGPU the flat card colour underneath is the fallback. */
.card-shader:not(.is-ready) {
  display: none;
}

/* Knocking a cell out of the pattern shows the colour behind it, so
   `--card-base` paints the element and `--card-fill` is handed to the shader. */
.card[data-card="01"] { --card-fill: var(--card-01); --card-base: #c4b944; }
.card[data-card="02"] { --card-fill: var(--card-02); --card-base: #af8f7c; }
.card[data-card="03"] { --card-fill: var(--card-03); --card-base: #7677b1; }
.card[data-card="04"] { --card-fill: var(--card-04); --card-base: #8ab094; }
.card[data-card="05"] { --card-fill: var(--card-05); --card-base: #6d7aaa; }

/* ---- The copy beside the cards ---- */
.cards-foot {
  position: relative;
  display: grid;
  flex: 1;
  min-width: 0;
  padding-top: 24px;
  /* Follows the open card down the column, in step with it. */
  transition: transform var(--card-speed) var(--card-ease);
}

/* The panels stack in one grid cell, so the region takes the height of the
   tallest rather than needing one of its own. */
.cards-foot .card-foot {
  grid-area: 1 / 1;
  visibility: hidden;
  pointer-events: none;
}

.cards-foot .card-foot.is-active {
  visibility: visible;
  pointer-events: auto;
  animation: foot-in var(--card-speed) var(--card-ease);
}

/* Revealed the way the card opens: top to bottom, on the card's own duration
   and curve. It used to wipe left to right, which is how the card used to
   grow — the card grows downward now and the copy should follow it. */
@keyframes foot-in {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

.card-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

.card-foot .headline {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.card-foot .headline h3 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 32px;
  line-height: 1;
  letter-spacing: -0.96px;
}

.card-foot .headline p {
  font-size: 17px;
  line-height: 24px;
  letter-spacing: 0.17px;
}

.card-foot .points {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.card-foot .points ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* `min-height`, not `height`: a label that runs to a second line grows its row
   rather than overflowing it and running under the icon beside it. A
   single-line label sits exactly where the design puts it either way. */
.card-foot .points li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 28px;
  font-size: 17px;
  font-weight: 500;
  line-height: 22px;
  letter-spacing: 0.17px;
}

.card-foot .points .glyph {
  width: 28px;
  height: 28px;
  flex: none;
}

/* ==========================================================================
   Deploy call to action
   ========================================================================== */
.deploy {
  margin-top: 156px;
  /* Cuts the rules' overhang back to the block — see `.deploy > .stripes`. */
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  /* The rules run to the left edge; the panel stops a gutter short of the
     right, which the canvas did with a fixed width and this does with padding. */
  padding-right: var(--gutter);
  height: 352px;
}

.deploy > .stripes {
  width: calc(var(--stripe-pitch) * 8);
  /* 352 plus the 2px of overhang at each end; the negative margins hand those
     4px back, so it still occupies the panel's 352 in the layout. */
  height: 356px;
  flex: none;
  color: var(--indigo);
  /* The rules are drawn past the block's top and bottom and the section clips
     them back to it. `.stripes` clips its own drifting layer, and that clip
     lands on whole device pixels while the box itself sits at whatever
     fraction the page offset gives it — so on a real screen it can round in
     and leave the rules a pixel short of the panel beside them. Painting past
     the edge and letting the section cut it removes the question: the panel's
     own box is the boundary, and the two round together. The margins take the
     padding back out of the layout. */
  padding: 2px 0;
  margin: -2px 0;
}

.deploy .panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  height: 352px;
  padding: 24px;
  background: var(--indigo);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  /* The panel's bottom edge meets the footer at a fractional device pixel
     (the page is zoomed), so the browser antialiases that row and it reads as
     a pale hairline. One extra painted pixel underneath covers it without
     touching the layout. */
  box-shadow: 0 1px 0 0 var(--indigo);
}

/* Keep the heading and button above the blocks */
.deploy h2,
.deploy .btn-invert {
  position: relative;
  z-index: 1;
}

.deploy h2 {
  width: 100%;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -1.32px;
  color: #fff;
}

/* ==========================================================================
   Footer
   ========================================================================== */
/* Ends on the last rule of the artwork (126 offset + 455), not on the bottom of
   the artwork's own box at 586 — those last 5px hold nothing but the clipped
   tips of the peaks, and read as a white band under the footer. */
.footer {
  position: relative;
  height: 581px;
  overflow: hidden;
}

/* Inlined Figma geometry rather than an <img>, so the lines are live DOM. */
/* Anchored to the foot and sized by its own aspect. Boxed at a fixed 460px
   against a 1440x450 viewBox it no longer matched once the page went fluid, so
   `preserveAspectRatio` centred the artwork inside the box and the slack showed
   up as blank paper under the rules. */
.footer-deco {
  position: absolute;
  top: auto;
  bottom: 0;
  left: 0;
  width: 100%;
  /* Held at the height it is drawn at, rather than scaling with the width.
     Bottom-anchored and free to grow, a wider page made the artwork taller —
     at 1720 it stood 87px further up than at 1440 and reached the buttons. The
     rules are horizontal, so taking up the extra width by stretching costs
     nothing: their weight is a vertical measure and does not change, and the
     staircase keeps the clearance it is drawn with at every width. */
  height: 450px;
  z-index: 0;
}

/* On hover each rule stretches by its own random amount rather than the whole
   drawing scaling. The rules are anchored at the right edge of the viewBox,
   so they grow leftwards from x=1440. */
.footer-deco path[stroke-width="1.5"] {
  transform-box: view-box;
  transform-origin: 1440px 0;
  transform: scaleX(var(--stretch, 1));
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  /* Relaxing back is held off so the line does not snap the instant the
     pointer leaves. initFooterLines clears the delay on the way out. */
  transition-delay: var(--stretch-delay, 0s);
}

.footer > *:not(.footer-deco) {
  position: relative;
  z-index: 1;
}

.footer .brand {
  position: absolute;
  top: 53px;
  left: var(--gutter);
}

.footer .actions {
  position: absolute;
  top: 53px;
  right: var(--gutter);
  display: flex;
  gap: 8px;
}

/* Only used below the breakpoint; on the canvas their children stay absolute. */
.footer-stack,
.footer-ident { display: contents; }

.footer .links {
  position: absolute;
  top: 47px;
  display: flex;
  flex-direction: column;
  /* Each link shrinks to its own text, so hovering the empty space beside a
     short label does not light it up. The 12px rhythm is carried by padding
     rather than a gap, which makes the links' hover boxes touch vertically —
     without that, crossing the gap between two links would blink the caret
     off and on. The column starts 6px higher to absorb the first link's
     padding, so nothing moves. */
  align-items: flex-start;
  gap: 0;
  width: 215px;
  font-size: 16px;
}

.footer .links a {
  position: relative;
  padding: 6px 0;
  transition: color 0.2s ease;
}

/* One caret per column that slides between links, mirroring the top nav */
.link-arrow {
  position: absolute;
  left: -14px;
  top: 0;
  width: 6px;
  height: 12px;
  background: var(--indigo);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  opacity: 0;
  transition: top 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
  pointer-events: none;
}

.footer .links:has(a:hover) .link-arrow {
  opacity: 1;
}

.footer .links a:hover {
  color: var(--indigo);
}

.footer .copyright {
  position: absolute;
  top: 272px;
  left: 48.5px;
  font-family: var(--pixel);
  font-size: 12px;
  line-height: normal;
  opacity: 0.4;
  white-space: nowrap;
}


/* ==========================================================================
   Tablet and mobile

   A tablet keeps the desktop composition down to a 1200px window and every
   detail comes with it. Below that the type would be too
   small to read, so the zoom is dropped and the page becomes a real fluid
   column: sections that sat side by side stack, the pieces that were
   positioned against canvas coordinates go back into flow, and the two things
   that cannot reflow — the Win95 dialog and the card artwork — are scaled as
   whole units instead.
   ========================================================================== */
@media (max-width: 1199px) {
  :root {
    --page: 100%;
    --page-zoom: 1;
    --gutter: 32px;
  }
  .page {
    width: 100%;
    zoom: 1;
  }
  /* ---- Menu bar ---- */
  .menubar {
    justify-content: flex-start;
    gap: 24px;
    height: 52px;
  }
  /* The button belongs beside the menu, not stranded in the middle. */
  .menubar .actions { margin-left: auto; }
  /* Down to here the six links still fit on the bar beside the brand and the
     button, so they stay there — a menu button in front of a menu that fits is
     a click asking for nothing. Below it they come off the bar into a panel. */
  @media (max-width: 959px) {
    .menubar { gap: 12px; }
    .menu-toggle { display: flex; }
    /* The links come off the bar and into a panel under it. */
    .menubar nav {
      /* Kept displayed and clipped instead of `display: none`, which cannot be
         transitioned. The panel unrolls downward out of the bar — the inset
         opens from the bottom edge while the list slides down behind it, so
         the links arrive with the panel rather than fading up through it.
         `visibility` waits out the close so nothing is clickable on the way. */
      display: flex;
      visibility: hidden;
      clip-path: inset(0 0 100% 0);
      transform: translateY(-8px);
      transition:
        clip-path 0.34s var(--card-ease),
        transform 0.34s var(--card-ease),
        visibility 0s 0.34s;
      position: absolute;
      /* Above the scrim, which comes later in the markup and would otherwise
         paint over the panel it is meant to sit behind. */
      z-index: 1;
      top: 100%;
      left: 0;
      right: 0;
      flex-direction: column;
      align-items: flex-start;
      gap: 4px;
      height: auto;
      padding: 8px var(--gutter) 20px;
      background: #fff;
      border-bottom: 1px solid rgb(9 12 49 / 0.1);
    }
    .menubar.is-open nav {
      visibility: visible;
      clip-path: inset(0 0 0 0);
      transform: none;
      transition:
        clip-path 0.34s var(--card-ease),
        transform 0.34s var(--card-ease),
        visibility 0s;
    }
    .menubar nav a { padding: 8px 0; }
    /* The same caret, turned to run down the list: it points at the item rather
       than sitting under it, exactly as the footer's does. The list is indented
       to leave it room. */
    .menubar nav { padding-left: calc(var(--gutter) + 16px); }
    .menubar .nav-arrow {
      left: var(--gutter);
      width: 6px;
      height: 12px;
      margin-top: -6px;
      /* The bar's caret is centred on the item it sits under; this one points at
         it from the side, so the half-width shift comes off. */
      transform: none;
      clip-path: polygon(0 0, 100% 50%, 0 100%);
      transition: top 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    }
  }
  /* ---- Hero ---- */
  .hero {
    display: flex;
    flex-direction: column;
    gap: 48px;
    height: auto;
    padding: 88px 0 0;
  }
  .trusted {
    position: static;
    width: auto;
    /* The desktop measure exists to leave room for the dialog beside it. There
       is nothing beside it here, so it goes. */
    max-width: none;
    padding: 0 var(--gutter);
  }
  /* The canvas measure, kept: at this width there is room for it, and it is
     what breaks the headline into the same three lines. */
  .hero-copy .lede { width: 537px; max-width: 100%; }
  /* On the canvas the headline is lifted out of the flow and laid over the
     artwork. Here the hero is a column and the headline is the first thing in
     it, so it has to come back into the flow — left absolute it ignores its
     order and paints over the dialog and the bands. */
  .hero-copy {
    order: 1;
    position: static;
    max-width: none;
    padding: 0 var(--gutter);
  }

  /* The tablet type scale. The canvas sizes are drawn for a 1440 measure and
     read as shouting once the column narrows, so every heading steps down
     together and keeps the ratios between them. */
  .hero-copy h1 { font-size: 46px; letter-spacing: -1.38px; }
  .intro p { font-size: 36px; letter-spacing: -1.08px; }
  .cards-pin h2,
  .deploy .panel h2,
  .systems .panel h2,
  .exec-panel h2 { font-size: 36px; letter-spacing: -1.08px; }
  .card-foot .headline h3 { font-size: 26px; letter-spacing: -0.78px; }
  .hero-art  { order: 2; }
  .legacy    { order: 3; }
  .trusted   { order: 4; }
  /* The bands keep their staircase: flush right, each a little wider than the
     one above, in the design's own proportions. main.js gives the region the
     dialog's height. */
  .hero-art {
    position: relative;
    top: 0;
    height: 300px;
  }
  .hero-art .band {
    left: auto;
    right: 0;
    /* The same 1px reach as on the canvas — see `.hero-art .band`. */
    height: calc(33.34% + 1px);
  }
  .hero-art .band-c { height: 33.34%; }
  /* The widest band runs the full width and the others step in from it, in
     the ratios the design gives them (796 / 841 / 926). On the canvas they all
     stop short of the left edge; there is no room for that here. */
  .band-a { top: 0;       width: 86%; }
  .band-b { top: 33.33%;  width: 90.8%; }
  .band-c { top: 66.66%;  width: 100%; }
  .hero-art .band .stripes { width: calc(var(--stripe-pitch) * 6); }
  /* Scaled as a unit rather than reflowed: it is a picture of an application,
     and its proportions are the point. main.js sets --legacy-scale. */
  .legacy {
    position: relative;
    top: 0;
    left: 0;
    align-self: center;
    transform: scale(var(--legacy-scale, 1));
    transform-origin: top center;
  }
  /* A carousel rather than a row that clips: on the canvas the overflow is
     deliberate, here it would just lose half the logos. main.js duplicates the
     set so the loop has something to run into. */
  .trusted {
    overflow: hidden;
    padding-right: 0;
  }
  .trusted .logos {
    flex-wrap: nowrap;
    height: auto;
    width: max-content;
    animation: logo-drift var(--logo-drift, 24s) linear infinite;
  }
  /* ---- Positioning statement ---- */
  .intro { margin-top: 96px; }
  .intro p { width: auto; }
  /* One paragraph rather than two lines with a gap between them. */
  .intro p br { display: none; }
  .intro .muted::before { content: " "; }
  /* ---- Systems ---- */
  /* Still a row, so the rules stay on the right where they belong. */
  .systems {
    margin-top: 96px;
    align-items: stretch;
  }
  .systems .panel {
    width: auto;
    height: auto;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 64px;
  }
  /* Likewise: the canvas measure is what gives the heading its own line
     breaks, and it still fits here. */
  .systems .panel h2 { max-width: 100%; }
  /* Two columns for as long as they fit, then one. */
  .systems .grid {
    flex-wrap: wrap;
    align-items: flex-start;
    width: 100%;
    height: auto;
    /* The row gap matches the gap between items inside a column, so once the
       two columns wrap onto one another the list reads as a single run. */
    gap: 12px 16px;
  }
  /* `min-width: 0` or the columns cannot shrink below the longest label and
     never sit side by side; the labels wrap instead. */
  .systems .col {
    flex: 1 1 240px;
    min-width: 0;
  }
  .systems .item {
    width: auto;
    min-width: 0;
    align-items: center;
  }
  /* The 32px lead-in is a canvas measurement — it was pushing the rows a
     third of a row apart here. */
  .systems .item .label { padding-top: 0; }
  .systems .col { gap: 12px; }
  .systems .item .label { min-width: 0; }
  .systems .item .label small { white-space: normal; }
  .systems > .stripes {
    width: var(--gutter);
    height: auto;
  }
  /* ---- Executions ---- */
  .executions { margin-top: 96px; }
  .exec-panel {
    left: auto;
    right: 0;
    width: fit-content;
    min-width: 0;
    max-width: 100%;
    /* The heading inside stops on the page's own gutter, level with every
       other block's right edge — the panel itself runs to the paper. */
    padding-right: var(--gutter);
  }
  .exec-panel .value {
    --odo-line: 64px;
    --odo-pad: 15px;
    font-size: 64px;
  }
  .cards-pin h2 { margin-bottom: 40px; }
  .card-foot .headline h3 { font-size: 26px; letter-spacing: -0.78px; }
  /* ---- Deploy ---- */
  .deploy {
    margin-top: 96px;
    align-items: stretch;
    height: auto;
  }
  .deploy .panel {
    width: auto;
    height: auto;
    flex: 1;
    min-width: 0;
    gap: 80px;
  }
  /* One sentence after the other, not one above the other. The space has to
     come from the span: dropping the `br` would otherwise run the two words
     together. */
  .deploy .panel h2 br { display: none; }
  .deploy .panel h2 span::before { content: " "; }
  /* A whole number of pitches, as everywhere else the rules are drawn: the
     gutter is not one, so `width: var(--gutter)` cut the last rule off part
     way and left an odd gap against the panel. 5 pitches is the nearest whole
     column that still fits the 32px gutter; the narrower tiers step down with
     their own gutters below. */
  .deploy > .stripes {
    width: calc(var(--stripe-pitch) * 5);
    height: auto;
  }
  /* The blocks are decoration sized against the desktop panel. */
  .deploy-blocks { display: none; }
  /* ---- Footer ---- */
  /* Four columns: the logo and its buttons, then the three link lists beside
     them. `minmax(0, 1fr)` because the full-width artwork below would
     otherwise stretch every track to its own size. */
  .footer {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: start;
    gap: 32px 24px;
    height: auto;
    padding: 48px var(--gutter) 0;
  }
  .footer-deco {
    position: static;
    left: auto;
    top: auto;
    white-space: normal;
  }
  /* Logo over the copyright, the long list, then the two short lists stacked a
     blank item apart, and the buttons in the last column. */
  /* The copyright sits at the foot of its column, level with the last link in
     the long list beside it — so the column has to take the row's height
     rather than its own. The 6px comes back off the link's own bottom
     padding, so it is the two lines of text that line up, not the boxes. */
  .footer-ident {
    grid-area: 1 / 1;
    display: flex;
    flex-direction: column;
    gap: 32px;
    align-items: flex-start;
    align-self: stretch;
    justify-content: space-between;
    padding-bottom: 6px;
  }
  /* Every one of these is absolutely positioned on the canvas, at drawn
     coordinates. They have to come back into the flow before the grid can
     place them — `grid-area` on an absolutely positioned child sets its
     containing block, not its track, so the columns kept their canvas
     percentages and, since the labels do not shrink with the page, ran into
     one another below about 1360. */
  .footer .brand,
  .footer .actions,
  .footer .links,
  .footer .copyright {
    position: static;
  }
  .footer .links   { width: auto; }
  .footer > nav.links { grid-area: 1 / 2; }
  .footer-stack {
    grid-area: 1 / 3;
    display: flex;
    flex-direction: column;
    gap: 32px;
  }
  /* Hard against the right of the content, as on the canvas. */
  .footer .actions { grid-area: 1 / 4; justify-self: end; }
  .footer-deco {
    grid-area: 2 / 1 / auto / -1;
    width: calc(100% + var(--gutter) * 2);
    max-width: none;
    height: auto;
    margin: 0 calc(var(--gutter) * -1);
  }
}
/* --------------------------------------------------------------------------
   The card block stacks later than the rest of the page: side by side it only
   needs room for a card and its copy, and it scales to fit until there is not.
   -------------------------------------------------------------------------- */
@media (max-width: 839px) {
  /* ---- Capability cards ---- */
  /* Nothing is pinned: the cards are all open, stacked, each above its own
     copy. main.js moves the copy panels next to their cards. */
  .cards-scroll { height: auto; }
  .cards-pin {
    position: static;
    top: auto;
  }
  /* The copy column goes under the cards rather than beside them. The base
     rule aligns children to the start, which in a column is the horizontal
     axis — left as it was, the cards would be their content's width. */
  .cards-stage {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .cards-foot {
    display: block;
    width: auto;
    padding-top: 0;
    transform: none;
  }
  /* Every card is open here, so height is content-driven again. */
  .cards,
  .cards-stage,
  .cards .card,
  .cards .card.is-active,
  .cards .card:not(.is-active):hover {
    height: auto;
  }

  .card-shader { height: 100%; }

  .cards {
    flex-direction: column;
    /* The base rule tops-aligns the tab strip; stacked, each block should run
       the full width instead. */
    align-items: stretch;
    /* Tight, because this gap also falls between a card and its own copy.
       The air between one card and the next is put back on the card. */
    gap: 0;
  }

  .cards .card + .card-foot { margin-top: 4px; }
  .cards .card-foot + .card { margin-top: 48px; }

  .cards .card,
  .cards .card.is-active {
    width: 100%;
    transition: none;
  }

  /* Exactly the desktop composition, scaled: the card at the canvas's 890/500
     and the shot at its 44 / 802 / 456 inside it, as percentages so the inset
     scales with everything else. */
  .card-visual {
    height: auto;
    aspect-ratio: 890 / 500;
  }

  .card-visual .shot {
    position: absolute;
    top: 8.8%;
    left: 4.944%;
    width: 90.112%;
    height: 91.2%;
    margin: 0;
    object-fit: fill;
  }

  /* The replay overlay tracks the shot's player region in percentages here,
     same fractions as the desktop calc() rule. */
  .card-visual .shot-replay {
    left: 58.596%;
    top: 17.4%;
    width: 35.114%;
    height: 47%;
  }

  /* Out of the shared grid cell and back into flow, one per card. The column
     layout has to survive: it is what puts air under the subtitle. */
  .cards-foot {
    display: block;
  }
  .cards .card-foot {
    width: auto !important;
    transform: none !important;
    visibility: visible;
    pointer-events: auto;
    animation: none;
    gap: 16px;
    padding-top: 16px;
  }
}


/* --------------------------------------------------------------------------
   Small tablet

   Between a tablet held in two hands and a phone. The layout is the stacked
   one either way; what changes here is the measure — gutters, the type scale
   and the hero staircase all step down before the phone's values take over.
   -------------------------------------------------------------------------- */
@media (max-width: 759px) {
  /* 4 pitches against this tier's gutter. */
  .deploy > .stripes { width: calc(var(--stripe-pitch) * 4); }

  :root { --gutter: 24px; }

  /* Below this the canvas measures are wider than the screen, so they become
     maxima and the text breaks wherever it must. */
  .hero-copy .lede { width: auto; max-width: 537px; }
  .systems .panel h2 { width: auto; max-width: 544px; }

  .hero-copy h1 {
    font-size: 38px;
    letter-spacing: -1.14px;
  }

  .intro p {
    font-size: 29px;
    letter-spacing: -0.87px;
  }

  .cards-pin h2,
  .deploy .panel h2,
  .systems .panel h2,
  .exec-panel h2 {
    font-size: 30px;
    letter-spacing: -0.9px;
  }

  .card-foot .headline h3 {
    font-size: 22px;
    letter-spacing: -0.66px;
  }

  .card-foot .headline p,
  .card-foot .points li { font-size: 15px; line-height: 21px; }

  /* A grid, not a wrapping row: the short link columns have to stack in the
     second column, under one another, which flex rows cannot do. */
  .footer {
    display: grid;
    /* `minmax(0, 1fr)`, or the full-size artwork below would stretch both
       tracks to 1440px and take the whole footer with it. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: 32px 24px;
    height: auto;
    padding: 48px var(--gutter) 0;
  }

  .footer .brand,
  .footer .actions,
  .footer .links,
  .footer .copyright,
  .footer-deco {
    position: static;
    left: auto;
    top: auto;
  }

  /* The logo takes the first row on its own; the copyright comes off the
     bottom, under the links. */
  .footer-ident {
    grid-area: 1 / 1 / auto / -1;
    display: contents;
  }

  .footer .brand   { grid-area: 1 / 1 / auto / -1; }
  .footer .actions { grid-area: 3 / 1 / auto / -1; justify-self: start; }

  .footer .links { width: auto; }
  .footer > nav.links { grid-area: 2 / 1; }

  /* One cell holding both short columns, a blank item's height apart. */
  .footer-stack {
    display: flex;
    flex-direction: column;
    gap: 32px;
    grid-area: 2 / 2;
  }

  .footer .copyright {
    grid-area: 4 / 1 / auto / -1;
    white-space: normal;
  }

  /* Below the copyright. The wider tier puts the artwork on row 4, which is
     where the copyright sits once the columns stack — and main.js centres the
     logo mark in it from this width down. */
  .footer-deco {
    grid-area: 5 / 1 / auto / -1;
    justify-self: stretch;
    width: calc(100% + var(--gutter) * 2);
    max-width: none;
    /* Its own aspect, always. A fixed height against a viewBox of a different
       shape leaves `preserveAspectRatio` to centre the artwork, and the slack
       shows up as blank paper under the rules. */
    height: auto;
    margin: 0 calc(var(--gutter) * -1);
  }

  /* Half the phone's step, since there is twice the room. */
  .band-a { width: calc(100% - 48px); }
  .band-b { width: calc(100% - 24px); }
  .band-c { width: 100%; }

  /* Half the measure exactly: the panel is one of two equal halves here, the
     rules running out to the left of it. */
  .exec-panel { width: 50%; }
}

@media (max-width: 639px) {
  /* 3 pitches against this tier's gutter. */
  .deploy > .stripes { width: calc(var(--stripe-pitch) * 3); }

  :root { --gutter: 20px; }

  .hero { gap: 36px; }

  /* The staircase is measured in pixels here, not percentages: the point is
     the gap between the screen's edge and the rules, and it should be the
     same gap whatever the phone. The top band starts 32px in and the step
     halves down to the bottom one, which runs to the edge. */
  .band-a { width: calc(100% - 32px); }
  .band-b { width: calc(100% - 16px); }
  .band-c { width: 100%; }

  .hero-copy h1 {
    font-size: 36px;
    letter-spacing: -1.08px;
  }

  /* Tighter under the headline, and a shorter button beneath it. */
  .hero-copy { gap: 24px; }
  .hero-copy .btn-primary { height: 40px; }

  /* Not an h2 in the markup, but it carries the same weight on the page. */
  .intro p {
    font-size: 28px;
    letter-spacing: -0.84px;
  }

  .systems .item .label b { font-size: 18px; letter-spacing: -0.54px; }
  .systems .item .label small { font-size: 13px; }

  /* Half the screen, rather than however wide the number happens to be. */
  .exec-panel {
    width: 50%;
    box-sizing: border-box;
  }

  /* ...which means the number has to fit in half a screen. 60px does on a
     large phone and does not on a small one, so it is capped by the viewport
     and the roll geometry follows it. */
  .exec-panel .value {
    --odo-line: min(60px, 13vw);
    --odo-pad: calc(var(--odo-line) * 0.235);
    font-size: var(--odo-line);
  }

  /* One size for every section heading on a phone. */
  .cards-pin h2,
  .deploy .panel h2,
  .systems .panel h2,
  .exec-panel h2 {
    font-size: 28px;
    letter-spacing: -0.84px;
  }

  /* A card's title sits under the section's, so it reads smaller than it. */
  .card-foot .headline h3 {
    font-size: 18px;
    letter-spacing: -0.54px;
  }

  .card-foot .headline { gap: 6px; }

  .card-foot .headline p { font-size: 14px; line-height: 20px; }

  /* A softer corner at this size, where 4px reads as a chamfer. */
  .card-visual .shot { border-radius: 2px 2px 0 0; }

  .card-foot .points { width: 100%; }

  /* Tighter than the desktop rhythm: the labels are smaller here, so the same
     10px gap read as loose. */
  .card-foot .points ul { gap: 6px; }

  /* The 28px floor is the desktop icon's size; against a 24px icon it left
     4px of slack under every item and the content sat high in its own slot.
     The item takes the icon's height, and the two centre on each other. */
  .card-foot .points li {
    align-items: center;
    min-height: 0;
    font-size: 14px;
    line-height: 20px;
  }

  /* Centred on the first line rather than on the label's box, so a label that
     wraps to two lines does not drag the icon down to the middle of the block.
     (20 - 24) / 2 is negative — the icon is taller than the line — which lifts
     it so the two centres meet. */
  .card-foot .points .glyph {
    width: 24px;
    height: 24px;
    margin-top: 0;
  }

  .footer .actions { flex-wrap: wrap; }

  /* Two buttons and a menu do not fit; Sign In is in the footer as well. */
  .menubar .actions .btn-outline { display: none; }
}
