/* RepoBoss — marketing page.
 *
 * Built from the design reference in RepoBoss/Docs/Website. Tokens, type and
 * spacing are that document's; the nav drawer is the FileRanger pattern
 * restyled, so every product page dismisses the same way.
 *
 * House style for this product is flat and square: no border-radius anywhere
 * except the app icon, hairline borders, and one accent.
 *
 * That accent is the app's own green, #5fcf8d. The design reference called for
 * amber and reserved green for depictions of real product UI; owner's call on
 * 2026-08-20 overrode it, which also settles a split the amber created — the
 * buy page was already green, so a visitor crossed a brand boundary on the way
 * to paying.
 */

:root {
  /* Ground */
  --rb-ground:      #0a0b0c;
  --rb-ground-alt:  #0d0e10;
  --rb-panel:       #131415;
  --rb-raised:      #1a1c1e;
  --rb-sunk:        #111213;

  /* Lines */
  --rb-line:        #26292c;
  --rb-rule:        #1d2023;   /* section separators — quieter than --rb-line */
  --rb-line-lit:    #2b2f33;

  /* Ink */
  --rb-ink:         #E9EBED;
  --rb-body:        #C3C9CE;
  --rb-muted:       #8D959D;
  --rb-faint:       #666D74;
  --rb-fainter:     #4a4f54;

  /* Accent. The app's own green, matching buy.html's --ok, so the landing page
     and the checkout are one product rather than two brands either side of a
     click. #5fcf8d measures 10.1:1 on this ground — better than the amber it
     replaced, at 8.8:1.

     The deep tone is NOT buy.html's --ok-line (#2c5f43). That value borders a
     #191a1c card over there and reads fine; on this page's near-black ground
     it drops to 2.65:1 and the footer's 2px rule all but vanishes. #367f58 is
     4.06:1, which is where the brass it replaces sat (3.96:1), so the rules and
     the featured card keep exactly the weight the design gave them. */
  --rb-accent:      #5fcf8d;
  --rb-accent-hi:   #8ce2ae;
  --rb-accent-deep: #367f58;

  /* Waiting, as opposed to done. #f0a83c measures 9.26:1 on the vault rows
     against the accent green's 9.64, so a queued status carries the same weight
     as a verified one rather than looking like a lesser thing -- it is not a
     warning, it is a copy that has not happened yet. */
  --rb-amber:       #f0a83c;

  --rb-display: "Oxanium", ui-sans-serif, system-ui, sans-serif;
  --rb-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  --rb-shell: 1120px;
  --rb-gutter: 32px;
  --rb-nav-h: 76px;
}

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  background: var(--rb-ground);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* The tile field is a fixed canvas at z-index 0, so the page's own content has
   to sit above it. nav already carries its own z-index. */
main, .site-footer { position: relative; z-index: 1; }

body {
  margin: 0;
  background: var(--rb-ground);
  color: var(--rb-body);
  font-family: var(--rb-mono);
  font-size: 14px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--rb-accent); text-decoration: none; }
a:hover { color: var(--rb-accent-hi); }
::selection { background: rgba(95, 207, 141, 0.25); }

img, video { max-width: 100%; }

/* One focus ring for the whole page. Amber on graphite clears AA comfortably,
   and the offset keeps it clear of the hairline borders it often lands on. */
:focus-visible {
  outline: 2px solid var(--rb-accent);
  outline-offset: 3px;
}

/* Off-screen but still announced. clip-path over display:none/visibility so
   screen readers keep reading it. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--rb-accent);
  color: var(--rb-ground);
  font-family: var(--rb-display);
  font-weight: 700;
  font-size: 13px;
  padding: 12px 20px;
}
.skip-link:focus {
  left: 0;
  color: var(--rb-ground);
}

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

.shell {
  max-width: var(--rb-shell);
  margin: 0 auto;
  padding-inline: var(--rb-gutter);
}

.section {
  border-top: 1px solid var(--rb-rule);
}
/* A translucent wash rather than the opaque --rb-ground-alt, so the tile field
   behind the page shows through these sections too. Over --rb-ground this
   resolves to #0d0e0f against the #0d0e10 it replaces — one level of blue,
   the alpha chosen so it quantises to exactly 3/255 (browsers round CSS alpha
   to 8 bits, and 0.014 rounds UP to 4/255, which lands a level off on two
   channels instead of one),
   which is below the threshold of anything but a colour picker, and the
   alternative was the effect switching off every other section. */
.section--alt { background: rgba(255, 255, 255, 0.0118); }
.section__inner { padding-block: 96px; }
.section[id] { scroll-margin-top: calc(var(--rb-nav-h) + 8px); }

.eyebrow {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--rb-accent);
}
/* The hero's eyebrow is tracked one notch wider than the section eyebrows. */
.eyebrow--wide { letter-spacing: 0.28em; }

