/* ============================================================
   Recruit Group — Global Careers | Recruit Proof System
   Master: corporate clarity + proof-first information hierarchy.
   Modes: editorial stories / limited builder-event accents.
   ============================================================ */

:root {
  /* Semantic color tokens */
  --brand-primary: #0065bd;
  --brand-primary-hover: #00529a;
  --brand-soft: #ddebf7;
  --text-primary: #161a1d;
  --text-secondary: #3f4b52;
  --text-muted: #51656f;
  --canvas: #f7f7f2;
  --surface: #ffffff;
  --surface-subtle: #eef3f5;
  --border-subtle: #dce1e4;
  --border-quiet: #e9edef;
  --status-open: #1f7a52;
  --status-open-bg: #e6f2ec;
  --status-upcoming: #7a5b0a;
  --status-upcoming-bg: #f4edda;
  --builder-black: #050608;
  --builder-night: #0d1040;
  --builder-indigo: #1a1f6e;
  --builder-signal: #8b9fff;

  /*
    Opportunity colours sit below Recruit Blue in the hierarchy. They name
    differences in values and content; they never replace the blue used for
    navigation, focus or calls to action. Each strong colour has a restrained
    surface partner so large areas stay calm and dark text remains readable.
    The inks are held a step off full saturation: at heading size, on top of
    their own soft surface, the saturated versions read as a product palette
    rather than as three names for the same kind of thing.
  */
  --opportunity-violet: #5b43a3;
  --opportunity-violet-soft: #eeecfa;
  --opportunity-coral: #904754;
  --opportunity-coral-soft: #fae9ec;
  --opportunity-amber-soft: #f7eedc;
  --opportunity-teal: #21695e;
  --opportunity-teal-soft: #e4f3f0;
  --opportunity-cyan-soft: #e3f4f8;

  /*
    RING business accents, derived from the current official product sites: an
    ink dark enough to keep small text and 1px borders above WCAG AA, and a soft
    surface to wash the card with. The products' own signal colours — Study
    Sapuri's cyan, Zexy's magenta, Alumy's teal — are not carried here: at tag
    size they only ever became a dot, and a dot in front of a word that is
    already a label is decoration. The product logo itself is never recoloured.
  */
  --ring-study-ink: #0b41a0;
  --ring-study-soft: #e3f6ff;
  --ring-zexy-ink: #9a0056;
  --ring-zexy-soft: #fce8f3;
  --ring-alumy-ink: #006e68;
  --ring-alumy-soft: #ebf8f7;

  /*
    Not ours. LinkedIn Blue, carried as a token only so the one place that draws
    the official mark has a name to reference. brand.linkedin.com/policies does
    not allow recolouring the mark, so this value never joins a scale and never
    gets substituted for a brand token.
  */
  --linkedin-blue: #0a66c2;

  /*
    Inverted surfaces — text, lines and fills that sit on a filled dark surface
    instead of on paper. The ink footer, the section-ink bands, the hero over
    photography, and anything on a brand fill all draw from these, so a white
    label on a button and a white heading in the footer cannot drift apart.
  */
  --text-inverse: #ffffff;
  --text-inverse-secondary: rgba(255, 255, 255, 0.86);
  --text-inverse-muted: rgba(255, 255, 255, 0.72);
  --on-ink-secondary: #aebbc1;
  --on-ink-muted: #83949d;
  --on-ink-border: #344149;
  --surface-ink-raised: #253139;

  /*
    Three steps of translucent white, by role rather than by opacity: a wash is
    a hover fill, a line is a hairline you should barely notice, a strong line
    is one meant to be seen. Before these existed the stylesheet carried eleven
    different alphas on white, including pairs like 0.3 and 0.35 that no one
    could tell apart.
  */
  --inverse-wash: rgba(255, 255, 255, 0.1);
  --inverse-line: rgba(255, 255, 255, 0.18);
  --inverse-line-strong: rgba(255, 255, 255, 0.4);
  --inverse-surface: rgba(255, 255, 255, 0.94);

  /*
    Translucent canvas, for the surfaces that blur what is behind them. The
    scrolled header is deliberately a second, more opaque step: off the top of
    the page it sits over content rather than over the canvas.
  */
  --backdrop: rgba(247, 247, 242, 0.96);
  --backdrop-solid: rgba(247, 247, 242, 0.98);
  --border-strong: #ccd4d8;
  /*
    Media scrim. Two shapes, because the copy sits in two different places.
    Both are set by measurement rather than by eye: the stills are high-key
    interiors, so the brightest pixels behind the copy are near white, and
    white type needs real density to survive them. A bottom-weighted ramp that
    looked right measured 1.1:1 behind the headline, which is invisible.

    The default is the full-frame ramp, and it has to cover the frame. Below
    1024px the copy spans the hero's whole width — 0–96% at 390x544, 0–65% at
    760x563 — so there is no part of the picture that is not behind text and
    nothing to protect by retreating. It stays a gradient rather than a flat
    wash so the frame keeps some depth, but it starts at 0.56. Measured worst
    case at 390x544: 4.4:1 headline, 5.4:1 lede, 8.3:1 note.

    --scrim-media-wide is the interesting one. Above 1024px the copy stops
    growing with the viewport and settles into the lower-left corner: at
    1721x1025 the glyphs occupy x 15–46%, y 42–93% and nothing else. Darkening
    the right half there buys legibility nothing needs, and the right half is
    where these photographs are worth the most — the window wall, the skyline,
    the one warm chair. So the wide step is an ellipse anchored at the bottom-
    left corner, which reads as light falling off rather than as a band laid
    across the picture.

    It is not a compromise. Against the worst pixel of the worst of the three
    stills it measures 4.7:1 behind the headline, 7.0:1 behind the lede and
    6.6:1 behind the note, where the full-frame ramp at the same width managed
    4.8 / 9.7 / 9.0 — no better where it counts, because the headline is the
    binding constraint either way — while covering the frame at a mean alpha
    of 0.58 against 0.41 here. Roughly a third more photograph for the same
    legibility.

    The radii are what keep it natural. 145% vertically against 80%
    horizontally makes the falloff slower than it is wide, so the top-left
    blinds stay lit and no visible edge is drawn across the glass. Four stops,
    because a fall this long bands on two.

    1024px is not a round number chosen for neatness: it is where the copy
    stops being a majority of the frame. At 760px the same ellipse measured
    1.8:1 behind the lede, because the lede still ran to 60% of the width.
  */
  --scrim-media: linear-gradient(
    180deg,
    rgba(22, 26, 29, 0.56) 0%,
    rgba(22, 26, 29, 0.68) 36%,
    rgba(22, 26, 29, 0.8) 70%,
    rgba(22, 26, 29, 0.9) 100%
  );
  --scrim-media-wide: radial-gradient(
    80% 145% at 0% 100%,
    rgba(22, 26, 29, 0.92) 0%,
    rgba(22, 26, 29, 0.86) 40%,
    rgba(22, 26, 29, 0.56) 70%,
    rgba(22, 26, 29, 0) 94%
  );
  /* Phone hero: the claim sits at the top and the actions at the bottom, with
     the photograph showing between them. Only the top block is on the scrim;
     the actions carry their own backing below. */
  --scrim-media-top: linear-gradient(
    180deg,
    rgba(22, 26, 29, 0.82) 0%,
    rgba(22, 26, 29, 0.66) 34%,
    rgba(22, 26, 29, 0.28) 54%,
    rgba(22, 26, 29, 0) 70%
  );
  --media-glass: rgba(22, 26, 29, 0.38);
  --media-text-shadow: 0 1px 3px rgba(22, 26, 29, 0.9), 0 0 14px rgba(22, 26, 29, 0.7);

  /* Brand blue has three steps in use; only two were ever named. */
  --brand-primary-active: #00447f;

  /* Backwards-compatible aliases used by the prototype markup */
  --ink: var(--text-primary);
  --ink-2: var(--text-secondary);
  --ink-3: var(--text-muted);
  --line: var(--border-subtle);
  --line-2: var(--border-quiet);
  --paper: var(--canvas);
  --paper-2: var(--surface-subtle);
  --white: var(--surface);
  --accent: var(--brand-primary);
  --accent-ink: var(--brand-primary-hover);
  --ok: var(--status-open);
  --ok-bg: var(--status-open-bg);
  --warn-bg: var(--status-upcoming-bg);
  --warn: var(--status-upcoming);

  --max: 1200px;
  --gutter: 24px;

  /*
    Measure. Two edges per column, and only two: running text stops at one,
    small print at the other. This used to be fifteen — every page set its own
    `max-width` in ch on the lede and the notes (58, 60, 62, 64, 66, 68, 70),
    so body copy in the same 1152px column broke at 490, 572, 576, 579, 587,
    595, 604, 614, 655, 671, 736, 757 and 778px. None of those edges lined up
    with a rule, a grid or each other, which is what made each paragraph look
    like it had been cut off at a random word.

    Lengths rather than ch, because the lede and the body are different sizes:
    at the same character count they would stop at different x, and a column
    that reads as one block is worth more here than matching their character
    counts exactly. 42rem is 67ch of body and 63ch of lede, 36rem is 65ch of
    .small — all inside the readable range, so nothing is given up for it.
  */
  --measure-text: 42rem;
  --measure-note: 36rem;
  /* The hero sets its own, narrower: the copy is over a photograph and the
     scrim is sized to it. See the .hero rules. */
  --measure-hero: 34rem;

  /*
    Motion — six durations and three curves, so every transition on the page
    comes from the same vocabulary. The curves are the ones Apple uses across
    apple.com: a near-linear ease for state changes on controls, and a long
    decelerating ease for anything that travels (cards lifting, blocks
    revealing on scroll). Elevation is expressed as scale and translate only;
    the proof system does not carry shadow.
  */
  --motion-press: 110ms;
  --motion: 180ms;
  --motion-lift: 260ms;
  --motion-page: 320ms;
  --motion-media: 520ms;
  --motion-reveal: 760ms;
  --reveal-travel: 20px;
  --ease-control: cubic-bezier(0.28, 0.11, 0.32, 1);
  --ease-lift: cubic-bezier(0.22, 0.61, 0.24, 1);
  --ease-reveal: cubic-bezier(0.16, 1, 0.3, 1);

  /*
    Radius scale — 4px grid, assigned by role so surfaces, controls,
    and labels never share a corner.
      xs   4px   nested marks (brand, tile icon)
      sm   8px   controls (buttons, chips, tabs, menu)
      md  12px   surfaces (cards, tiles, callouts, steps)
      lg  20px   media (hero, large photos)
      pill       labels only (status, tags) — never on controls
      none 0     lists, tables, photos clipped inside cards
  */
  --radius-none: 0;
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 20px;
  --radius-pill: 999px;
  --radius-control: var(--radius-sm);
  --radius-surface: var(--radius-md);
  --radius-media: var(--radius-lg);
  --radius: var(--radius-surface);

  --z-skip: 100;
  --z-header: 50;
  --z-sticky: 40;

  /*
    Three role-specific families, all variable and self-hosted as latin and
    latin-ext WOFF2 subsets. Instrument Sans gives display type a controlled
    width axis; Source Sans 3 keeps dense recruitment copy open and readable;
    Source Serif 4 is reserved for the interview article and nothing else.
    Code on the internal design-system page uses a system mono stack, so no
    fourth webfont is paid for by candidates.
  */
  --display: "Instrument Sans", "Arial Narrow", Arial, sans-serif;
  --sans: "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
    "Hiragino Sans", Arial, sans-serif;
  --serif: "Source Serif 4", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --action: var(--display);
  --label: var(--sans);
  --code: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;

  /*
    Size scale — six static steps for UI furniture, eight fluid steps for
    anything that reads as a headline. The old scale had 23 values including
    0.5px neighbours (14.5 next to 14, 13.5 next to 13).
    --text-h4 is new: the markup was overriding .h3 down to 19px in 34 places,
    which means the gap in the old scale sat between h2 and h3-as-used.
  */
  /* --text-4xs exists for one string: the company link in the footer, whose
     size the brand rule fixes at 10px. Nothing else may reach for it. */
  /* The static steps are set for Source Sans 3, whose x-height (0.486em) sits
     well under Arial's (0.519em): each step reads about a pixel smaller than
     its number. 15px here looks like 14px Arial, so nothing a reader is meant
     to read sits below it. */
  --text-4xs: 10px;
  --text-3xs: 12px;
  --text-2xs: 14px;
  --text-xs: 15px;
  --text-sm: 16px;
  --text-base: 18px;
  --text-md: 19px;
  /* --text-read is the interview's body copy and nothing else. It is the one
     page that is read in sentences from the top rather than scanned, and at
     18px — a size chosen for copy sitting next to UI — a column of it reads as
     fine print on a laptop. It travels with a wider measure, so the line
     length in characters does not grow with the face: see .page-story. */
  --text-read: 20px;

  --text-display: clamp(42px, 5.4vw, 72px);
  --text-h1: clamp(34px, 4.4vw, 54px);
  --text-h2: clamp(26px, 3vw, 36px);
  --text-h3: clamp(20px, 2vw, 26px);
  --text-h4: clamp(19px, 1.6vw, 22px);
  --text-lede: clamp(19px, 1.6vw, 21px);
  --text-quote: clamp(21px, 2.2vw, 28px);

  /* Four shared weights plus two display-only instances. H1 and H2 deliberately
     land between the usual steps so Instrument Sans's weight axis is visible as
     hierarchy rather than behaving like a family of static files. */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-h2: 560;
  --fw-action: 560;
  --fw-semibold: 600;
  --fw-action-hover: 620;
  --fw-h1: 650;
  --fw-bold: 700;

  /* Tracking by role. Instrument Sans supplies width, not optical-size, so the
     headline tracking is deliberate; body copy stays at the face's spacing. */
  --tracking-display: -0.025em;
  --tracking-tight: -0.018em;
  --tracking-snug: -0.01em;
  --tracking-normal: 0;
  --tracking-wide: 0.04em;
  --tracking-caps: 0.08em;
  --tracking-caps-wide: 0.12em;

  --leading-display: 1.05;
  --leading-heading: 1.16;
  --leading-snug: 1.3;
  /* Serif pull-quotes set at 21-36px need more than --leading-snug and less
     than running text. */
  --leading-quote: 1.4;
  --leading-body: 1.6;
  --leading-loose: 1.75;
}

