/*
 * Marketing landing page.
 *
 * Everything is scoped under `.chr`, so these rules and the product's utility classes never
 * reach into each other in either direction: nothing here styles a product screen, and no
 * product rule restyles this page.
 *
 * The palette, the faces and the heading tracking are not declared here. They come from the
 * product tokens in app/globals.css, and `.chr` only gives them the short names this sheet was
 * written against. What stays local is this page's own furniture: the band and plate levers
 * below, and the `chr-` component classes. The few literal colours further down are artwork, not
 * palette: the grey placeholder frames, the near-black ground behind the toolchain art, and the
 * masks' opaque black.
 */

.chr {
  /*
   * Short names for the product tokens. Aliases only: a hue is changed in app/globals.css and
   * this page follows. Sampled off the riso plate itself, so page and print are one sheet of
   * paper.
   */
  --paper: var(--color-paper);
  --paper-2: var(--color-paper-2);
  --ink: var(--color-ink);
  /* Between --ink and --ink-60. Third tone for the trio header, kept inside the same
     blue-green family so three words read as one ladder rather than three hues. */
  --ink-80: var(--color-ink-80);
  --ink-60: var(--color-ink-60);
  --accent: var(--color-accent);
  /*
   * Errors, and only errors. Named and scoped like --sky below: an error cannot be the same ink
   * as the label it sits under, and this rust is the one hue on this paper that reads as wrong
   * without shouting. Measured 5.4:1 on --paper, so it is usable for 13px type.
   */
  --flag: var(--color-flag);
  /*
   * A third hue, for the trio header only. It breaks the two-role rule on purpose, so it is
   * named and scoped rather than sprinkled. Both tones clear 3:1 on this paper, which is the
   * large-text threshold: a true pastel baby blue lands near 2:1 and is not usable for type.
   */
  --sky: var(--color-sky);
  --sky-lit: var(--color-sky-lit);
  /* Type on an accent fill. */
  --accent-ink: var(--color-paper);
  --rule: var(--color-rule);

  --display: var(--font-display);
  --body: var(--font-body);
  --display-tracking: var(--tracking-display);
  --display-leading: 0.98;

  --pad-x: clamp(18px, 3vw, 50px);
  --border: 1px;

  /*
   * Section headers. Declared here, not on `.chr-sec`, so one inline override on an ancestor
   * reaches every section: a var redeclared on the section itself would beat an inherited one.
   */
  --band-heading: 3.8rem;
  --band-y: 0px;
  --band-sub: 1.88rem;
  --band-sub-y: 22px;
  /* One distance from the top of a band to its heading, shared by every band. */
  --band-top: clamp(88px, 14vh, 150px);

  /*
   * Measure per band, in ch of the element's own size so it stays proportional when the size
   * changes. `--w-h` / `--w-s` are what the rules read; each band maps its own pair onto them,
   * which keeps the measure rules to one each instead of one per band.
   */
  --w1-h: 26;
  --w1-s: 72;
  --w2-h: 80;
  --w2-s: 48;
  --w6-h: 26;
  --w6-s: 62;

  /* Band 2's heading saturates its container near 34ch, so size is the only lever left. */
  --w2-scale: 0.9;

  /* Gap between band 1's sub-line and the "Because..." turn. */
  --because-y: 80px;

  --eyebrow-size: 50px;
  --wt-h: 39;
  --wt-size: 3.52rem;
  --steps-w: 1484px;
  --list-w: 46%;
  --list-scale: 1.5;

  /* Band 2's workbench card. */
  /* The crystallography photograph in the train example: size, place, and a feather distance
     and rate per corner, because the dark edge on it is not symmetric. */
  /* Type and bar sizing inside the train example. */
  --barname-gpu: 20px;
  --barname-pose: 25px;
  --ex-size: 25px;
  --cap-size: 18px;
  --how-size: 21px;
  --goal-size: 2.2rem;
  /*
   * The toolchain carousel. Width is how much of the ring is on screen, so it is how many tool
   * families you can see at once; height is the card, and the rake derives its pitch and
   * recession from that. Height is a ceiling rather than a size: the row it sits in shrinks it
   * on a short screen.
   */
  --cf-w: 912px;
  --cf-h: 270px;
  /* Slides the whole block off its right-edge anchor. Negative moves it left, over the value
     list; positive moves it toward the screen edge. */
  --cf-x: 110px;
  /* The two single-image steps beside the carousel. Same pair of levers. */
  --art-size: 470px;
  --art-x: 0px;
  --bar1-h: 62px;
  --bar2-h: 100px;

  --crys-w: 728px;
  --crys-x: -234px;
  --crys-y: 96px;
  /* Feathers every side. Corner-only fades leave the straight runs between them square, which
     is what makes an image read as pasted on. */
  --cf-edge: 5%;

  /*
   * Band 6's ligand background. Treated like band 4's crystal photograph: absolutely placed,
   * sized by a width, offset from the right so it can bleed off the edge, feathered so no
   * rectangle shows, and behind the copy. The placement numbers start from band 4's own
   * (728px / -234px / 96px, edge feather 5%) scaled up for a wider band and a wider asset.
   *
   * The colour is ours, not the file's. The artwork is pale strokes on transparency, which is
   * invisible on this paper: measured, at full strength it moved the band by 9/255. But it was
   * built by taking luminance as alpha, so its colour channel is a flat white carrying nothing
   * and the whole drawing is in the alpha. So the PNG is used as a mask and the ink is painted
   * through it. Nothing is filtered or inverted, the alpha is untouched, and the soft falloff
   * the asset exists for survives intact.
   */
  --lig-w: 1240px;
  --lig-x: -300px;
  --lig-y: 400px;
  --lig-o: 0.55;
  --lig-ink: var(--ink-60);
  --lig-edge: 5%;
  /*
   * --cp-* moves where each corner reaches full transparency, inward from the corner itself.
   * Without it the fade is pinned so it only just clears the corner, which caps how much can be
   * taken off. Everything beyond that point is gone; --cf-* is the feather run before it.
   */
  --cp-tl: 12%;
  --cp-tr: 0%;
  --cp-bl: 13%;
  --cp-br: 0%;
  --cf-tl: 34%;
  --cr-tl: 59;
  --cf-tr: 14%;
  --cr-tr: 60;
  --cf-bl: 46%;
  --cr-bl: 55;
  --cf-br: 24%;
  --cr-br: 60;
  --os-w: 1416px;
  --os-h: 812px;

  --w-h: var(--w1-h);
  --w-s: var(--w1-s);
  --grid-size: 1.1;
  --grid-y: 20px;

  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.58;
  -webkit-font-smoothing: antialiased;
  color-scheme: light;
  overflow-x: clip;
}

.chr h1,
.chr h2,
.chr h3 {
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: var(--display-tracking);
  text-wrap: balance;
  margin: 0;
}

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

.chr :focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ── nav ─────────────────────────────────────────────────────────────────── */

/* Sticky throughout: a reader who does not want the argument must be able to leave from
   anywhere on the page. */
.chr-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 30px;
  padding: 14px var(--pad-x);
  background: var(--paper);
  border-bottom: var(--border) solid var(--ink);
}