h1, h2, h3 {
  font-family: var(--rb-display);
  color: var(--rb-ink);
  margin: 0;
}
h1 {
  font-weight: 800;
  font-size: clamp(40px, 5vw, 62px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
h2 {
  font-weight: 700;
  font-size: clamp(26px, 3vw, 36px);
  line-height: 1.12;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
h3 { font-weight: 700; }

p { margin: 0; }

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  font-family: var(--rb-display);
  font-weight: 700;
  text-align: center;
  border: 1px solid transparent;
  cursor: pointer;
}
.btn--primary {
  background: var(--rb-accent);
  color: var(--rb-ground);
  font-size: 16px;
  padding: 14px 32px;
}
.btn--primary:hover { background: var(--rb-accent-hi); color: var(--rb-ground); }

.btn--outline {
  border-color: var(--rb-accent);
  color: var(--rb-accent);
  font-size: 13px;
  padding: 12px 24px;
}
.btn--outline:hover { background: rgba(95, 207, 141, 0.12); color: var(--rb-accent-hi); }

.btn--fill-sm {
  background: var(--rb-accent);
  color: var(--rb-ground);
  font-size: 13px;
  padding: 12px 24px;
}
.btn--fill-sm:hover { background: var(--rb-accent-hi); color: var(--rb-ground); }

@media (prefers-reduced-motion: no-preference) {
  .btn { transition: background 140ms linear, color 140ms linear, border-color 140ms linear; }
}

/* ---------- Nav ----------
   One DOM for both modes. Above 64rem .nav__menu is `display: contents`, so
   the links and the CTA are direct flex children of .nav__inner and the bar
   is exactly the row it would have been without a drawer. Below 64rem the
   same element becomes the drawer. A duplicated mobile menu would mean two
   copies of every link in the accessibility tree and two states to keep in
   sync. */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  /* Frosted rather than opaque: the page keeps moving underneath it. */
  background: rgba(10, 11, 12, 0.72);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-bottom: 1px solid var(--rb-rule);
}
.nav__inner {
  max-width: var(--rb-shell);
  margin: 0 auto;
  padding-inline: var(--rb-gutter);
  height: var(--rb-nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.nav__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  color: var(--rb-ink);
}
.nav__brand:hover { color: var(--rb-ink); }
.nav__logo { display: block; flex-shrink: 0; border-radius: 5px; }
.nav__wordmark {
  font-family: var(--rb-display);
  font-weight: 800;
  font-size: 24px;
  letter-spacing: -0.01em;
  color: var(--rb-ink);
}
.nav__links {
  display: flex;
  align-items: center;
  gap: 26px;
  font-size: 15px;
}
.nav__link { color: var(--rb-body); }
.nav__link:hover { color: var(--rb-ink); }
.nav__cta {
  font-family: var(--rb-display);
  font-weight: 700;
  font-size: 15px;
  /* 1px less padding each way than the old solid button, so the stroke keeps
     the box the same outer size and the nav row does not shift */
  padding: 10px 23px;
  background: transparent;
  border: 1px solid var(--rb-accent);
  color: var(--rb-accent);
}
/* Fills on hover: the outline states the offer quietly while you read, and
   commits to it under the cursor. Text goes to the ground colour rather than
   pure black so it matches the page's own darkest tone, and the border takes
   the fill colour so the box neither grows nor gains an outline of a second
   shade. */
.nav__cta:hover,
.nav__cta:focus-visible {
  background: var(--rb-accent);
  border-color: var(--rb-accent);
  color: var(--rb-ground);
}

/* No-JS baseline: the toggle is a control we cannot operate without the
   script, so it stays hidden until nav.js announces itself; the menu is
   meanwhile a plain wrapping row, usable at every width. */
.nav__toggle,
.nav__scrim { display: none; }

@media (max-width: 63.99rem) {
  .nav__inner { flex-wrap: wrap; height: auto; padding-block: 12px; row-gap: 12px; }
  .nav__menu { display: flex; align-items: center; gap: 12px 24px; flex-wrap: wrap; }
}

.has-js-nav .nav__toggle {
  /* 44x44 hit area around a 20px glyph — the target is the button, not the
     bars. The negative margin keeps the enlarged box from pushing the bar
     past its 68px. */
  width: 44px;
  height: 44px;
  margin-right: -10px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid transparent;
  background: none;
  color: var(--rb-ink);
  cursor: pointer;
}
.nav__toggle:hover { border-color: var(--rb-line); }

.nav__toggle-box { width: 20px; height: 14px; position: relative; display: block; }
.nav__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
}
.nav__bar:nth-child(1) { top: 0; }
.nav__bar:nth-child(2) { top: 6px; }
.nav__bar:nth-child(3) { top: 12px; }

/* Bars fold into a cross about the middle bar's line, so the outer two
   converge on the centre rather than pivoting in place. */
.nav__toggle[aria-expanded='true'] .nav__bar:nth-child(1) { top: 6px; transform: rotate(45deg); }
.nav__toggle[aria-expanded='true'] .nav__bar:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded='true'] .nav__bar:nth-child(3) { top: 6px; transform: rotate(-45deg); }

@media (prefers-reduced-motion: no-preference) {
  .nav__bar { transition: transform 220ms ease, top 220ms ease, opacity 140ms linear; }
}

@media (max-width: 63.99rem) {
  .has-js-nav .nav__inner { flex-wrap: nowrap; height: var(--rb-nav-h); padding-block: 0; }

  .has-js-nav .nav__menu {
    position: fixed;
    /* Positioned against .nav, NOT the viewport: .nav carries a
       backdrop-filter, and any backdrop-filter other than `none` makes an
       element the containing block for its fixed-position descendants.
       Anchoring to the bar's own bottom edge sizes this correctly without
       giving up the blur. */
    top: 100%;
    right: 0;
    left: auto;
    width: min(22rem, 100%);
    /* dvh so the panel ends at the true bottom edge rather than under a
       mobile browser's collapsing toolbar. */
    height: calc(100dvh - var(--rb-nav-h));
    z-index: 99;

    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;

    padding:
      20px
      max(20px, env(safe-area-inset-right))
      max(24px, env(safe-area-inset-bottom))
      20px;

    background: var(--rb-ground-alt);
    border-left: 1px solid var(--rb-line);
    box-shadow: -24px 0 60px rgba(0, 0, 0, 0.55);

    overflow-y: auto;
    overscroll-behavior: contain;

    /* Closed state. A clip-path wipe rather than a translateX(100%) park:
       a fixed element sitting outside the viewport still counts toward the
       document's scrollable width, which is the page-level horizontal
       scrollbar this drawer exists to avoid. visibility (not display:none)
       so the panel stays a transition target; it is out of the tab order
       and the accessibility tree either way. */
    visibility: hidden;
    opacity: 0;
    clip-path: inset(0 0 0 100%);
  }

  .has-js-nav .nav__menu[data-open] {
    visibility: visible;
    opacity: 1;
    clip-path: inset(0);
  }

  @media (prefers-reduced-motion: no-preference) {
    .has-js-nav .nav__menu {
      transition:
        clip-path 260ms cubic-bezier(0.32, 0.72, 0, 1),
        opacity 180ms linear,
        visibility 0s linear 260ms;
    }
    .has-js-nav .nav__menu[data-open] { transition-delay: 0s; }
  }

  .has-js-nav .nav__links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    font-size: 16px;
  }

  /* Full-width rows at 48px tall — the link is the target, not the word. */
  .has-js-nav .nav__link {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 0 12px;
    border-bottom: 1px solid var(--rb-rule);
    color: var(--rb-body);
  }
  .has-js-nav .nav__link:hover { background: var(--rb-panel); color: var(--rb-ink); }

  .has-js-nav .nav__cta {
    margin-top: 16px;
    padding: 15px 20px;
    font-size: 16px;
    text-align: center;
  }

  /* Scrim: sits under the drawer and over the page, and is what makes an
     outside tap close the menu. */
  .has-js-nav .nav__scrim {
    display: block;
    position: fixed;
    top: 100%;
    left: 0;
    right: 0;
    height: 100dvh;
    z-index: 98;
    background: rgba(6, 7, 8, 0.62);
    border: 0;
    padding: 0;
    opacity: 0;
    visibility: hidden;
  }
  .has-js-nav .nav__scrim[data-open] { opacity: 1; visibility: visible; }

  @media (prefers-reduced-motion: no-preference) {
    .has-js-nav .nav__scrim { transition: opacity 200ms linear, visibility 0s linear 200ms; }
    .has-js-nav .nav__scrim[data-open] { transition-delay: 0s; }
  }
}