/* Latin ranges are English plus Latin-1 accents; latin-ext covers the Central
   and Eastern European letters that turn up in names. unicode-range keeps the
   ext files off the wire until a page actually needs one. */
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("fonts/instrument-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("fonts/instrument-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-sans-3-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-sans-3-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-serif-4-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
    U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-serif-4-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: 90px; }
html:has(.sticky-apply) { scroll-padding-top: 160px; }

body {
  margin: 0;
  font-family: var(--sans);
  color: var(--ink);
  background: var(--paper);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  /* Source Serif 4 uses its optical-size axis where editorial mode calls it;
     unsupported families simply ignore this property. */
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  /* Keeps a paragraph from ending on a single orphaned word. Headings take
     `balance` instead, further down. */
  text-wrap: pretty;
}

/* `height: auto` is load-bearing, not tidying. Every photograph here carries
   its intrinsic `width`/`height` so the browser can reserve the box before the
   bytes arrive; without this the attribute height survives the width being
   capped and the picture is stretched to it. Slots that crop on purpose
   (`.ph`, `.video`, `.hero-slide`) set their own height and `object-fit`. */
img { max-width: 100%; height: auto; display: block; }

a { color: inherit; }

/* Interactive vs static: cursor is the first affordance.
   Status/tags inherit so they stay default on static cards
   and pointer when nested inside a link (e.g. role rows). */
a,
button,
summary,
.chip,
.btn,
.nav-toggle,
[role="tab"] {
  cursor: pointer;
  /* Drops the 300ms tap delay without disabling pinch zoom. */
  touch-action: manipulation;
}
.card,
.tile,
.process-steps li {
  cursor: default;
}
a.card { cursor: pointer; }

.skip-link {
  position: absolute;
  left: 16px;
  top: 8px;
  z-index: var(--z-skip);
  padding: 10px 16px;
  background: var(--brand-primary);
  color: var(--text-inverse);
  border-radius: var(--radius-control);
  text-decoration: none;
  font-weight: var(--fw-medium);
  transform: translateY(-120%);
}
.skip-link:focus {
  transform: translateY(0);
}

:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 3px;
}

h1, h2, h3, h4, h5 {
  margin: 0;
  font-family: var(--display);
  font-stretch: 96%;
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-heading);
  /* Evens out the last line instead of leaving one word stranded. */
  text-wrap: balance;
}

p { margin: 0; }

/*
  `pretty` on the body only rescues a last line that is a single word, and
  almost none of ours are: the stub is usually two or three words left over
  from a hyphenated compound, and it sits there at a fifth of the column.
  `balance` evens every line instead, which across the ten pages at 390, 768
  and 1024 took the blocks ending on a stub from 142 to 16.

  It is only safe on blocks that stay short. `balance` minimises the longest
  line and has no special regard for the last one, so on running text of seven
  lines or more it reads the stub as cheap and leaves it there — worse than
  what `pretty` does. Those paragraphs are the ones with no class of their own
  (the story body, the working-here examples), so naming the short blocks here
  leaves long-form copy on the body default, which is what it wants.
*/
.lede,
.intro,
.hero-note,
.what,
.card-desc,
.stat-note,
.label,
.small,
.muted,
li,
dd {
  text-wrap: balance;
}

/* ---------- layout ---------- */

.wrap {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.wrap-narrow { max-width: 780px; }

section { position: relative; }

.section { padding: 88px 0; }
.section-sm { padding: 56px 0; }
.section-tint { background: var(--paper-2); }

/* The first shelf under the Stories title block. Both paddings in full put
   144px of empty paper between the lede and `Stories`, while the heading sits
   64px above the cards it heads — so it read as floating in the gap rather
   than as the head of what follows. The other two sections that open straight
   after a title block are tinted, and the change of background absorbs the
   same distance. */
#stories { padding-top: 32px; }
.section-ink { background: var(--ink); color: var(--paper); }

.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

.stack > * + * { margin-top: 16px; }

/* ---------- type ---------- */

.eyebrow {
  font-family: var(--label);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: var(--fw-semibold);
}

/*
  Heading floors are the phone sizes. The old floors were desktop-scale (44 / 36 /
  30px), so at 375px a headline broke every two or three words. Each floor now
  holds until the viewport is wide enough for the fluid term to take over.
*/
.display {
  font-family: var(--display);
  font-stretch: 100%;
  font-size: var(--text-display);
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-display);
  font-weight: var(--fw-h1);
}

.h1 {
  font-family: var(--display);
  font-stretch: 100%;
  font-size: var(--text-h1);
  font-weight: var(--fw-h1);
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-display);
}
.h2 {
  font-family: var(--display);
  font-stretch: 98%;
  font-size: var(--text-h2);
  font-weight: var(--fw-h2);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-heading);
}
.h3 {
  font-family: var(--display);
  font-stretch: 98%;
  font-size: var(--text-h3);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
}
.h4 {
  font-family: var(--display);
  font-stretch: 100%;
  font-size: var(--text-h4);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
}

.lede {
  font-size: var(--text-lede);
  line-height: var(--leading-body);
  color: var(--ink-2);
  max-width: var(--measure-text);
}

.muted { color: var(--ink-3); }
/* A step down from body, for the lines that qualify something else: the note
   under a grid, the label-and-value pair in a meta row, the caption on a
   figure. Not for paragraphs. Source Sans 3 has a small x-height for its em
   — 0.486 against Arial's 0.519 and Hiragino Sans's 0.550 — so 16px here is
   the apparent size of about 15px in Arial, and sentences set in it read as
   fine print on a page whose body is 18px. A paragraph that is the section's
   or the tile's own copy takes body size; this class is what the reader is
   meant to skip. */
.small { font-size: var(--text-sm); }
.tiny { font-size: var(--text-2xs); }

/* Small print — the notes under a heading, under a grid, or under a figure.
   Inside a card or a grid cell the cell is narrower than this, so the cap
   only bites where the note sits in a full column. */
.small.muted { max-width: var(--measure-note); }

.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 36px;
}
.section-head p { max-width: var(--measure-text); }

/* ---------- buttons ---------- */

/* Interaction keeps each control on its 96/98% width instance. Only the
   weight axis moves: its natural sub-pixel advance change gives the requested
   micro-shift without opening the width axis enough to reflow the header. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 22px;
  min-height: 44px;
  border: 1px solid var(--brand-primary);
  border-radius: var(--radius-control);
  background: var(--brand-primary);
  color: var(--text-inverse);
  font-family: var(--action);
  font-stretch: 98%;
  font-size: var(--text-sm);
  font-weight: var(--fw-action);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion) var(--ease-control), border-color var(--motion) var(--ease-control),
    color var(--motion) var(--ease-control), font-weight var(--motion) var(--ease-control),
    transform var(--motion-press) var(--ease-control);
}
.btn:hover {
  background: var(--brand-primary-hover);
  border-color: var(--brand-primary-hover);
  font-weight: var(--fw-action-hover);
}
.btn:focus-visible { font-weight: var(--fw-action-hover); }
.btn:active {
  background: var(--brand-primary-active);
  border-color: var(--brand-primary-active);
  font-weight: var(--fw-action-hover);
}

/*
  Secondary, not faint. This used to be --line on transparent, which measures
  1.23:1 against the canvas: the button had no shape, only a label, and the
  tinted bands had to override it to get one back. It now fills white and
  takes the brand outline everywhere a light surface is under it — the
  treatment the Innovation Cup site uses, where a secondary action is still
  drawn as a button. Dark surfaces invert it further down.
*/
.btn-ghost {
  background: var(--surface);
  color: var(--brand-primary);
  border-color: var(--brand-primary);
}
.btn-ghost:hover { background: var(--brand-soft); border-color: var(--brand-primary-hover); color: var(--brand-primary-hover); }
.btn-ghost:active { background: var(--brand-soft); border-color: var(--brand-primary-active); color: var(--brand-primary-active); }

.btn-accent { background: var(--accent); border-color: var(--accent); color: var(--text-inverse); }
.btn-accent:hover { background: var(--accent-ink); border-color: var(--accent-ink); }
.btn-accent:active { background: var(--brand-primary-active); border-color: var(--brand-primary-active); }

.btn-sm { min-height: 44px; padding: 8px 14px; font-size: var(--text-xs); }

.btn-row { display: flex; flex-wrap: wrap; gap: 12px; }

.link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  color: var(--brand-primary);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  transition: color var(--motion) var(--ease-control), gap var(--motion) var(--ease-control);
}
.link:hover { color: var(--brand-primary-hover); gap: 11px; }

.section-ink .link {
  color: var(--text-inverse);
  border-bottom-color: var(--inverse-line-strong);
}
.section-ink .link:hover {
  color: var(--text-inverse);
  border-bottom-color: var(--text-inverse);
}
/* ---------- tags & status ---------- */

.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-normal);
  color: var(--ink-2);
  background: var(--white);
  white-space: nowrap;
}
.tag-list { display: flex; flex-wrap: wrap; gap: 7px; list-style: none; margin: 0; padding: 0; }

/* Language requirement, shown on every card and row in the same place and
   the same form so a candidate can scan a list and see where they can apply.
   Three levels: English / English + Japanese support / Japanese (Business). */
.tag-lang {
  color: var(--brand-primary);
  border-color: currentColor;
  font-weight: var(--fw-semibold);
}
.tag-lang::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.lang-legend {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px 28px;
  margin: 18px 0 0;
  padding: 18px 0 0;
  border-top: 1px solid var(--line);
}
.lang-legend dt { margin: 0; }
.lang-legend dd {
  margin: 8px 0 0;
  font-size: var(--text-xs);
  color: var(--ink-2);
  max-width: var(--measure-note);
}
@media (max-width: 760px) {
  .lang-legend { grid-template-columns: 1fr; }
}

.status {
  font-family: var(--label);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: var(--text-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  padding: 4px 11px;
  border-radius: var(--radius-pill);
}
.status::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status-open { background: var(--ok-bg); color: var(--ok); }
.status-upcoming { background: var(--warn-bg); color: var(--warn); }
.status-past { background: var(--line-2); color: var(--ink-3); }
/* Not the same state as Past, which is closed and still has a page to read.
   Coming soon has nothing behind it yet, so it reads forward, in brand. */
.status-soon { background: var(--brand-soft); color: var(--brand-primary-hover); }

/* ---------- header ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--backdrop);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap {
  display: flex;
  align-items: center;
  gap: 28px;
  height: 68px;
}

/* The header lockup follows the Innovation Cup site, which cleared brand
   review: the corporate artwork — brand mark, RECRUIT, and the company name
   set under it — then a hairline, then the name of this site. Because the
   artwork is the corporate logo it routes to the corporate site, and the site
   name beside it is what returns here. */
.brand {
  display: flex;
  align-items: center;
  gap: 16px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  font-size: var(--text-base);
  white-space: nowrap;
}
.brand-mark {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  min-height: 44px;
}
/* The artwork's two lines are weighted to the top — RECRUIT is solid, the
   company name under it is small and light — so its optical centre sits below
   the centre of the image box. The nudge puts the wordmark on the divider's
   middle instead of the whole canvas. */
.brand-logo {
  display: block;
  height: 32px;
  width: auto;
  transform: translateY(-3px);
}
/* Larger here than the Innovation Cup header sets it (32px in a header of the
   same height). That size carries the mark and RECRUIT, but leaves the company
   name under them as texture, and the header is the one place on the site that
   names the company. The 68px header is what caps it. */
.site-header .brand-logo {
  height: 40px;
  transform: translateY(-4px);
}
.brand-divider {
  flex: 0 0 auto;
  width: 1px;
  height: 32px;
  background: var(--line);
}
.brand .sub {
  display: flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink-3);
  font-family: var(--action);
  font-stretch: 98%;
  font-weight: var(--fw-action);
  line-height: 1;
  text-decoration: none;
  transition: color var(--motion) var(--ease-control), font-weight var(--motion) var(--ease-control);
}
.brand .sub:hover,
.brand .sub:focus-visible { color: var(--ink); font-weight: var(--fw-action-hover); }

