/* ============================================================================
   SECTIONS — masthead, the sticky chapter mechanism, and the five sections.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   MASTHEAD
   The title line, bounded above and below by a thin black line.
   ------------------------------------------------------------------------ */
.masthead {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 100;
  height: var(--masthead-h);
  background: var(--paper);
  border-block: var(--rule);
}

.masthead__inner {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--grid-4);
}

/* The wordmark asset is white-on-transparent (it was drawn for the old
   photographic hero). brightness(0) renders it as ink without needing a second
   file; swap in a black asset and drop the filter if one is ever produced. */
.masthead__mark img {
  height: calc(var(--grid) * 2.5);
  width: auto;
  filter: brightness(0);
}

/* Full-height cells divided by hairlines, so the nav reads as a row of the same
   ruled table the rest of the page is drawn with. Boxed at both ends and kept
   inside the content column, so it lines up with the page rather than running
   out to the window. */
.masthead__nav {
  display: flex;
  align-self: stretch;
  align-items: stretch;
  border-inline-start: var(--rule);
  /* Only one of the nav and the toggle is ever in flow, so both claim the
     right-hand end and whichever is present lands there. */
  margin-inline-start: auto;
}

.masthead__nav a {
  display: flex;
  align-items: center;
  padding-inline: var(--grid-3);
  border-inline-end: var(--rule);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-mid);
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.masthead__nav a:hover,
.masthead__nav a:focus-visible {
  background: var(--tint);
  color: var(--accent);
}

/* The toggle is drawn as one more cell of the same ruled row, so the phone
   masthead reads as a shortened version of the desktop one rather than as a
   different component. Hidden until a breakpoint asks for it. */
.masthead__toggle {
  display: none;
  margin-inline-start: auto;
  align-self: stretch;
  align-items: center;
  padding-inline: var(--grid-2);
  border: 0;
  border-inline: var(--rule);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-mid);
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.masthead__toggle:hover,
.masthead__toggle:focus-visible {
  background: var(--tint);
  color: var(--accent);
}

/* Both labels are always present and stacked in the same grid cell, so the cell
   is as wide as the longer word and does not resize when the state flips. The
   inactive one is hidden with visibility, which keeps its footprint but takes
   it out of the accessible name — display:none would collapse the width and
   defeat the point. */
.masthead__toggle-label { grid-area: 1 / 1; }

.masthead__toggle[aria-expanded="false"] .masthead__toggle-label + .masthead__toggle-label,
.masthead__toggle[aria-expanded="true"] .masthead__toggle-label:first-child {
  visibility: hidden;
}

/* Four cells no longer fit beside the wordmark, so they move into a panel that
   drops out of the masthead. All four stay reachable — the previous rule hid
   two of them, which quietly cut the page in half on a phone.

   Scoped to .js: the toggle only works with scripting, so without it the
   button stays hidden and the inline nav remains, tightened up to fit. */
@media (max-width: 640px) {
  .masthead__nav a { padding-inline: var(--grid); }

  .js .masthead__toggle { display: grid; place-items: center; }

  .js .masthead__nav {
    position: absolute;
    /* An absolutely positioned box resolves 100% against its containing
       block's PADDING box, so plain 100% lands one pixel above the masthead's
       lower rule and the panel's paper paints straight over it. The line
       thickness puts the panel below the rule instead, which is what leaves a
       black line sitting still at the top of the menu while the panel rolls
       out from under it. */
    inset-block-start: calc(100% + var(--line-weight));
    inset-inline: 0;
    display: block;
    border-inline-start: 0;
    border-block-end: var(--rule);
    background: var(--paper);
    /* Collapsed rather than display:none so the panel can be animated open and
       so its links stay out of the tab order while it is shut. The open height
       is set by nav.js, in pixels: animating to a guess like 100svh means the
       panel hits its real height in the first fraction of the transition and
       opening reads as instant, while closing — travelling the same distance
       the other way — still eases across the visible range. That asymmetry is
       what made it appear to roll shut but never roll open. */
    visibility: hidden;
    max-height: 0;
    overflow: hidden;
    transition: max-height var(--dur-ui) var(--ease),
                visibility var(--dur-ui) var(--ease);
  }

  .js .masthead__nav a {
    padding: var(--grid-2) var(--page-pad);
    border-inline-end: 0;
    border-block-start: var(--rule-soft);
  }

  .js .masthead__nav a:first-child { border-block-start: 0; }

  .js .masthead[data-menu="open"] .masthead__nav {
    visibility: visible;
    /* max-height comes from nav.js — see above. */
  }
}

/* ---------------------------------------------------------------------------
   CHAPTER MECHANISM
   Each chapter is a tall track containing a frame that sticks for the length
   of the track. Inner elements activate against the track's scroll progress,
   which JS writes to --progress on the frame.
   Below the breakpoint the whole mechanism collapses to normal flow.
   ------------------------------------------------------------------------ */
main { padding-block-start: var(--masthead-h); }

.chapter { position: relative; }
.chapter + .chapter { border-block-start: var(--rule); }

.chapter__track { height: var(--scroll-length, 300vh); }