/* Desktop bar returns at 64rem, matching the other product pages. */
@media (min-width: 64rem) {
  /* Matched on .has-js-nav so this outranks the drawer rules, which are
     themselves scoped to it — a media query adds no specificity of its own. */
  .has-js-nav .nav__toggle,
  .has-js-nav .nav__scrim,
  .nav__toggle,
  .nav__scrim { display: none; }

  /* display:contents dissolves the wrapper, restoring the plain
     brand | links | CTA flex row. */
  .nav__menu { display: contents; }
}

.has-nav-open { overflow: hidden; }

/* While the banner is behind it the bar has no background of its own — the
   video runs straight up to the top of the window. The frost arrives only once
   the banner has scrolled past. `.at-top` is set by assets/js/nav.js; without
   the script the bar is simply always frosted, which is right on every page.

   Not applied while the drawer is open: a transparent bar over an open drawer
   reads as a rendering fault. */
.at-top:not(.has-nav-open) .nav {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}
@media (prefers-reduced-motion: no-preference) {
  .nav { transition: background 240ms linear, border-color 240ms linear; }
}

/* ---------- Hero ----------
   Full-bleed video banner, the shape the ctrlcolab.com homepage uses.

   The five clips are one locked-off frame with a different poster on the wall
   each time, so the composition is a constant — and it was measured off the
   plate rather than eyeballed:

     his head starts     8.4% down
     the window is lit   to 30% across and 54% down   (the bright hazard)
     the monitor holds   40-58% across, 36-58% down   (the other one)
     the bottom third    sits at 0.11-0.21 luminance  (the clear zone)

   Everything below follows from those four numbers. The crop is anchored near
   the top, so it is always the desk that goes and never his head. Only the
   eyebrow and the headline ride the plate, in that bottom band; the rest of
   the lockup would have to start at 45% down, straight through both hazards,
   so it sits under the banner instead. */

.hero { position: relative; }

/* A sticky bar stays in flow, so without this the banner would begin below it
   and the page would open on a 69px strip of flat ground under a transparent
   bar. Pulling the hero up by the bar's full box — its height plus the hairline
   under it — runs the banner behind it instead, which is the whole point of the
   bar having no background up there. Desktop only: below 64rem the plate is a
   16:9 strip and this would just crop its top for nothing. */
@media (min-width: 64rem) {
  .hero { margin-top: calc(-1 * var(--rb-nav-h) - 1px); }
}

.hero__stage {
  position: relative;
  width: 100%;
  /* Tall enough to hold the headline in the clear band, wide-capped so the
     crop stays modest: the 40vw floor stops an ultrawide monitor slicing the
     frame into a strip, and svh keeps the banner still while a mobile
     browser's toolbar collapses. At common desktop sizes this lands between
     2.2:1 and 2.5:1 — a 20-30% vertical crop, of which the anchor below takes
     only a couple of percent off the top. */
  height: clamp(520px, max(72svh, 40vw), 1100px);
  overflow: hidden;
  background: var(--rb-ground);
}

/* Hidden with opacity, NOT display:none. A display:none video has no box, so
   IntersectionObserver never reports it as intersecting, so play() is never
   called — and with preload="none" nothing loads until play() is called. That
   deadlocks: the clip can only become visible once it has loaded, and it can
   only load once it is visible. An always-boxed element costs nothing here,
   because preload="none" is what actually holds the download back. */
.hero__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 12% of the excess comes off the top, the rest off the bottom. At the
     aspect ratios the height rule permits that is at most ~5% of the frame,
     comfortably clear of the 8.4% where his head begins. */
  object-position: 50% 12%;
  opacity: 0;
}
.hero__media[data-live] { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .hero__media { transition: opacity 320ms linear; }
}

/* NO SCRIM. The plate runs clean edge to edge, on the owner's call, to see how
   it looks. Two things this gives up, both measured rather than guessed:

   1. The banner now ends in a hard horizontal cut against the section below it,
      where before it dissolved into the page.
   2. The lockup is over bare plate. Worst-pixel contrast under each element,
      sampled through its real glyph box: eyebrow 1.3, headline 1.6, body 2.9,
      platform line 3.4 — against 4.5 for normal text and 3.0 for the 62px
      headline. The headline is effectively invisible where it crosses the
      monitor, which is a 0.50-0.60 luminance light source.

   To put it back, the gradient below is the one that was solved for AA on all
   five clips (eyebrow 5.0, headline 7.9, body 6.4). It is kept here rather than
   in the history so the swap is one edit:

     background: linear-gradient(
       to top,
       rgba(10, 11, 12, 0.97) 0%,   rgba(10, 11, 12, 0.93) 10%,
       rgba(10, 11, 12, 0.90) 25%,  rgba(10, 11, 12, 0.89) 38%,
       rgba(10, 11, 12, 0.80) 48%,  rgba(10, 11, 12, 0.24) 56%,
       rgba(10, 11, 12, 0.06) 62%,  rgba(10, 11, 12, 0) 68%
     );
*/
.hero__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: none;
}