.nav { display: flex; gap: 26px; margin-left: auto; }
.nav a {
  position: relative;
  text-decoration: none;
  font-family: var(--action);
  font-stretch: 96%;
  font-size: var(--text-sm);
  font-weight: var(--fw-action);
  color: var(--ink-2);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  white-space: nowrap;
  border-bottom: 1.5px solid transparent;
  transition: color var(--motion) var(--ease-control), font-weight var(--motion) var(--ease-control);
}
/* The rule used to be the element's own bottom border, which can only fade in.
   A pseudo-element can be wiped in from the centre, which is what reads as a
   nav responding rather than a colour swapping. */
.nav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1.5px;
  height: 1.5px;
  background: var(--ink-3);
  transform: scaleX(0);
  transition: transform var(--motion) var(--ease-control);
}
.nav a:hover,
.nav a:focus-visible { color: var(--ink); font-weight: var(--fw-action-hover); }
.nav a:hover::after { transform: scaleX(1); }
.nav a[aria-current="page"] { color: var(--ink); font-weight: var(--fw-action-hover); }
.nav a[aria-current="page"]::after { background: var(--accent); transform: scaleX(1); }

.header-cta { margin-left: 4px; white-space: nowrap; }

.nav-toggle {
  display: none;
  margin-left: auto;
  background: none;
  border: 1px solid var(--line);
  min-height: 44px;
  border-radius: var(--radius-control);
  padding: 7px 14px;
  font-family: var(--action);
  font-stretch: 96%;
  font-size: var(--text-xs);
  font-weight: var(--fw-action);
  cursor: pointer;
}
.nav-toggle:hover,
.nav-toggle:focus-visible { font-weight: var(--fw-action-hover); }

/* The site name leaves the header lockup once the nav collapses, so the menu
   is where the way home lives. The full nav already has the lockup for it.
   Scoped under .nav so it outranks `.nav a { display: inline-flex }`. */
.nav .nav-home { display: none; }

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 22px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transition: transform var(--motion) var(--ease-control), background-color var(--motion) var(--ease-control);
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  content: "";
  position: absolute;
  left: 0;
}
.nav-toggle-bars::before { top: -7px; }
.nav-toggle-bars::after { top: 7px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background-color: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after { transform: translateY(-7px) rotate(-45deg); }

/* ---------- hero ---------- */

.hero { padding: 76px 0 0; }
/* One sentence per line, set by the markup rather than by the measure. Left to
   wrap, the pair broke "Founder-scale" at its own hyphen, which read as a word
   snapped in half instead of as the second of two claims. The measure only has
   to be wide enough for the longer sentence now. */
.hero .display { max-width: 22ch; }
.hero .display > span { display: block; }
.hero .lede { margin-top: 24px; }
/* Narrower than the rest of the site, and the one place that is still tuned
   to the picture rather than to the column — so it is scoped to the hero that
   has one. This is the line that poked out of the copy column: the headline,
   the note and the link all end by 46% of the hero width and the corner scrim
   is sized to that, and at 60ch the lede ran to 59% and trailed off into the
   clear part of the photograph at 4.2:1. It still sets two lines. The note
   takes the same value so the block under the headline has one right edge
   rather than two near-misses. */
.hero-bleed .lede,
.hero-bleed .hero-note { max-width: var(--measure-hero); }
.hero .btn-row { margin-top: 34px; }

.hero-bleed {
  position: relative;
  min-height: 78vh;
  display: flex;
  align-items: flex-end;
  padding: 0;
  overflow: hidden;
}
.hero-bleed .hero-bg {
  position: absolute;
  inset: 0;
  border-radius: 0;
}

/* Hero photographs. Three 2400px-wide stills (assets/hero/) are stacked,
   cross-faded and slowly pushed in (Ken Burns) in a loop. Each slide is on for
   5s plus a 1.25s overlap with the next, so the cycle is 15s. A slide zooms for
   its whole visible window (fade-in start to fade-out end), so nothing ever
   sits still, and it resets while it is invisible. The first slide fades in
   from the base colour on load, softened further by hero-intro below.
   Positioned above centre because `cover` crops hard on the horizontal at
   phone aspect ratios, and this keeps the room itself in frame, not the floor. */
/* The base shows for the 2.2s the first still fades in over, so it is the dark
   end of the photographs, not paper — otherwise the hero flashes cream and then
   drops into the scrim. */
.hero-bleed { background-color: #2f4755; }
.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: hero-fade 15s linear infinite;
  animation-delay: var(--slide-delay, 0s);
}
.hero-slide:nth-child(1) { --slide-delay: 0s; }
.hero-slide:nth-child(2) { --slide-delay: 5s; }
.hero-slide:nth-child(3) { --slide-delay: 10s; }
.hero-slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  transform-origin: 50% 42%;
  animation: hero-zoom 15s linear infinite;
  animation-delay: var(--slide-delay, 0s);
  will-change: transform;
}
@keyframes hero-fade {
  0%        { opacity: 0; animation-timing-function: ease-in-out; }
  8.3333%   { opacity: 1; }
  33.3333%  { opacity: 1; animation-timing-function: ease-in-out; }
  41.6667%  { opacity: 0; }
  100%      { opacity: 0; }
}
@keyframes hero-zoom {
  0%        { transform: scale(1); }
  41.6667%  { transform: scale(1.09); }
  100%      { transform: scale(1.09); }
}
.hero-bleed .hero-bg::after { display: none; }
.hero-bleed .hero-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--scrim-media);
}
/* Wide enough that the copy is a minority of the frame, so the scrim can pull
   back into the corner it actually needs. */
@media (min-width: 1024px) {
  .hero.hero-bleed { --scrim-media: var(--scrim-media-wide); }
}
.hero-bleed .wrap {
  position: relative;
  z-index: 2;
  padding: 80px 0 72px;
}
/* The copy sits on the dark end of the scrim, so it runs on the inverse scale —
   but only its top two steps. The muted step exists to rank text on a flat ink
   surface; over a photograph it just spends contrast, and spending it forces a
   heavier scrim to win it back. Dropping the note from the muted step to the
   secondary one took it from 6.5:1 to 9.3:1 and let the full-frame scrim come
   down from 0.62 to 0.56, so the stills give up less to the veil. */
.hero-bleed .display { color: var(--text-inverse); }
.hero-bleed .lede { color: var(--text-inverse); }
.hero-bleed .hero-note { color: var(--text-inverse-secondary); }
.hero-bleed .hero-note strong { color: var(--text-inverse); }
.hero-bleed .link { color: var(--text-inverse); border-bottom-color: var(--inverse-line-strong); }
.hero-bleed .link:hover { color: var(--text-inverse); border-bottom-color: var(--text-inverse); }

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0 40px;
  margin-top: 48px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}
.hero-meta div { padding: 8px 0; min-width: 150px; }
.hero-meta dt {
  font-family: var(--label);
  font-size: var(--text-3xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}
/* Figures sit under each other across the row, so the digits have to line up. */
.hero-meta dd {
  margin: 3px 0 0;
  font-size: var(--text-base);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}

/* ---------- photo placeholders ---------- */

/* The fills below are the only literal colours left outside :root, and they are
   meant to stay that way: they stand in for photography that has not been shot
   yet, so they are sample content rather than part of the colour system. Every
   one of them disappears when a real image lands.

   The frame itself outlives the fill. A slot that has its image already keeps
   the same .ph element, drops the label and nests a <picture>, so the size, the
   corner, the inset hairline and the hover all stay with the slot rather than
   being rebuilt per image. The fill stays underneath as the colour the image
   loads over. */

.ph {
  position: relative;
  overflow: hidden;
  background: #36444b;
  border-radius: var(--radius-media);
  display: grid;
  place-items: center;
  color: var(--text-inverse-muted);
}
.ph::after {
  content: "";
  position: absolute; inset: 0;
  border: 1px solid var(--inverse-line);
}
.ph-label {
  position: relative;
  z-index: 1;
  font-size: var(--text-3xs);
  letter-spacing: var(--tracking-caps-wide);
  text-transform: uppercase;
  padding: 0 14px;
  text-align: center;
}
.ph > picture,
.ph > picture img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ph-a { background: #2f4755; }
.ph-b { background: #506773; }
.ph-c { background: #26353d; }
.ph-d { background: var(--builder-night); }

/* Not a stand-in for photography like the fills above: this is the Innovation
   Cup canvas, and the asset's own background is the same value, so the frame
   and the render meet without a seam. */
.ph-cup { background: var(--builder-black); }

/* The one light fill among them. It is here because the mark it carries is
   someone else's and is only drawn for white surfaces: recolouring it to suit
   our dark fills is not ours to do, so the fill moves instead of the mark.
   The hairline and the ink have to come back the other way round for the same
   reason.

   White, which is the surface the mark was drawn for, and the same value the
   card itself is on — so the slot and the body are one surface and the rule
   below the mark is the only thing dividing them. That rule is why this works:
   it is --border-subtle, the value .card takes for its own border, so the
   division reads as part of the card rather than as a box drawn inside it.

   Not --surface-subtle, which is what --paper-2 resolves to: these cards sit
   in a .section-tint band, which is built on that value, and a slot on it
   dissolved into the band and read as a thumbnail that had failed to load. */
.ph-light {
  background: var(--white);
  color: var(--text-primary);
}
/* And only that rule. The hairline on .ph runs all four sides because on a
   dark fill it reads as the inner edge of a photograph; here the fill is the
   card's own white and the slot is flush with the card's padding box, so the
   other three sides land on .card's border in .card's own colour. The frame
   came out 2px beside the slot and 1px beside the body, and the square corners
   ran across the 12px radius — the box this was written to avoid. */
.ph-light::after {
  border-color: transparent;
  border-bottom-color: var(--border-subtle);
}

/* The mark of whatever the card links to, sitting on the slot rather than in
   the body. Where the destination publishes no usable image the mark is all
   the slot has, so it takes the middle of it and the size that holds it. */
.ph-mark {
  position: relative;
  z-index: 1;
  width: 197px;
  height: auto;
}

/* Some destinations do not sign their key visual: the Cup's trophy render
   carries no name at all, and the event site puts the name above the mark
   rather than inside it. So the slot reproduces that lockup — a line of type
   over the mark — instead of the mark alone. The event's own <h1> is built the
   same way, a <span> for the name and the mark below it at the width the name
   takes, which is why the mark here is sized by its container and not by a
   number of its own. Lifting their OG image instead would have brought the
   closed application deadline with it. */
.ph-lockup {
  position: relative;
  z-index: 1;
}
.ph-name {
  display: block;
  font-family: var(--display);
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-display);
  color: var(--text-inverse-secondary);
}
/* The two lines end on the same edge, which is the whole reason the lockup
   reads as one block. That edge is the name's, so the name is the only thing
   allowed to set the width: left at `width: 100%` the mark's own 196px becomes
   the floor the box shrinks to, and the name stops 19px short of it. A zero
   width takes the mark out of that measurement and the minimum puts it back
   afterwards, against the width the name settled on. */
.ph-lockup .ph-mark {
  display: block;
  width: 0;
  min-width: 100%;
  margin-top: 7px;
}
/* The Cup has its own key visual underneath. The lockup moves out of the
   trophy's way rather than sitting on top of it. */
.ph-cup .ph-lockup {
  justify-self: start;
  margin-left: 28px;
}

/* Last resort, when the destination publishes neither an image nor a mark we
   are allowed to serve: one word, standing where the mark would stand and
   taking the width the mark would take. Not `.ph-label`, which is an 11px
   caption naming a photograph nobody has shot yet — this one is the slot's
   content rather than a note about what is missing from it. */
.ph-word {
  position: relative;
  z-index: 1;
  max-width: 197px;
  font-family: var(--display);
  font-stretch: 96%;
  font-size: var(--text-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
  text-align: center;
  text-wrap: balance;
}

/* YouTube films. Nothing from youtube.com loads until the reader asks for it:
   the page ships a local still and swaps in the player on click. That is what
   keeps the block from rendering as YouTube's black "confirm you're not a bot"
   panel, which it does for visitors it does not recognise — shared office IPs,
   VPNs, signed-out sessions. It also keeps a megabyte of third-party script off
   a page where most readers will never press play.

   The element is a real link to the watch page, so it still works with the
   script blocked and the player itself refusing to load. The ratio is held by
   the frame rather than by padding, so the iframe can fill it without a
   wrapper hack. The caption is not decoration: a bare player says nothing
   about what it shows until it has been played. */
.video {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-media);
  overflow: hidden;
  background: var(--ink);
}
.video img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--motion-media) var(--ease-lift), filter var(--motion-media) var(--ease-lift);
}
.video:hover img { transform: scale(1.03); filter: brightness(1.06); }
.video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.video .play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--inverse-surface);
  display: grid;
  place-items: center;
  transition: transform var(--motion-lift) var(--ease-lift);
}
/* A triangle drawn from borders rather than a glyph, so it does not depend on
   which font happens to carry ▶ and does not shift with the text baseline. */