/* The wordmark takes the slack, so the links and the CTA sit together on the right. */
.chr-wordmark {
  display: flex;
  align-items: center;
  margin-right: auto;
  gap: 11px;
  font-family: var(--body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.16em;
}

.chr-nav__links {
  display: flex;
  gap: 22px;
  font-size: 15.5px;
  color: var(--ink-60);
}

.chr-nav__links a:hover {
  color: var(--accent);
}

/* Log in and sign up, in place of the demo CTA that used to close the nav. Log in is type and
   sign up is the button: two buttons side by side make neither one the answer. */
.chr-nav__auth {
  display: flex;
  align-items: center;
  gap: 18px;
}

.chr-nav__login {
  font-family: var(--body);
  font-size: 15.5px;
  font-weight: 600;
}

.chr-nav__login:hover {
  color: var(--accent);
}

.chr-btn {
  display: inline-block;
  /* Declared here rather than on a `button.chr-btn` selector, which would outrank --solid. */
  background: transparent;
  /* padding is in `em` so a button can be resized by font-size alone and stay in
     proportion; at the 14.5px default this is the same 10px / 19px as before */
  padding: 0.69em 1.31em;
  border: var(--border) solid var(--ink);
  font-family: var(--body);
  font-size: 14.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
}

.chr-btn:hover {
  background: var(--ink);
  color: var(--paper);
}

.chr-btn--solid {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.chr-btn--solid:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/*
 * `.chr a` a few rules up sets `color: inherit` so that wrapped links take their surrounding ink.
 * That selector is (0,1,1) and the button variants are (0,1,0), so it silently wins for any anchor
 * styled as a button — every `<a class="chr-btn chr-btn--solid">` was rendering ink on the accent
 * fill instead of paper. Restated here at a specificity that holds. Buttons are unaffected; only
 * anchors ever hit the inherit rule.
 */
.chr a.chr-btn--solid {
  color: var(--accent-ink);
}

.chr a.chr-btn--solid:hover {
  color: var(--paper);
}

/* ── hero ────────────────────────────────────────────────────────────────── */

/*
 * The plate and the words share one 12-column grid and overlap inside it. The whole thing
 * rests on the plate's paper being the page's paper: with `mix-blend-mode: darken` the cream
 * cancels exactly and only the two inks survive, so the drawing has no edges and type can
 * cross into it. Nothing is ever cropped — width and zoom grow the whole drawing.
 *
 * These plate values are the locked framing; mirror any change into lib/marketing/plate.ts.
 * The two type sizes below are tunable from the same adjuster and mirrored in the same file.
 */
.chr-hero {
  --plate-cols: 3 / -1;
  --plate-zoom: 1.18;
  --plate-x: -13%;
  --plate-y: -15%;
  --plate-fade-tl: 29%;
  --plate-fade-br: 24%;
  --plate-top-span: 50%;
  --plate-rate-tl: 95;
  --plate-rate-br: 80;

  /* Headline: a multiplier over the fluid clamp, so tuning keeps the viewport response.
     Measure is a percentage of the text column, not a ch count — the column is what actually
     constrains this headline, and a ch measure wide enough to matter varies with the scale,
     which would leave most of the slider doing nothing. */
  --h1-scale: 0.94;
  --h1-measure: 100;
  --h1-x: 0px;
  --h1-y: 0px;

  /* Gap between the nav rule and the headline. */
  --hero-top: 80px;

  /* Sub-line: size in px, measure as a multiple of that size. */
  --hero-subhead: 32px;
  --sub-measure: 20;
  --sub-x: 0px;
  --sub-y: 114px;

  --hero-cta: 23px;

  position: relative;
  isolation: isolate;
  overflow-x: clip;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-items: start;
  row-gap: clamp(18px, 2.4vw, 34px);
  min-height: min(62vh, 580px);
  padding-top: var(--hero-top);
  padding-bottom: clamp(28px, 3.5vw, 56px);
  background: var(--paper);
}

.chr-hero__text {
  grid-row: 1;
  grid-column: 1 / 8;
  position: relative;
  z-index: 1;
  padding-inline: var(--pad-x);
}

/*
 * One wrapping sentence, so the measure is what sets the line breaks over the plate. The
 * offsets are transforms rather than margins: they compose the type against a fixed drawing
 * without reflowing what sits below it.
 */
.chr-hero__text h1 {
  margin-bottom: 24px;
  /* Sized for a sentence, not the three short lines this replaced: at the old clamp a
     57-character headline broke into five lines inside this column. */
  font-size: calc(clamp(2.4rem, 4.4vw, 4rem) * var(--h1-scale));
  line-height: var(--display-leading);
  max-width: calc(var(--h1-measure) * 1%);
  transform: translate(var(--h1-x), var(--h1-y));
}

/*
 * The differentiator carries the brand green — the same green as the button below it, so the
 * claim and the action rhyme.
 */
.chr-hero__accent {
  color: var(--accent);
}

/*
 * The deck stacks above the button rather than sitting beside it: at this size a shared row
 * would leave the paragraph a cramped measure and strand the button. Width is a multiple of
 * the sub-line's own size, so the measure — and the rule capping it — stay in proportion
 * however far the adjuster is dragged.
 */
.chr-hero__say {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(22px, 2.4vw, 32px);
  max-width: calc(var(--hero-subhead) * var(--sub-measure));
  margin-top: clamp(32px, 4.6vw, 74px);
  transform: translate(var(--sub-x), var(--sub-y));
}

/*
 * A short tick in the headline's ink rather than a hairline spanning the measure: it reads as
 * a mark belonging to the deck instead of a divider partitioning the section, and it has no
 * arbitrary right-hand end. The block is a flex column, so this is a flex item and the
 * column `gap` spaces it — hence no margin and no padding on the block.
 */
.chr-hero__say::before {
  content: '';
  flex: none;
  width: 72px;
  height: 2px;
  background: var(--ink);
}

.chr-hero__say p {
  margin: 0;
  font-size: var(--hero-subhead);
  line-height: 1.38;
  letter-spacing: -0.011em;
}

.chr-hero__say .chr-btn {
  font-size: var(--hero-cta);
}


/*
 * The plate frame. It keeps the drawing's own ratio and the image is `contain`ed inside it,
 * so no edge of the original is ever lost. The mask feathers two corners independently:
 * top+left and bottom+right, each with its own distance and falloff rate. The top fade is on
 * the inner element so it can be limited to the left span — the stretch carrying the intake
 * rail — leaving the factory at the top right untouched.
 */
.chr-hero__plate {
  grid-row: 1;
  grid-column: var(--plate-cols);
  aspect-ratio: 2400 / 1018;
  width: calc(100% * var(--plate-zoom));
  position: relative;
  /* Custom 1 hangs the plate slightly below the headline block; without this the intake
     rail rides up into the second line. */
  margin-top: clamp(16px, 2.5vw, 44px);
  transform: translateY(var(--plate-y));
  mix-blend-mode: darken;
  pointer-events: none;
  z-index: 0;

  -webkit-mask-image:
    linear-gradient(
      to right,
      transparent 0,
      calc(var(--plate-fade-tl) * var(--plate-rate-tl) / 100),
      #000 var(--plate-fade-tl),
      #000 calc(100% - var(--plate-fade-br)),
      calc(100% - var(--plate-fade-br) * var(--plate-rate-br) / 100),
      transparent 100%
    ),
    linear-gradient(
      to bottom,
      #000 0,
      #000 calc(100% - var(--plate-fade-br)),
      calc(100% - var(--plate-fade-br) * var(--plate-rate-br) / 100),
      transparent 100%
    );
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(
      to right,
      transparent 0,
      calc(var(--plate-fade-tl) * var(--plate-rate-tl) / 100),
      #000 var(--plate-fade-tl),
      #000 calc(100% - var(--plate-fade-br)),
      calc(100% - var(--plate-fade-br) * var(--plate-rate-br) / 100),
      transparent 100%
    ),
    linear-gradient(
      to bottom,
      #000 0,
      #000 calc(100% - var(--plate-fade-br)),
      calc(100% - var(--plate-fade-br) * var(--plate-rate-br) / 100),
      transparent 100%
    );
  mask-composite: intersect;
}

.chr-hero__plateinner {
  position: absolute;
  inset: 0;
  transform: translateX(var(--plate-x));

  -webkit-mask-image:
    linear-gradient(
      to bottom,
      transparent 0,
      calc(var(--plate-fade-tl) * var(--plate-rate-tl) / 100),
      #000 var(--plate-fade-tl)
    ),
    linear-gradient(to right, transparent var(--plate-top-span), #000 var(--plate-top-span));
  -webkit-mask-composite: source-over;
  mask-image:
    linear-gradient(
      to bottom,
      transparent 0,
      calc(var(--plate-fade-tl) * var(--plate-rate-tl) / 100),
      #000 var(--plate-fade-tl)
    ),
    linear-gradient(to right, transparent var(--plate-top-span), #000 var(--plate-top-span));
  mask-composite: add;
}

.chr-hero__plateinner img {
  object-fit: contain;
}

/* ── partners ────────────────────────────────────────────────────────────── */

.chr-partners {
  display: flex;
  align-items: center;
  gap: clamp(20px, 4vw, 56px);
  flex-wrap: wrap;
  padding: 18px var(--pad-x);
  background: var(--paper-2);
  border-block: var(--border) solid var(--rule);
}

.chr-partners > span {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.chr-partners ul {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(12px, 3vw, 44px);
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--display);
  font-size: clamp(1.1rem, 2vw, 1.5rem);
  font-weight: 600;
}

/* ── sections ────────────────────────────────────────────────────────────── */

.chr-section {
  padding: clamp(56px, 7vw, 110px) var(--pad-x);
  border-bottom: var(--border) solid var(--rule);
}

.chr-section__head {
  max-width: 62ch;
  margin-bottom: clamp(28px, 3.5vw, 52px);
}

.chr-section__head h2 {
  font-size: clamp(1.7rem, 3.2vw, 2.6rem);
  line-height: 1.1;
  margin-bottom: 16px;
}

.chr-section__head p {
  margin: 0;
  color: var(--ink-60);
}

.chr-section--lede h2 {
  max-width: 24ch;
  font-size: clamp(1.9rem, 3.8vw, 3rem);
  line-height: 1.12;
}

/*
 * Reserved frames. These are deliberate placeholders, not empty boxes: ruled like a plate
 * waiting on the press, labelled with what is coming and why it is not here yet.
 */
.chr-frame {
  display: grid;
  place-content: center;
  gap: 8px;
  text-align: center;
  padding: clamp(28px, 5vw, 64px);
  border: var(--border) solid var(--ink);
  background:
    repeating-linear-gradient(
      -45deg,
      transparent 0 11px,
      color-mix(in srgb, var(--ink) 5%, transparent) 11px 12px
    ),
    var(--paper-2);
}

.chr-frame__label {
  font-family: var(--display);
  font-size: clamp(1rem, 1.7vw, 1.25rem);
  font-weight: 600;
}

.chr-frame__note {
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--ink-60);
}

/* ── quotes ──────────────────────────────────────────────────────────────── */

.chr-quotes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(18px, 2.5vw, 32px);
}

.chr-quotes blockquote {
  margin: 0;
  padding-top: 18px;
  border-top: var(--border) solid var(--ink);
}

.chr-quotes p {
  margin: 0 0 18px;
  font-size: 17px;
}

.chr-quotes footer {
  display: grid;
  gap: 2px;
  font-size: 13.5px;
  color: var(--ink-60);
}

.chr-quotes strong {
  color: var(--ink);
}

/* ── footer ──────────────────────────────────────────────────────────────── */

.chr-footer {
  padding: clamp(56px, 7vw, 110px) var(--pad-x) 28px;
  background: var(--paper);
}

.chr-footer__cta {
  max-width: 46ch;
  margin-bottom: clamp(44px, 6vw, 84px);
}

.chr-footer__cta h2 {
  font-size: clamp(1.8rem, 3.6vw, 2.9rem);
  line-height: 1.1;
  margin-bottom: 14px;
}

.chr-footer__cta p {
  margin: 0 0 26px;
  color: var(--ink-60);
}

.chr-footer__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: clamp(20px, 3vw, 44px);
  padding-bottom: clamp(30px, 4vw, 56px);
}

.chr-footer__cols h3 {
  margin-bottom: 12px;
  font-family: var(--body);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.chr-footer__cols ul {
  display: grid;
  gap: 7px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 15px;
}

.chr-footer__cols a:hover {
  color: var(--accent);
}

.chr-footer__legal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-top: 22px;
  border-top: var(--border) solid var(--rule);
  font-size: 13px;
  color: var(--ink-60);
}

/* ── narrow ──────────────────────────────────────────────────────────────── */

/*
 * Below 900px the overlap stops: the plate's blank left field is no longer wide enough to
 * hold the headline, so words come first on clean paper and the plate follows as a band.
 */
@media (max-width: 900px) {
  .chr-hero {
    display: block;
    min-height: 0;
    /* the adjuster still moves this below the cap; it just cannot leave a phone-sized
       screen opening on empty paper */
    padding-top: min(var(--hero-top), 64px);
  }

  /*
   * The offsets exist to compose type against the plate; below 900px the plate sits under the
   * text instead, so there is nothing to compose against and they are dropped.
   */
  .chr-hero__text h1 {
    max-width: 100%;
    font-size: calc(clamp(1.75rem, 7.4vw, 3rem) * var(--h1-scale));
    transform: none;
  }

  .chr-hero__say {
    margin-top: 28px;
    max-width: 100%;
    gap: 24px;
    transform: none;
  }

  /* the adjuster still moves these below the cap; it just cannot overflow a phone */
  .chr-hero__say p {
    font-size: min(var(--hero-subhead), 21px);
  }

  .chr-hero__say .chr-btn {
    font-size: min(var(--hero-cta), 16px);
  }

  .chr-hero__plate {
    width: 100%;
    margin-top: clamp(24px, 5vw, 40px) !important;
    transform: none;
  }

  .chr-hero__plateinner {
    transform: none;
  }

  .chr-nav {
    flex-wrap: wrap;
    gap: 12px 16px;
  }

  .chr-wordmark {
    min-width: 0;
    font-size: 12px;
    letter-spacing: 0.1em;
  }

  .chr-wordmark svg {
    width: 74px;
    height: auto;
  }

  .chr-nav__auth {
    margin-left: auto;
    gap: 14px;
  }

  .chr-nav__links {
    order: 3;
    width: 100%;
    margin-right: 0;
    gap: 16px;
    flex-wrap: wrap;
    font-size: 14.5px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chr * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* ── full-viewport sections ──────────────────────────────────────────────── */

.chr-sec {
  --sec-pad: clamp(88px, 17vh, 180px);

  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 40%) minmax(0, 60%);
  align-content: center;
  column-gap: clamp(28px, 4vw, 72px);
  padding: var(--sec-pad) var(--pad-x);
  border-bottom: var(--border) solid var(--rule);
}

.chr-sec__copy h2 {
  max-width: 22ch;
  margin-bottom: 20px;
  font-size: clamp(1.7rem, 3.1vw, 2.7rem);
  line-height: 1.08;
}

.chr-sec__copy p {
  margin: 0;
  max-width: 52ch;
  font-size: clamp(1rem, 1.3vw, 1.12rem);
  /* Same ink as the hero sub-line, which takes it from `.chr`. One standard colour for body
     copy across every band, rather than a muted variant per section. */
  color: var(--ink);
}

/*
 * Centred sections. The header block spans the column, so the measure is capped on the text
 * itself rather than on the container: centred lines with a wide container drift apart.
 *
 * Size and offsets come from the inherited band vars so the adjuster can drive them.
 */
.chr-sec--mid {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  row-gap: 0;
}

/*
 * The vertical offset trades top padding for bottom rather than transforming the header.
 * A transform does not reflow, so a negative value lifted the header clean out of its own
 * section and over the one above it. With `align-content: center` the centred block shifts by
 * half the padding difference, so the two halves of the trade give exactly --band-y, and the
 * `max(0px, ...)` floor means it can never climb past the top of its own section.
 */
.chr-sec--mid {
  /*
   * Aligned to the top, not centred. Centring made the gap above each heading depend on how
   * tall that band's content happened to be, so band 1 sat 124px down, section 2 sat 28px down
   * and the closing band sat 149px down. A fixed offset is the only way the distance can be
   * the same in every band, which is the point.
   *
   * --band-y offsets that shared distance, floored so a heading cannot reach its own top edge.
   */
  align-content: start;
  padding-top: max(28px, calc(var(--band-top) + var(--band-y)));
  padding-bottom: var(--sec-pad);
}

.chr-sec--mid .chr-sec__copy {
  max-width: 100%;
}

.chr-sec--mid .chr-sec__copy h2 {
  max-width: calc(var(--w-h) * 1ch);
  margin: 0 auto var(--band-sub-y);
  font-size: var(--band-heading);
  line-height: 1.06;
}

.chr-sec--mid .chr-sec__copy p {
  max-width: calc(var(--w-s) * 1ch);
  margin: 0 auto;
  font-size: var(--band-sub);
  line-height: 1.45;
}

.chr-sec--mid .chr-sec__cta {
  justify-content: center;
}

.chr-sec__cta {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: clamp(30px, 4vh, 48px);
}

/* The closing band stacks: no figure column to sit beside. */
.chr-sec--partner {
  grid-template-columns: minmax(0, 1fr);
  row-gap: clamp(24px, 3vh, 40px);
}

/*
 * The ligand structure, painted rather than drawn: a solid ink rectangle masked by the PNG's
 * alpha. See --lig-ink above for why that is the right way round.
 *
 * Only the even edge feather is carried over from band 4, not its four per-corner ramps. Those
 * exist because the photograph has a dark crescent down its left side that had to be dissolved
 * asymmetrically. This asset is a transparent field whose own strokes already fade out, so
 * there is no rectangle edge to hide beyond the crop.
 */
.chr-lig {
  position: absolute;
  top: var(--lig-y);
  right: var(--lig-x);
  z-index: 0;
  width: var(--lig-w);
  aspect-ratio: 2400 / 1340;
  margin: 0;
  pointer-events: none;
  background: var(--lig-ink);
  opacity: var(--lig-o);

  -webkit-mask-image:
    url('landing/ligand-binding.png'),
    linear-gradient(to right, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to left, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to bottom, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to top, transparent 0, #000 var(--lig-edge));
  -webkit-mask-size: 100% 100%, auto, auto, auto, auto;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: source-in;
  mask-image:
    url('landing/ligand-binding.png'),
    linear-gradient(to right, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to left, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to bottom, transparent 0, #000 var(--lig-edge)),
    linear-gradient(to top, transparent 0, #000 var(--lig-edge));
  mask-size: 100% 100%, auto, auto, auto, auto;
  mask-repeat: no-repeat;
  mask-composite: intersect;
}

/*
 * Each band points the measure rules at its own pair. The platform headline is much longer
 * than the others, which is why this is per band rather than one global measure.
 */
#problem {
  --w-h: var(--w1-h);
  --w-s: var(--w1-s);
}

#partner {
  --w-h: var(--w6-h);
  --w-s: var(--w6-s);
}

/* ── band 2: the workbench, opening ──────────────────────────────────────── */

/*
 * A tall track with a pinned inner, so the screen tilts up flat as you scroll past.
 *
 * The heading sits inside the same pinned block rather than scrolling away above it, which is
 * the one change from the reference: there the heading translates up and out, so by the time
 * the screen is flat you can no longer read what it is a screen of. Here both hold the same
 * viewport throughout, and the heading keeps the same distance from the top as every other band.
 *
 * The perspective lives inside the transform rather than on an ancestor, so the card owns its
 * own projection and no wrapper has to become a 3D context.
 */
.chr-os {
  min-height: 190vh;
  border-bottom: var(--border) solid var(--rule);
  view-timeline-name: --dg;
  view-timeline-axis: block;
}

.chr-os__inner {
  position: sticky;
  top: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  /* This is the gap you see once the screen is flat; the tilt starts above it. */
  gap: clamp(24px, 4.4vh, 48px);
  /*
   * A fixed height, not a minimum. With `min-height` the card could grow the grid past the
   * viewport it is pinned inside, which also meant the card's own `max-height: 100%` resolved
   * against a row the card had itself enlarged, so it capped nothing.
   */
  height: 100vh;
  padding: max(28px, calc(var(--band-top) + var(--band-y))) var(--pad-x)
    clamp(28px, 5vh, 56px);
}

.chr-os__head {
  text-align: center;
}

.chr-os__head h2 {
  max-width: calc(var(--w2-h) * 1ch);
  margin: 0 auto;
  font-size: calc(var(--band-heading) * var(--w2-scale));
  line-height: 1.06;
}

.chr-os__stage {
  display: grid;
  /*
   * Anchored to the top of the row, not centred. Centred, a taller card started higher and ate
   * its own gap: 106px of clearance under the heading at one height, 46px at another. Anchored,
   * the flat gap is just the grid gap, whatever the height.
   */
  place-items: start center;
  min-height: 0;
}

.chr-os__card {
  width: 100%;
  /*
   * Both dimensions are tunable, each against a cap it cannot cross: the width cannot exceed
   * its column, and the height cannot exceed 74vh. Without the height cap a value that looks
   * right on a tall display pushes the card past the bottom of the viewport it is pinned
   * inside on a short one.
   *
   * Height is explicit rather than `height: 100%`, which could not resolve against an
   * indefinite grid row and silently fell back to content height.
   */
  max-width: min(var(--os-w), 100%);
  /*
   * The knob, against a ceiling in viewport units.
   *
   * Percentages are not usable here in either direction. `height: 100%` is cyclic against a
   * `1fr` row, so it falls back to the panel's natural content height and overflows; and
   * `max-height: 100%` beside a length height never resolves, so it clamps nothing. Both were
   * tried and both let the card run past the bottom of the viewport it is pinned inside.
   *
   * So the ceiling is measured the same way the space above it is. The reserved parts are the
   * top padding (14vh, capped), the two-line heading (about 116px), the gap (4.4vh) and a small
   * clearance below, which comes to 18.4vh + 140px. What is left is the true maximum: any taller
   * and either the heading gives up its position or the card's bottom leaves the viewport.
   * A much larger --band-heading would need the 140 revisited.
   *
   * Resolved into a variable because the tilt below has to compensate for the height actually
   * used, not the height asked for. Reading the raw knob there meant that past the ceiling the
   * lift kept growing while the card did not, and the overlap ran away.
   */
  --os-h-used: min(var(--os-h), calc(81.6vh - 140px - var(--band-y)));

  height: var(--os-h-used);
  padding: 10px;
  /* It rises into the heading at the start, so it has to sit above it. */
  position: relative;
  z-index: 1;
  border-radius: 22px;
  background: var(--ink);
  box-shadow:
    0 18px 36px color-mix(in srgb, var(--ink) 16%, transparent),
    0 54px 80px color-mix(in srgb, var(--ink) 9%, transparent);
  /* Flat and settled is the finished state, so it is also the no-animation state. */
  transform: perspective(1400px) translateY(0) rotateX(0deg) scale(1);
  transform-origin: 50% 100%;
}

.chr-os__screen {
  height: 100%;
  overflow: hidden;
  border-radius: 13px;
  background: var(--paper);
}

/* One frame, not two: the card is the device, so the panel drops its own border. */
.chr-os__screen .chr-ui {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 0;
}

.chr-os__screen .chr-ui__panel {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.chr-os__screen .chr-ui__panelbody {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/*
 * Sized by its own aspect rather than stretched to the flex box. Filling the box let the SVG
 * letterbox inside itself, so the plot sat small and adrift with dead space either side; this
 * way the element is exactly the shape of its viewBox and simply centres.
 */
.chr-os__screen .chr-ui__chart {
  flex: 1;
  min-height: 0;
  height: 100%;
  width: auto;
  max-width: 100%;
  aspect-ratio: 620 / 240;
  margin-inline: auto;
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .chr-os__card {
      animation: chr-os-open linear both;
      animation-timeline: --dg;
      /*
       * Starts while the band is still rising, at roughly a fifth of it in view, and finishes a
       * little way into the pinned phase. `contain 0%` is the moment the band's top reaches the
       * top of the viewport, so starting there meant the screen sat tilted through the whole
       * approach and only began turning once everything had already settled.
       */
      animation-range: entry 35% contain 30%;
    }
  }
}

/*
 * Tilted and riding up into the last line of the heading, then descending as it flattens, so it
 * settles with clear air above it. translateY sits left of rotateX so the move reads as a plain
 * vertical one rather than a slide along the tilted plane.
 */
@keyframes chr-os-open {
  from {
    /*
     * The lift has two parts. The constant is the overlap itself: about 12px into the heading's
     * descender space, which reads as depth without covering letterforms on the second line.
     *
     * The height term cancels the tilt. Rotating about the bottom edge makes the projected top
     * recede downward as the card grows, so a fixed offset that overlapped at one height became
     * a gap at a taller one. 0.245 is fitted to the measured recede at three heights, which is
     * steeper than linear because of the perspective foreshortening; it holds the overlap to
     * roughly a graze across the whole range instead of 29 down to 0.
     *
     * Tuned at the animation's own 0%, which is where the card is highest. Calibrating at
     * `contain 0%` instead is misleading: the range starts back in `entry`, so by then the card
     * is already about 70% flattened and a value that looks right there buries the heading
     * completely when the band first comes into view.
     */
    transform: perspective(1400px) translateY(calc(-8px - var(--os-h-used) * 0.225))
      rotateX(17deg) scale(1.04);
  }

  to {
    transform: perspective(1400px) translateY(0) rotateX(0deg) scale(1);
  }
}

/* ── sections 3 to 5 ─────────────────────────────────────────────────────── */



/*
 * Three flat tones rather than the brief's looping gradients: an 8s infinite animation is the
 * ambient motion this page rules out, and a gradient is the fill it rules out. The tones step
 * through the same family the headline ladder uses, so the three words read as one phrase.
 */
/*
 * All three words are ink; one at a time is lit by a blue-to-green gradient, in sequence,
 * including its period. The lit copy is a ::after overlaying the real text rather than a
 * recolouring of it, because `background-clip: text` needs `color: transparent` and the base
 * has to stay ink underneath. Same gradient on all three.
 *
 * Positive delays on a 9s cycle, so the three light in reading order with no overlap.
 * Both gradient stops clear 3:1 on this paper, so a lit word is never the unreadable one.
 */
.chr-trio__w {
  position: relative;
  display: inline-block;
  color: var(--ink);
}

/*
 * The words are rendered from an array, so there is no whitespace text node between them, and
 * inline-block means they would sit flush even if there were. The heading also carries the
 * display tracking, which is negative, so the space has to be explicit and a little generous:
 * these are three separate statements, not one phrase.
 */


@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  @media (prefers-reduced-motion: no-preference) {
    .chr-trio__w::after {
      content: attr(data-word);
      position: absolute;
      inset: 0;
      background-image: linear-gradient(100deg, var(--sky-lit) 0%, var(--accent) 100%);
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      opacity: 0;
      animation: chr-lit 9s infinite;
    }

    .chr-trio__w--2::after {
      animation-delay: 3s;
    }

    .chr-trio__w--3::after {
      animation-delay: 6s;
    }
  }
}

@keyframes chr-lit {
  0% {
    opacity: 0;
  }

  8%,
  25% {
    opacity: 1;
  }

  33%,
  100% {
    opacity: 0;
  }
}

/* No rule between the shared header and the first band it introduces: they are one unit. */
/* Band 2 already closes with a rule, so the first band inside this group does not add one. */
.chr-trio > :first-child {
  border-top: 0;
}

/* ── a band whose value adds advance on scroll ───────────────────────────── */

.chr-band {
  border-top: var(--border) solid var(--rule);
}

/* Inside the pinned block now, so the padding that used to separate it lives on that block. */
.chr-band__lead {
  margin: 0 auto;
  text-align: center;
}

/* At 24px this clears the large-text contrast threshold in the accent. Going much below that
   in the adjuster would put it under 3:1 on this paper. */
.chr-band__eyebrow {
  margin: 0 0 0.55em;
  font-size: var(--eyebrow-size);
  font-weight: 600;
  letter-spacing: 0.14em;
  line-height: 1.1;
  text-transform: uppercase;
  color: var(--accent);
}

/*
 * The measure belongs on the heading, where a ch is a character of the heading. On the wrapper
 * it was a character of 17px body text, so 30ch came out at 312px and set this to nine lines.
 */
.chr-band__lead h3 {
  max-width: calc(var(--wt-h) * 1ch);
  margin: 0 auto;
  font-size: var(--wt-size);
  line-height: 1.08;
}

/*
 * The track is tall and its inner block is sticky, so the three steps advance while the block
 * stays put. That is what makes the list move with the scroll rather than on a timer.
 */
.chr-steps {
  position: relative;
  min-height: 200vh;
  view-timeline-name: --steps;
  view-timeline-axis: block;
}

/*
 * Two rows: the lead, then the steps beside the image. A fixed height rather than a minimum, so
 * the second row is bounded and cannot push the pinned block past the viewport. The heading also
 * keeps the same distance from the top here as every other band.
 */
.chr-steps__inner {
  position: sticky;
  top: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: clamp(22px, 3.4vh, 46px);
  /* Its own width. This was `1140px * var(--grid-size)`, which tied the whole band to band 1's
     reason-grid knob: two unrelated things moving on one slider. */
  max-width: var(--steps-w);
  height: 100vh;
  margin: 0 auto;
  padding: max(28px, calc(var(--band-top) + var(--band-y))) var(--pad-x)
    clamp(26px, 4.5vh, 52px);
}

.chr-steps__grid {
  display: grid;
  /* The list takes its share, the frame takes the rest. A percentage rather than a fraction,
     because a fr value cannot be built out of a custom property. */
  grid-template-columns: minmax(0, var(--list-w)) minmax(0, 1fr);
  /*
   * Bounded by this box, not by what is in it. Left implicit, the single row is auto-sized to
   * max-content, so on a short screen the carousel sized the row instead of the row capping the
   * carousel and the slab hung 50px below the fold. The dashed placeholders never showed it
   * because they carry a fixed 4:3 and no minimum of their own.
   */
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  gap: clamp(30px, 4.5vw, 76px);
  min-height: 0;
}

.chr-steps__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: calc(clamp(26px, 3.4vh, 44px) * var(--list-scale));
}

/* Everything in a step scales together, so the list grows as one block rather than the type
   drifting away from the numeral beside it. */
.chr-step {
  display: flex;
  align-items: flex-start;
  gap: calc(20px * var(--list-scale));
  color: var(--ink);
}

/* Numbered, not ticked. A tick says "done"; these are three properties that are all true. */
/* 19px bold, so the digit clears the large-text threshold once it turns accent. */
.chr-step__n {
  flex: none;
  display: grid;
  place-items: center;
  width: calc(42px * var(--list-scale));
  height: calc(42px * var(--list-scale));
  border: 2px solid currentColor;
  border-radius: 50%;
  font-family: var(--display);
  font-size: calc(19px * var(--list-scale));
  font-weight: 700;
  color: inherit;
}

.chr-step__body {
  display: block;
}

.chr-step__title {
  display: block;
  margin-bottom: 4px;
  font-family: var(--display);
  font-size: calc(clamp(1.15rem, 1.7vw, 1.5rem) * var(--list-scale));
  font-weight: 600;
  line-height: 1.15;
  color: inherit;
}

/* Held at ink whichever step is active: the accent is 3.7:1, fine for the heading and the
   numeral at their sizes, short of the 4.5:1 body copy needs. */
.chr-step__text {
  display: block;
  font-size: calc(clamp(0.95rem, 1.15vw, 1.05rem) * var(--list-scale));
  line-height: 1.5;
  color: var(--ink);
}

.chr-steps__media {
  display: grid;
  /*
   * A grid item will not shrink below its min-content unless told to. With a carousel in here
   * that min-content is the widest model name times three columns, which pushed this 67px past
   * its own track and off the right edge of the screen. The dashed placeholders never hit it.
   */
  min-width: 0;
  /*
   * Stretched, against the grid's `align-items: center`, so this cell has the row's height
   * rather than its own content's. That is what a live shot measures itself against; without it
   * a percentage height here is circular and resolves to auto.
   */
  align-self: stretch;
  min-height: 0;
}

/* Placeholder frames. Deliberately plain: real captures go here, not generated art. */
.chr-shot {
  grid-area: 1 / 1;
  min-width: 0;
  /* The cell stretches now, so a frame has to opt back out of filling it. */
  align-self: center;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  width: 100%;
  /* Capped by width, not height: with the ratio fixed, bounding the width is what keeps the
     frame from outgrowing the row, and a max-height here would not resolve against it. */
  max-width: calc(46vh * 4 / 3);
  justify-self: center;
  margin: 0;
  border: var(--border) dashed #b9b5a8;
  border-radius: 12px;
  background: #e4e1d8;
  opacity: 0;
  /*
   * All three frames sit in one cell, so a hidden one still lies over the others. Harmless
   * while they were three identical grey boxes; not harmless once one of them is a carousel
   * you drag. Nothing in a placeholder is interactive, so none of them take the pointer.
   */
  pointer-events: none;
}

/*
 * A step with real media instead of a frame. Loses the dashed box and the 4:3 cap: it brings
 * its own shape, and it is the one thing in the stack a reader can touch.
 */
.chr-shot--live {
  display: grid;
  /*
   * Bounded by this box, not by what is in it. Left implicit the single row is auto-sized to
   * content, so a percentage height inside it resolves to the content's own height and clamps
   * nothing: the block simply grew past the row and off the bottom of a short screen. Same trap
   * as .chr-steps__grid. `align-items`, not `align-content`, because the row now fills the box
   * and it is the block inside it that needs centring.
   */
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  align-self: stretch;
  aspect-ratio: auto;
  min-height: 0;
  /* The placeholders' 4:3 cap does not apply to something that is not a 4:3 frame. */
  max-width: none;
  border: 0;
  border-radius: 0;
  background: none;
  pointer-events: auto;
}


.chr-shot--1 {
  opacity: 1;
}

.chr-shot figcaption {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #8d897c;
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .chr-step,
    .chr-shot {
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --steps;
    }

    /*
     * Three keyframes, not one. `both` holds the 0% frame before the range and the 100% frame
     * after it, so a single dim-up-dim curve left every step dimmed while the band was entering
     * and again once it had left. The first holds lit until its handoff and the last stays lit
     * afterwards, so something is always the active step.
     */
    /*
     * Steps change colour, images change opacity. Dimming the steps made the whole list read as
     * greyed-out furniture; at full ink they all stay present and the active one simply goes
     * accent. The images still have to swap, so those keep the opacity curve.
     */
    .chr-step--1 {
      animation-name: chr-lit-first;
    }

    .chr-step--2 {
      animation-name: chr-lit-mid;
    }

    .chr-step--3 {
      animation-name: chr-lit-last;
    }

    .chr-shot--1 {
      animation-name: chr-step-first;
    }

    .chr-shot--2 {
      animation-name: chr-step-mid;
    }

    .chr-shot--3 {
      animation-name: chr-step-last;
    }

    /* One third of the pinned scroll each, so the list keeps pace with the reader. */
    .chr-step--1,
    .chr-shot--1 {
      animation-range: contain 0% contain 34%;
    }

    .chr-step--2,
    .chr-shot--2 {
      animation-range: contain 33% contain 67%;
    }

    .chr-step--3,
    .chr-shot--3 {
      animation-range: contain 66% contain 100%;
    }
  }
}

/*
 * Without scroll-driven animation none of these apply and every step stays legible.
 *
 * Out is opacity 0, not 0.28. The frames are stacked in one cell, so a dimmed one is a veil
 * over the active one. Three identical grey boxes hid that; a carousel under a 28% grey box
 * does not. `pointer-events` rides along so the stack cannot swallow a drag either: it is a
 * discrete property, so it flips halfway through the fade rather than ramping.
 */
@keyframes chr-step-first {
  0%,
  72% {
    opacity: 1;
    pointer-events: auto;
  }

  100% {
    opacity: 0;
    pointer-events: none;
  }
}

@keyframes chr-step-mid {
  0% {
    opacity: 0;
    pointer-events: none;
  }

  22%,
  78% {
    opacity: 1;
    pointer-events: auto;
  }

  100% {
    opacity: 0;
    pointer-events: none;
  }
}

@keyframes chr-step-last {
  0% {
    opacity: 0;
    pointer-events: none;
  }

  28%,
  100% {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Same three-phase shape as the images, on colour instead of opacity. */
@keyframes chr-lit-first {
  0%,
  72% {
    color: var(--accent);
  }

  100% {
    color: var(--ink);
  }
}

@keyframes chr-lit-mid {
  0% {
    color: var(--ink);
  }

  22%,
  78% {
    color: var(--accent);
  }

  100% {
    color: var(--ink);
  }
}

@keyframes chr-lit-last {
  0% {
    color: var(--ink);
  }

  28%,
  100% {
    color: var(--accent);
  }
}

/*
 * No horizontal padding here. It was insetting the clipping box by --pad-x, so the photograph
 * was cut 43px short of the screen edge and left a visible border down the right. The inner
 * carries the inset instead, which keeps the copy where it was and lets the image run out.
 */
.chr-band__figure {
  padding: clamp(40px, 6vh, 80px) 0 clamp(72px, 10vh, 120px);
}

/*
 * The figure slot gets its own screen when what it holds is a worked example rather than a
 * chart that fills one already. Centred inside it, so the example lands the way every other
 * band lands rather than trailing under the steps.
 */
.chr-band__figure--full {
  display: grid;
  /* One full-width column, so the item inside has a definite track to fill. */
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  min-height: 100vh;
  /*
   * Symmetric, overriding the figure's own 62px top and 104px bottom. That asymmetry is for the
   * train chart, which has a caption row under it; here it put a text block 42px above the
   * centre of its own screen while still being perfectly centred in its content box.
   */
  padding-block: clamp(40px, 6vh, 80px);
  /* Hosts band 6's background artwork, and contains it so it can bleed off the right edge
     without widening the page. `clip` rather than `hidden`: hidden would make this a scroll
     container. Nothing in here is sticky, so clipping costs nothing. */
  position: relative;
  overflow: clip;
}

/* ── the toolchain carousel, in band 3's first value add ────────────────── */

/*
 * The carousel sits directly on the paper: no slab, no bezel, no border.
 *
 * It was a dark device, because the handoff is explicit that its near-black covers read as
 * seventeen dark rectangles on a light ground. That is the trade taken here on purpose: the
 * cards are the dark rectangles, and they read as prints laid on the sheet rather than as a
 * screen cut into it. Everything that was coloured for the dark surface, which is every label
 * in here, is now ink.
 *
 * Width and height are the two knobs. Width is how much of the ring you see, so it is how many
 * families are on screen at once; height is the card. Both are separate because the art is
 * square and the frame around it is not.
 */
.chr-tc {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: 100%;
  /*
   * Sizes and places itself rather than filling its slot, so the two single-image steps beside
   * it can do something different. Anchored to the right edge of the media column: widening it
   * runs leftward across the gap and over the value list, which is where the space is, and the
   * frame's edge mask means the cards fade into the paper there rather than ending on a line.
   *
   * A margin rather than `justify-self: end`, which does nothing: grid clamps end and centre
   * alignment back to start the moment an item is larger than its track, so the block grew
   * rightward off the screen instead. Resolved into a variable because the margin has to be
   * computed from the width actually used; the raw knob would keep pulling left after the
   * viewport cap had stopped the box growing. `justify-self: start` because a centred item
   * splits its margins with the free space, which halved every shift.
   */
  --cf-w-used: min(var(--cf-w), calc(100vw - var(--pad-x) * 2));

  justify-self: start;
  width: var(--cf-w-used);
  margin-left: calc(100% - var(--cf-w-used) + var(--cf-x));
}

.chr-tc__inner {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
}

.chr-tc__count {
  flex: none;
  margin: 12px 0 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink-60);
}

/*
 * Pitch, recession and the viewer distance are all multiples of the card's measured width, so
 * the whole rake scales off one number. It is measured rather than declared: the card is a
 * square as tall as the ring, and the ring's height is whatever survived the shrink.
 */
.chr-cf {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
}

.chr-cf__stage {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
}

.chr-cf__frame {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 0 1 auto;
  min-height: 0;
  /* Vertical padding keeps the drop shadows clear of the overflow clip. */
  padding: 22px 0 30px;
  overflow: hidden;
  outline: none;
  cursor: grab;
  /* Horizontal drag is ours; the page keeps vertical scrolling. */
  touch-action: pan-y;
  /*
   * The ring is wider than the frame, so the outer cards leave through the sides. Cut square
   * they end mid-artwork and read as broken; faded out they read as continuing past the frame,
   * which is what a ring does. Same device as the hero plate and the crystal photograph, and
   * the reason the block can be widened over the text on its left without a hard edge.
   */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
}

.chr-cf__frame:active {
  cursor: grabbing;
}

.chr-cf__frame:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The knob is the height it asks for; flex-shrink is what a short row does about it. */
.chr-cf__ring {
  position: relative;
  height: var(--cf-h);
  flex: 0 1 auto;
  min-height: 0;
  transform-style: preserve-3d;
  user-select: none;
}

/* Square, so the ring's height is also the card's width. */
.chr-cf__card {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  width: auto;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 14px;
  /* The art's own ground, so there is no flash of a lighter square before it arrives. */
  background: #0b0e13;
  /*
   * Three shadows, in the order they read.
   *
   * The rim is the family's accent at low alpha: enough to separate a near-black square from
   * the cream and to carry the same hue as the linework printed inside it, not enough to become
   * a coloured border. The glow spreads that hue a little way onto the paper, which is what
   * stops seventeen dark squares reading as seventeen holes. The last one is an ordinary drop
   * shadow that puts the card on the sheet rather than in it.
   *
   * `color-mix` rather than an rgba() per family: the accents arrive as hex in the data, and
   * mixing with transparent is the only way to take an alpha off a custom property.
   */
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--cf-accent, #22d3ee) 45%, transparent),
    0 0 20px -2px color-mix(in srgb, var(--cf-accent, #22d3ee) 32%, transparent),
    0 12px 28px color-mix(in srgb, var(--ink) 22%, transparent);
}

/*
 * Nothing to do here. The covers arrive already converted: see scripts/toolchain-art.mjs, which
 * bakes the delivered near-black art onto this paper and writes public/toolchain.
 *
 * This was a `filter` plus `mix-blend-mode: multiply`, which is a blended layer on a child of
 * each of seventeen cards that are all being transformed. Doing it once per file instead costs
 * the compositor nothing, cannot be squashed, and renders the same in every browser.
 */
.chr-cf__card img {
  object-fit: cover;
  user-select: none;
}

/* Below the rake, not over it. */
.chr-cf__nav {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 200;
  display: flex;
  justify-content: center;
  gap: 8px;
  pointer-events: none;
}

.chr-cf__nav button {
  display: grid;
  place-items: center;
  padding: 7px;
  border: 0;
  border-radius: 50%;
  background: var(--paper-2);
  box-shadow: inset 0 0 0 1px var(--rule);
  color: var(--ink-60);
  cursor: pointer;
  pointer-events: auto;
  transition: background 160ms ease, color 160ms ease;
}

.chr-cf__nav button:hover {
  background: var(--paper);
  color: var(--ink);
}

.chr-cf__caption {
  flex: none;
  display: grid;
  justify-items: center;
  padding-inline: 12px;
  animation: chr-cf-in 300ms ease-out;
}

.chr-cf__title {
  margin: 0;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/*
 * Held at the height of the longest list, so the row of dots underneath does not hop as
 * categories come round. Ten models over three columns is the tallest of the seventeen.
 */
.chr-cf__items {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-content: start;
  gap: 5px 22px;
  min-height: 88px;
  width: 100%;
  max-width: 540px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.chr-cf__items li {
  display: flex;
  min-width: 0;
  align-items: baseline;
  gap: 7px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  /* 4.9:1 on the paper. */
  color: var(--ink-60);
}

.chr-cf__items li > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The category's own accent, which is the hue of the linework on its cover. Mixed toward ink,
 * because these hues were picked to glow on near-black: the yellows and limes among them all
 * but disappear on cream at 4px.
 */
.chr-cf__dot {
  flex: none;
  align-self: center;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--cf-accent, #22d3ee) 72%, var(--ink));
}

.chr-cf__dots {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 7px;
  margin-top: 14px;
}

.chr-cf__dots button {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--ink);
  opacity: 0.22;
  cursor: pointer;
  transition: opacity 160ms ease;
}

.chr-cf__dots button.is-on {
  opacity: 1;
}

/*
 * On a short screen the fixed chrome under the rake is most of the block, and everything the
 * shrink takes comes out of the cards. Tightening the list and the paddings here hands about
 * 40px back to the ring, which is the only part that carries the artwork.
 */
@media (max-height: 820px) {
  .chr-cf__frame {
    padding: 14px 0 26px;
  }

  .chr-cf__items {
    min-height: 64px;
    gap: 3px 18px;
    margin-top: 10px;
  }

  .chr-cf__dots {
    margin-top: 10px;
  }

  .chr-tc__count {
    margin-top: 9px;
  }
}

/*
 * The caption cross-fade. The rake's own motion is user-driven, which is the case reduced
 * motion is meant to allow; this fade is not, so it goes.
 */
@keyframes chr-cf-in {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chr-cf__caption {
    animation: none;
  }
}

/*
 * A single delivered artwork as a step's media. The carousel card's treatment, for the same
 * reason: near-black art on cream needs its accent as a rim to sit on the paper, and the three
 * steps of the band should read as three of one thing rather than three ideas.
 */
.chr-art {
  /*
   * Height drives, width follows. A definite width with `max-height` beside it does not shrink
   * when the row does: at 1280x760 the box went 450 tall and stayed 470 wide, and `object-fit:
   * cover` started cutting the label off the bottom of a square artwork.
   *
   * `margin-left: auto` for the right-edge anchor rather than the carousel's computed margin,
   * because this box is smaller than its track: an auto margin holds the anchor at whatever
   * size the row leaves, and falls back to the start edge rather than overflowing if it ever
   * does not. The offset is a transform, which is free to do here: nothing about this box is a
   * drag target or a mask boundary.
   */
  height: min(var(--art-size), 100%);
  width: auto;
  max-width: none;
  margin: 0 0 0 auto;
  transform: translateX(var(--art-x));
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 14px;
  background: #0b0e13;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--cf-accent, #22d3ee) 45%, transparent),
    0 0 20px -2px color-mix(in srgb, var(--cf-accent, #22d3ee) 32%, transparent),
    0 12px 28px color-mix(in srgb, var(--ink) 22%, transparent);
}

.chr-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── the worked example that closes a band ──────────────────────────────── */

/*
 * Train's example is its bar chart, so it lives in that component. Bands 6 and 8 have no
 * measurement to make and no diagram any more, so they use this shell: the same three lines in
 * the same order, sharing the train example's type sizes and its left edge, on their own screen.
 *
 * Centred vertically only, by the figure that holds it. Not horizontally: this is the same block
 * of copy as band 4's, so it starts on the same line down the page.
 */
.chr-ex {
  /* Above band 6's artwork. Band 4 does the same for everything that is not its photograph. */
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(8px, 1.2vh, 14px);
  /*
   * `width: 100%` is load bearing next to the auto margins. This is a grid item, and an auto
   * margin on a grid item shrinks it to fit instead of stretching it, so the block's width
   * followed the length of its own copy and band 6 sat 113px right of band 4. Band 4's own
   * example does not hit this: it is a sticky block, where `margin: 0 auto` fills and centres.
   * With a definite width the margins only centre the leftover above the cap.
   */
  width: 100%;
  max-width: var(--steps-w);
  margin: 0 auto;
  /* Matches the train example, which doubles it because its own figure contributes none. */
  padding-inline: calc(var(--pad-x) * 2);
}

/* ── product UI placeholder ──────────────────────────────────────────────── */

/*
 * Styles for the swappable placeholder in components/marketing/ProductUI.tsx. Kept together
 * and prefixed so replacing that component means deleting one block here, or nothing at all.
 */
.chr-ui {
  border: var(--border) solid var(--ink);
  background: var(--paper);
}

.chr-ui__bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  border-bottom: var(--border) solid var(--rule);
  background: var(--paper-2);
}

.chr-ui__dots {
  display: flex;
  gap: 6px;
}

.chr-ui__dots i {
  width: 9px;
  height: 9px;
  border: var(--border) solid var(--ink-60);
  border-radius: 50%;
}

.chr-ui__title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.chr-ui__tabs {
  display: flex;
  flex-wrap: wrap;
  border-bottom: var(--border) solid var(--rule);
}

.chr-ui__tab {
  padding: 12px 20px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font-family: var(--body);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-60);
  cursor: pointer;
}

.chr-ui__tab:hover {
  color: var(--ink);
}

.chr-ui__tab--on {
  border-bottom-color: var(--accent);
  color: var(--ink);
}

.chr-ui__panel {
  padding: clamp(18px, 2.5vw, 30px);
}

.chr-ui__panel:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

.chr-ui__legend {
  display: flex;
  gap: 20px;
  margin-bottom: 14px;
  font-size: 12.5px;
  color: var(--ink-60);
}

.chr-ui__key {
  display: flex;
  align-items: center;
  gap: 7px;
}

.chr-ui__key::before {
  content: '';
  width: 16px;
  height: 2px;
  background: var(--accent);
}

.chr-ui__key--b::before {
  background: var(--ink-60);
}

.chr-ui__chart {
  display: block;
  width: 100%;
  height: auto;
}

.chr-ui__axis {
  stroke: var(--ink-60);
  stroke-width: 1.2;
}

.chr-ui__grid {
  stroke: var(--rule);
  stroke-width: 1;
}

.chr-ui__curve {
  fill: none;
  stroke-width: 2;
  stroke-dasharray: 1;
}

.chr-ui__curve--a {
  stroke: var(--accent);
}

.chr-ui__curve--b {
  stroke: var(--ink-60);
}

.chr-ui__tick {
  fill: var(--ink-60);
  font-family: var(--body);
  font-size: 11px;
}

.chr-ui__tick--mid {
  text-anchor: middle;
}

.chr-ui__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 10px clamp(20px, 4vw, 56px);
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: var(--border) solid var(--rule);
}

.chr-ui__stats dt {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.chr-ui__stats dd {
  margin: 4px 0 0;
  font-size: 14px;
}

.chr-ui__placeholder {
  margin: 0 0 16px;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--ink-60);
}

.chr-ui__rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

.chr-ui__rows li {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding-block: 12px;
  border-top: var(--border) solid var(--rule);
  font-size: 14px;
}

.chr-ui__meter {
  display: block;
  height: 10px;
  background: var(--paper-2);
  border: var(--border) solid var(--rule);
}

.chr-ui__meter span {
  display: block;
  height: 100%;
  background: var(--accent);
}

.chr-ui__val {
  font-size: 12px;
  color: var(--ink-60);
}

.chr-ui__steps {
  margin: 0;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.7;
}

/* ── diagram motion end states ───────────────────────────────────────────── */

/*
 * What is left of the diagrams section. The two SVG diagrams that filled bands 6 and 8 are gone
 * and every .chr-dg rule went with them. The .chr-vx machinery stays: the reason grid and the
 * "Because..." reveal still animate through it.
 */

/* End state for the group that belongs only to stage 1. */
.chr-vx--recede {
  opacity: 0;
}

/* ── motion ──────────────────────────────────────────────────────────────── */

/*
 * Scroll-driven CSS animation, native and off the main thread. No animation library: a
 * smooth-scroll shim would fight the browser's own scrolling and cost more than this page
 * needs.
 *
 * The finished state is the default. Every rule below sits inside @supports and
 * prefers-reduced-motion, and every keyframe is `from`-only, so the styles above are already
 * the end state. Reduced motion, or a browser without scroll-timeline support and JS off, both
 * land on the completed page rather than an empty one.
 *
 * Timelines are named on the section rather than taken anonymously per element: SVG children
 * have no CSS layout box of their own to measure a view timeline against, and section 3 pins
 * its figure, which would freeze a timeline measured on the figure itself.
 */

@keyframes chr-rise-k {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
}

@keyframes chr-fade-k {
  from {
    opacity: 0;
  }
}

/* Reveal letters arrive with a small lift, as the brief's version did. */
@keyframes chr-lift-k {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

/* Default opacity is 0, so this holds stage 1's labels visible until stage 2 takes over. */
@keyframes chr-recede-k {
  from {
    opacity: 1;
  }
}

@keyframes chr-draw-k {
  from {
    stroke-dashoffset: 1;
  }
}

@keyframes chr-pop-k {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
}

@keyframes chr-grow-k {
  from {
    transform: scaleX(0);
  }
}

/* Dashed, neutral and faded is the *start*: the route stage 1 draws as expensive. */
@keyframes chr-open-k {
  from {
    stroke: var(--ink-60);
    stroke-width: 1.6;
    stroke-dasharray: 7 7;
    opacity: 0.45;
  }
}

.chr-vx--pop {
  transform-box: fill-box;
  transform-origin: center;
}

.chr-vx--grow {
  transform-box: fill-box;
  transform-origin: left center;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /*
     * `cover`, not `entry`. For a section taller than the viewport the entry range is only one
     * viewport long, which crushed section 3's three stages into its first screen of scroll,
     * finishing before the figure had even pinned. Cover spans the section's whole passage, and
     * --r0/--r1 carve out the window where the section is actually on screen.
     */
    /* Only .chr-sec now: the example blocks stopped needing a timeline when their diagrams
       went, and nothing inside them animates on scroll any more. */
    .chr-sec {
      view-timeline-name: --dg;
      view-timeline-axis: block;
      --r0: 30;
      --r1: 56;
    }

    .chr-rise {
      animation: chr-rise-k linear both;
      animation-timeline: view();
      animation-range: entry 4% entry 60%;
    }

    /* `at(0)` to `at(100)` in the components maps onto cover --r0% to --r1% of the section. */
    .chr-vx {
      animation-fill-mode: both;
      animation-timing-function: linear;
      animation-timeline: --dg;
      animation-range:
        cover calc((var(--r0) + var(--from) * (var(--r1) - var(--r0)) / 100) * 1%)
        cover calc((var(--r0) + var(--to) * (var(--r1) - var(--r0)) / 100) * 1%);
    }

    .chr-vx--fade {
      animation-name: chr-fade-k;
    }

    .chr-vx--lift {
      animation-name: chr-lift-k;
    }

    .chr-vx--draw {
      animation-name: chr-draw-k;
    }

    .chr-vx--pop {
      animation-name: chr-pop-k;
    }

    .chr-vx--grow {
      animation-name: chr-grow-k;
    }

    .chr-vx--recede {
      animation-name: chr-recede-k;
    }

    .chr-vx--open {
      animation-name: chr-open-k;
    }

    /* Draws once on entry, and never loops. */
    .chr-ui__draw {
      animation: chr-draw-k linear both;
      animation-timeline: --dg;
      animation-range: cover 32% cover 66%;
    }
  }
}

/* ── motion: Firefox fallback ────────────────────────────────────────────── */

/*
 * ScrollFallback.tsx adds `.chr-io` only when `animation-timeline` is unsupported, then
 * toggles `.is-in` on each `[data-io]` section as it enters and leaves. Transitions rather
 * than scroll-linked animations, staggered by reusing each element's own `--from` as a delay,
 * so the sequencing survives and it still reverses on scroll-up.
 *
 * Only entrances are handled. The state-change classes (`--recede`, `--open`) keep their
 * end-state defaults, which is the required fallback behaviour for a stage transform.
 */
@media (prefers-reduced-motion: no-preference) {
  .chr-io [data-io] .chr-rise,
  .chr-io [data-io] .chr-vx--fade,
  .chr-io [data-io] .chr-vx--lift,
  .chr-io [data-io] .chr-vx--pop {
    opacity: 0;
    transition: opacity 520ms ease, transform 520ms ease;
    transition-delay: calc(var(--from, 0) * 5ms);
  }

  .chr-io [data-io] .chr-rise {
    transform: translateY(16px);
  }

  .chr-io [data-io] .chr-vx--lift {
    transform: translateY(10px);
  }

  .chr-io [data-io] .chr-vx--draw,
  .chr-io [data-io] .chr-ui__draw {
    stroke-dashoffset: 1;
    transition: stroke-dashoffset 760ms ease;
    transition-delay: calc(var(--from, 0) * 5ms);
  }

  .chr-io [data-io] .chr-vx--grow {
    transform: scaleX(0);
    transition: transform 620ms ease;
    transition-delay: calc(var(--from, 0) * 5ms);
  }

  .chr-io [data-io].is-in .chr-rise,
  .chr-io [data-io].is-in .chr-vx--fade,
  .chr-io [data-io].is-in .chr-vx--lift,
  .chr-io [data-io].is-in .chr-vx--pop {
    opacity: 1;
    transform: none;
  }

  .chr-io [data-io].is-in .chr-vx--draw,
  .chr-io [data-io].is-in .chr-ui__draw {
    stroke-dashoffset: 0;
  }

  .chr-io [data-io].is-in .chr-vx--grow {
    transform: scaleX(1);
  }
}

/* ── sections, narrow ────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .chr-sec {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(28px, 5vh, 44px);
    min-height: 0;
    padding-block: clamp(52px, 8vh, 84px);
  }

  /* Nothing to centre against on a phone: the example just follows the steps. */
  .chr-band__figure--full {
    min-height: 0;
  }

  /*
   * The carousel on a phone. Nothing here is broken without it, but a 272px card in a 354px
   * slab hides both neighbours, so the rake stops reading as a rake, and three columns of model
   * names truncate to about nine characters each.
   */
  .chr-tc {
    width: 100%;
    margin-left: 0;
  }

  /* Width-bound on a phone, not height-bound, and no offset to give. */
  .chr-art {
    height: auto;
    width: 100%;
    margin: 0 auto;
    transform: none;
  }

  .chr-cf__ring {
    height: min(var(--cf-h), 52vw);
  }

  .chr-cf__items {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 4px 16px;
  }

  /*
   * Same reason the section headings are capped: one size cannot serve 1440px and 390px. These
   * were only ever tuned at desktop, and a 3.5rem lead heading sets to seven lines on a phone.
   * The adjuster still moves them under the cap; it just cannot set a 56px heading here.
   */
  .chr-band__eyebrow {
    font-size: min(var(--eyebrow-size), 28px);
  }

  .chr-band__lead h3 {
    max-width: 100%;
    font-size: min(var(--wt-size), 2rem);
  }

  .chr-bars__eyebrow,
  .chr-ex__eyebrow {
    font-size: min(var(--ex-size), 17px);
  }

  .chr-bars__goal,
  .chr-ex__goal {
    font-size: min(var(--goal-size), 1.55rem);
  }

  .chr-bars__how,
  .chr-ex__how {
    font-size: min(var(--how-size), 1.02rem);
  }

  .chr-sec__copy h2,
  .chr-sec__copy p {
    max-width: 100%;
  }

  /*
   * One heading size cannot serve 1440px and 390px. The adjuster still moves these below the
   * cap; it just cannot set a 60px heading on a phone.
   */
  .chr-sec--mid .chr-sec__copy h2 {
    font-size: min(var(--band-heading), 2.2rem);
  }

  .chr-sec--mid .chr-sec__copy p {
    font-size: min(var(--band-sub), 1.15rem);
  }

  .chr-because {
    font-size: min(calc(var(--band-sub) * 1.12), 1.3rem);
  }

  /*
   * A 940-unit diagram scaled into 354px of phone puts its labels at about 6px. Rather than
   * inflating type until it bursts its boxes, each diagram keeps a legible minimum width and
   * scrolls inside its own container. The page itself never scrolls sideways.
   */
  .chr-ui__rows li {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .chr-ui__rows .chr-ui__meter {
    grid-column: 1 / -1;
  }
}

/*
 * The turn from the claim into its reasons. Same width as the grid below it and left aligned
 * inside that width, so it starts exactly on the first card's edge rather than floating
 * centred above them.
 */
.chr-because {
  width: 100%;
  max-width: calc(1140px * var(--grid-size));
  margin: var(--because-y) auto 0;
  text-align: left;
  font-family: var(--display);
  font-size: calc(var(--band-sub) * 1.12);
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
}

/* inline-block so each letter can be moved; the space needs a width of its own */
.chr-reveal__ch {
  display: inline-block;
  white-space: pre;
}

/* ── section 1: the four reasons ─────────────────────────────────────────── */

/*
 * The card grid from the brief, built in this stylesheet rather than with shadcn's Card: its
 * tokens resolve against the dark product theme, which is the bleed `.chr` exists to stop.
 *
 * Four items sit on one row on a wide screen and pair up below that, because 4 into 3 columns
 * leaves an orphan.
 */
.chr-reasons {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(16px, 1.8vw, 26px);
  width: 100%;
  max-width: calc(1140px * var(--grid-size));
  margin: 0 auto;
  padding: 0;
  margin-top: var(--grid-y);
  list-style: none;
  text-align: left;
  font-size: calc(1rem * var(--grid-size));
}

.chr-reason {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: calc(22px * var(--grid-size));
  /* Scaled with the grid so the corner keeps its proportion as the cards resize. */
  border-radius: calc(12px * var(--grid-size));
  border: var(--border) solid var(--rule);
  background: var(--paper-2);
}

/* The icons are printed on the page's own paper, so darken cancels it and leaves the two inks
   with no tile edge, the same trick the hero plate uses. */
.chr-reason__icon {
  display: block;
  width: calc(60px * var(--grid-size));
  height: calc(60px * var(--grid-size));
  mix-blend-mode: darken;
}

.chr-reason__icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.chr-reason h3 {
  margin: 0;
  font-size: calc(1.18em);
  line-height: 1.15;
}

.chr-reason p {
  margin: 0;
  font-size: 0.86em;
  line-height: 1.5;
  color: var(--ink);
}

@media (max-width: 900px) {
  .chr-reasons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-top: min(var(--grid-y), 36px);
  }
}

@media (max-width: 560px) {
  .chr-reasons {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── train: compute, becoming accuracy ──────────────────────────────────── */

/*
 * One bar chart that changes what it measures. The fine-tuning bar grows in both axes, turns
 * from accent to neutral, and sprouts a green tip: the compute that bought the model becomes
 * the accuracy the model reached.
 *
 * The track is one scale, 0 to 100. The pose numbers are true to it (65 grey, 6 green, 71
 * total). The compute numbers are not: 50 against 50,000 is 0.1%, which at a true scale is
 * about a pixel, so the ratio is stated as a figure instead.
 */
.chr-bars {
  position: relative;
  /*
   * Contains the photograph on every side, so it can be pushed off the right edge or lifted
   * above the block without reaching the band above it. `clip` rather than `hidden` on purpose:
   * hidden would make this a scroll container and the inner would stop pinning. The root's own
   * clip does not help here, because it does not contain a sticky descendant.
   */
  overflow: clip;
  min-height: 190vh;
  view-timeline-name: --tc;
  view-timeline-axis: block;
}

.chr-bars__inner {
  position: sticky;
  isolation: isolate;
  top: 0;
  display: grid;
  align-content: center;
  gap: clamp(10px, 1.4vh, 18px);
  min-height: 100vh;
  max-width: var(--steps-w);
  margin: 0 auto;
  /* Doubled, because the figure around it no longer contributes any. */
  padding-inline: calc(var(--pad-x) * 2);
}

.chr-bars__intro {
  display: grid;
  gap: clamp(8px, 1.2vh, 14px);
}

/*
 * Behind the block, bled off the right edge. Everything else in the inner is lifted above it,
 * so the type and the bars cross the field rather than sitting next to it.
 */
.chr-bars__photo {
  position: absolute;
  top: var(--crys-y);
  right: var(--crys-x);
  z-index: 0;
  width: var(--crys-w);
  margin: 0;
  aspect-ratio: 2047 / 2457;
  pointer-events: none;
}

.chr-bars__inner > *:not(.chr-bars__photo) {
  position: relative;
  z-index: 1;
}

/*
 * One radial gradient per corner, intersected, so each corner carries its own feather distance
 * and its own falloff. The second stop in each is a bare `calc()` with no colour: that is a
 * gradient interpolation hint, which moves the halfway point of the fade without adding a hard
 * stop. Same device the hero plate uses for its two corners.
 *
 * Each gradient is centred on the OPPOSITE corner from the one it feathers. Centred on its own
 * corner the iso-lines curve around that point, which scoops the corner out concavely. Centred
 * across the diagonal the same arcs bulge outward instead, so the corner rounds off rather than
 * being bitten. That is the only reason the centres look transposed.
 */
.chr-bars__photo img {
  display: block;
  width: 100%;
  height: 100%;

  -webkit-mask-image:
    linear-gradient(to right, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to left, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to bottom, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to top, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    radial-gradient(farthest-corner at 100% 100%, #000 calc(100% - var(--cp-tl) - var(--cf-tl)), calc(100% - var(--cp-tl) - var(--cf-tl) + var(--cf-tl) * var(--cr-tl) / 100), transparent calc(100% - var(--cp-tl))),
    radial-gradient(farthest-corner at 0 100%, #000 calc(100% - var(--cp-tr) - var(--cf-tr)), calc(100% - var(--cp-tr) - var(--cf-tr) + var(--cf-tr) * var(--cr-tr) / 100), transparent calc(100% - var(--cp-tr))),
    radial-gradient(farthest-corner at 100% 0, #000 calc(100% - var(--cp-bl) - var(--cf-bl)), calc(100% - var(--cp-bl) - var(--cf-bl) + var(--cf-bl) * var(--cr-bl) / 100), transparent calc(100% - var(--cp-bl))),
    radial-gradient(farthest-corner at 0 0, #000 calc(100% - var(--cp-br) - var(--cf-br)), calc(100% - var(--cp-br) - var(--cf-br) + var(--cf-br) * var(--cr-br) / 100), transparent calc(100% - var(--cp-br)));
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to left, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to bottom, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    linear-gradient(to top, transparent 0, calc(var(--cf-edge) * 0.55), #000 var(--cf-edge)),
    radial-gradient(farthest-corner at 100% 100%, #000 calc(100% - var(--cp-tl) - var(--cf-tl)), calc(100% - var(--cp-tl) - var(--cf-tl) + var(--cf-tl) * var(--cr-tl) / 100), transparent calc(100% - var(--cp-tl))),
    radial-gradient(farthest-corner at 0 100%, #000 calc(100% - var(--cp-tr) - var(--cf-tr)), calc(100% - var(--cp-tr) - var(--cf-tr) + var(--cf-tr) * var(--cr-tr) / 100), transparent calc(100% - var(--cp-tr))),
    radial-gradient(farthest-corner at 100% 0, #000 calc(100% - var(--cp-bl) - var(--cf-bl)), calc(100% - var(--cp-bl) - var(--cf-bl) + var(--cf-bl) * var(--cr-bl) / 100), transparent calc(100% - var(--cp-bl))),
    radial-gradient(farthest-corner at 0 0, #000 calc(100% - var(--cp-br) - var(--cf-br)), calc(100% - var(--cp-br) - var(--cf-br) + var(--cf-br) * var(--cr-br) / 100), transparent calc(100% - var(--cp-br)));
  mask-composite: intersect;

  /* The same darken the hero uses: the field is lighter than the paper in most places, so those
     areas fall back to the paper and only the crystal structure survives. That is what makes it
     read as printed on the page rather than laid on top of it. */
  mix-blend-mode: darken;
}

.chr-bars__eyebrow,
.chr-ex__eyebrow {
  margin: 0;
  font-size: var(--ex-size);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
}

.chr-bars__goal,
.chr-ex__goal {
  margin: 0;
  font-family: var(--display);
  font-size: var(--goal-size);
  font-weight: 600;
  line-height: 1.15;
}

.chr-bars__how,
.chr-ex__how {
  max-width: 68ch;
  margin: 0;
  font-size: var(--how-size);
  line-height: 1.5;
  color: var(--accent);
}

/* Both captions occupy one cell so the chart below never shifts as they swap. */
.chr-bars__caps {
  display: grid;
  margin-top: clamp(18px, 3vh, 40px);
}

.chr-bars__cap {
  grid-area: 1 / 1;
  margin: 0;
  font-size: var(--cap-size);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}

/* Hidden by default rather than transparent: without the morph the static pose row carries
   its own label, so a second caption above the compute rows would mislabel them. */
.chr-bars__cap--pose {
  display: none;
}

.chr-bars__rows {
  display: grid;
  gap: clamp(16px, 2.4vh, 30px);
}

.chr-bars__row {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  align-items: center;
  gap: 20px;
}

/*
 * Name over value, both right aligned against the bar's start. Putting the value here rather
 * than at the bar's end is what keeps the two values on one x: bar lengths differ by a factor
 * of twenty, so anything anchored to the end lands somewhere different each time, and the long
 * one ran into the photograph.
 */
.chr-bars__label {
  display: grid;
  justify-items: end;
  gap: 2px;
  font-size: 15px;
  text-align: right;
}

.chr-bars__name {
  display: grid;
}

/*
 * The two compute rows share a size and the accuracy row carries its own, because the accuracy
 * row is what the compute row grows into: they are never on screen together, so matching them
 * is a choice rather than a constraint.
 */
.chr-bars__row--full .chr-bars__name,
.chr-bars__name .chr-bars__sw--gpu {
  font-size: var(--barname-gpu);
}

.chr-bars__row--pose .chr-bars__name,
.chr-bars__name .chr-bars__sw--pose {
  font-size: var(--barname-pose);
}

/*
 * No track behind the bars. The segments and the value are laid in a row, so the value follows
 * whatever length its bar happens to be instead of sitting at a fixed right-hand stop that
 * would read as a 100% mark.
 *
 * Widths are percentages of this element, and the longest bar stops well short of full so the
 * value it carries always has room.
 */
.chr-bars__plot {
  display: flex;
  align-items: center;
  height: var(--bar1-h);
}

/* The standalone accuracy row is already at its grown size. */
.chr-bars__row--pose .chr-bars__plot {
  height: var(--bar2-h);
}

.chr-bars__fill {
  flex: none;
  height: 100%;
}

.chr-bars__fill--full {
  width: 82%;
  background: var(--ink-60);
}

/* Its compute state. The animation grows it into the accuracy state. */
.chr-bars__fill--ours {
  width: 2.5%;
  background: var(--accent);
}

/* 65 on a 0 to 100 axis that ends at 82% of the row. */
.chr-bars__fill--base {
  width: 53.3%;
  background: var(--ink-60);
}

.chr-bars__gain {
  flex: none;
  display: grid;
  place-items: center;
  width: 0;
  height: 100%;
  overflow: hidden;
  background: var(--accent);
}

/* 6 points on the same axis. */
.chr-bars__gain--static {
  width: 4.9%;
}

.chr-bars__gainlab {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
  color: var(--accent-ink);
}

.chr-bars__val {
  display: grid;
  font-family: var(--display);
  font-size: clamp(1rem, 1.35vw, 1.2rem);
  font-weight: 600;
  white-space: nowrap;
}

/* The two wordings for the morphing row share a cell, same reason as the captions. */
.chr-bars__sw--pose {
  display: none;
}

/* The ratio the bars cannot show. Sized to be the thing you read after the two lengths. */
.chr-bars__ratio {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: clamp(14px, 2.4vh, 30px) 0 0;
  padding-left: calc(15rem + 20px);
}

.chr-bars__ratiofig {
  font-family: var(--display);
  font-size: clamp(2.6rem, 5vw, 4.2rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--display-tracking);
  color: var(--accent);
}

.chr-bars__ratiolab {
  font-size: clamp(0.95rem, 1.2vw, 1.1rem);
  color: var(--ink-60);
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    /* The morph replaces the standalone pose row. */
    .chr-bars__row--pose {
      display: none;
    }

    /* Each pair now shares one cell and cross-fades, so both halves come back into play. */
    .chr-bars__cap {
      grid-area: 1 / 1;
    }

    .chr-bars__sw {
      grid-area: 1 / 1;
    }

    .chr-bars__cap--pose,
    .chr-bars__sw--pose {
      display: block;
    }

    .chr-bars__cap--pose,
    .chr-bars__sw--pose {
      opacity: 0;
    }

    .chr-bars__row--full,
    .chr-bars__ratio,
    .chr-bars__cap--gpu,
    .chr-bars__cap--pose,
    .chr-bars__sw--gpu,
    .chr-bars__sw--pose,
    .chr-bars__fill--ours,
    .chr-bars__gain,
    .chr-bars__row--ours .chr-bars__plot {
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --tc;
    }

    /* the retrain bar leaves, so the one that remains can become something else */
    .chr-bars__row--full {
      animation-name: chr-bars-out;
      animation-range: contain 40% contain 52%;
    }

    .chr-bars__cap--gpu {
      animation-name: chr-bars-out;
      animation-range: contain 42% contain 52%;
    }

    .chr-bars__ratio {
      animation-name: chr-bars-inout;
      animation-range: contain 20% contain 52%;
    }

    .chr-bars__cap--pose {
      animation-name: chr-bars-in;
      animation-range: contain 50% contain 60%;
    }

    .chr-bars__sw--gpu {
      animation-name: chr-bars-out;
      animation-range: contain 42% contain 52%;
    }

    .chr-bars__sw--pose {
      animation-name: chr-bars-in;
      animation-range: contain 54% contain 64%;
    }

    /* grown on both axes: wider because the scale changed, taller because it matters more */
    .chr-bars__fill--ours {
      animation-name: chr-bars-grow;
      animation-range: contain 44% contain 66%;
    }

    .chr-bars__row--ours .chr-bars__plot {
      animation-name: chr-bars-taller;
      animation-range: contain 44% contain 66%;
    }

    .chr-bars__gain {
      animation-name: chr-bars-tip;
      animation-range: contain 64% contain 78%;
    }
  }
}

@keyframes chr-bars-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes chr-bars-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

/* In on the compute chart, out when it becomes the accuracy chart. */
@keyframes chr-bars-inout {
  0% {
    opacity: 0;
    transform: translateY(14px);
  }

  15%,
  82% {
    opacity: 1;
    transform: translateY(0);
  }

  100% {
    opacity: 0;
    transform: translateY(0);
  }
}

@keyframes chr-bars-grow {
  from {
    width: 2.5%;
    background-color: var(--accent);
  }

  to {
    width: 53.3%;
    background-color: var(--ink-60);
  }
}

@keyframes chr-bars-taller {
  from {
    height: var(--bar1-h);
  }

  to {
    height: var(--bar2-h);
  }
}

@keyframes chr-bars-tip {
  from {
    width: 0;
  }

  to {
    width: 4.9%;
  }
}

@media (max-width: 900px) {
  /* No pinned scroll to tilt through, so the screen is simply flat and in flow. */
  .chr-os {
    min-height: 0;
  }

  .chr-os__inner {
    position: static;
    min-height: 0;
    grid-template-rows: auto auto;
  }

  .chr-os__card {
    height: auto;
    max-height: none;
    animation: none !important;
    transform: none;
  }

  .chr-os__screen .chr-ui,
  .chr-os__screen .chr-ui__panelbody {
    display: block;
    height: auto;
  }

  .chr-os__screen .chr-ui__chart {
    height: auto;
  }

  .chr-steps__inner,
  .chr-bars__inner {
    position: static;
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
  }

  .chr-steps__inner {
    height: auto;
    grid-template-rows: auto auto;
  }

  .chr-steps__grid {
    grid-template-columns: minmax(0, 1fr);
  }


  .chr-shot {
    max-width: none;
  }

  .chr-steps,
  .chr-bars {
    min-height: 0;
  }

  /* Pinning needs height to scroll through; on a phone the steps simply read as a list. */
  .chr-step,
  .chr-shot,
  .chr-bars__row--full,
  .chr-bars__ratio,
  .chr-bars__fill--ours,
  .chr-bars__gain,
  .chr-bars__plot {
    animation: none !important;
    opacity: 1;
  }

  /* No pinned scroll to advance through, so every step reads at full ink. */
  .chr-step {
    color: var(--ink);
  }

  /*
   * Placeholder frames are interchangeable, so a phone shows one and drops the rest. Real
   * artwork is not: each step has its own, and with no pinned scroll to swap them the two
   * behind the first would never be seen at all. So live media stacks, in step order, under
   * the list it belongs to.
   */
  .chr-shot {
    display: none;
  }

  .chr-shot--1,
  .chr-shot--live {
    display: grid;
  }

  .chr-shot--live {
    grid-area: auto;
  }

  .chr-steps__media {
    grid-auto-flow: row;
    gap: clamp(20px, 4vh, 34px);
  }

  /* No morph on a phone, so the three bars simply stack and read as a chart. */
  .chr-bars__row--pose {
    display: grid;
  }

  /*
   * The value follows its bar, which on a phone means bar plus value is wider than the screen.
   * So the label goes above and the value wraps below, leaving the full width for the bar.
   * (The third column here was left from the old fixed value column and no longer exists.)
   */
  .chr-bars__row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }

  .chr-bars__label {
    justify-items: start;
    text-align: left;
  }

  .chr-bars__plot {
    flex-wrap: wrap;
    height: auto;
    row-gap: 6px;
  }

  .chr-bars__label {
    grid-auto-flow: column;
    justify-content: start;
    gap: 10px;
  }

  /*
   * Behind narrow type it would swamp the copy, so it drops back to a quiet wash. Kept inside
   * the viewport rather than bled off it: a negative offset here pushed the page 37px wide,
   * and `overflow-x: clip` on the root does not contain a sticky ancestor.
   */
  .chr-bars__photo {
    width: min(var(--crys-w), 74vw);
    top: auto;
    bottom: 0;
    right: 0;
    opacity: 0.45;
  }

  /*
   * The ligand takes band 4's mobile rule verbatim, which is what makes the pair still read as a
   * pair on a phone: same 74vw cap, same bottom-right anchor inside the viewport, same 0.45.
   *
   * It matters more here than it does there. At its desktop width the left 46% of this artwork
   * is empty by design, so on a 390px screen the box spanned -550 to 690 and the only thing on
   * screen was the blank half; the structure was entirely off to the right.
   */
  .chr-lig {
    width: min(var(--lig-w), 74vw);
    top: auto;
    bottom: 0;
    right: 0;
    opacity: 0.45;
  }

  /* No label column to clear, so no indent. */
  .chr-bars__ratio {
    flex-wrap: wrap;
    gap: 4px 12px;
    padding-left: 0;
  }

  .chr-bars__ratiofig {
    font-size: 2.4rem;
  }

  /* The defaults already describe the no-morph layout, so nothing to undo here. */
}

/* ── blog ────────────────────────────────────────────────────────────────── */

/*
 * The blog index, ported from the shadcn `Blog8` block: centred head, then one full-width row
 * per post with the copy on one side and a 16/9 thumbnail on the other.
 *
 * Blog8's own measure was `lg:col-span-10 lg:col-start-2` of a 12-column grid, so the rows sat
 * at about 83% of a centred container. `--blog-w` is that idea as one number, which also gives
 * each half of a row a ~60ch measure at 17px without needing a second cap on the copy.
 *
 * Everything the original left to shadcn's semantic tokens is a `.chr` role here: muted text is
 * `--ink-60`, `border-border` is `--rule`, and the underline-on-hover is the accent-colour hover
 * the nav and footer already use, so a link behaves the same everywhere on this surface.
 */
.chr-blog {
  --blog-w: 1080px;
  /* Gap between posts. Blog8 ran 10 to 20 (40px to 80px) across its breakpoints. */
  --blog-gap: clamp(56px, 7vw, 96px);

  /* The same band padding and closing rule as `.chr-section`, so the footer meets it the way it
     meets every other section. */
  padding: clamp(56px, 7vw, 110px) var(--pad-x);
  border-bottom: var(--border) solid var(--rule);
}

.chr-blog__head {
  max-width: var(--blog-w);
  margin: 0 auto clamp(48px, 6vw, 88px);
  text-align: center;
}

/* The measure is in ch of the heading's own size, the way the band leads do it. */
.chr-blog__head h1 {
  max-width: 30ch;
  margin: 0 auto 18px;
  font-size: clamp(1.9rem, 3.8vw, 3rem);
  line-height: 1.08;
}

.chr-blog__head p {
  max-width: 62ch;
  margin: 0 auto;
  color: var(--ink-60);
  font-size: clamp(1rem, 1.4vw, 1.15rem);
}

.chr-blog__empty {
  margin: 0;
  text-align: center;
  color: var(--ink-60);
}

/* Same as the blog's, for the same reason: an empty board should read as a sentence. */
.chr-jobs__empty {
  margin: 0;
  text-align: center;
  color: var(--ink-60);
}

.chr-blog__list {
  display: grid;
  gap: var(--blog-gap);
  max-width: var(--blog-w);
  margin: 0 auto;
}

.chr-blog__post {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: center;
  gap: clamp(24px, 3vw, 48px);
}

/* Same 11.5px / 0.16em uppercase as the footer column headings: both are a quiet label over a
   block of links, so they should be the same piece of type. */
.chr-blog__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px clamp(14px, 1.6vw, 24px);
  margin: 0 0 clamp(14px, 1.6vw, 22px);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-60);
}

.chr-blog__title {
  font-size: clamp(1.35rem, 2.5vw, 1.95rem);
  line-height: 1.1;
}

.chr-blog__title a {
  transition: color 200ms ease;
}

.chr-blog__title a:hover {
  color: var(--accent);
}

.chr-blog__summary {
  margin: clamp(14px, 1.6vw, 20px) 0 0;
  color: var(--ink-60);
}

.chr-blog__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin: clamp(18px, 2.2vw, 28px) 0 0;
  font-size: 14px;
  color: var(--ink-60);
}

/* Dev-only, so it is deliberately the quietest thing in the row: the palette's two roles are
   spoken for, and a placeholder badge has no business borrowing the accent. */
.chr-blog__flag {
  padding: 1px 8px;
  border: var(--border) solid var(--rule);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.chr-blog__more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: clamp(18px, 2.2vw, 28px);
  font-size: 15.5px;
  font-weight: 600;
  transition: color 200ms ease;
}

.chr-blog__more:hover {
  color: var(--accent);
}

/* Blog8 put `transition-transform` on this arrow but never gave it a transform to run. The nudge
   is what the class was for. */
.chr-blog__more svg {
  width: 16px;
  height: 16px;
  transition: transform 200ms ease;
}

.chr-blog__more:hover svg {
  transform: translateX(4px);
}

.chr-blog__figure {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: var(--border) solid var(--rule);
  border-radius: 12px;
}

.chr-blog__figure img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 200ms ease;
}

/* Only the thumbnail on the index is a link; a post's own image is a plain box and must not dim. */
a.chr-blog__figure:hover img {
  opacity: 0.7;
}

/* ── blog, narrow ────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .chr-blog__post {
    grid-template-columns: minmax(0, 1fr);
  }

  /*
   * Thumbnail above the copy, which is the order Blog8 used and the right one: on a phone the
   * image is what gets scanned, and stacking it under a paragraph of lede buries it. Done with
   * `order` rather than DOM order so the reading order stays title-first for a screen reader.
   */
  .chr-blog__figure {
    order: -1;
  }
}

/* ── inner pages ─────────────────────────────────────────────────────────── */

/*
 * The shell every page that is not the landing page uses: /careers, /demo, and anything that
 * follows. One column at the copy measure, not the landing page's two-column band, because a
 * list or a form has no figure to sit beside.
 *
 * Top distance is --band-top, the same one every band on the landing page uses, so moving
 * between the two does not move the first line. min-height keeps a short page from pulling the
 * footer up under its own heading.
 */
.chr-page {
  min-height: 74vh;
  padding: var(--band-top) var(--pad-x) clamp(88px, 14vh, 170px);
}

.chr-page__head {
  max-width: 62ch;
  margin-bottom: clamp(34px, 6vh, 68px);
}

.chr-page__head h1 {
  margin-bottom: 18px;
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  line-height: 1.06;
}

.chr-page__head p {
  margin: 0;
  max-width: 52ch;
  font-size: clamp(1rem, 1.3vw, 1.12rem);
  color: var(--ink-60);
}

.chr-page__body {
  max-width: 74ch;
}

/* ── careers ─────────────────────────────────────────────────────────────── */

/*
 * The job board at /careers, inside the page shell above. A row spanning the full page would put
 * the location too far from the title for the pair to read as one row.
 */
.chr-jobs__groups {
  display: grid;
  gap: clamp(42px, 7vh, 84px);
}

/* Ink under the team, paper rule under each role: the heavier rule is what separates one team
   from the next, so the rows inside a team can stay quiet. */
.chr-jobs__group h2 {
  padding-bottom: 13px;
  border-bottom: var(--border) solid var(--ink);
  font-size: clamp(1.1rem, 1.9vw, 1.4rem);
}

.chr-jobs__group ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.chr-jobs__group li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 17px 0;
  border-bottom: var(--border) solid var(--rule);
}

.chr-jobs__role {
  font-family: var(--display);
  font-size: clamp(1rem, 1.5vw, 1.16rem);
  font-weight: 600;
  letter-spacing: var(--display-tracking);
}

.chr-jobs__role:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Borrows the button's shape and nothing else. It is where the role sits, not something to
   press, so it takes the paper rule rather than ink and never lights on hover. */
.chr-jobs__where {
  flex: none;
  padding: 0.4em 0.92em;
  border: var(--border) solid var(--rule);
  border-radius: 999px;
  font-family: var(--body);
  font-size: 13.5px;
  color: var(--ink-60);
  white-space: nowrap;
}

/* Side by side there is not enough width left for a two-line title, so the pair stacks and the
   location reads as a line under the role. */
@media (max-width: 560px) {
  .chr-jobs__group li {
    flex-direction: column;
    align-items: flex-start;
    gap: 9px;
  }
}

/* ── blog, a single post ─────────────────────────────────────────────────── */

/*
 * One centred column. The tag row, meta line and image box are the index's, reused rather than
 * restated, so a post and the row it was opened from are visibly the same furniture.
 *
 * 720px is about 62ch at the body size, which is the prose measure; the index can run wider
 * because each of its halves is a summary, not a page of reading.
 */
.chr-post {
  --post-w: 720px;

  padding: clamp(56px, 7vw, 110px) var(--pad-x);
  border-bottom: var(--border) solid var(--rule);
}

.chr-post__head,
.chr-post__figure,
.chr-post__body {
  max-width: var(--post-w);
  margin-inline: auto;
}

/* Mirror of the index's "Read more", pointing the other way: same size, same weight, same
   nudge, negated. */
.chr-post__back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: clamp(24px, 3vw, 40px);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-60);
  transition: color 200ms ease;
}

.chr-post__back:hover {
  color: var(--accent);
}

.chr-post__back svg {
  width: 15px;
  height: 15px;
  transition: transform 200ms ease;
}

.chr-post__back:hover svg {
  transform: translateX(-4px);
}

.chr-post__head h1 {
  margin: 0;
  font-size: clamp(1.9rem, 4vw, 2.9rem);
  line-height: 1.06;
}

.chr-post__figure {
  margin-block: clamp(32px, 4vw, 56px);
}

/* `em` so the gap between paragraphs tracks the type rather than the viewport. */
.chr-post__body > p {
  margin: 0 0 1.15em;
}

.chr-post__body > p:last-child {
  margin-bottom: 0;
}

/* ── forms ───────────────────────────────────────────────────────────────── */

/*
 * One set of form styles for every form on this surface: the demo request and both auth pages.
 *
 * Labels are always visible and sit above their control. Placeholder-as-label saves a line and
 * costs the reader the label at the exact moment they are typing into the field.
 */
.chr-form {
  display: grid;
  gap: 20px;
  /* A form wider than this makes the eye travel from the end of one field to the start of the
     next, which is how people skip fields. */
  max-width: 34rem;
}

.chr-form__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}

.chr-field {
  display: grid;
  gap: 7px;
}

.chr-field label {
  font-family: var(--body);
  font-size: 14px;
  font-weight: 600;
}

.chr-input {
  width: 100%;
  padding: 0.66em 0.78em;
  border: var(--border) solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  /* 16px, not 15: iOS zooms the whole page in on any field smaller than that when it takes
     focus, and never zooms back out. */
  font-size: 16px;
  line-height: 1.45;
}

.chr-input:hover {
  border-color: var(--ink-60);
}

/* The global :focus-visible rule already draws the accent ring; this is the field itself
   answering, so focus does not rest entirely on an outline the browser may draw differently. */
.chr-input:focus {
  border-color: var(--ink);
  outline: none;
}

.chr-input[aria-invalid='true'] {
  border-color: var(--flag);
}

textarea.chr-input {
  resize: vertical;
  min-height: 7em;
}

.chr-field__hint,
.chr-field__err {
  margin: 0;
  font-size: 13px;
}

.chr-field__hint {
  color: var(--ink-60);
}

.chr-field__err {
  color: var(--flag);
  font-weight: 600;
}

/* Whatever the server or the auth provider said, above the form rather than on a field: it is
   about the attempt, not about one answer. */
.chr-form__err {
  margin: 0;
  padding: 12px 14px;
  border: var(--border) solid var(--flag);
  color: var(--flag);
  font-size: 14.5px;
}

.chr-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.chr-check input {
  margin: 3px 0 0;
  width: 16px;
  height: 16px;
  /* The native control, in our ink. A div dressed as a checkbox loses the keyboard, the label
     click target and the platform's own focus ring, and buys nothing at this size. */
  accent-color: var(--accent);
}

.chr-check label {
  font-size: 14.5px;
  color: var(--ink-60);
}

.chr-pw {
  position: relative;
}

/* Room for the toggle, so a long password never runs under it. */
.chr-pw .chr-input {
  padding-right: 2.7em;
}

.chr-pw__toggle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  width: 2.7em;
  border: 0;
  background: none;
  color: var(--ink-60);
  cursor: pointer;
}

.chr-pw__toggle:hover {
  color: var(--ink);
}

/* Full-width submit. `flex` rather than `block` so a button carrying an icon centres both. */
.chr-btn--wide {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
}

.chr-btn--oauth svg {
  flex: none;
}

.chr-btn:disabled {
  opacity: 0.55;
  cursor: progress;
}

/* A labelled rule. The label is the point, so the rules are decoration on the paragraph rather
   than elements of their own. */
.chr-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-60);
}

.chr-or::before,
.chr-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--rule);
}