/* The media, held apart from the lockup so the two can stack below 64rem —
   absolutely-positioned media inside a stage that also has to grow around
   flowed copy is a contradiction. */
/* The cover the hero fades up from. Built by script, never in the markup --
   see index.html for why -- so this styles an element that only exists once
   something is there to reveal.

   Ground colour rather than black: it is then the same colour the stage
   already shows while its media loads, so the cover appearing is invisible and
   only its departure is seen. Above the lockup (2) and the sprite layer (1) so
   it holds back the copy as well as the picture -- the whole hero arriving at
   once is the point, and text fading up a beat before the scene it sits on
   would rebuild the very staggering this removes. */
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--rb-ground);
  pointer-events: none;
}
.hero__stage[data-hero-ready] .hero__veil { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  /* Quick, and out: a slow lift on a picture that is already fully assembled
     reads as the page being sluggish rather than as an effect. */
  .hero__veil { transition: opacity 260ms ease-out; }
}

.hero__frame { position: absolute; inset: 0; }

.hero__lockup {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  /* Raised 120px off the frame's bottom edge, which lands the block over the
     machine — headline across the monitor's lower half, the rest over the desk
     and the keyboard. Tuned at 1440x900 and expressed in svh so it holds its
     place in the frame at other heights. */
  padding-bottom: clamp(40px, 5svh, 76px);
}
.hero__lockup .shell { display: flex; flex-direction: column; gap: 18px; }
/* Over video a stack of shadows does what a single one cannot: a tight opaque
   pass that cuts the glyph edges, a mid pass for weight, and a wide soft pass
   that dims the plate immediately behind the text rather than the whole frame.
   That last one is why this reads as a shadow and the scrim read as a vignette
   — it is the same idea confined to the shape of the letters.

   It genuinely helps the eye, and a contrast checker still cannot see it: WCAG
   measures foreground against background, and a shadow is neither. The measured
   figures are the ones in the scrim note above. */
.hero__lockup .shell {
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.92),
    0 2px 10px rgba(0, 0, 0, 0.72),
    0 6px 28px rgba(0, 0, 0, 0.55);
}
.hero__lockup h1 {
  text-shadow:
    0 2px 3px rgba(0, 0, 0, 0.92),
    0 4px 18px rgba(0, 0, 0, 0.78),
    0 10px 48px rgba(0, 0, 0, 0.62);
}
/* The button is a filled slab carrying dark type; a dark shadow under that type
   would only muddy it, so the slab takes the lift instead. */
.hero__lockup .btn { text-shadow: none; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5); }

/* Description and call to action, tucked directly under the headline. Two
   columns so the pair stays one shallow band instead of a second stack. */
.hero__say {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 18px 56px;
  align-items: start;
  margin-top: 2px;
}
.hero__sub { font-size: 15.5px; line-height: 1.75; max-width: 52ch; }
.hero__act { display: flex; flex-direction: column; gap: 14px; align-items: flex-start; }
.hero__cta { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.hero__docs { font-size: 13.5px; border-bottom: 1px solid rgba(95, 207, 141, 0.35); }
.hero__meta { font-size: 12px; color: var(--rb-body); }

/* Below the nav breakpoint the banner reverts to the plate's own 16:9 and the
   lockup stacks under it. A viewport taller than it is wide would crop this
   frame to its middle quarter — the monitor and an elbow — and a 211px-tall
   plate has no band to put a headline in anyway. */
@media (max-width: 63.99rem) {
  .hero__stage { height: auto; overflow: visible; }
  .hero__frame { position: relative; inset: auto; aspect-ratio: 16 / 9; overflow: hidden; }
  .hero__media { object-position: 50% 50%; }
  .hero__lockup { position: static; padding: 32px 0 64px; }
  .hero__lockup .shell,
  .hero__lockup h1 { text-shadow: none; }
  .hero__lockup .btn { box-shadow: none; }
  .hero__say { grid-template-columns: 1fr; gap: 22px; }
}

/* ---------- The three jobs ---------- */

.jobs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 40px;
  padding-block: 88px;
}
/* Reused inside a section that already has its own heading, where the row is a
   set of points under it rather than the section itself — so it loses the
   standalone block's vertical padding and closes up a little. */
.jobs--tight { padding-block: 0; gap: 32px; }

.job { display: flex; flex-direction: column; gap: 10px; }
.job h2 { font-size: 19px; line-height: 1.3; letter-spacing: 0; }
.job p { font-size: 14px; line-height: 1.75; }

/* ---------- Two-column split (vaults, access cards, agents) ---------- */

.split {
  display: grid;
  grid-template-columns: minmax(320px, 1fr) minmax(320px, 1fr);
  gap: 56px;
  align-items: center;
}
.split--top { align-items: start; }

/* A third column for the agents animation. 300px is what the clip needs; the
   two text columns give it up and grow taller instead, which suits copy that
   was running wide anyway. Below the breakpoint the split is a single stack and
   a reserved column would just be an empty gap, so the bot leaves -- and the
   sprite's own minWidth is set to the same 1024 so the two cannot disagree
   about whether it exists. */
.split--agents { grid-template-columns: minmax(260px, 1fr) minmax(260px, 1fr) 300px; }
.agents__bot {
  width: 300px;
  /* The clip's own ratio, so the row is the right height before it arrives and
     nothing shifts when it does. */
  aspect-ratio: 480 / 586;
  align-self: center;
}
@media (max-width: 63.99rem) {
  .agents__bot { display: none; }
}
.split > * { min-width: 0; }
.split__copy { display: flex; flex-direction: column; gap: 16px; }
.split__copy p { font-size: 14.5px; line-height: 1.8; max-width: 56ch; }
.split__copy p.is-muted { color: var(--rb-muted); }

@media (max-width: 63.99rem) {
  .split { grid-template-columns: 1fr; gap: 40px; }
  .section__inner { padding-block: 72px; }
  .jobs { padding-block: 72px; }
}

/* ---------- Product shot ----------
   The one place on this page that shows real product UI, which is the one place
   the green is doing its job as product state rather than as brand colour. The
   shot's own background is within a couple of values of the page ground, so the
   hairline is the whole separation — same border as the cards and the vault
   panel, so it reads as another panel rather than a pasted-in picture. */