.chapter__frame {
  position: sticky;
  inset-block-start: var(--masthead-h);
  height: calc(100svh - var(--masthead-h));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Collapse to normal flow when a chapter cannot hold its content in one
   screen, which is a question of width AND height together rather than of
   either alone.

   The mission is the floor. It holds a near-constant line count as the window
   widens, because its type scales with the window — so it stays around 210px
   tall and is the one block that cannot be shrunk away. What does change with
   width is its measure: the block is centred under FIG. 02 and therefore
   capped at twice that centre, so on a narrow window it is forced narrow, runs
   to more lines, and needs more height than the same content does on a wide
   one. Hence two height bounds rather than one.

   Measured, not guessed — at the tight corners (1201x681 and 901x751) the
   section fits with a line of text to spare, so a machine whose font metrics
   run a little larger does not clip. A short window is common (a browser that is not maximised on a
   1440x900 display is already under 760px), so buying headroom by raising a
   single height bound costs sticky on ordinary desktops. That is the mistake
   this replaces.

   KEEP IN SYNC with STICKY_MQ in scripts/reveal.js — the script must agree
   about when progress-driven reveals apply. It states the same rule from the
   other side, as the two shapes that DO fit. */
@media (max-width: 900px),
       (max-height: 680px),
       (max-width: 1200px) and (max-height: 750px) {
  .chapter__track { height: auto; }
  .chapter__frame {
    position: static;
    height: auto;
    overflow: visible;
    padding-block: var(--grid-12);
  }

  /* The hero still owns a full screen — it is the entry to the page. */
  .chapter--hero .chapter__frame {
    min-height: calc(100svh - var(--masthead-h));
    padding-block: 0;
  }
}

/* ?flat unpins everything for reading and proofing. Same three declarations as
   the collapse above — keep the two in step. */
.is-flat .chapter__track { height: auto; }

.is-flat .chapter__frame {
  position: static;
  height: auto;
  overflow: visible;
  padding-block: var(--grid-12);
}

/* The hero keeps its full screen here too. Without this it shrinks to its own
   content and the page reads as though the spacing has collapsed. */
.is-flat .chapter--hero .chapter__frame {
  min-height: calc(100svh - var(--masthead-h));
  padding-block: 0;
}

/* Unmistakable marker: flat is a reading mode, not how the page really looks. */
.is-flat body::after {
  content: "Flat view · remove ?flat for the real page";
  position: fixed;
  inset-block-end: 0;
  inset-inline-start: 0;
  z-index: 200;
  padding: var(--grid) var(--grid-2);
  background: var(--flag);
  color: #fff;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   01 · HERO
   Two lines of text as the whole graphic: a typed variable above a fixed
   constant. The tagline sits on the baseline of the viewport under a rule.
   ------------------------------------------------------------------------ */
.hero__frame { justify-content: space-between; }

.hero__statement {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: var(--grid-8);
}

/* Both lines are one sentence, so they share a face, a size, and a colour —
   only the first one changes. */
.hero__heading {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--t-hero);
  line-height: 1.05;
  letter-spacing: -0.03em;
}

/* A little air between the variable line and the constant, without loosening
   the line-height of either. */
.hero__typed {
  display: block;
  min-height: 1.05em;
  margin-block-end: 0.06em;
}

.hero__constant { display: block; }

.hero__foot {
  border-block-start: var(--rule);
  padding-block: var(--grid-3);
  background: var(--box-fill);
}

.hero__foot .shell {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--grid-4);
}

/* Sized against the wordmark rather than off the scale by feel. The mark's
   letters stand 20px in the masthead; at --t-lead this line's caps were 12.8px,
   which read as a footnote under it. One step up the scale puts them at 15.7px
   — closer to the mark's weight without matching it, which would make the two
   compete for the same job. */
.hero__tagline {
  font-family: var(--font-serif);
  font-size: var(--t-prose);
  letter-spacing: -0.01em;
}

.hero__scroll {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

@media (max-width: 640px) {
  .hero__scroll { display: none; }

  /* The bump above is a desktop move. A phone has no room for it: the line
     already wraps to two at 18px, and holding it to one would mean 14.9px —
     smaller than it started, to buy a single line nobody asked for. So the
     phone keeps the size it always had. */
  .hero__tagline { font-size: var(--t-lead); }
}

/* ---------------------------------------------------------------------------
   02 · STORY
   Four figures in a row, then the mission.
   ------------------------------------------------------------------------ */
.story__frame { justify-content: center; }

/* Two different distances, so the gap is set per child rather than once for
   the column. Under the rule it is --head-gap, the same as every other section.
   Above the bracket it is closer, because that space is not a separator — the
   bracket is a line reaching up out of FIG. 02, and the further it starts from
   the figure the less it reads as belonging to it. */
.story__frame > .shell {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.story__frame > .shell > .panels { margin-block-start: var(--head-gap); }
.story__frame > .shell > .focus { margin-block-start: var(--grid-2); }

.story__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--grid-3);
  border-block-end: var(--rule-soft);
  padding-block-end: var(--grid-2);
}

/* Four across at every width above the phone breakpoint: the figures shrink
   with the columns rather than rewrapping, so the sequence stays readable
   left to right. */
.panels {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(var(--grid-2), 2vw, var(--grid-4));
  width: 100%;
  max-width: 1240px;
}

/* The column IS the figure. Sizing the panel rather than the box means the
   number's trailing rule, the title and the note all stop at the box's right
   edge, instead of running the full width of the grid column and leaving the
   box looking inset. Everything in the panel is measured off one edge. */
.panel {
  display: flex;
  flex-direction: column;
  gap: clamp(var(--grid), 1.4vh, var(--grid-2));
  width: min(100%, var(--fig-cap));
}

.panel__id {
  display: flex;
  align-items: center;
  gap: var(--grid);
}

/* The rule that trails each figure number, ending at the box edge. */
.panel__id::after {
  content: "";
  flex: 1;
  border-block-start: var(--rule-soft);
}