/* What a form says after it has been submitted, in place of it or above it. Second paper so it
   reads as a different kind of thing from the fields it replaced. */
.chr-note {
  display: grid;
  gap: 10px;
  max-width: 34rem;
  padding: clamp(20px, 3vw, 28px);
  border: var(--border) solid var(--ink);
  background: var(--paper-2);
}

.chr-note h2 {
  font-size: clamp(1.3rem, 2.2vw, 1.7rem);
}

.chr-note p {
  margin: 0;
}

.chr-note .chr-btn {
  justify-self: start;
  margin-top: 6px;
}

/* Same `color: inherit` trap: without this the escape hatch under a note's CTA reads as body
   copy, which is worse than not offering it. */
.chr-note p a {
  color: var(--accent);
  font-weight: 600;
}

.chr-note p a:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.chr-note--warn {
  border-color: var(--flag);
}

.chr-note--warn h2 {
  color: var(--flag);
}

/* The failed note keeps the filled form underneath it, so the two need separating. */
.chr-note + .chr-form {
  margin-top: 26px;
}

@media (max-width: 480px) {
  .chr-form__pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── auth pages ──────────────────────────────────────────────────────────── */

/*
 * /signup and /signin. One card, centred, on the second paper so the card's own paper lifts off
 * it without a shadow: this surface has no shadows anywhere else and does not need its first one
 * here.
 *
 * No nav across the top. Someone signing in has one job, and four other destinations at that
 * moment are four ways to lose them; the mark at the top of the card is the way back out.
 */
.chr-auth {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: clamp(30px, 6vh, 72px) var(--pad-x);
  background: var(--paper-2);
}

.chr-auth__card {
  width: 100%;
  max-width: 27rem;
  border: var(--border) solid var(--ink);
  background: var(--paper);
}

.chr-auth__head {
  display: grid;
  justify-items: center;
  gap: 7px;
  padding: clamp(26px, 4vh, 36px) clamp(22px, 4vw, 34px) 0;
  text-align: center;
}

.chr-auth__mark {
  display: inline-flex;
  margin-bottom: 8px;
}

.chr-auth__head h1 {
  font-size: clamp(1.5rem, 3vw, 1.9rem);
  line-height: 1.1;
}

.chr-auth__head p {
  margin: 0;
  max-width: 32ch;
  font-size: 15px;
  color: var(--ink-60);
}

/*
 * The access notice on /signup. Padded to the card's own gutter and with no bottom padding, so it
 * reads as attached to the form below rather than floating between two blocks.
 */
.chr-auth__notice {
  padding: clamp(18px, 2.6vh, 24px) clamp(22px, 4vw, 34px) 0;
}

.chr-gate {
  display: flex;
  gap: 11px;
  padding: 13px 14px;
  border: var(--border) solid var(--flag);
  /* A wash rather than a fill: the border and the mark carry the signal, and a saturated panel
     at this size shouts over the heading directly above it. */
  background: color-mix(in srgb, var(--flag) 6%, transparent);
}

/* The mark. A square, not a circle or an icon — it matches what the product uses for the same
   meaning, and it needs no font to render. */
.chr-gate__mark {
  flex: none;
  width: 9px;
  height: 9px;
  margin-top: 5px;
  background: var(--flag);
}

.chr-gate__body {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.chr-gate__head {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--flag);
  text-wrap: pretty;
}

.chr-gate__body .chr-btn {
  justify-self: start;
  padding: 0.45em 0.9em;
  font-size: 13px;
}

.chr-auth__body {
  padding: clamp(22px, 3.4vh, 30px) clamp(22px, 4vw, 34px) clamp(24px, 4vh, 32px);
}

/* The card is already at the measure the form wants, so the form takes the card. */
.chr-auth__body .chr-form {
  max-width: none;
}

.chr-auth__foot {
  padding: 16px;
  border-top: var(--border) solid var(--rule);
  text-align: center;
  font-size: 14px;
  color: var(--ink-60);
}

.chr-auth__foot p {
  margin: 0;
}

.chr-auth__foot a {
  color: var(--accent);
  font-weight: 600;
}

.chr-auth__foot a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