.video .play::before {
  content: "";
  margin-left: 5px;
  border-left: 19px solid var(--ink);
  border-top: 12px solid transparent;
  border-bottom: 12px solid transparent;
}
.video:hover .play { transform: translate(-50%, -50%) scale(1.07); }
.video:active .play { transform: translate(-50%, -50%) scale(0.98); }
/* The still already carries the film's title, burned in by the editor. The
   label is here for the accessible name and for anyone who cannot see it. */
.video .vlabel {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The caption ends on the way out to YouTube, so it ends on the film's right
   edge. The measure that keeps a sentence readable is narrower than the film,
   and it broke the line a word short of the link every time — the last words
   of the sentence sat on a second row with the link beside them. Without the
   measure the caption fits on one row under a full-width film. A film in a
   column is narrower than the caption needs, and there balancing splits the
   sentence down the middle rather than leaving its last word to open a row
   the link then finishes. */
.video-caption {
  margin-top: 12px;
  font-size: var(--text-xs);
  color: var(--ink-3);
  text-align: right;
  text-wrap: balance;
}

.photo-strip {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr;
  gap: 10px;
  margin-top: 64px;
  height: 340px;
}
.photo-strip .ph:nth-child(2),
.photo-strip .ph:nth-child(3) { height: 100%; }
.photo-strip .col { display: grid; grid-template-rows: 1fr 1fr; gap: 10px; }

.photo-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.photo-grid .ph { aspect-ratio: 4 / 3; }
.photo-grid .ph.wide { grid-column: span 2; aspect-ratio: 8 / 3; }

/* ---------- logo bar ---------- */

.logobar {
  display: flex;
  align-items: center;
  gap: 40px;
  flex-wrap: wrap;
  padding: 26px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.logobar .label {
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}
.logo-word {
  font-family: var(--display);
  font-stretch: 96%;
  font-size: var(--text-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  color: var(--ink-2);
  opacity: 0.75;
}
a.logo-word {
  opacity: 1;
  color: var(--brand-primary);
  text-decoration: none;
  border-bottom: 1.5px solid currentColor;
  padding-bottom: 2px;
  transition: color var(--motion) ease, border-color var(--motion) ease;
}
a.logo-word:hover {
  color: var(--brand-primary-hover);
}

/* ---------- cards ---------- */

.grid { display: grid; gap: 20px; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

.card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-surface);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
a.card {
  position: relative;
  color: inherit;
  text-decoration: none;
  transition: transform var(--motion-lift) var(--ease-lift), border-color var(--motion) var(--ease-control),
    background var(--motion) var(--ease-control);
}
/* The lift is scale and translate, never shadow — the proof system has no
   elevation layer. Raising z-index keeps the growing card above its grid
   neighbours instead of being clipped under the next column. */
a.card:hover {
  z-index: 1;
  transform: translateY(-4px) scale(1.015);
  border-color: var(--brand-primary);
}
a.card:active {
  transform: translateY(-1px) scale(0.995);
  background: var(--surface-subtle);
}
a.card:hover .card-title { color: var(--brand-primary-hover); }
a.card:hover .btn {
  background: var(--brand-primary-hover);
  border-color: var(--brand-primary-hover);
}
.card .ph { border-radius: var(--radius-none); height: 200px; }
/* Slower than the card itself, so the image settles after the frame does.
   The placeholders are flat fills, so brightness is what carries the move
   until real photography lands. */
a.card .ph {
  transition: transform var(--motion-media) var(--ease-lift), filter var(--motion-media) var(--ease-lift);
}
a.card:hover .ph { transform: scale(1.05); filter: brightness(1.08) saturate(1.04); }
a.card .ph-label { transition: transform var(--motion-media) var(--ease-lift); }
a.card:hover .ph-label { transform: translateY(-3px); }
.card-body { padding: 22px; display: flex; flex-direction: column; gap: 14px; flex: 1; }
.card-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.card-title {
  font-family: var(--display);
  font-stretch: 98%;
  font-size: var(--text-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
  text-wrap: balance;
  transition: color var(--motion) var(--ease-control);
}
.card-desc { font-size: var(--text-sm); color: var(--ink-2); }
.card-foot {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line-2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* RING businesses keep the common white card and Recruit-blue action. The
   product colour is carried by the tag and by a soft wash that expands on
   hover or keyboard focus — never by a bar across the top of the card. */
#ring .card[data-brand] {
  --item-ink: var(--brand-primary-hover);
  --item-soft: var(--brand-soft);
}
#ring .card[data-brand="study-sapuri"] {
  --item-ink: var(--ring-study-ink);
  --item-soft: var(--ring-study-soft);
}
#ring .card[data-brand="zexy"] {
  --item-ink: var(--ring-zexy-ink);
  --item-soft: var(--ring-zexy-soft);
}
#ring .card[data-brand="alumy"] {
  --item-ink: var(--ring-alumy-ink);
  --item-soft: var(--ring-alumy-soft);
}
#ring .card[data-brand] .card-body {
  position: relative;
  overflow: hidden;
}
#ring .card[data-brand] .card-body::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: 0;
  background: linear-gradient(180deg, var(--item-soft) 0%, transparent 78%);
  opacity: 0;
  transition: opacity var(--motion) var(--ease-control);
  pointer-events: none;
}
#ring .card[data-brand] .card-body > * {
  position: relative;
  z-index: 1;
}
#ring .card[data-brand] .tag {
  color: var(--item-ink);
  border-color: var(--item-ink);
  background: var(--item-soft);
  font-weight: var(--fw-medium);
}
#ring a.card[data-brand]:hover,
#ring a.card[data-brand]:focus-visible {
  border-color: var(--item-ink);
}
#ring a.card[data-brand]:hover .card-title,
#ring a.card[data-brand]:focus-visible .card-title {
  color: var(--item-ink);
}
#ring a.card[data-brand]:hover .card-body::before,
#ring a.card[data-brand]:focus-visible .card-body::before {
  opacity: 1;
}
#ring a.card[data-brand]:active {
  background: var(--white);
  border-color: var(--item-ink);
}
#ring a.card[data-brand]:active .card-title { color: var(--item-ink); }
#ring a.card[data-brand]:active .card-body::before { opacity: 1; }

/* The one thing in Ring that is not a business is a tool the programme runs on,
   and it takes the product cards' vocabulary: an ink for the tag, a wash across
   the surface. What it cannot take is the timing. Those cards are links, so the
   wash waits for a hover; this tile is not, so a wash that waited would never
   arrive. It stands at rest and runs edge to edge, the way the value panels'
   gradients do above — faded out towards a corner it read as a shadow under a
   white card rather than as a surface of its own.
   The pair is the page's own violet into blue, not a fourth Ring colour: the
   tool has no public brand colour to derive one from, and the gradient is doing
   the same work here that it does on Wow the World — saying this block is about
   something being made, not about a product. The chip goes white rather than
   soft so it is still a chip once the surface underneath it is tinted. */
#ring .tile[data-brand="ai-kabeuchi-kun"] {
  --item-ink: var(--opportunity-violet);
  position: relative;
  overflow: hidden;
}
#ring .tile[data-brand="ai-kabeuchi-kun"]::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: 0;
  background: linear-gradient(135deg, var(--opportunity-violet-soft), var(--brand-soft));
  pointer-events: none;
}
#ring .tile[data-brand="ai-kabeuchi-kun"] > * {
  position: relative;
  z-index: 1;
}
#ring .tile[data-brand="ai-kabeuchi-kun"] .tag {
  color: var(--item-ink);
  border-color: var(--item-ink);
  background: var(--white);
  font-weight: var(--fw-medium);
}

.meta-list {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 6px 14px;
  font-size: var(--text-xs);
}
.meta-list dt { color: var(--ink-3); }
.meta-list dd { margin: 0; }

.meta-list dt,
.role-row .col-label {
  font-family: var(--label);
  letter-spacing: var(--tracking-wide);
}

/* A figure list standing on the canvas rather than inside a card. The card
   used to be what held the label and its number together; with the panel
   gone, a rule per row does that job, and the pairs stop floating. The
   label column takes the remaining width because these labels are
   sentences, not the fixed-width keys .meta-list is otherwise used for. */