/* Fills the panel, which is what caps it — see .panel above. */
.panel__box {
  aspect-ratio: 1;
  width: 100%;
  border: var(--rule);
  background: var(--box-fill);
  overflow: hidden;
}

.panel__box svg { width: 100%; height: 100%; color: var(--ink); }

/* Hairlines stay hairlines no matter how large the figure is drawn. */
.panel__box svg * { vector-effect: non-scaling-stroke; }

/* The one place colour appears: the centre of FIG. 04. */
.panel__box .is-accent {
  stroke: var(--accent);
  fill: var(--accent);
  fill-opacity: 0.1;
}

/* Shapes assemble when their figure activates. */
.panel__box [data-shape] {
  opacity: 0;
  transform: scale(0.5);
  transform-box: fill-box;
  transform-origin: center;
  transition:
    opacity 460ms var(--ease),
    transform 460ms var(--ease);
  transition-delay: calc(var(--i, 0) * 9ms);
}

.panel.is-in .panel__box [data-shape] {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .panel__box [data-shape] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.panel__title {
  font-family: var(--font-serif);
  font-size: clamp(1rem, 2.3vh, var(--t-h3));
  letter-spacing: -0.01em;
}

.panel__note {
  font-size: clamp(0.75rem, 1.5vh, var(--t-sm));
  line-height: 1.5;
  color: var(--ink-mid);
}

/* Italic AND underlined — the italic reads as the word being used as a name,
   the rule makes it visible at 13px where an italic sans alone barely registers.
   from-font rather than the browser's default heft, which at this size lands
   thick enough to close up the descender of the word it sits under. Takes the
   note's colour, so it goes red with the rest of the panel when FIG. 02
   flags. */
.panel__note u {
  font-style: italic;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.18em;
}

/* --- the flagged figure ---------------------------------------------------
   Once all four are on screen, FIG. 02 turns red — it is the one the mission
   below is about. The whole panel goes: box, shapes, number, title and note.
   The bracket and the mission stay in ink. */
.panel__box,
.panel__box svg,
.panel__id,
.panel__title,
.panel__note {
  transition: color var(--dur-reveal) var(--ease),
              border-color var(--dur-reveal) var(--ease);
}

.panel.is-highlighted .panel__box { border-color: var(--flag); }
.panel.is-highlighted .panel__box svg { color: var(--flag); }
.panel.is-highlighted .panel__id,
.panel.is-highlighted .panel__title,
.panel.is-highlighted .panel__note { color: var(--flag); }

/* --- the bracket ----------------------------------------------------------
   A rule drops from the centre of FIG. 02 and opens into a bracket that
   embraces the mission text.

   The figure boxes are left-aligned inside their columns and can be narrower
   than them, so a column's centre is NOT its box's centre and the block cannot
   simply be centred.

   The fix is to repeat the figures' grid and drop this into column 2. A
   percentage margin on a grid item resolves against its own track, so
   `min(100%, --fig-cap)` here computes exactly the width FIG. 02's box takes
   in that same track — which makes the offset below its true centre, with no
   measurement needed. minmax(0, 1fr) stops the over-wide item from
   stretching its track. */
.focus {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  --focus-gap: clamp(var(--grid-2), 2vw, var(--grid-4));
  gap: var(--focus-gap);
  width: 100%;
  max-width: 1240px;
}

/* Spans the whole grid so percentages below resolve against the full row, then
   places itself with a margin. Sitting in column 2 instead would make every %
   relative to that one column, which is the wrong basis for this arithmetic.

   FIG. 02's box centre is rebuilt from the same numbers the figures use, so the
   bracket tracks it without measuring anything:

     --col     one column's width
     --box     the box inside it, capped at --fig-cap and left-aligned
     --centre  the box's centre, from the left edge of the row

   The width is then capped at TWICE that centre, which is what keeps the block
   on the page: staying centred on a box that sits left of the row's middle
   means the block cannot be wider than the space to its left allows. A fixed
   780px overflowed the left edge by ~75px once the viewport reached 900px. */
.focus__inner {
  grid-column: 1 / -1;

  --col: calc((100% - 3 * var(--focus-gap)) / 4);
  --box: min(var(--col), var(--fig-cap));
  --centre: calc(var(--col) + var(--focus-gap) + var(--box) / 2);
  --focus-w: min(780px, calc(2 * var(--centre)));

  width: var(--focus-w);
  /* Zero when the width is centre-limited, positive when the 780px cap binds.
     Never negative, so it cannot be pushed off the left. */
  margin-inline-start: calc(var(--centre) - var(--focus-w) / 2);
}


.bracket {
  position: relative;
  height: clamp(calc(var(--grid) * 3.5), 5.2vh, calc(var(--grid) * 5));
}

/* the drop from the figure */
.bracket::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  width: var(--line-weight);
  height: 50%;
  background: var(--line-ink);
  transition: background 900ms linear;
}

/* the bracket itself: a rule with a tick turned down at each end */
.bracket::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  height: 30%;
  border: var(--line-weight) solid var(--line-ink);
  border-block-end: 0;
  transition: border-color 900ms linear;
}

/* Held to the first word's colour by mission.js — same red, same 900ms
   release, so the mark and the word it leads never disagree. */
.bracket.is-fresh::before {
  background: var(--flag);
  transition: background 0ms;
}

.bracket.is-fresh::after {
  border-color: var(--flag);
  transition: border-color 0ms;
}