.shot { margin: 0; }
.shot img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rb-line);
}

/* ---------- Safety Vaults schematic ---------- */

.vault {
  background: var(--rb-panel);
  border: 1px solid var(--rb-line);
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 12.5px;
}
.vault__commit {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--rb-raised);
  border: 1px solid var(--rb-line-lit);
  padding: 12px 16px;
}
.vault__commit b { color: var(--rb-ink); font-weight: 700; }
.vault__commit span { color: var(--rb-muted); }
.vault__arrow { display: flex; justify-content: center; color: var(--rb-faint); }
.vault__list { display: flex; flex-direction: column; gap: 8px; }
.vault__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--rb-sunk);
  border: 1px solid var(--rb-line);
  padding: 11px 16px;
}
.vault__row span { color: var(--rb-body); }
/* Scoped to the row deliberately. `.vault__chip` alone loses to
   `.vault__row span` above -- same element, one class against a class plus a
   type -- so the accent was declared here and never applied, and VERIFIED has
   been rendering in body grey since the diagram was built. */
.vault__row .vault__chip {
  font-weight: 700;
  letter-spacing: 0.1em;
  font-size: 11px;
  color: var(--rb-accent);
  flex: none;
}
/* The label dims to say "unplugged"; the chip does NOT, because it is the one
   thing in the row that has to stay readable. Dimming the whole row was the
   obvious way to write this and it multiplies against the pulse below -- amber
   at 0.6 row opacity and a 0.65 trough lands near 2.4:1, a status you cannot
   read at the moment it is trying to tell you something. */
.vault__row--queued span { color: var(--rb-faint); }
.vault__row--queued .vault__chip { color: var(--rb-amber); }

/* ---------- the vault diagram, running ----------

   The schematic above is a still of a process, so it animates the PROCESS
   rather than decorating itself: a commit lands, a pulse travels down the rule,
   and each vault verifies in turn -- except the unplugged one, which pointedly
   does not. That row staying dim while its QUEUED chip breathes is the entire
   claim of the section, and a still image cannot make the distinction.

   Everything here is opacity and transform on a handful of small elements, so
   it runs on the compositor and costs nothing per frame. It is also gated on
   the diagram being in view, which is less about the cost than about the
   telling: arriving at a cycle already half-run reads as decoration, and
   starting from the commit reads as a sequence.

   The timings below are one cycle: about three seconds of activity and three of
   rest, so it is legible without becoming a flicker in the corner of the eye
   while somebody is reading the paragraph beside it. */
@keyframes vault-land {
  0%, 100%  { border-color: var(--rb-line-lit); }
  2%,  9%   { border-color: var(--rb-accent); }
  15%       { border-color: var(--rb-line-lit); }
}
@keyframes vault-travel {
  0%,  8%   { opacity: 0; transform: translateY(-7px); }
  11%       { opacity: 1; }
  18%       { opacity: 1; transform: translateY(7px); }
  21%, 100% { opacity: 0; transform: translateY(7px); }
}
/* The row's own light. Delays below stagger the two verified rows so they land
   one after the other; each holds long enough to be read as a state rather than
   a blink, then relaxes so the next cycle has somewhere to arrive from. */
@keyframes vault-verify {
  0%,  100% { box-shadow: inset 0 0 0 0 var(--rb-accent); border-color: var(--rb-line); }
  4%,  45%  { box-shadow: inset 2px 0 0 0 var(--rb-accent); border-color: var(--rb-line-lit); }
  55%       { box-shadow: inset 0 0 0 0 var(--rb-accent); border-color: var(--rb-line); }
}
/* The chip goes neutral, then GREEN, at the moment its vault verifies -- so
   the colour is the event rather than a permanent label. The reset at the wrap
   is deliberate here, unlike a fade: a new commit has arrived and nothing has
   been checked yet. */
@keyframes vault-chip-verify {
  0%,  2%   { color: var(--rb-muted); }
  6%, 100%  { color: var(--rb-accent); }
}
/* The queued row never verifies. It breathes amber instead, on its own slower
   clock and out of step with the six-second cycle, so it reads as something
   waiting on its own account rather than as part of the sequence.

   The trough is 0.65 rather than something deeper because amber at 0.45 over
   this row measures 2.77:1 -- legible only for half of each breath. At 0.65 it
   holds 4.51:1 throughout, and a shallow breath is what "slowly pulsing" wants
   anyway. */
@keyframes vault-waiting {
  0%, 100% { opacity: 0.65; }
  50%      { opacity: 1; }
}
/* Its edge, echoing the green one the verified rows get, so the two states
   speak the same visual language in different colours. */
@keyframes vault-queued-edge {
  0%, 100% { box-shadow: inset 2px 0 0 0 rgba(240, 168, 60, 0.30); }
  50%      { box-shadow: inset 2px 0 0 0 rgba(240, 168, 60, 0.85); }
}

/* Anchored to the rule GLYPH, not to the row. The row centres the whole string
   "| copied to", which puts the bar left of centre -- a pulse placed on the row
   would travel down the middle of the words instead of down the line. */
.vault__rule { position: relative; }
/* Drawn over the existing glyph rather than replacing it, so the diagram is
   unchanged whenever nothing is running. */
.vault__rule::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 3px;
  height: 3px;
  margin: -8px 0 0 -1.5px;
  background: var(--rb-accent);
  opacity: 0;
  pointer-events: none;
}

/* One cycle, read as a sentence: the commit lands (0.1s), a pulse carries it
   down (0.5-1.1s), the first vault verifies (1.2s), then the second (1.7s),
   and the rest of the six seconds is quiet so this is never a flicker in the
   corner of the eye of somebody reading the paragraph beside it. */
.vault.is-live .vault__commit { animation: vault-land 6s linear infinite; }
.vault.is-live .vault__rule::before { animation: vault-travel 6s linear infinite; }

.vault.is-live .vault__row:nth-child(1) { animation: vault-verify 6s linear infinite 1.2s; }
.vault.is-live .vault__row:nth-child(2) { animation: vault-verify 6s linear infinite 1.7s; }
.vault.is-live .vault__row:nth-child(1) .vault__chip { animation: vault-chip-verify 6s linear infinite 1.2s; }
.vault.is-live .vault__row:nth-child(2) .vault__chip { animation: vault-chip-verify 6s linear infinite 1.7s; }