.meta-list-figures {
  grid-template-columns: 1fr auto;
  gap: 0;
  margin-top: 14px;
}
.meta-list-figures dt,
.meta-list-figures dd {
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
/* Figures sit under each other down the column, so the digits have to line
   up, and they carry the weight the card's frame used to. The number centres
   on its row rather than sitting on the first line, because a label long
   enough to wrap is what sets that row's height. */
.meta-list-figures dd {
  align-self: center;
  text-align: right;
  font-family: var(--sans);
  font-size: var(--text-base);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Builder Signal is deliberately restricted to Hackathon cards, and it lives
   in the artwork rather than on the edge of the card. */
.card-builder .ph { background: var(--builder-night); }
.card-builder .ph-label { color: var(--text-inverse); }
.card-builder .card-title { color: var(--builder-night); }

/* teaser tile */
.tile {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-surface);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* A tile is terminal — nothing links out of it — so the paragraph under its
   heading is not a teaser for a page that carries the full version, the way
   .card-desc is. It is the whole of what the tile has to say, which makes it
   running text, and running text reads at body size. The secondary ink is what
   keeps the heading ahead of it instead.
   Zero specificity on the tile side, so anything the markup says explicitly
   still wins: a tile carrying real small print says .small or .tiny, and that
   is how an unfilled slot standing in a row of live cards (.tile.soon) stays
   as quiet as the cards beside it. */
:where(.tile) p {
  font-size: var(--text-base);
  color: var(--ink-2);
}
/* Coming soon is a state, not a frame. The chip and the sentence under it say
   the slot is not filled yet; the surface never says it. A dashed outline made
   the one slot with nothing behind it the loudest thing in the row and read as
   a card that failed to load, so it is gone: a slot standing beside live cards
   takes the same solid surface as its neighbours (`.tile.soon`), and one
   standing on its own takes no surface at all (`.soon`). Neither ever carries a
   link to itself — there is nowhere to send the reader yet. */
.soon {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.soon > .status { align-self: flex-start; }

/* ---------- locked ---------- */

/*
  The third state, between a live card and .tile.soon. The page behind this
  slot is written and signed off; it is simply not published at launch. The
  copy stays, because hiding finished work tells the reader less than showing
  it, and what goes is the route: the card stops being an <a>, so there is
  nothing to click and nothing to focus. The action it used to carry is left
  in place and muted rather than deleted, because a card whose foot is empty
  reads as broken, while one whose foot is greyed out reads as held back.
*/
.locked {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
/* It was never a control — the card around it was — so it loses its colour
   and keeps its shape. The underline comes off currentColor, so muting the
   text mutes the rule under it too. */
.locked .link {
  color: var(--ink-3);
  cursor: not-allowed;
}
.locked .link:hover { color: var(--ink-3); gap: 7px; }
.locked .link:hover .arw { transform: none; }
.card-locked .card-title { color: var(--ink-2); }

/* A real element, not generated content, so a screen reader reads the state
   straight after the action it qualifies. Only its appearance is conditional. */
.locked-chip { white-space: nowrap; }

/* Where there is a pointer, the chip answers it: it lifts out of the row and
   sits above the action, which is the only direction with room — the card
   clips its own overflow and the foot is its last line. Where there is no
   pointer, which is every touch screen, there is no hover to wait for, so
   the chip stands in the row from the start. */
@media (hover: hover) {
  .locked-chip {
    position: absolute;
    left: 0;
    bottom: calc(100% + 10px);
    pointer-events: none;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--motion) var(--ease-control), transform var(--motion) var(--ease-control);
  }
  .locked:hover .locked-chip,
  .card-locked:hover .locked-chip {
    opacity: 1;
    transform: none;
  }
}

.tile .icon {
  width: 34px; height: 34px;
  border-radius: var(--radius-xs);
  background: var(--brand-soft);
  color: var(--brand-primary-hover);
  display: grid; place-items: center;
  font-family: var(--label);
  font-size: var(--text-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
}

/* The chip takes either a two-letter mark or a line glyph, on the same
   34px square so a row mixing the two keeps one optical size. Stroked and
   never filled: a solid glyph at this size reads as a second mark beside the
   brand logo, and the chip's own colour is what carries the accent. */
.tile .icon svg {
  width: 20px; height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Mark and heading on one line. Above the heading, the chip read as a third
   element in the stack and pushed the title down a row; beside it, it labels
   the heading it belongs to. Centred on the heading's box rather than its
   first line, so a title that wraps to two lines keeps the chip level with
   the block instead of riding up to the top line. */
.tile-head {
  display: flex;
  align-items: center;
  gap: 12px;
}
.tile-head .icon { flex: none; }

/* Headings in a row of tiles rarely all wrap to the same number of lines, and a
   heading that takes two lines where its neighbours take one starts that tile's
   copy a line below the copy beside it. On this grid the tiles take their rows
   from the grid instead of stacking their own, so the heading row is as tall as
   the longest heading in the row and every tile's copy begins on the same line.
   Two conditions: each tile carries the same two parts — the heading, then the
   block under it — and this comes after .tile, which sets the display the rule
   below has to override at the same specificity. The space between the two
   parts is the grid's row gap once the tile is a subgrid, which is why these
   tiles do not keep their own. */
.grid-aligned { grid-template-rows: auto auto; }
.grid-aligned > * { display: grid; grid-template-rows: subgrid; grid-row: span 2; }

/* ---------- value columns ---------- */

/* The vision is the one block on About addressed to no column. It stands
   between the film and the three values and belongs to both, so it is centred
   on the section rather than set against its left edge like the headings that
   open a column of text. The sentence keeps the text measure and is centred
   in the band with it: a centred heading over a full-width paragraph reads as
   two blocks that happen to touch. */
.vision-head { text-align: center; }
.vision-head .h2 { margin-top: 10px; }
.vision-head .lede { margin-top: 16px; margin-inline: auto; }

/* The rule over each column is a divider between three things standing side by
   side, at the same 1px the rest of the page divides with. At 2px of ink it
   was a bar on top of a block, which is the one thing it must not read as. */
.values { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; }
.values > div {
  padding: 34px 32px 34px 0;
  border-top: 1px solid var(--line);
  margin-right: 32px;
}
.values > div:last-child { margin-right: 0; padding-right: 0; }
.values h3 { margin-bottom: 10px; }
.values p { font-size: var(--text-sm); color: var(--ink-2); }
.section-ink .values > div { border-color: var(--on-ink-border); }
.section-ink .values p { color: var(--on-ink-secondary); }

/* Three values, one layout language. The gradient sits behind the heading
   rather than turning a static value into something that resembles a link.
   Explicit modifiers keep the meaning stable if the values are reordered. */
.values-opportunity {
  --value-ink: var(--brand-primary-hover);
  --value-from: var(--brand-soft);
  --value-to: var(--surface-subtle);
  gap: 20px;
}
.values-opportunity > .value-panel,
.values-opportunity > .value-panel:last-child {
  margin: 0;
  padding: 0;
  border: 0;
}
.values-opportunity > .value-panel.value-wow {
  --value-ink: var(--opportunity-violet);
  --value-from: var(--brand-soft);
  --value-to: var(--opportunity-violet-soft);
}
.values-opportunity > .value-panel.value-passion {
  --value-ink: var(--opportunity-coral);
  --value-from: var(--opportunity-coral-soft);
  --value-to: var(--opportunity-amber-soft);
}
.values-opportunity > .value-panel.value-social {
  --value-ink: var(--opportunity-teal);
  --value-from: var(--opportunity-cyan-soft);
  --value-to: var(--opportunity-teal-soft);
}
.values-opportunity .value-panel { --motion-reveal: var(--motion-page); }
/* Centred in the panel, and the sentence under it centred to the same axis.
   Bottom-left put the heading in the corner of a shape that has nothing else
   in it, so the panel read as a container missing its contents. */
.values-opportunity .value-panel h3 {
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 112px;
  margin-bottom: 18px;
  padding: 24px;
  color: var(--value-ink);
  text-align: center;
}
/* Centred text has no straight edge to carry a ragged break, so the one
   sentence long enough to wrap left three words alone on its second row.
   Balancing splits it down the middle instead. */
.values-opportunity .value-panel p { text-align: center; text-wrap: balance; }
.values-opportunity .value-panel h3::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: var(--radius-surface);
  background: linear-gradient(135deg, var(--value-from), var(--value-to));
  opacity: 1;
  transform: none;
  transform-origin: 50% 100%;
  transition: opacity var(--motion) var(--ease-control), transform var(--motion-page) var(--ease-reveal);
  transition-delay: var(--reveal-delay, 0ms);
}
.values-opportunity > [data-reveal].value-panel h3::before {
  opacity: 0;
  transform: translateY(8px) scale(0.97);
}
.values-opportunity > [data-reveal].value-panel.is-revealed h3::before {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .values-opportunity > [data-reveal].value-panel h3::before {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ---------- filters / tabs ---------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-bottom: 32px;
}
.filters .flabel {
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-right: 4px;
}
/* Separates a second filter group from the previous group's chips.
   Kept in the stylesheet rather than inline so the phone layer can
   reset it when each group moves onto its own row. */
.filters .flabel:not(:first-child) { margin-left: 20px; }
.chip {
  border: 1px solid var(--line);
  background: var(--white);
  min-height: 44px;
  border-radius: var(--radius-control);
  padding: 7px 15px;
  font-family: var(--action);
  font-stretch: 98%;
  font-size: var(--text-xs);
  font-weight: var(--fw-action);
  cursor: pointer;
  color: var(--ink-2);
  transition: background var(--motion) var(--ease-control), border-color var(--motion) var(--ease-control),
    color var(--motion) var(--ease-control), font-weight var(--motion) var(--ease-control),
    transform var(--motion-press) var(--ease-control);
}
.chip:hover {
  border-color: var(--brand-primary);
  background: var(--brand-soft);
  color: var(--brand-primary-hover);
  font-weight: var(--fw-action-hover);
}
.chip:focus-visible { font-weight: var(--fw-action-hover); }
.chip[aria-pressed="true"],
.chip[aria-selected="true"] {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: var(--text-inverse);
  font-weight: var(--fw-action-hover);
}
.chip[aria-pressed="true"]:hover,
.chip[aria-selected="true"]:hover {
  background: var(--brand-primary-hover);
  border-color: var(--brand-primary-hover);
  color: var(--text-inverse);
}
.chip:active { background: var(--brand-soft); transform: scale(0.96); }
.filters .spacer { margin-left: auto; }
.result-count { font-size: var(--text-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }

.is-hidden { display: none !important; }

/* What the filters show when nothing matches. Solid, for the same reason the
   coming soon slots are: a dashed frame reads as a panel that failed rather
   than as an answer to the query. */
.empty-state {
  padding: 60px 20px;
  text-align: center;
  color: var(--ink-3);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-surface);
}

/* ---------- detail page ---------- */

.breadcrumb {
  display: flex;
  gap: 8px;
  font-size: var(--text-xs);
  color: var(--ink-3);
  padding: 20px 0 0;
}
.breadcrumb a {
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  color: var(--brand-primary);
}
.breadcrumb a:hover { color: var(--brand-primary-hover); }

.detail-hero { padding: 28px 0 0; }
.detail-hero .h1 { max-width: 18ch; margin-top: 16px; }

.page-role-detail {
  --motion-reveal: 360ms;
  --reveal-travel: 12px;
}
.page-role-detail .header-cta { display: none; }

.role-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

/* A ruled snapshot gives the six decision-making facts a stable reading order
   without turning every value into a separate card. */
.page-role-detail .hero-meta {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  margin-top: 40px;
  padding: 1px;
  border: 0;
  background: var(--border-strong);
}
.page-role-detail .hero-meta div {
  min-width: 0;
  padding: 16px 18px;
  background: var(--paper);
}
.page-role-detail .hero-meta dt { font-size: var(--text-2xs); }
.page-role-detail .hero-meta dd { margin-top: 6px; }

.sticky-apply {
  position: sticky;
  top: 68px;
  z-index: var(--z-sticky);
  background: var(--backdrop);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  transition: opacity var(--motion) var(--ease-control),
    transform var(--motion) var(--ease-control),
    visibility 0s linear 0s;
}
.sticky-apply .wrap {
  display: flex;
  align-items: center;
  gap: 18px;
  padding-top: 12px;
  padding-bottom: 12px;
  flex-wrap: wrap;
}
.sticky-apply .deadline { font-size: var(--text-xs); color: var(--ink-2); font-variant-numeric: tabular-nums; }
.sticky-apply .deadline-mobile { display: none; }
.sticky-apply .btn { margin-left: auto; }
.page-role-detail .sticky-apply.is-suppressed,
.page-role-detail .site-header.open + main .sticky-apply {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-8px);
  transition-delay: 0s, 0s, var(--motion);
}

.role-content-heading { margin-top: 28px; }
.role-support-note { margin-top: 20px; }
.role-support-callout { margin-top: 24px; }

.prose { max-width: var(--measure-text); }
.prose p + p { margin-top: 16px; }
.prose ul { padding-left: 20px; margin: 14px 0 0; color: var(--ink-2); }
.prose li + li { margin-top: 8px; }

.split { display: grid; grid-template-columns: 260px 1fr; gap: 48px; align-items: start; }
.split h2 { position: sticky; top: 130px; }
html:has(.sticky-apply) .split h2 { top: 160px; }

/* timeline */
.timeline { border-top: 1px solid var(--line); }
.timeline-row {
  display: grid;
  grid-template-columns: 170px 1fr;
  gap: 24px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}
/* Dates stack down the left column, so the digits are set tabular. */
.timeline-row .when {
  font-family: var(--label);
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary-hover);
}
.timeline-row .what { color: var(--ink-2); font-size: var(--text-sm); }

/* ---------- employee story ----------
   One layout for every interview, and one column inside it. What changes per
   member is the copy — how many headings there are, what they say, how long
   each section runs, how many rows the career list holds — so none of that is
   expressed here. See the README. */

/* One column, and nothing wider than it.

   The article is a single rectangle: the breadcrumb, the title, the profile,
   every paragraph, every heading, the career list and all three photographs
   share a left edge and a right edge, at every width, with no exceptions and
   nothing to switch at a breakpoint. The container is the measure plus the
   gutters, so there is one number in play and no second width for anything to
   disagree with.

   Three versions of this page had something wider than the writing, and all
   three read as jagged. The body indented 152px under a full-width title put
   two left edges 168px apart, and since the indent was fixed and the
   container was not, the space right of the body fell from 168px to 68px
   between a 1040px window and a 940px one, then snapped to zero and threw the
   body to the far left. Centring the writing with the photographs bleeding
   past it equally on both sides only moved the problem: the title then
   stepped 160px in from the breadcrumb above it and the photograph below it,
   and 160px of empty paper beside a headline is not a margin, it is a missing
   element. Hanging the writing off the photographs' left edge instead left a
   third of the window empty down the right of every paragraph.

   The photographs are smaller for it. That is the price of the alignment, and
   it is worth paying: at the column's width each one is still the widest
   thing on its screen, the 16:9 the handoff asks for, and uncropped. */
.wrap-article { max-width: calc(var(--measure-text) + var(--gutter) * 2); }

/* Name, then role, then university. No heading over it and no second copy of
   it further down: the three lines are the whole profile. */
.story-profile { margin-top: 22px; }
.story-profile .name { font-weight: var(--fw-semibold); color: var(--ink); }
.story-profile p { font-size: var(--text-sm); color: var(--ink-3); }
.story-profile .name + p { margin-top: 8px; }
.story-profile p + p { margin-top: 3px; }

/* Not a .ph: that frame exists to hold a slot open for photography nobody has
   shot. This photograph is here, and its intrinsic size is on the <img>, so
   there is nothing to reserve. */
.story-figure { margin: 36px 0 0; }
.story-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-media);
}

/* Two sizes of vertical break and no more. The opening photograph to the
   writing, and one section to the next, are the same kind of break and take
   the same 80px; 72px against 80px only read as one of them having been
   mistyped. The space is also the whole of the section break — it carries
   what the 28px mark over each heading used to: the headings are the member's
   own wording and vary in number, so a mark per section added a decoration
   per section without adding information. */
.story-body { margin-top: 80px; }
.story-section + .story-section { margin-top: 80px; }

.story-section > h2 + * { margin-top: 20px; }

/* 写真C, the member's own photograph, dropped beside the episode it belongs to.
   It sits in the prose, between the two paragraphs it belongs between, and
   takes the column like everything else. Only its spacing is its own: it has
   writing under it as well as over it, where the other two do not. */
.prose > .story-figure { margin: 32px 0; }

/* The frame that closes the article. It is the one break that is bigger
   than the others — at the same 80px as a section break it read as an
   illustration belonging to the last section rather than as the end of the
   piece. */
.story-closing { margin-top: 112px; }

/* The career list is the plain .timeline, which is a top rule and a row per
   entry — it reads the same with one row as with five, and some members have
   only one. The date column stays at the shared 170px: the longest range here
   sets 110px, and widening it to 220px only opened a hole in the middle of the
   row once the list came in to the text measure. */
.story-career { margin-top: 24px; }

/* steps */
.steps { counter-reset: step; display: grid; gap: 2px; }
.step {
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 18px;
  padding: 20px 0;
  border-bottom: 1px solid var(--line);
  align-items: start;
}
.step::before {
  counter-increment: step;
  content: "0" counter(step);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  color: var(--accent);
  letter-spacing: var(--tracking-wide);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}
.step p { font-size: var(--text-sm); color: var(--ink-2); margin-top: 4px; }

/* accordion */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  list-style: none;
  cursor: pointer;
  padding: 20px 40px 20px 0;
  font-size: var(--text-md);
  font-weight: var(--fw-medium);
  position: relative;
  color: var(--ink);
  transition: color var(--motion) ease;
}
.faq summary:hover { color: var(--brand-primary); }
.faq summary::-webkit-details-marker { display: none; }
/* The marker used to swap the glyph from + to –, and `content` cannot be
   transitioned, so opening a row happened in one frame. Rotating the same plus
   onto its diagonal reads as the row turning over, and it animates. */