/* Phones lay the figures out two by two, so the four-column geometry no longer
   describes anything and the paragraph takes the full width.

   The bracket goes entirely. Its whole job was to tie the paragraph to FIG. 02,
   and two by two there is no honest place to put it: the second column holds
   FIG. 02 above FIG. 04, so a drop line under that column points at "Everything
   else". Off-centre it would be wrong and centred it would be decoration. The
   red figure carries the emphasis by itself.

   Which leaves the paragraph with nothing above it, so it takes its own space
   where the bracket used to sit — declared further down, after the .mission
   rule it has to beat. Same specificity, so source order is the whole of it. */
@media (max-width: 640px) {
  .focus__inner {
    width: 100%;
    margin-inline-start: 0;
  }

  .bracket { display: none; }
}

.mission {
  font-family: var(--font-sans);
  /* Sized against the space the pinned frame has, not fixed. Weighted toward
     vh because height is what the frame rations — width only changes the
     measure.

     Solved against both tight corners at once so neither is the one that
     breaks, and aimed at roughly 50px of clear space rather than at the last
     pixel that fits: a frame filled to its edge looks wrong long before it
     clips. That puts it near 18px at both corners, leaving about 45px at
     1201x681 and 57px at 901x751, and it reaches the --t-prose cap on any
     ordinary desktop — where this paragraph and the team's are the same size.

     Two things to know before touching these coefficients. Headroom moves in
     steps, not smoothly, because the paragraph gains or loses a whole line:
     at 1201x681, 17px leaves 75px of room and 17.5px leaves 45px, with nothing
     between. Aim at a band, not a number. And measure only after a reload —
     reading the frame straight after a viewport resize gives a stale 100svh
     and reports about 15px more room than is really there. */
  font-size: clamp(1rem, 0.3vw + 1.95vh, var(--t-prose));
  line-height: 1.55;
  /* No rule of its own — the bracket above is what separates it now. */
  padding-block-start: var(--grid-2);
}

.mission strong { font-weight: 600; }

/* --- written out word by word ---------------------------------------------
   .is-writing is set by mission.js only when scroll progress is driving the
   paragraph, so flat view and collapsed layouts show it whole.

   Unwritten words are transparent rather than absent: they hold their space,
   so nothing reflows and each word arrives where it will finally sit.

   Taking the red is INSTANT; only giving it up is timed. A word sits in the
   band for about ten words of scrolling — under 100ms at a normal scroll speed
   — so any ramp-in at all means it never reaches full red before it starts
   fading back, and the whole effect washes out to pink with no visible decay.
   Snapping in and fading out slowly makes the band crisp and the tail long at
   any scroll speed. */
.mission [data-word] {
  transition: opacity 160ms var(--ease), color 900ms linear;
}

.mission.is-writing [data-word] { opacity: 0; }
.mission.is-writing [data-word].is-written { opacity: 1; }

.mission.is-writing [data-word].is-fresh {
  opacity: 1;
  color: var(--flag);
  transition: opacity 90ms var(--ease), color 0ms;
}

/* Phones get the figures two by two and small, with only their titles under
   them. Four full-width figures each carrying a paragraph made the section a
   long scroll through four near-identical blocks before the argument arrived;
   at a quarter of the height the four read as one set, which is what they are
   — a sequence, and the point is the progression across it.

   The notes go rather than shrink. At this size they would set to two or three
   words a line beneath a box narrower than the text, and the mission below
   makes the same argument in full. Worth knowing: this hides them from screen
   readers too, so on a phone the figures are titles only.

   Everything stays ranged left, as it is at every other width: the figure
   number, the box and the title share one left edge. */
@media (max-width: 640px) {
  .panels {
    grid-template-columns: 1fr 1fr;
    gap: var(--grid-4) var(--grid-3);
  }

  /* Fills its column, so the figure number's rule and the title still stop at
     the box edge. */
  .panel { width: 100%; }

  .panel__note { display: none; }

  /* One size for every piece of running text on a phone: the figure titles and
     the mission below them, matching .careers__sub further down. Those three
     move together — when the team's paragraph grew, these grew with it, since
     the whole point is that a phone reads them as one column of text. */
  .panel__title { font-size: var(--t-h3); }

  /* Replaces the space the bracket used to hold. Must live here rather than
     with the other phone rules above: those come before the .mission rule this
     overrides, and at equal specificity the later one wins.

     The mission and the team's prose are the two blocks of running text on the
     page, and on a phone they follow each other down a single column, so they
     set at one size — this matches .careers__sub. */
  .mission {
    padding-block-start: var(--grid-4);
    font-size: var(--t-h3);
    line-height: 1.45;
  }

  /* Ragged right here. Justifying needs a measure wide enough to absorb the
     extra word spacing; at phone width it opens rivers instead — the same
     reason every other block on the page is already ragged. */
  .mission p {
    text-align: start;
    hyphens: none;
    -webkit-hyphens: none;
  }

}

/* The word FIG. 02 is about, in the figure's own red, at every width. On a
   phone it is the only tie back to the figure, since the bracket has nowhere
   honest to sit in a two-by-two grid; on a wide screen it doubles the bracket
   rather than replacing it. It settles red instead of ink once the write-out's
   band has passed over it, which is the one place the two reds meet. */
.mission [data-word].is-key { color: var(--flag); }

/* The closing sentence, at every width — the turn from what is wrong to what
   Riven is doing about it. Set in the accent rather than the flag red, which
   belongs to FIG. 02 and to the band that writes the paragraph out. Marked by
   mission.js; see there for why it cannot come from the copy. */
.mission [data-word].is-close { color: var(--accent); }