/* Deliberately excluded from the verify sequence above -- this is the row the
   section is about. */
.vault.is-live .vault__row--queued {
  animation: vault-queued-edge 3.2s ease-in-out infinite;
}
.vault.is-live .vault__row--queued .vault__chip {
  animation: vault-waiting 3.2s ease-in-out infinite;
}

/* Asked not to animate, the diagram is exactly the still it was before. */
@media (prefers-reduced-motion: reduce) {
  .vault.is-live .vault__commit,
  .vault.is-live .vault__rule::before,
  .vault.is-live .vault__row,
  .vault.is-live .vault__chip { animation: none; }
}

/* ---------- Access card ----------
/* ---------- the reachability badges ----------
   Each card carries a small green mark in its top-right corner restating the
   mechanism its heading names. They sit in the padding above the 01/02/03, and
   the heading is given room to their left so a long one wraps before it reaches
   them rather than running underneath. */
.step { position: relative; }
.step__badge {
  position: absolute;
  top: 18px;
  right: 20px;
  color: var(--rb-accent);
  display: flex;
  align-items: center;
  gap: 9px;
  pointer-events: none;
}
.step__badge svg { width: 46px; height: 46px; display: block; }

/* ---------- 01: the port and its lights ----------
   The icon sits over a pair of LEDs at its lower corners, the way an RJ45 jack
   carries link and activity. The badge becomes a column for it, and the icon
   comes down so the lights have somewhere to be without the whole mark growing.

   Both lights are driven by keyframes stepping rather than interpolating: a
   real LED is on or off, and anything that fades between the two reads as a
   pulsing lamp instead of a port passing traffic. Their cycles are 2.3s and
   1.45s -- deliberately not multiples of each other, so the pattern between
   them keeps shifting and never settles into a visible loop. */
.step__badge--lan { flex-direction: column; align-items: center; gap: 4px; }
.step__badge--lan svg { width: 32px; height: 32px; }

/* Narrower than the icon above it, which walks both lights in from the corners
   they started at -- 8px, then 3px back out, so 5px net. The width is derived
   rather than guessed: two 6.25px lights whose centres must sit 5px in from 2.5
   and 29.5 leave 23.25px between their outer edges, and space-between does the
   rest. Widening this by 2n moves each light n outward. */
.lan__leds {
  display: flex;
  justify-content: space-between;
  width: 23.25px;
}
.lan__led {
  width: 6.25px;
  height: 6.25px;
  display: block;
}

/* Link: lit most of the time, dipping briefly -- a connection that is up and
   occasionally has something to say. */
.lan__led--link {
  background: var(--rb-accent);
  animation: lan-link 2.3s steps(1, end) infinite;
}
/* Activity: busier, more broken up, and never landing on the same beat. */
.lan__led--act {
  background: var(--rb-amber);
  animation: lan-act 1.45s steps(1, end) infinite;
}

@keyframes lan-link {
  0%   { opacity: 1;    box-shadow: 0 0 6px 1px rgba(95, 207, 141, 0.85); }
  38%  { opacity: 0.22; box-shadow: none; }
  44%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(95, 207, 141, 0.85); }
  70%  { opacity: 0.22; box-shadow: none; }
  76%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(95, 207, 141, 0.85); }
  100% { opacity: 1;    box-shadow: 0 0 6px 1px rgba(95, 207, 141, 0.85); }
}
@keyframes lan-act {
  0%   { opacity: 1;    box-shadow: 0 0 6px 1px rgba(240, 168, 60, 0.85); }
  14%  { opacity: 0.2;  box-shadow: none; }
  22%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(240, 168, 60, 0.85); }
  30%  { opacity: 0.2;  box-shadow: none; }
  48%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(240, 168, 60, 0.85); }
  54%  { opacity: 0.2;  box-shadow: none; }
  62%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(240, 168, 60, 0.85); }
  68%  { opacity: 0.2;  box-shadow: none; }
  86%  { opacity: 1;    box-shadow: 0 0 6px 1px rgba(240, 168, 60, 0.85); }
  94%  { opacity: 0.2;  box-shadow: none; }
  100% { opacity: 0.2;  box-shadow: none; }
}

/* Asked not to animate, the port shows a live link and an idle activity light
   rather than going dark -- a still frame of the same thing. */
@media (prefers-reduced-motion: reduce) {
  .lan__led { animation: none; }
  .lan__led--act { opacity: 0.3; }
}

/* 02 is a word rather than a glyph, in the display face, sized to carry the
   same weight as the icons beside it. */
.step__badge--word {
  font-family: var(--rb-display);
  font-weight: 700;
  font-size: 26px;
  letter-spacing: 0.02em;
}

/* Reserve the corner so a heading wraps instead of colliding with the badge --
   but ONLY on the cards that carry one. The same .step class builds the four
   "Buy it, install it" cards further down the page, and reserving a corner
   there would push their headings around for a badge that does not exist. */
.step h3 { font-size: 16px; }
.step:has(.step__badge) h3,
.step:has(.step__badge) .step__n { padding-right: 64px; }
/* The tunnel badge is far the widest, hence its own reservation. */
.step:has(.step__badge--tunnel) h3,
.step:has(.step__badge--tunnel) .step__n { padding-right: 118px; }

/* ---------- 03: traffic through the tunnel ---------- */
.step__badge--tunnel { gap: 5px; }
.tunnel__cloud { position: relative; display: block; color: var(--rb-accent); }
.tunnel__cloud svg { width: 49px; height: 49px; }
/* The cloud's drawn body sits low in its own box, so the label is nudged down
   from the geometric centre to land inside the shape rather than on its rim. */
.tunnel__vps {
  position: absolute;
  left: 0;
  right: 0;
  top: 58%;
  /* The 2px is a nudge on top of the centring, not a replacement for it, so the
     label stays vertically centred on whatever size the cloud is and simply
     sits that much lower inside the shape. */
  transform: translateY(calc(-50% + 2px));
  text-align: center;
  font-family: var(--rb-display);
  font-weight: 700;
  font-size: 9.5px;
  letter-spacing: 0.04em;
  color: var(--rb-accent);
}