.faq summary::after {
  content: "+";
  position: absolute;
  right: 6px; top: 50%;
  transform: translateY(-50%) rotate(0deg);
  font-size: var(--text-md);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
  transition: transform var(--motion-lift) var(--ease-lift), color var(--motion) var(--ease-control);
}
.faq summary:hover::after { color: var(--brand-primary); }
.faq details[open] summary::after { transform: translateY(-50%) rotate(135deg); }

/* Height animation for the panel. `::details-content` plus interpolate-size is
   Chromium-only for now; everywhere else the panel still opens, just instantly,
   which is the same behaviour this file had before. */
@supports selector(::details-content) {
  .faq details { interpolate-size: allow-keywords; }
  .faq details::details-content {
    block-size: 0;
    overflow: clip;
    opacity: 0;
    transition: block-size var(--motion-lift) var(--ease-lift), opacity var(--motion-lift) var(--ease-control),
      content-visibility var(--motion-lift) allow-discrete;
  }
  .faq details[open]::details-content { block-size: auto; opacity: 1; }
}
.faq .answer { padding: 0 60px 22px 0; color: var(--ink-2); font-size: var(--text-base); }

/* quote */
/* The size, the hanging quote mark and the attribution under it are what mark
   this as quoted speech. The body face is the one it is set in: the serif is
   the interview article's voice and does not travel to a pull-quote dropped
   into a recruitment page. There is no rule down the left either — it would be
   a second, louder claim to the same thing, in a colour the page uses for
   links. */
.quote blockquote {
  margin: 0;
  font-size: var(--text-quote);
  line-height: var(--leading-quote);
  letter-spacing: var(--tracking-snug);
  text-wrap: balance;
  /* Pulls the opening quote mark into the margin so the first line starts on
     the same optical edge as the lines under it. Safari only; harmless where
     it is not supported. */
  hanging-punctuation: first;
}
.quote figcaption { margin-top: 16px; font-size: var(--text-xs); color: var(--ink-3); }

/* support rail */
.rail {
  position: sticky;
  top: 110px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--text-sm);
}
.rail a {
  --rail-indent: 14px;
  text-decoration: none;
  color: var(--ink-3);
  padding: 10px 12px 10px var(--rail-indent);
  min-height: 44px;
  display: flex;
  align-items: center;
  border-left: 2px solid var(--line);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  transition: color var(--motion) var(--ease-control), border-color var(--motion) var(--ease-control),
    background var(--motion) var(--ease-control), padding-left var(--motion) var(--ease-control);
}
/* Indents instead of translating, so the 2px rule the rail is built on stays
   where it is while the label steps away from it. */
.rail a:hover { color: var(--ink); border-color: var(--ink-3); background: var(--surface-subtle); padding-left: calc(var(--rail-indent) + 5px); }
.rail a.active { color: var(--ink); border-color: var(--accent); font-weight: var(--fw-medium); background: var(--brand-soft); }

/* Two levels, one rule: the 2px line stays at a single x so the rail still
   reads as one column, and the level shows in the label indent and weight
   rather than in a second, competing vertical line. */
.rail a.rail-group { color: var(--ink-2); font-weight: var(--fw-medium); }
.rail a.rail-group + .rail-sub { margin-top: 4px; }
.rail a.rail-sub { --rail-indent: 28px; }
.rail a.rail-sub + .rail-group { margin-top: 14px; }

.callout {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-surface);
  padding: 18px 20px;
  font-size: var(--text-sm);
  color: var(--ink-2);
}

/* table */
.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.table th, .table td { text-align: left; padding: 14px 16px 14px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.table td { font-variant-numeric: tabular-nums; }
.table th {
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: var(--fw-semibold);
}

/* role rows */
.role-row {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr auto;
  gap: 24px;
  align-items: center;
  padding: 20px 16px;
  margin: 0 -16px;
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-surface);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion) var(--ease-control);
}
.role-row:first-child { border-top: 1px solid var(--line); }
.role-row:hover { background: var(--surface-subtle); }
.role-row:hover .role-title { color: var(--brand-primary); }
.role-row:hover .btn-ghost {
  background: var(--brand-soft);
  border-color: var(--brand-primary);
  color: var(--brand-primary-hover);
}
.role-title {
  font-family: var(--display);
  font-stretch: 98%;
  font-size: var(--text-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
  margin: 0;
  transition: color var(--motion) ease;
}
.role-row .col-label { font-size: var(--text-3xs); letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--ink-3); }
.role-row .col-val { font-size: var(--text-sm); color: var(--ink-2); }

/* cta band */
.cta-band { padding: 84px 0; }
.cta-band .h2 { max-width: 20ch; }
.cta-band .inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  flex-wrap: wrap;
}
.page-role-detail .cta-band {
  padding: 72px 0;
  background: var(--surface-ink-raised);
  border-bottom: 1px solid var(--on-ink-border);
}
.page-role-detail .cta-kicker { color: var(--on-ink-secondary); }
.page-role-detail .cta-heading { margin-top: 12px; }
/* On ink the fill has to come back off, or the white face the light surfaces
   rely on turns the secondary into the loudest thing on the band. */
.section-ink .btn-ghost { background: transparent; color: var(--paper); border-color: var(--inverse-line-strong); }
.section-ink .btn-ghost:hover { background: var(--inverse-wash); border-color: var(--text-inverse); color: var(--text-inverse); }
.section-ink .btn:not(.btn-ghost) { background: var(--paper); color: var(--ink); border-color: var(--paper); }

/* ---------- Editorial mode: the interview article only ----------
   The serif is one member talking, so it reaches exactly as far as their
   article does — the title and the headings they gave their own story. It used
   to run on the Stories index too, where it set the card titles of research
   output (Megagon Labs, RecSys Challenge) in the voice of a person.

   Instrument Sans's axes are off here: there is no width axis to turn, and the
   tracking the display face is set at (-0.025em) closes a serif up until the
   title reads as grey. Weight does the work instead — 600 against the body's
   400, which is what a title needs from a face this fine. */

/* The reading page. 42rem of 17px was already running to around 85 characters
   a line, which is past the far end of what a line should be, so most of the
   answer to a column that looks small on a laptop is a bigger face rather
   than a longer line. 46rem of --text-read is 64px wider on the screen and
   still a shorter line to read than what it replaces. The measure moves with
   the size because the piece is one column — every element in the article
   takes this one number, so widening it widens all of them together and
   nothing comes out of alignment. */
.page-story {
  --measure-text: 46rem;
}
.page-story .prose {
  font-size: var(--text-read);
  line-height: var(--leading-loose);
}
/* The gap between paragraphs has to clear the line spacing it sits in. At the
   35px leading this face now has, 16px reads as a slightly wide line break
   rather than as a new paragraph. */
.page-story .prose p + p { margin-top: 24px; }
/* The career list is scanned rather than read, so it stays below the reading
   size — but at the UI steps it sat 6px under the body and read as a footnote
   attached to the article instead of a part of it. */
.page-story .timeline-row .when { font-size: var(--text-sm); }
.page-story .timeline-row .what { font-size: var(--text-base); }

.page-story h1,
.page-story .story-section h2 {
  font-family: var(--serif);
  font-stretch: normal;
}

.page-story h1 {
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-display);
}

.page-story .story-section h2 {
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-normal);
}

/* ---------- footer ---------- */

.site-footer {
  background: var(--ink);
  color: var(--on-ink-secondary);
  padding: 64px 0 32px;
  font-size: var(--text-sm);
}
.site-footer .cols { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 32px; }
.site-footer h4,
.footer-heading {
  color: var(--text-inverse);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps-wide);
  text-transform: uppercase;
  margin: 0 0 14px;
  font-weight: var(--fw-semibold);
}
.site-footer a { color: var(--on-ink-secondary); text-decoration: none; display: flex; align-items: center; min-height: 44px; padding: 5px 0; border-bottom: 1px solid transparent; }
.site-footer a:hover { color: var(--text-inverse); border-bottom-color: var(--inverse-line-strong); }
/* The corporate lockup follows the Innovation Cup site, which cleared brand
   review: the mark carries the company name beside it, baseline-aligned on the
   wordmark rather than centred on the image box, and the corporate site is a
   text link directly beneath. Both strings are set in Meiryo, which is the
   company name's prescribed face and intentionally sits outside the site's
   shared typography system. */
.footer-brand {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
}
.footer-corp,
.site-footer a.footer-corp-link {
  font-family: Meiryo, "メイリオ", sans-serif;
  line-height: 1;
}
.footer-corp {
  font-size: var(--text-xs);
  color: var(--on-ink-secondary);
}
/* Two selectors' worth of specificity, so the generic `.site-footer a` rule
   above cannot reimpose its own colour, size, weight or underline on this link.
   Brand sets the company name in full white on ink, not the muted grey. The
   44px target is kept in padding rather than in type, so the 10px the brand
   rule sets for the company link stays as it is. */
.site-footer a.footer-corp-link {
  display: inline-flex;
  color: var(--text-inverse);
  font-size: var(--text-4xs);
  font-weight: var(--fw-bold);
  text-decoration: underline;
  text-underline-offset: 2px;
  border-bottom: 0;
}
.site-footer a.footer-corp-link:hover {
  color: var(--text-inverse);
  border-bottom-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ---------- IA v0.2 section types ---------- */

.do-grid { margin-top: 28px; }

/* ---------- PRD v0.3 section types ---------- */

/* The one hero control that still needs a surface of its own. A hairline on
   paper is enough over flat canvas, but over photography the border alone
   disappears into whatever the picture is doing behind it, so the ghost button
   takes the same translucent white that labels sitting on media use. */
/* Outlined, not a white slab: on the dark bed a filled pill reads as loud as
   the brand-blue primary beside it and the pair stops having an order. */
.hero-bleed .btn-ghost { background: transparent; color: var(--text-inverse); border-color: var(--inverse-line-strong); }
.hero-bleed .btn-ghost:hover { background: var(--inverse-wash); border-color: var(--text-inverse); color: var(--text-inverse); }

/* External-destination marker. Research and IR content stays on owned sites. */
.ext {
  font-family: var(--label);
  font-size: var(--text-3xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}
a[target="_blank"] .arw { transform: rotate(-45deg); }

.subsection { margin-top: 64px; }
.subsection > .eyebrow + .h3 { margin-top: 8px; }
/* :where() so this does not outrank the role of the paragraph. A note set in
   .small.muted here is still small print and still takes the note measure;
   without the zeroed specificity this selector handed it the wider one.
   .h4 is in because Ring's sub-blocks head their copy with one: without it the
   paragraph under those headings was the only body column on the site running
   the full width of the wrap. */
.subsection > :where(.h3 + p, .h4 + p) { margin-top: 12px; max-width: var(--measure-text); }

/*
  A subsection inside a subsection — Ring's own sub-blocks. The scale has
  nothing between .h3 and .h4, so a heading this deep can only repeat its
  parent's size or land on the cards underneath it. A rule takes over the
  break, which frees the heading to drop to .h4 where its rank puts it.
*/
.subsection .subsection {
  margin-top: 48px;
  padding-top: 40px;
  border-top: 1px solid var(--line);
}

/* stat tiles */
/* auto-fit so a four- or five-stat set fills the row without an empty cell */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius-surface); overflow: hidden; }
.stat-grid > div { background: var(--white); padding: 26px 22px; }
.stat-grid .figure {
  font-family: var(--display);
  font-stretch: 100%;
  font-size: var(--text-h2);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-display);
  font-variant-numeric: tabular-nums;
}
.stat-grid .label { margin-top: 10px; font-size: var(--text-xs); color: var(--ink-2); }
.stat-note { margin-top: 16px; font-size: var(--text-2xs); color: var(--ink-3); max-width: var(--measure-note); }