/* ---------------------------------------------------------------------------
   03 · CAREERS
   Ruled job table on the left; masthead, investors, and partners on the right.
   Cells share their lines so each grid reads as one drawn table.
   ------------------------------------------------------------------------ */
.careers__frame { justify-content: center; }

/* Prose on the left, the roles table on the right — the table needs the wider
   share so long role titles keep their location on the same line. */
.careers__grid {
  display: grid;
  grid-template-columns: 1fr 1.25fr;
  /* Split, because the one row gap this grid has sits under the head bar and
     a column gutter is the wrong size for it — sharing the token put 64px
     there against News's 48. The columns still want the wider gutter. */
  column-gap: var(--grid-8);
  row-gap: var(--head-gap);
  align-items: start;
}

.careers__roles {
  display: flex;
  flex-direction: column;
  gap: var(--grid-2);
}

/* Label with a rule running out to the edge of the table below it. */
.roles__head {
  display: flex;
  align-items: center;
  gap: var(--grid-2);
}

.roles__head::after {
  content: "";
  flex: 1;
  border-block-start: var(--rule-soft);
}

/* --- shared ruled-table primitive ---------------------------------------- */
.ruled {
  display: grid;
  border-block-start: var(--rule);
  border-inline-start: var(--rule);
  background: var(--box-fill);
}

.ruled > * {
  border-block-end: var(--rule);
  border-inline-end: var(--rule);
  background: var(--box-fill);
}

/* --- jobs -----------------------------------------------------------------
   One column of full-width rows: role on the left, location on the right. */
.jobs { grid-template-columns: 1fr; }

.job {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--grid-3);
  padding: var(--grid-2) var(--grid-3);
  min-height: calc(var(--grid) * 8);
  transition: background var(--dur-ui) var(--ease);
}

.job:hover,
.job:focus-visible { background: var(--tint); }

.job__title {
  font-size: var(--t-base);
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.job__meta {
  display: flex;
  align-items: center;
  gap: var(--grid-3);
  flex: none;
}

.job__arrow {
  font-family: var(--font-mono);
  color: var(--ink-soft);
  transition: transform var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.job:hover .job__arrow {
  color: var(--accent);
  transform: translateX(3px);
}

/* --- right column --------------------------------------------------------- */
/* The three blocks here — the team copy, the careers link, and the backer
   marks — are separate thoughts, so they get a wider gap than a normal stack. */
.careers__aside {
  display: flex;
  flex-direction: column;
  gap: var(--grid-6);
}

.careers__title {
  font-family: var(--font-serif);
  font-size: var(--t-h2);
  line-height: 1;
  margin-block-end: var(--grid-2);
}

/* The same head row Vision and News carry, spanning both columns so its rule
   crosses the page rather than stopping at the end of the left one. */
.careers__head {
  grid-column: 1 / -1;
  border-block-end: var(--rule-soft);
  padding-block-end: var(--grid-2);
}

.careers__sub {
  font-size: var(--t-prose);
  line-height: 1.45;
  color: var(--ink-mid);
  max-width: 40ch;
}

.careers__sub + .careers__sub { margin-block-start: var(--grid-2); }

@media (max-width: 900px) {
  .careers__grid { grid-template-columns: 1fr; column-gap: var(--grid-6); }

  /* Stacked, the one row gap has two jobs — under the head, and between the
     two columns now sitting one above the other. The head keeps --head-gap
     with the rest of the page; the roles table buys its own separation back. */
  .careers__roles { margin-block-start: var(--grid-4); }
}

/* Set in ink on a phone. The mid grey reads as secondary against the prose it
   sits beside on a wide screen; in a single column at the same size as the
   mission a screen above it, the two are near enough to be compared, and the
   grey looks like a mistake rather than a hierarchy. */
@media (max-width: 640px) {
  .careers__sub {
    color: var(--ink);
    /* Back to the size the figure titles and the mission use — a phone reads
       the three as one column of text, so they hold together rather than
       following the desktop measure. */
    font-size: var(--t-h3);
  }
}

/* ---------------------------------------------------------------------------
   LOGOS — investor and partner marks, inside the team column.

   Deliberately unboxed: they sit straight on the dot ground rather than in the
   ruled grid the rest of the page uses, so they read as supporting evidence for
   the sentence above them, not as another table.

   Every mark is forced to one ink so files in seven different brand colours
   read as one set. Size is capped in BOTH directions — a per-logo --h plus a
   width cap — because these run from an 8.7:1 wordmark to a 1:1 symbol and
   matching heights alone would let the widest ones dominate.
   ------------------------------------------------------------------------ */
.logos__label { margin-block-end: var(--grid-2); }

/* Two explicit rows of 4 and 3 rather than a wrapping grid: the marks have no
   cell of their own, just even gaps. The column is only as wide as the top
   row, so the short row centres under it while the block stays left-aligned
   with the prose above. */
.logos {
  display: flex;
  flex-direction: column;
  gap: var(--grid-4);
  width: fit-content;
}

.logos__row {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--grid-4);
  width: 100%;
}

.logos img {
  height: var(--h, 22px);
  width: auto;
  max-width: min(100%, 112px);
  object-fit: contain;
  /* Fallback ink, for the no-mask case below. */
  filter: brightness(0);
}

/* The link hugs the mark rather than the row, so the focus ring traces the
   logo. inline-flex, not block: a block anchor takes the image's INTRINSIC
   width, which for a wide mark held back by max-width is wider than the mark
   actually draws — the ring would sit off the logo. */
.logos a,
.logos__mark {
  display: inline-flex;
  /* The same cap the image carries. A flex container's max-content size does
     not account for a max-width on its item, so without this the box keeps the
     mark's intrinsic width and the ring hangs off the wide ones. */
  max-width: min(100%, 112px);
}

/* --- the ink ---------------------------------------------------------------
   The marks arrive in seven different colours and have to read as one set, so
   they are painted rather than displayed: the span fills itself with a colour
   and uses the mark as a MASK, which makes the ink an ordinary token that can
   be transitioned to any other colour. A filter cannot do that — brightness(0)
   only reaches black, and the chain that reaches an arbitrary hue is a string
   of magic numbers that no longer answers to --accent.

   The ring stays on the anchor, outside the mask: a mask clips everything the
   element paints, an outline included.

   Same caveat as the filter it replaces — a knockout mark, whose shape depends
   on white punched out of a solid field, has no alpha to mask against and must
   be recoloured at source and opted out of this. */
@supports ((mask-image: url("")) or (-webkit-mask-image: url(""))) {
  .logos__mark {
    background: var(--ink);
    /* mask-image itself is set on the element — see render_backers. */
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    transition: background var(--dur-ui) var(--ease);
  }

  /* Still in the layout and still in the accessibility tree — it sets the box
     the mask is drawn into and carries the alt text. Only its pixels go. */
  .logos__mark img { opacity: 0; }

  .logos a:hover .logos__mark,
  .logos a:focus-visible .logos__mark { background: var(--accent); }
}

/* On a phone the rows of four and three would run past the screen. Letting each
   row wrap on its own gives 3-1-3 — the row of four breaks, the row of three
   does not — which reads as a mistake rather than a layout.

   So the two rows stop being boxes at all and the seven marks flow through one
   three-column grid: 3-3-1, with the odd one out at the end where it belongs.
   A grid rather than wrapping flex because the marks run from 46px to 112px
   wide, so wrapping would break wherever the widths happened to land rather
   than every third mark.

   display: contents is what dissolves the two <ul>s. It can cost the list its
   semantics in some screen readers; the marks keep their alt text and links,
   which is what carries the meaning here. */
@media (max-width: 560px) {
  .logos {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: center;
    gap: var(--grid-4) var(--grid-3);
    width: 100%;
  }

  .logos__row { display: contents; }
}

/* ---------------------------------------------------------------------------
   04 · NEWS — out of the sticky mechanism, in normal flow.
   ------------------------------------------------------------------------ */
.news {
  border-block-start: var(--rule);
  padding-block: var(--grid-12);
}

.news__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--grid-4);
  border-block-end: var(--rule-soft);
  padding-block-end: var(--grid-2);
  margin-block-end: var(--head-gap);
}