.tunnel__track {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
}
.tunnel__track i {
  width: 5px;
  height: 5px;
  background: var(--rb-accent);
  opacity: 0.4;
  display: block;
}

/* The dots themselves carry the traffic: each lifts from dim green to white and
   settles back, one after the next. A separate white dot running OVER them was
   the first attempt and read as a fourth object passing three stationary ones,
   which is not what a signal moving down a line looks like -- the line is the
   same line, it is the lit part that moves.

   All six share one sequence, left to right, so a packet appears to enter on
   the left and leave on the right rather than two sides blinking independently.
   The gap between the third and fourth is the transit: it is the cloud. */
@keyframes tunnel-dot {
  0%,  100% { background: var(--rb-accent); opacity: 0.4; }
  5%        { background: var(--rb-ink);    opacity: 1; }
  16%       { background: var(--rb-accent); opacity: 0.4; }
}
.tunnel__track i { animation: tunnel-dot 2.4s linear infinite; }
.tunnel__track--in  i:nth-child(1) { animation-delay: 0s; }
.tunnel__track--in  i:nth-child(2) { animation-delay: 0.18s; }
.tunnel__track--in  i:nth-child(3) { animation-delay: 0.36s; }
.tunnel__track--out i:nth-child(1) { animation-delay: 0.72s; }
.tunnel__track--out i:nth-child(2) { animation-delay: 0.9s; }
.tunnel__track--out i:nth-child(3) { animation-delay: 1.08s; }

@media (prefers-reduced-motion: reduce) {
  .tunnel__track i { animation: none; }
}/* ---------- Access card ----------
   A real card rather than artwork of one, at the product's own landscape size:
   1200x750 is 1.6:1, which is exactly the slot the design was drawn around.

   No border and no panel behind it. The card carries its own hairline frame and
   its corners are genuinely transparent — measured, alpha 0 with an antialiased
   ramp — so it sits on the page ground as a card and not as a rectangle with
   black corners. Its body is #14161A against a #0a0b0c ground, which is what
   lifts it. */

.card-figure { margin: 0; display: flex; justify-content: center; }

/* The clip for the scan. 15px is the card's own 45-unit corner radius at the
   400px it is displayed at, so the beam and the dots stop where the card does
   instead of squaring off its corners. It scales a little large on a narrow
   phone, which costs a pixel of beam at each corner and is invisible; too small
   would let the beam hang outside the card, which would not be. */
.card-stage {
  position: relative;
  width: 400px;
  max-width: 100%;
  border-radius: 15px;
  overflow: hidden;
}
.card-shot { display: block; width: 100%; height: auto; }

/* ---------- The scan ----------
   Decorative. A scan every three seconds: the beam crosses in 2.2s and clears
   the edge by 2.5s, and the last square finishes fading at exactly 3.0s, which
   is the instant the next sweep begins. Continuous rhythm with no overlap —
   the fade was solved for the cycle rather than the cycle padded around the
   fade. The last square strikes at 2.02s, so it has 0.98s to go out.

   Beam and dots share ONE 7.5s period and are timed from the same number, which
   is what keeps them locked together. The beam is 16% of the card wide, so a
   translateX of -100% puts its leading edge exactly at the left edge and 625%
   puts it at the right — the leading edge therefore sweeps 0 to 1 across the
   card over the first 73.33% of the cycle. Each square carries its own position
   as --x, 0 to 1, and delays by that fraction of 2.2s. One at 0.5 lights at
   1.1s, which is the instant the leading edge reaches the middle. Change the
   sweep in one place and both follow; change it in two and they drift apart. */

.card-dots { position: absolute; inset: 0; width: 100%; height: 100%; }
.card-dots rect {
  fill: var(--rb-accent);
  opacity: 0;
  /* Without fill-box the scale below would resolve against the whole SVG
     viewport and fling every dot toward the origin. */
  transform-box: fill-box;
  transform-origin: center;
}

.card-beam {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 16%;
  /* Bright at the right, which is the leading edge, trailing off to nothing
     behind it — so what the eye reads is a line pulling a wake, and the wake is
     where the dots are still lit. */
  background: linear-gradient(
    90deg,
    rgba(95, 207, 141, 0) 0%,
    rgba(95, 207, 141, 0.05) 52%,
    rgba(95, 207, 141, 0.28) 82%,
    rgba(140, 232, 180, 0.75) 95%,
    rgba(198, 255, 224, 0.95) 100%
  );
  /* drop-shadow rather than box-shadow: it follows the gradient's own alpha, so
     the glow hugs the bright edge instead of outlining the element's box. */
  filter: drop-shadow(0 0 7px rgba(95, 207, 141, 0.6));
  transform: translateX(-100%);
  will-change: transform;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .card-beam { animation: rbScanBeam 3s linear infinite; }
  .card-dots rect {
    animation: rbScanDot 3s linear infinite;
    animation-delay: calc(var(--x) * 2200ms);
  }
}

@keyframes rbScanBeam {
  0%      { transform: translateX(-100%); }
  73.33%  { transform: translateX(625%); }   /* 2.2s — leading edge at the far edge */
  83.33%  { transform: translateX(725%); }   /* 2.5s — and now fully past it */
  100%    { transform: translateX(725%); }
}

@keyframes rbScanDot {
  0%      { opacity: 0;        transform: scale(0.4); }
  7.5%    { opacity: var(--o); transform: scale(1); }    /* 0.225s to strike */
  32.67%  { opacity: 0;        transform: scale(0.92); } /* 0.98s to fade out */
  100%    { opacity: 0;        transform: scale(0.4); }
}

/* ---------- Hosting steps ---------- */

.stack { display: flex; flex-direction: column; gap: 36px; }
.stack__head { display: flex; flex-direction: column; gap: 16px; max-width: 640px; }
.stack__head p { font-size: 14.5px; line-height: 1.8; }

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 16px;
}
.step {
  background: var(--rb-panel);
  border: 1px solid var(--rb-line);
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.step__n {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--rb-faint);
}
.step p { font-size: 13px; line-height: 1.7; }