/* brand word list */
.brand-list { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: auto auto auto; column-gap: 1px; row-gap: 0; background: var(--line); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/* The cells take their rows from the list rather than stacking their own, so a
   unit name that wraps to two lines lengthens that row in every cell instead of
   pushing its own brands and description out of line with the ones beside it.
   The row gap has to stay at 0 for this: the gaps in this grid are the rules,
   and a row gap would draw them through the middle of each cell. There is only
   ever one row of cells at this width, so the rules between cells are the
   column gaps and the band's own top and bottom borders. */
.brand-list > div { background: var(--paper); padding: 22px 24px; display: grid; grid-template-rows: subgrid; grid-row: span 3; }
/* The outer two cells drop their outside padding so the band's own edges stay
   on the content column — the gutter is there to hold the text off the rules
   between cells, and the band has no rule of its own to clear on those sides. */
.brand-list > div:first-child { padding-left: 0; }
.brand-list > div:last-child { padding-right: 0; }
.brand-list .name {
  font-family: var(--display);
  font-stretch: 96%;
  font-size: var(--text-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
}
.brand-list .what { margin-top: 10px; font-size: var(--text-sm); color: var(--ink-2); max-width: var(--measure-note); }

/* The rules in this grid are 1px gaps showing the background through, so a row
   that is short of cells shows a band of gap colour instead of paper. Three
   cells divide evenly at three columns and at one, so the fix for this list is
   to skip the two-column step rather than to add a filler cell — see the 940px
   query below. */

/* Both groups in this section are a label in a gutter plus the content beside
   it, and they share this grid so neither looks like an annotation on the
   other. The label column is sized in ch so it holds the longest of them
   ("Emerging technology") on two lines without a fixed pixel width. */
.brand-row {
  display: grid;
  grid-template-columns: 14ch minmax(0, 1fr);
  gap: 24px;
}

/* Megagon Labs is a research organization, not one of the business units. It
   gets its own labelled band under the grid rather than a cell inside it, so it
   never reads as a sixth commercial brand. The margin is on this class rather
   than on .brand-row because About places the same band under a tile grid. */
.brand-aside { margin-top: 30px; }
.brand-aside .name {
  font-family: var(--display);
  font-stretch: 96%;
  font-size: var(--text-h4);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
}
.brand-aside .what { margin-top: 6px; font-size: var(--text-sm); color: var(--ink-2); max-width: var(--measure-note); }

/* ---------- stay connected ---------- */

.stay-connected .section-head { align-items: center; margin-bottom: 0; }

/* The mark is the official LinkedIn bug in LinkedIn Blue, which
   brand.linkedin.com/policies does not allow us to recolour. That fixes the
   button to a light surface — on the brand-blue fill the two blues collide. */
.btn-social {
  background: var(--white);
  border-color: var(--border-strong);
  color: var(--text-primary);
}
.btn-social:hover { background: var(--white); border-color: var(--text-primary); color: var(--text-primary); }
.btn-social:active { background: var(--surface-subtle); border-color: var(--text-primary); color: var(--text-primary); }
/* Sized against the label, which wraps to two lines in this button: at the
   16px of an icon beside one line of text the bug read as a bullet point.
   28px fits inside the two-line content box, so the button does not grow. */
.social-mark { flex: 0 0 auto; fill: var(--linkedin-blue); }

/* Reinvention timeline. One column read top to bottom: a rail runs down the
   left and every era sits on a marker, so the sequence holds at any width. */
.timeline-h {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-text);
}
.timeline-h > li {
  position: relative;
  padding: 0 0 36px 34px;
}
.timeline-h > li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--line);
}
/* The rail runs from the first marker to the last, not past either of them. */
.timeline-h > li:first-child::before { top: 10px; }
.timeline-h > li:last-child { padding-bottom: 0; }
.timeline-h > li:last-child::before { bottom: auto; height: 10px; }
.timeline-h > li::after {
  content: "";
  position: absolute;
  left: 0;
  top: 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--brand-primary-hover);
}
/* The era label is the heading of its entry, so it sits a step above the
   prose beneath it rather than below it — at 13px against 14px body it read
   as a caption on the rail instead of the thing the entry is about. */
.timeline-h .when {
  font-family: var(--label);
  font-size: var(--text-base);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary-hover);
}
.timeline-h .what { margin-top: 10px; font-size: var(--text-sm); color: var(--ink-2); }
.timeline-h .link { margin-top: 14px; }

/* support: two named patterns */
/* The rule divides the two patterns, so it stays at the page's hairline rather
   than the 2px of ink it used to be. */
.support-pattern { border-top: 1px solid var(--line); padding-top: 28px; }
/*
  The rail is the pinned element in this .split, and the pattern titles sit in
  the scrolling column, so leaving .split's sticky h2 in place slides each
  title down over its own body copy.
*/
.support-pattern h2 { position: static; }
.support-pattern + .support-pattern { margin-top: 72px; }
/* The 60px inset, not the 68ch measure every other body column uses: this
   paragraph sits directly under the pattern rule and above the accordion, and
   68ch stopped it 160px short of both, so the column read as three different
   right edges. Matching the answer panels gives the column one. */
.support-pattern .intro { padding-right: 60px; margin-top: 14px; }

@media (max-width: 940px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  /* Three cells into two columns leaves the last row half empty, and the gaps
     in this grid are the rules, so the hole shows as a band of rule colour.
     Below three columns this list goes straight to one. Stacked cells follow
     each other instead of sharing a row, so they drop the subgrid and take the
     1px row gap back as the rule between them. */
  .brand-list { grid-template-columns: 1fr; grid-template-rows: none; row-gap: 1px; }
  /* One column leaves no rule beside the text, so the horizontal gutter has
     nothing to clear and every cell sits flush on the content column. */
  .brand-list > div { display: block; grid-row: auto; padding-left: 0; padding-right: 0; }
}
@media (max-width: 720px) {
  .stat-grid { grid-template-columns: 1fr; }
  .subsection { margin-top: 48px; }
  .timeline-h > li { padding-bottom: 28px; }
}

.status-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 28px;
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  font-size: var(--text-sm);
  color: var(--ink-2);
}
.status-legend li { display: flex; align-items: center; gap: 10px; }

.process-steps {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 16px;
  counter-reset: step;
}
.process-steps li {
  list-style: none;
  margin: 0;
  padding: 18px;
  border: 1px solid var(--line);
  background: var(--white);
  border-radius: var(--radius-surface);
  cursor: default;
}
.process-steps li::before {
  counter-increment: step;
  content: counter(step);
  display: block;
  font-family: var(--label);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary);
  margin-bottom: 8px;
}
.process-steps { padding: 0; margin: 20px 0 0; }

@media (max-width: 940px) {
  .process-steps { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 720px) {
  .process-steps { grid-template-columns: 1fr; }
}

/* ---------- responsive ---------- */

@media (max-width: 940px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .do-grid { grid-template-columns: 1fr; }
  .values { grid-template-columns: 1fr; }
  .values > div { margin-right: 0; padding: 24px 0; }
  .values-opportunity {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px;
  }
  .values-opportunity > .value-panel,
  .values-opportunity > .value-panel:last-child { padding: 0; }
  .values-opportunity .value-panel h3 { min-height: 120px; padding: 20px; }
  .split { grid-template-columns: 1fr; gap: 20px; }
  .split h2 { position: static; }
  .rail {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
  }
  /* Laid out as a row, the rail has no column for an indent to read against,
     so the two levels fall back to weight alone. */
  .rail a {
    --rail-indent: 14px;
    border-left: 0;
    border-bottom: 2px solid var(--line);
    border-radius: var(--radius-control);
    padding: 10px 14px;
  }
  .rail a.rail-group + .rail-sub,
  .rail a.rail-sub + .rail-group { margin-top: 0; }
  .site-footer .cols { grid-template-columns: 1fr 1fr; }
  .photo-strip { grid-template-columns: 1fr 1fr; height: auto; }
  .photo-strip > .ph:first-child { aspect-ratio: 4/3; }
  .photo-strip .col { grid-template-rows: auto auto; }
  .photo-strip .col .ph { aspect-ratio: 4/3; }
  .role-row { grid-template-columns: 1fr 1fr; }
}

/* The full navigation stops fitting before the phone layout begins. Keep the
   content breakpoints intact and switch only the header at the measured edge. */
@media (max-width: 1040px) {
  .nav, .header-cta { display: none; }
  .nav-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-right: -11px;
    padding: 0;
    border: 0;
    color: var(--ink);
    font-family: var(--action);
  }
  .brand-divider,
  .brand .sub { display: none; }
  .site-header.open .nav .nav-home { display: flex; }
  .site-header .wrap { gap: 12px; }
  .brand { gap: 12px; }
  .site-header .brand-logo { height: 30px; transform: translateY(-3px); }
  .brand-divider { height: 26px; }

  .site-header.open .wrap {
    flex-wrap: wrap;
    height: auto;
    padding-bottom: 18px;
  }
  /* With the wrap's height released, the lockup row has to hold the 68px
     itself, or the logo and toggle jump up when the menu opens. */
  .site-header.open .brand { min-height: 68px; }
  .site-header.open .nav {
    display: flex;
    order: 3;
    width: 100%;
    flex-direction: column;
    gap: 0;
    border-top: 1px solid var(--line);
    margin-top: 2px;
  }
  .site-header.open .nav a {
    padding: 13px 0;
    min-height: 48px;
    font-size: var(--text-base);
    border-bottom: 1px solid var(--line-2);
  }
  .site-header.open .header-cta,
  .page-role-detail .site-header.open .header-cta {
    display: inline-flex;
    order: 4;
    width: 100%;
    margin: 16px 0 0;
    justify-content: center;
  }
}

/* ==========================================================
   Phone layer (<=720px)
   Organised by concern rather than by component, because the
   phone problems were systemic: desktop rows never stacked,
   running text stayed at desktop caption sizes, and the
   primary action was hidden outright.
   ========================================================== */