/* A row of fixed-width cards rather than columns that divide the width — one
   announcement stays card-sized instead of stretching across the page, and
   further ones wrap alongside it. */
.news__list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-3);
}

/* A flex container itself, so the card inside stretches to the row's height
   without a percentage to resolve — see .news__item below. */
.news__list > li { flex: 0 1 360px; display: flex; }

.news__item {
  border: var(--rule);
  background: var(--box-fill);
  padding: var(--grid-3);
  display: flex;
  flex-direction: column;
  gap: var(--grid-2);
  min-height: calc(var(--grid) * 28);
  /* The li stretches to the tallest card on the row, but this box sits inside
     it and would otherwise size to its own text — leaving two cards side by
     side with ragged bottoms. Filling the li squares them off, and the auto
     margin on .news__sub below spends the slack between title and standfirst
     rather than at the foot. Only shows with two cards of unequal length,
     which is why it went unnoticed while there was one.

     flex, not height: 100%. A percentage height against a flex item that gets
     its own height from stretching is the one form of this Safari declines to
     resolve — it reads as auto there, so the bottoms stay ragged while Chrome
     looks correct. Stretching inside a flex li needs no percentage at all. */
  flex: 1;
  transition: background var(--dur-ui) var(--ease);
}

/* Only when it is actually a link — a card with no destination should not
   light up under the cursor as though it had one. */
a.news__item:hover { background: var(--tint); }

.news__date { color: var(--ink-soft); }

.news__title {
  font-family: var(--font-serif);
  font-size: var(--t-h3);
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.news__sub {
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--ink-mid);
  margin-block-start: auto;
}

/* ---------------------------------------------------------------------------
   05 · FOOTER
   ------------------------------------------------------------------------ */
/* A trim single band, not a full section: one row of signup, links and
   credit, on white so it reads as the edge of the page rather than another
   chapter of it. */
.footer {
  border-block-start: var(--rule);
  background: var(--box-fill);
  padding-block: var(--grid-4);
}

.footer__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--grid-6);
  flex-wrap: wrap;
}

.signup__label {
  display: block;
  margin-block-end: var(--grid);
}

/* Above the row, the three items in the footer line up on their middles — and
   the middle that matters for the sign-up is the RECTANGLE's, not the whole
   block's. Left in flow, the label counts toward the block's height and drags
   its centre about 12px above the rectangle's, so centring the block leaves
   the rectangle sitting low against the links beside it. Lifting the label out
   of flow makes the block exactly the rectangle, and align-items: center then
   means what it appears to mean.

   Only above the breakpoint: once these stack, the label is simply a caption
   above the field and an absolute one would overlap what is above it. */
@media (min-width: 761px) {
  .signup { position: relative; }

  .signup__label {
    position: absolute;
    inset-block-end: 100%;
    inset-inline-start: 0;
  }
}

.signup__row {
  display: flex;
  border: var(--rule);
  background: var(--box-fill);
  width: min(320px, 100%);
}