/* The four steps are a sequence, not a set, so they are an <ol> — the numbers
   are the content, and a reader who cannot see the 01/02/03 should still be
   told there is an order. The list's own markers and indent come off, because
   the printed numbers are already doing that job. */
.steps--ordered { list-style: none; margin: 0; padding: 0; }

/* The line under the reachability tiers. Wider measure than the cards above it
   and quieter than the section lead, so it reads as the conclusion the three
   draw rather than a fourth item. */
.stack__note {
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--rb-muted);
  max-width: 78ch;
}
.stack__note em { font-style: normal; color: var(--rb-body); }

/* ---------- Honesty block ---------- */

.honesty {
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-left: 2px solid var(--rb-accent-deep);
  padding-left: 28px;
  align-self: center;
}
.honesty h3 { font-size: 18px; }
.honesty p { font-size: 13.5px; line-height: 1.8; }
.honesty em {
  font-style: normal;
  color: var(--rb-ink);
  border-bottom: 1px solid var(--rb-accent-deep);
}

/* ---------- the guided tour ----------
   The four steps above run on a clock, and the product runs with them: the card
   whose turn it is takes the accent, and the shot beneath changes to match.

   The cycle is 17 seconds -- three each for steps 1 to 3, then four two-second
   frames under step 4, which has four screens to show rather than one. That
   unevenness is why it is driven from script: a CSS animation can hold a
   pattern, but not one where a single step's highlight has to stay lit across
   four separate image changes. */
.tour {
  display: flex;
  justify-content: center;
  margin-top: 44px;
}
/* The frame's box without its clipping. The shot is centred in the shell and
   the number hangs off its left edge, positioned against this rather than
   against the frame -- the frame has to keep overflow:hidden to hold the
   screenshots, which would cut the number off. Absolute, so the number costs
   the centring nothing: the picture stays centred rather than the pair of them
   being centred together and pushing the shot to the right. */
.tour__stage {
  position: relative;
  width: 786px;
  max-width: 100%;
}
.tour__frame {
  position: relative;
  width: 100%;
  /* Held by the shot's own ratio so the row does not resize as frames change,
     and does not collapse before the first one has decoded. */
  aspect-ratio: 1191 / 800;
  border: 1px solid var(--rb-line);
  background: var(--rb-sunk);
  overflow: hidden;
}
/* Top-aligned with the picture it labels, out in the margin beside it. */
.tour__n {
  position: absolute;
  top: 0;
  right: calc(100% + 20px);
  margin: 0;
  font-family: var(--rb-mono);
  font-weight: 700;
  font-size: 43px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--rb-accent);
  pointer-events: none;
}
.tour__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
.tour__shot.is-on { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  /* Long enough to read as a change of screen rather than a cut, short enough
     to be over well inside the shortest frame it has to fit in. */
  .tour__shot { transition: opacity 260ms linear; }
}

/* The step whose turn it is. .step already carries a 1px border, so this is a
   colour change rather than a new outline -- nothing moves as it lights. */
.step.is-current { border-color: var(--rb-accent); }
.step.is-current .step__n,
.step.is-current h3 { color: var(--rb-accent); }
@media (prefers-reduced-motion: no-preference) {
  .step { transition: border-color 260ms linear; }
  .step .step__n, .step h3 { transition: color 260ms linear; }
}

@media (max-width: 63.99rem) {
  /* No margin left to hang in once the shot fills the column, so it goes back
     into the flow above the picture rather than off the side of the page. */
  .tour { flex-direction: column; align-items: stretch; }
  .tour__n { position: static; font-size: 30px; margin-bottom: 10px; }
}

/* ---------- Pricing ---------- */

.pricing { display: flex; flex-direction: column; gap: 40px; align-items: center; }
.pricing__head {
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: center;
  max-width: 620px;
}
.pricing__head p { font-size: 14px; line-height: 1.8; color: var(--rb-muted); }
.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 20px;
  width: 100%;
  max-width: 760px;
}
.plan {
  background: var(--rb-panel);
  border: 1px solid var(--rb-line);
  padding: 36px 32px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.plan--featured { border-color: var(--rb-accent-deep); }
.plan h3 { font-size: 18px; }
.plan__price {
  font-family: var(--rb-display);
  font-weight: 800;
  font-size: 44px;
  line-height: 1.1;
  color: var(--rb-ink);
}
.plan__price span {
  font-family: var(--rb-mono);
  font-weight: 400;
  font-size: 12px;
  color: var(--rb-muted);
}
.plan__what { font-size: 13px; line-height: 1.7; }
.plan .btn { margin-top: 10px; }

/* ---------- FAQ ---------- */

.faq__inner {
  max-width: 820px;
  margin: 0 auto;
  padding-block: 96px;
  padding-inline: var(--rb-gutter);
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.faq__list { display: flex; flex-direction: column; }
.faq__item { border-top: 1px solid var(--rb-line); padding: 18px 0; }
.faq__item:last-child { border-bottom: 1px solid var(--rb-line); }
.faq__item summary {
  cursor: pointer;
  font-size: 14px;
  font-weight: 700;
  color: var(--rb-ink);
  list-style: none;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
.faq__item summary::-webkit-details-marker { display: none; }
/* The marker is drawn rather than relying on the UA triangle, which differs
   per engine and cannot be recoloured in all of them. */
.faq__item summary::after {
  content: "+";
  flex: none;
  color: var(--rb-accent);
  font-weight: 400;
  font-size: 16px;
  line-height: 1;
}
.faq__item[open] summary::after { content: "\2212"; }
.faq__item summary:hover { color: var(--rb-accent-hi); }
.faq__item p { margin-top: 12px; font-size: 13.5px; line-height: 1.8; }

@media (max-width: 63.99rem) {
  .faq__inner { padding-block: 72px; }
}

/* ---------- Footer ---------- */

.site-footer { border-top: 2px solid var(--rb-accent-deep); }
.site-footer__inner {
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  padding-block: 36px;
  font-size: 12px;
  color: var(--rb-faint);
}
.site-footer__links { display: flex; gap: 20px; flex-wrap: wrap; margin-left: auto; }
.site-footer__links a { color: var(--rb-muted); }
.site-footer__links a:hover { color: var(--rb-ink); }