@media (max-width: 720px) {

  :root { --gutter: 16px; }

  /* One column puts the longest ledes at nine lines, which is past the point
     where balancing still protects the last line. They go back to the body
     default here; the shorter blocks stay balanced at every width. */
  .lede, .intro { text-wrap: pretty; }

  /* --- vertical rhythm ---
     Six to eight sections per page at desktop spacing made every
     page an endless scroll. One step down across the board. */
  .section { padding: 52px 0; }
  .section-sm { padding: 36px 0; }
  .cta-band { padding: 56px 0; }
  .page-role-detail .cta-band { padding: 56px 0; }
  .subsection { margin-top: 44px; }
  .support-pattern + .support-pattern { margin-top: 52px; }
  /* The article's two breaks keep their ratio, one step down: 80/112 would be
     an eighth of a phone screen each. */
  .story-body,
  .story-section + .story-section { margin-top: 52px; }
  .story-figure { margin-top: 28px; }
  .prose > .story-figure { margin: 24px 0; }
  .story-closing { margin-top: 72px; }
  .section-head { margin-bottom: 26px; }
  /* --- running text ---
     The small steps hold their desktop values. Body copy comes down
     one step: a 342px column at 18px wraps every few words, and the
     card and table copy below is lifted to body size anyway. */
  :root {
    --text-base: 17px;
    /* The reading size is a desktop affordance. A phone column is 342px, where
       20px would run to 35 characters a line — the line breaks start doing the
       reading for you. */
    --text-read: var(--text-base);
    /* At phone widths the campaign line stays large but uses the display
       family's width axis so each authored phrase holds as one line. */
    --text-display: clamp(36px, 10.25vw, 40px);
    /* The photographed hero keeps the desktop width instance, so it steps
       down in size instead and leaves a ragged right edge. */
    --text-display-hero: clamp(26px, 7.4vw, 32px);
  }

  /* Anything a reader reads in sentences goes to 16px, the iOS
     no-auto-zoom threshold. */
  .card-desc,
  .values p,
  .timeline-row .what,
  .timeline-h .what,
  .step p,
  .callout,
  .role-row .col-val,
  .table,
  .status-legend,
  .rail,
  .faq .answer { font-size: var(--text-base); }

  /* Long headlines need a touch more room between lines and less negative
     tracking once they are down at phone sizes. H1/H2 keep their role-specific
     width instances so the axis hierarchy survives every breakpoint. */
  .display { font-stretch: 82%; line-height: var(--leading-heading); letter-spacing: var(--tracking-tight); }
  .h1 { line-height: var(--leading-heading); letter-spacing: var(--tracking-tight); }
  .h2 { line-height: var(--leading-snug); }

  /* --- one-column collapses ---
     Rows that only made sense side by side. */
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .values-opportunity { grid-template-columns: 1fr; gap: 24px; }
  .values-opportunity .value-panel h3 {
    min-height: 88px;
    margin-bottom: 12px;
    padding: 18px 20px;
  }
  .section-head,
  .stay-connected .section-head { flex-direction: column; align-items: flex-start; gap: 14px; }
  .cta-band .inner { flex-direction: column; align-items: flex-start; gap: 24px; }
  .timeline-row { grid-template-columns: 1fr; gap: 4px; }
  .photo-grid { grid-template-columns: 1fr 1fr; }
  .photo-grid .ph.wide { grid-column: span 2; }
  /* Numbered steps keep their numbers. The old rule hid the
     counter on phones, which silently dropped the ordering. */
  .step { grid-template-columns: 30px 1fr; gap: 12px; }

  /* --- hero ---
     dvh, not vh: vh is measured against the largest viewport, so
     the hero was taller than the visible area while the address
     bar was showing. */
  .hero { padding-top: 36px; }
  /* 60dvh is deliberate: it is what keeps the next section visible under the
     fold. It also means the copy fills the frame, which is why the scrim here
     is the full-frame ramp — see the token block. */
  .hero.hero-bleed { padding: 0; min-height: 60vh; min-height: 60dvh; }
  /* This rule owns all four sides, so keep the shared gutter here. Zeroing the
     inline padding makes the larger display face touch the physical edge. */
  .hero-bleed .wrap { padding: 44px var(--gutter) 36px; }
  .hero .display { max-width: none; }
  .hero .lede { margin-top: 18px; }
  .hero .btn-row { margin-top: 26px; }
  .hero-meta { margin-top: 32px; gap: 0 24px; }
  .hero-meta div { min-width: 130px; }
  .detail-hero .h1 { max-width: none; }
  .role-tags {
    display: grid;
    grid-template-columns: max-content max-content;
    gap: 8px;
  }
  .page-role-detail .hero-meta {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px;
    margin-top: 32px;
  }
  .page-role-detail .hero-meta div { min-width: 0; padding: 14px; }
  .page-role-detail .hero-meta div:nth-child(5),
  .page-role-detail .hero-meta div:nth-child(6) { grid-column: 1 / -1; }

  /* Full-width stacked buttons read as one clear primary action
     followed by a secondary, instead of two half-width pills. */
  .hero .btn-row .btn,
  .cta-band .btn-row .btn { width: 100%; justify-content: center; }

  /* The photographed hero takes most of the first screen and splits its copy:
     the claim at the top, the actions at the bottom, and the picture showing
     in the gap between them. The scrim only has to carry the top block, so it
     fades out before the gap; the actions sit on the photograph itself. */
  .hero.hero-bleed {
    /* 90% of the first screen including the header: 68px plus its 1px rule. */
    min-height: calc(90vh - 69px);
    min-height: calc(90svh - 69px);
    align-items: stretch;
    --scrim-media: var(--scrim-media-top);
  }
  .hero-bleed .wrap {
    display: flex;
    flex-direction: column;
    width: 100%;
    padding: 36px var(--gutter) 28px;
  }
  .hero-bleed .display {
    font-stretch: 100%;
    font-size: var(--text-display-hero);
  }
  .hero-bleed .btn-row.hero-cta { margin-top: auto; padding-top: 32px; }
  .hero-bleed .btn-ghost {
    background: var(--media-glass);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
  .hero-bleed .hero-note { text-shadow: var(--media-text-shadow); }

  /* --- stat, brand and timeline bands --- */
  .stat-grid > div { padding: 20px 18px; }
  .brand-list > div { padding: 18px 0; }
  /* The gutter label stacks above its content here; 14ch of a narrow screen is
     too much to give away, and both groups have to collapse together. */
  .brand-row { grid-template-columns: 1fr; gap: 10px; }
  .timeline-h > li { padding-bottom: 24px; }

  /* --- roles list ---
     Title spans the width, Team and Location pair up underneath,
     and the affordance goes full width. Previously all four
     cells stacked into six lines per role. */
  .role-row {
    grid-template-columns: 1fr 1fr;
    gap: 14px 16px;
    padding: 20px 14px;
    margin: 0 -14px;
  }
  .role-row > div:first-child,
  .role-row > div:last-child { grid-column: 1 / -1; }
  .role-row > div:last-child .btn { width: 100%; justify-content: center; }

  /* --- definition tables ---
     Two real columns cannot survive 327px, so the row header
     becomes a label above its value. */
  .table,
  .table tbody,
  .table tr,
  .table th,
  .table td { display: block; }
  .table tr { padding: 14px 0; border-bottom: 1px solid var(--line); }
  .table th,
  .table td { padding: 0; border-bottom: 0; }
  .table th { margin-bottom: 4px; }

  /* --- accordions and prose --- */
  .faq .answer,
  .support-pattern .intro { padding-right: 0; }
  .quote { padding-left: 18px; }

  /* --- process steps --- */
  .process-steps li { padding: 16px; }

  /* --- footer ---
     A single column turned four groups of 44px links into a
     700px wall. The brand block spans, the three link groups
     pair up. */
  .site-footer { padding: 48px 0 28px; }
  .site-footer .cols { grid-template-columns: 1fr 1fr; gap: 26px 20px; }
  .site-footer .cols > div:first-child { grid-column: 1 / -1; }
  .footer-brand { flex-direction: column; align-items: flex-start; gap: 12px; }
  .site-footer .footer-brand .brand-logo { transform: none; }
  .site-footer a.footer-corp-link { font-size: var(--text-sm); }

  /* The 44px target is an overlay, not a taller box. Both of these carry a
     1px rule under the words as their only link cue, and on a 44px box that
     rule went to the bottom of the box — 12px clear of the text it belongs
     to, where it read as a stray line across the row rather than as an
     underline. The overlay reaches into the gaps either side, which nothing
     else is using. */
  .breadcrumb a,
  .link { position: relative; }
  .breadcrumb a::after,
  .link::after {
    content: "";
    position: absolute;
    inset: -12px -4px;
  }
  .page-role-detail .breadcrumb-current,
  .page-role-detail .breadcrumb-separator-current { display: none; }

  /* --- on-this-page rail ---
     The page is a single column of two sections on a phone; the
     headings already do the work the rail does on desktop. */
  .rail { display: none; }

  /* --- filters ---
     Each group gets its own row so the chips read as two sets
     rather than one long wrapped run. */
  .filters { padding: 14px 0; }
  .filters .flabel { width: 100%; margin: 0; }
  .filters .flabel:not(:first-child) { margin-top: 10px; }
  .filters .spacer { display: none; }
  .filters .result-count { width: 100%; margin-top: 4px; }

  /* --- apply bar ---
     A second sticky bar under a 68px sticky header ate a fifth of
     the screen. On a phone the apply action belongs at the thumb
     end instead, as a fixed bottom bar. */
  .sticky-apply {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    border-top: 1px solid var(--line);
    border-bottom: 0;
    background: var(--backdrop);
  }
  .sticky-apply .wrap {
    gap: 8px;
    padding-top: 10px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  }
  .sticky-apply .deadline { width: 100%; }
  .sticky-apply .deadline-note { display: none; }
  .sticky-apply .btn {
    margin-left: 0;
    width: 100%;
    justify-content: center;
  }

  .page-role-detail .sticky-apply .wrap {
    gap: 12px;
    flex-wrap: nowrap;
  }
  .page-role-detail .sticky-apply .deadline {
    width: auto;
    min-width: 0;
    white-space: nowrap;
  }
  .page-role-detail .sticky-apply .deadline-desktop { display: none; }
  .page-role-detail .sticky-apply .deadline-mobile { display: inline; }
  .page-role-detail .sticky-apply .sticky-button-detail { display: none; }
  .page-role-detail .sticky-apply .btn {
    flex: 0 0 auto;
    width: auto;
    margin-left: auto;
    padding-inline: 18px;
  }
  .page-role-detail .sticky-apply.is-suppressed,
  .page-role-detail .site-header.open + main .sticky-apply { transform: translateY(100%); }

  /* Keep the existing two-row clearance on other detail pages. */
  html:has(.sticky-apply) body:not(.page-role-detail) {
    padding-bottom: calc(104px + env(safe-area-inset-bottom, 0px));
  }
  /* This compact bar uses its measured height, so text zoom and safe-area
     changes cannot leave content behind the fixed action. */
  body.page-role-detail:has(.sticky-apply:not(.is-suppressed)) {
    padding-bottom: var(--sticky-apply-height, calc(74px + env(safe-area-inset-bottom, 0px)));
  }
  html:has(.sticky-apply) { scroll-padding-top: 88px; }
  html:has(.page-role-detail .sticky-apply) {
    scroll-padding-bottom: calc(var(--sticky-apply-height, 74px) + 16px);
  }
}

/* ==========================================================
   Motion layer
   Everything above describes state; this describes how the
   page gets between states. Four jobs only:
     1. affordance — the pointer gets an answer within 180ms
     2. travel     — things that move, move on one curve
     3. arrival    — blocks resolve as they enter the viewport
     4. navigation — one page hands over to the next
   Elevation stays scale-and-translate: the proof system has
   no shadow, so depth is read from size and parallax instead.
   ========================================================== */

/* --- 1. affordance ------------------------------------------------------- */

.skip-link { transition: transform var(--motion) var(--ease-control); }

/* The arrow is the smallest unit of feedback on the page and it sits in
   buttons, text links, cards and role rows alike, so it is defined once. */
.arw {
  display: inline-block;
  transition: transform var(--motion) var(--ease-control);
}
.btn:hover .arw,
.link:hover .arw,
a.card:hover .arw,
.role-row:hover .arw { transform: translateX(3px); }
/* External markers are already on a 45° diagonal, so they travel along it
   rather than being flattened back to horizontal. */
a[target="_blank"]:hover .arw { transform: rotate(-45deg) translateX(3px); }

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.97); }
/* A card already answers the pointer by lifting. Letting the pill inside it
   lift too gives one gesture two different speeds. */
a.card .btn:hover,
.role-row .btn:hover { transform: none; }

.nav-toggle {
  transition: border-color var(--motion) var(--ease-control), font-weight var(--motion) var(--ease-control),
    transform var(--motion-press) var(--ease-control);
}
.nav-toggle:hover { border-color: var(--ink-3); }
.nav-toggle:active { transform: scale(0.96); }

.site-header {
  transition: background var(--motion-lift) var(--ease-control),
    border-color var(--motion-lift) var(--ease-control);
}
/* Off the top of the page the header is over content rather than over the
   canvas, so it commits to being opaque and draws a harder edge. */
.site-header.is-scrolled {
  background: var(--backdrop-solid);
  border-bottom-color: var(--border-strong);
}

/* --- 2. travel ----------------------------------------------------------- */

/* A slow fade-up on load, so the first thing the page does is breathe rather
   than appear. The dark base colour on .hero-bleed is what it fades up from. */
.hero-bg-photo { animation: hero-intro 2200ms ease-out both; }
@keyframes hero-intro {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* --- 3. arrival ---------------------------------------------------------- */

/*
  data-reveal is written by app.js, never by the markup, so a visitor without
  JavaScript gets the page fully visible and never sees a blank section. The
  stagger within a group comes from --reveal-delay, also set in app.js.
*/
[data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-travel));
  transition: opacity var(--motion-reveal) var(--ease-reveal),
    transform var(--motion-reveal) var(--ease-reveal);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  /* The blanket rule near the top of this file collapses durations to 0.01ms,
     which is enough for a transition but not for an element whose resting
     state is opacity 0. app.js also opts out of marking anything, so this is
     belt and braces for a preference that changes mid-session. */
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
  .hero-bg-photo { animation: none !important; }
  /* No loop: hold the first photograph still. */
  .hero-slide, .hero-slide img { animation: none !important; }
  .hero-slide:first-child { opacity: 1; }
}

/* --- 4. navigation ------------------------------------------------------- */

/*
  Every page here is its own document served from one origin, so the browser
  can carry one into the next without a router: both documents opt in, and the
  navigation itself is the trigger. A browser that does not implement this
  navigates the way it always did, which is why there is no fallback below.
*/
@view-transition {
  navigation: auto;
}

/* A page is the largest thing on the site that moves, but it only changes
   opacity, so it stays on the control curve rather than the travel one. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--motion-page);
  animation-timing-function: var(--ease-control);
}

@media (prefers-reduced-motion: reduce) {
  /* The blanket rule further up this file is written against *, which never
     reaches the pseudo-element tree the browser builds for a transition. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}


/* ---------- Top: apply-first hierarchy ---------- */

/* One primary action (apply to the open internship), one secondary (apply for a
   role directly), and a text link for people who are still finding out who
   Recruit is. Size and fill carry the order; the text link never competes. */
.btn-lg { min-height: 52px; padding: 15px 28px; font-size: var(--text-base); }

.hero-cta { gap: 14px; }
.hero-note {
  margin-top: 20px;
  font-size: var(--text-sm);
  color: var(--ink-2);
  max-width: var(--measure-note);
  font-variant-numeric: tabular-nums;
}
.hero-note strong { color: var(--ink); font-weight: var(--fw-semibold); }
.hero-explore { margin-top: 22px; }

@media (max-width: 560px) {
  .hero-cta .btn { width: 100%; justify-content: center; }
}