.signup__row input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: calc(var(--grid) * 1.25) var(--grid-2);
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  color: var(--ink);
}

.signup__row input::placeholder { color: var(--ink-soft); }
.signup__row input:focus { outline: none; }
.signup__row:focus-within { outline: var(--line-weight) solid var(--accent); }

.signup__row button {
  border: 0;
  border-inline-start: var(--rule);
  background: transparent;
  padding-inline: var(--grid-2);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

/* Same lift as the nav cells, job rows and news cards — one hover treatment
   across the page rather than this one inverting to black. */
.signup__row button:hover,
.signup__row button:focus-visible {
  background: var(--tint);
  color: var(--accent);
}

/* One row, not a 2x2 block. */
/* A grid rather than wrapping flex. Wrapped flex sheds one link at a time as
   the window narrows — four, then three and a stray, then two and two — and
   the lopsided middle step looks like a mistake. Two column counts, one jump
   between them. */
.footer__nav {
  display: grid;
  grid-template-columns: repeat(4, max-content);
  align-items: center;
  gap: var(--grid-2) var(--grid-4);
}

/* The whole address moves to the next line together. Left to wrap, it breaks
   after any word — "1361 Amsterdam Ave, NY," on one line and "NY 10027" on the
   next reads as a broken string rather than an address. */
.footer__address { white-space: nowrap; }

.footer__nav a {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  color: var(--ink-mid);
  transition: color var(--dur-ui) var(--ease);
}

.footer__nav a:hover,
.footer__nav a:focus-visible { color: var(--accent); }

/* Wraps, now that it carries a street address. nowrap was safe while the line
   was "Riven Systems · New York"; at 67 characters it would push the page
   sideways on a phone. */
.footer__meta { text-wrap: pretty; }

@media (max-width: 760px) {
  .footer__inner {
    align-items: flex-start;
    gap: var(--grid-3);
  }

  /* Four across no longer fit beside anything, so the links square up. */
  .footer__nav { grid-template-columns: repeat(2, max-content); }
}


/* ---------------------------------------------------------------------------
   THE DIALOG
   Drawn as one more box on the dot ground: a ruled rectangle on paper, with
   the same hairline everything else uses. Nothing here is a new idea, which is
   the point — it should read as part of the page rather than as a widget.
   ------------------------------------------------------------------------ */
.modal {
  width: min(560px, calc(100vw - var(--grid-4)));
  /* Restores what the UA gives a dialog and the reset in base.css takes away.
     Without it the box sits in the top-left corner rather than centred. */
  margin: auto;
  padding: 0;
  max-height: calc(100svh - var(--grid-8));
  overflow: auto;
  border: var(--rule);
  background: var(--box-fill);
  color: var(--ink);
}

.modal::backdrop { background: rgb(17 17 16 / 0.28); }

/* Two scrollers at once was the bug a friend caught on a phone: on a short
   screen the dialog outgrows its max-height and scrolls, while the page behind
   it stays scrollable too — two live scrollbars, and a drag that reaches the
   dialog's end carries on into the page underneath.

   contain stops the chaining. The lock stops the second scrollbar existing at
   all: keyed off [open] rather than set by forms.js, so it cannot fall out of
   step with a dialog closed by Escape or by the backdrop, neither of which goes
   through our own close path. scrollbar-gutter keeps the page from shifting
   sideways as the scrollbar goes. */
.modal { overscroll-behavior: contain; }

html { scrollbar-gutter: stable; }

html:has(.modal[open]) {
  overflow: hidden;
}

/* Out of the flow. In it, the close cell reserved a full row across the top of
   the dialog, which left a dead band above short content — a one-line
   confirmation sat with 70px of nothing above it and 32 below. A modal dialog
   is position: fixed, so it is already the containing block for this. */
.modal__panel {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
}

.modal__close {
  border: 0;
  border-inline-start: var(--rule);
  border-block-end: var(--rule);
  background: var(--box-fill);
  color: var(--ink-mid);
  font-size: 1.25rem;
  line-height: 1;
  padding: var(--grid) var(--grid-2);
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.modal__close:hover,
.modal__close:focus-visible { background: var(--tint); color: var(--accent); }

.modal__body { padding: var(--grid-4); }

/* Clears the close cell, which now overlaps this row rather than sitting
   above it. */
.modal__title {
  font-size: var(--t-h2);
  line-height: 1.1;
  padding-inline-end: var(--grid-6);
}

.modal__blurb {
  margin-block-start: var(--grid);
  color: var(--ink-mid);
  font-size: var(--t-sm);
  max-width: 46ch;
}

/* Off-screen rather than display:none — a bot reading the DOM fills in what it
   can find, and a field that is merely hidden is still found. */
.modal__trap {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.modal__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--grid-2);
  margin-block-start: var(--grid-3);
}

.field { display: flex; flex-direction: column; gap: var(--grid); }
.field--wide { grid-column: 1 / -1; }
.field[hidden] { display: none; }

.field input,
.field textarea {
  border: var(--rule);
  background: var(--box-fill);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
  padding: var(--grid) var(--grid-2);
  border-radius: 0;
  resize: vertical;
}

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent);
}

.modal__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--grid-2);
  margin-block-start: var(--grid-3);
}

.modal__status {
  font-size: var(--t-sm);
  color: var(--ink-mid);
  margin: 0;
}

.modal__submit {
  border: var(--rule);
  background: var(--box-fill);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  padding: var(--grid) var(--grid-3);
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.modal__submit:hover:not(:disabled),
.modal__submit:focus-visible { background: var(--tint); color: var(--accent); }

.modal__submit:disabled { color: var(--ink-soft); cursor: default; }

.modal__done {
  font-size: var(--t-base);
  padding-inline-end: var(--grid-6);   /* clears the close cell */
  text-wrap: pretty;
  max-width: 44ch;
}

/* The status word, set as one more machine-voice label like the section
   markers and figure numbers. */
.modal__done .label {
  display: block;
  margin-block-end: var(--grid);
}

.modal__done:focus-visible { outline: none; }

@media (max-width: 520px) {
  .modal__grid { grid-template-columns: 1fr; }
  .modal__body { padding: var(--grid-3); }
}

/* A standing address, set quieter than the blurb above it: the mono label names
   the category, the address is the only thing to act on. */
.modal__note {
  margin-block-start: var(--grid-2);
  font-size: var(--t-sm);
  color: var(--ink-mid);
}

.modal__note .label { margin-inline-end: var(--grid); }

.modal__note[hidden] { display: none; }

/* The masthead is fixed, so an anchor jump would otherwise land its target
   underneath it. Only matters where the page is in normal flow — pinned, links
   are retargeted to a scroll position by reveal.js rather than to an element. */
:target,
#roles {
  scroll-margin-block-start: calc(var(--masthead-h) + var(--grid-3));
}

/* ---------------------------------------------------------------------------
   A PAGE, NOT A CHAPTER
   Used by careers/. Reuses the team section's grid, prose and roles table, but
   in normal flow: no track, no sticky frame, no cues. Everything is present on
   arrival, which is what a page reached by following a link should be — the
   scroll machinery only makes sense for a single continuous story.
   ------------------------------------------------------------------------ */
.page {
  padding-block: var(--grid-12) var(--grid-16);
}

/* The prose column runs longer here than in the teaser on the main page, so it
   gets its own measure rather than the 40ch that suits three short lines.

   The aside is a flex column with a --grid-6 gap, which on the main page
   separates three distinct blocks. Here its children ARE the paragraphs, so
   that gap was landing between each of them and adding to their own margin —
   24px of margin reading as a 72px hole. Spacing is set by margin alone. */
.page .careers__aside { gap: 0; }

.page .careers__sub { max-width: 54ch; }
.page .careers__sub + .careers__sub { margin-block-start: var(--grid-3); }

/* The one place a wider break belongs: the address is not another paragraph. */
.page__contact { margin-block-start: var(--grid-6); }

/* The roles table is the point of this page, so it holds its own beside the
   prose rather than sitting in the narrower teaser column. */
@media (min-width: 901px) {
  .page .careers__grid { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------------------
   A MIRRORED POSTING
   One column of running text under a ruled head. The ATS's own markup is
   thrown away by tools/fetch_jobs.py, so everything here is set in the site's
   type rather than the editor's.
   ------------------------------------------------------------------------ */
/* The back link IS the marker, so it inherits that type rather than being a
   link parked above it. */
.careers__head .marker a { color: inherit; }
.careers__head .marker a:hover,
.careers__head .marker a:focus-visible { color: var(--accent); }

.job-post__head {
  padding-block-start: var(--head-gap);
}

.job-post__head-inner {
  border-block-end: var(--rule-soft);
  padding-block-end: var(--grid-3);
}

.job-post__title {
  font-family: var(--font-serif);
  font-size: var(--t-h2);
  line-height: 1.05;
}

/* The three facts, spaced as one row of labels rather than run together. */
.job-post__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid) var(--grid-4);
  margin-block-start: var(--grid-2);
}

/* One call to action, at the end, where a candidate has actually read the
   thing. */
.job-post__apply { margin-block-start: var(--grid-6); }

/* The one call to action on the site, so it is drawn rather than merely
   underlined — the same ruled box as everything else, at rest and on hover. */
.button {
  display: inline-block;
  border: var(--rule);
  background: var(--box-fill);
  padding: var(--grid-2) var(--grid-4);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink);
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease);
}

.button:hover,
.button:focus-visible { background: var(--tint); color: var(--accent); }

.job-post__body {
  margin-block-start: var(--head-gap);
  max-width: 68ch;
}

/* A step above the body rather than level with it — these carry the shape of
   a long posting, and at --t-h3 they were doing it too quietly. */
.job__heading {
  font-family: var(--font-serif);
  font-size: var(--t-prose);
  line-height: 1.2;
  margin-block-start: var(--grid-6);
  margin-block-end: var(--grid-2);
}

.job-post__body > :first-child { margin-block-start: 0; }

.job__text { margin-block-end: var(--grid-2); }

.job__list {
  list-style: none;
  margin-block-end: var(--grid-2);
}

/* A drawn tick rather than a bullet glyph — the same hairline the page is
   built from, and it lines the text up on one edge. */
.job__list li {
  position: relative;
  padding-inline-start: var(--grid-3);
  margin-block-end: var(--grid);
}

.job__list li::before {
  content: "";
  position: absolute;
  inset-block-start: 0.55em;
  inset-inline-start: 0;
  width: 0.4em;
  height: 0.4em;
  background: var(--ink);
}

.job__text,
.job__list li { font-size: var(--t-lead); line-height: 1.55; }

/* The range reads as a fact, the note beneath it as the caveat it is. */
.job__pay { font-variant-numeric: tabular-nums; }

.job__pay-note {
  color: var(--ink-mid);
  font-size: var(--t-base);
  max-width: 58ch;
}

/* An empty label renders as a stray gap in the meta row — a posting with no
   parenthetical simply has one fewer fact. */
.job-post__meta .label:empty { display: none; }
