/* ============================================================================
   Rounds — product landing page. Second art-direction pass.

   Deliberately self-contained: this page does NOT load /styles.css. Everything
   is scoped under .rn so it can never leak into the rest of the site.

   Pass one replaced the reference's surface (colour, type, depth, motion).
   This pass replaces its skeleton. The reference reads as a column of centred
   blocks: hero → full-bleed pinned slab → centred chapter head → two-column
   bento → inset dark band → dark CTA. This page now reads asymmetric:

   - The hero is a split. Copy left, the app window bleeding off the right
     edge — the window is bigger than the page, which is the pitch ("the
     search is larger than the container you keep it in").
   - The pinned showcase keeps its scroll-scale but the chapter head rides a
     sticky left rail BESIDE the shot, and the panel half-bleeds right.
   - The four feature cards hang off a vertical spine, alternating sides —
     the section's anatomy is a structure, not a grid. The layout shows the
     "rounds × people × questions" shape instead of describing it.
   - "Native" mirrors the hero: shot bleeding LEFT, copy right.
   - Privacy is a full-bleed dark act (the trust engine gets the gravity),
     and the dark run continues unbroken through the CTA and footer.

   Palette: "bright tech", not macOS grey. White paper, icy indigo-tinted
   sections, electric indigo accent with a cyan far-stop, indigo-black ink.
   Every text colour re-verified against WCAG AA — ratios inline below.
   ========================================================================= */

/* Families are loaded by the <link> in index.html, not @import: @import
   serialises behind this stylesheet and delays the first paint of text. */

.rn {
  /* Surfaces. White paper, and the tinted sections carry a cool cast that is
     now mostly grey — the first pass sat at full chroma and read as pastel
     baby blue across whole sections. Chroma is down to 40% of that, with
     lightness pulled back a touch so desaturating didn't also wash the tonal
     step away from white (a greyer tint at the same lightness reads lighter).
     Ink stays indigo-black, not neutral black. */
  --paper:      #FFFFFF;   /* hero + spec bar */
  --frost:      #FFFFFF;   /* sections are white — the light half of the
                              page is now one continuous surface */
  --frost-2:    #FFFFFF;   /* was the deeper strip tint; kept as a token so
                              the strip can be re-tinted without markup edits */
  --card:       #FFFFFF;
  --ink:        #0A0A1F;   /* dark run: privacy → CTA → footer */
  --ink-2:      #14142E;   /* raised surface inside dark run */

  /* Text. Measured, not eyeballed:
     --t-mute  #53536E — 7.42:1 on --paper, 6.92:1 on --frost, 6.62:1 on
                --frost-2. Every sub-24px use clears 4.5:1 with margin.
     --t-body  #363650 — 10.4:1 on --frost-2, higher everywhere lighter.
     --t-faint is scaffolding-only: placeholder labels, deleted with them. */
  --t-strong:   #101024;
  --t-body:     #363650;
  --t-mute:     #53536E;
  --t-faint:    #9A9DB5;   /* placeholder labels only — never real copy */
  --t-on-dark:  #F2F4FF;
  --t-on-dark-mute: rgba(235,238,255,.72);   /* ≈12:1 on --ink */

  /* Accent. Electric indigo, cyan far stop.
     --acc   #3D46F2 — 6.26:1 under white text, 6.26:1 as text on --paper,
              5.58:1 on --frost-2. AA everywhere it appears.
     --acc-2 #0090C8 — gradient far stop; 3.61:1 on --paper, 3.22:1 on
              --frost-2 — clears the 3:1 large-text floor (only ever display
              sizes ≥42px).
     --acc-lit #8B93FF — accent as text/nodes on --ink: 7.17:1. */
  --acc:        #3D46F2;
  --acc-hover:  #2F38E4;
  --acc-ink:    #FFFFFF;
  /* Accent used as FOREGROUND (headline highlight, mono kickers, focus ring).
     Split from --acc because the button background keeps its contrast against
     the button, while accent-as-text has to clear the page behind it. In dark
     mode this becomes --acc-lit; --acc itself does not move. */
  --acc-fg:     #3D46F2;
  --acc-2:      #0090C8;
  --acc-lit:    #8B93FF;
  /* Near-neutral now. At full chroma this band was the largest area of colour
     on the page and read as lavender rather than grey — the tint gradient
     covers most of a pinned 100vh section. */
  --cool:       #E7E8EC;   /* showcase atmosphere band — background-only */
  --cool-2:     #E6E9EA;   /* spare tint (cyan) */
  --cool-3:     #E9E8EC;   /* spare tint (violet) */
  --spine:      rgba(61,70,242,.30);   /* decorative line — no contrast duty */

  --line:       rgba(16,16,40,.08);
  --line-strong:rgba(16,16,40,.14);
  --line-dark:  rgba(255,255,255,.14);

  /* Surfaces that were hardcoded before dark mode existed. Tokenised so the
     dark block can move them without touching the rules themselves. */
  /* Mesh blobs. Off brand-indigo entirely: the background used to run the same
     hue as the accent, which made the whole page one note of blue and left the
     buttons nothing to pop against. Now the accent is the only saturated thing
     on screen and the field behind it is nearly neutral.

     Chroma is low and the work is done by value and temperature instead: a
     graphite-violet, a muted mauve, a cool slate, and one warm stone that
     stops the field reading as cold grey. That warm-against-cool at low
     saturation is the whole trick; saturated gradients read as software,
     desaturated ones with a temperature shift read as material. */
  --mesh-1:     rgba(96,92,126,.13);   /* graphite violet */
  --mesh-2:     rgba(158,138,148,.09); /* muted mauve     */
  --mesh-3:     rgba(74,80,98,.10);    /* cool slate      */
  --mesh-4:     rgba(176,158,138,.09); /* warm stone      */

  --nav-glass:  rgba(255,255,255,.72);   /* toolbar material when stuck */
  --nav-solid:  rgba(255,255,255,.97);   /* its reduced-transparency fallback */
  --ph-bg:      #F6F6F7;                 /* placeholder slot */
  --ph-hl:      rgba(255,255,255,.85);   /* light catching its top edge */
  --seg-shadow: 0 1px 2px rgba(10,10,31,.10);

  /* Geometry. 1200/980 from a 12-col × 76px grid with 24px gutters. The
     half-bleed sections compute their own left/right inset from the same
     --max so bled edges and contained edges share one grid. */
  --gutter:     24px;
  --max:        1200px;
  --bento:      980px;
  --r-card:     24px;
  --r-sect:     40px;
  --r-pill:     999px;
  /* Inset from viewport edge to the content column — the bleed sections pad
     one side with this and run the other side to the edge. */
  --edge:       max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter)));

  /* Rhythm */
  --sect-pad:   144px;

  /* Depth: hairline-first. Cool-tinted ambient shadows, slightly bluer than
     pass one to sit with the new palette. */
  --lift:
    0 1px 2px rgba(10,10,31,.05),
    0 8px 24px rgba(10,10,31,.06),
    0 28px 64px -16px rgba(20,20,60,.14);
  --lift-sm:
    0 1px 2px rgba(10,10,31,.04),
    0 4px 14px -2px rgba(10,10,31,.08);

  /* Apple's sheet curve: fast out of the gate, long decelerating settle. */
  --ease: cubic-bezier(.32,.72,0,1);
  /* For colour/opacity only — transform on this curve looks like braking. */
  --ease-io: cubic-bezier(.45,0,.25,1);

  /* V3's pairing. Instrument Sans carries the body, Bricolage Grotesque
     the display sizes via --disp. DM Mono stays for the small caps labels;
     V3 had no mono face to replace it with. */
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-optical-sizing: auto;
  font-size: 16px;
  line-height: 1.5;
  color: var(--t-body);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body.rn-body { margin: 0; background: #FFFFFF; }
/* The one colour on the page that is not a token, and therefore the one the
   dark palette could not reach.

   `.rn` swaps every surface variable for dark, so every *section* painted
   correctly and the page looked right — until you scrolled past the last
   section or rubber-banded past the first, where the body itself shows
   through and it was still white. Same three states as the palette below:
   the media query is guarded so an explicit light choice still wins, and the
   attribute rule wins in the other direction. */
@media (prefers-color-scheme: dark) {
  :root:not([data-rn-theme="light"]) body.rn-body { background: #0C0C1A; }
}
:root[data-rn-theme="dark"] body.rn-body { background: #0C0C1A; }
:root[data-rn-theme="light"] body.rn-body { background: #FFFFFF; }

:where(.rn, .rn *, .rn *::before, .rn *::after) { box-sizing: border-box; }
/* Element resets are wrapped in :where() so they carry zero specificity —
   plain `.rn figure` selectors were (0,1,1) and outranked every (0,1,0)
   component rule. */
:where(.rn p, .rn h1, .rn h2, .rn h3, .rn h4, .rn ul, .rn ol, .rn figure, .rn dl) { margin: 0; }
:where(.rn ul, .rn ol) { padding: 0; list-style: none; }
:where(.rn a) { color: inherit; text-decoration: none; }
:where(.rn img, .rn video, .rn svg) { display: block; max-width: 100%; }

/* ---------------------------------------------------------------- primitives */

.rn-wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter); }
.rn-narrow { max-width: var(--bento); margin-inline: auto; }

/* Display type. Size-specific tracking; lh 1.04 so the display breathes. */
.rn-d1, .rn-d2, .rn-d3 {
  /* Bricolage runs wider and heavier than Inter at the same number, so the
     display weight comes down from 640 and the tracking tightens. */
  font-family: var(--disp);
  font-weight: 600;
  letter-spacing: -.025em;
  color: var(--t-strong);
  line-height: 1.04;
  text-wrap: balance;
}
/* Two lines is the ceiling for a hero headline. In the old narrow split column
   this had to drop to 60px to avoid a third line; on the full centred measure
   it can carry 72px and still set in two. */
.rn-d1 { font-size: clamp(40px, 5.4vw, 72px); letter-spacing: -.028em; }
.rn-d2 { font-size: clamp(32px, 4.2vw, 52px); letter-spacing: -.024em; }
.rn-d3 { font-size: clamp(24px, 2.8vw, 34px); letter-spacing: -.018em; }

.rn-lede {
  font-size: clamp(18px, 1.7vw, 21px);
  line-height: 1.45;
  color: var(--t-body);
  letter-spacing: -.012em;
  text-wrap: pretty;
}

/* Mono eyebrow — the typographic tell that says "software". */
.rn-mono {
  font-family: "DM Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--t-mute);
}

/* Headline emphasis: gradient ink, indigo → cyan. Both stops clear the 3:1
   large-text floor on every surface they appear on (6.26:1 and 3.61:1 on
   paper), so no point of the sweep dips below it. */
/* Flat accent, not a two-stop gradient. Once the page went white and the
   washes came out, the gradient headline was the only gradient left.
   Plain colour rather than the background-clip:text trick the gradient
   needed — clip:text requires a transparent text-fill, which means the real
   colour is invisible to any tool inspecting `color` (including a contrast
   audit, which read it as 1:1). 6.26:1 on white. */
.rn-hl { color: var(--acc-fg); }   /* unused since the hero went single tone;
                                      kept for any future accent word */

/* Buttons. Feedback lives on the press and it's instant — :active fires on
   pointer-down with a fast transition down and the slower settle back up. */
.rn-btn {
  display: inline-flex; align-items: center; gap: 10px;
  height: 48px; padding: 0 22px;
  border: 0; border-radius: var(--r-pill);
  font: inherit; font-size: 16px; font-weight: 500; letter-spacing: -.012em;
  cursor: pointer;
  transition: transform .45s var(--ease), background-color .3s var(--ease-io), opacity .3s var(--ease-io), box-shadow .45s var(--ease);
}
.rn-btn:hover { transform: translateY(-1px); }
.rn-btn:active { transform: scale(.97); transition-duration: .1s; }
.rn-btn-dark  { background: var(--ink); color: #fff; }
.rn-btn-dark:hover { background: #000; }
.rn-btn-light { background: #fff; color: var(--ink); box-shadow: var(--lift-sm); }
.rn-btn-acc   { background: var(--acc); color: var(--acc-ink); }   /* 6.26:1 */
.rn-btn-acc:hover { background: var(--acc-hover); }
.rn-btn-ghost { background: transparent; color: var(--t-body); }
.rn-btn-ghost:hover { color: var(--t-strong); }
/* "Coming soon" is a status, not a control. It is a <span>, so it takes no
   focus and offers no pointer, and it is styled as a quiet outlined marker
   rather than a filled button: a prominent button that does nothing on click
   reads as broken, and this page has no release and nothing to collect. The
   real action ("See how it works") carries the filled treatment instead. */
.rn-soon {
  display: inline-flex; align-items: center; gap: 8px;
  height: 48px; padding: 0 20px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  font-size: 16px; font-weight: 500; letter-spacing: -.012em;
  color: var(--t-mute);
  background: transparent;
  cursor: default;
  user-select: none;
}
.rn-soon::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--acc);
  flex: none;
}
.rn-soon-sm { height: 38px; padding: 0 15px; font-size: 14px; }
.rn-soon-lg { height: 48px; }
/* On the dark closing act the hairline and muted text need the inverted pair. */
.rn-cta .rn-soon, .rn-nav.is-dark .rn-soon {
  border-color: var(--line-dark);
  color: var(--t-on-dark-mute);
}
.rn-cta .rn-soon::before, .rn-nav.is-dark .rn-soon::before { background: var(--acc-lit); }

.rn-btn-sm { height: 38px; padding: 0 16px; font-size: 15px; }


.rn-badge {
  display: inline-flex; align-items: center; gap: 10px;
  height: 32px; padding: 0 14px 0 6px;
  background: rgba(255,255,255,.72);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--lift-sm);
  font-size: 13px; color: var(--t-body); letter-spacing: -.01em;
}
.rn-badge b {
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  background: var(--acc); color: var(--acc-ink);   /* 6.26:1 */
  font-size: 12px; font-weight: 600; letter-spacing: 0;
}

.rn-skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: #fff; padding: 12px 18px; border-radius: 0 0 8px 0;
}
.rn-skip:focus { left: 0; }

.rn :focus-visible { outline: 2px solid var(--acc-fg); outline-offset: 3px; border-radius: 4px; }

/* ---------------------------------------------------------------- placeholder

   No product shots exist yet. Every visual slot renders as a correctly
   proportioned frosted blank carrying a mono label that names the asset that
   belongs there. Delete .rn-ph and drop an <img> in its place. */

.rn-ph {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 100%;
  /* Flat, and only just off-white: with the page now white, a placeholder
     needs to read as a reserved area without becoming the darkest thing on
     the screen. The hairline border does most of the work. */
  background: var(--ph-bg);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--ph-hl);   /* light catching the top edge */
  border-radius: 16px;
  overflow: hidden;
}
.rn-ph::after {
  content: attr(data-ph);
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--t-faint);
  text-align: center; padding: 0 20px; line-height: 1.7;
}
.rn-ph-dark {
  background: #17172E;
  border-color: var(--line-dark);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06);
}
.rn-ph-dark::after { color: rgba(255,255,255,.38); }

.rn-ph-16x10 { aspect-ratio: 16/10; }
.rn-ph-16x9  { aspect-ratio: 16/9; }
.rn-ph-4x3   { aspect-ratio: 4/3; }
.rn-ph-1x1   { aspect-ratio: 1/1; }
.rn-ph-fill  { position: absolute; inset: 0; border-radius: inherit; }

/* A slot that has been filled.

   The frame stays — every container rule above hangs off `.rn-ph`, and it is
   what owns the aspect ratio, the corner radius and the overflow clip. What
   changes is that it stops drawing itself: no frosted fill, no border, no
   inset highlight, and `::after` renders nothing because the `data-ph`
   attribute is gone.

   The two images are stacked in the same box and the theme rules further down
   show exactly one of them. `object-fit: cover` with a top-anchored position
   because these are app screenshots: if a ratio has to give, lose the bottom
   of the window, never the toolbar. */
.rn-ph.is-shot { background: none; border: 0; box-shadow: none; }
.rn-ph.is-shot > img, .rn-ph.is-shot > video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
  border-radius: inherit;
}

/* ---------------------------------------------------------------------- nav */

.rn-nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  display: flex; align-items: center;
  height: 68px;
  transition: background-color .3s var(--ease-io), border-color .3s var(--ease-io), backdrop-filter .3s var(--ease-io);
  border-bottom: 1px solid transparent;
}
/* Toolbar material: content scrolls under a translucent layer. */
.rn-nav.is-stuck {
  background: var(--nav-glass);
  backdrop-filter: saturate(180%) blur(22px);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  border-bottom-color: var(--line);
}
/* Over the dark run the bar inverts rather than disappearing. */
.rn-nav.is-dark { color: #fff; }
.rn-nav.is-dark.is-stuck { background: rgba(10,10,31,.72); border-bottom-color: var(--line-dark); }
.rn-nav.is-dark .rn-nav-links a { color: var(--t-on-dark-mute); }
.rn-nav.is-dark .rn-nav-links a:hover { color: #fff; }
.rn-nav.is-dark .rn-btn-dark { background: #fff; color: var(--ink); }
.rn-nav.is-dark .rn-mark-name { color: #fff; }
.rn-nav.is-dark .rn-back { color: var(--t-on-dark-mute); border-color: var(--line-dark); }
/* The divider between the portfolio link and the product mark is a dark
   hairline; over the dark run it disappears entirely. */
.rn-nav.is-dark .rn-mark { border-left-color: var(--line-dark); }

.rn-nav-inner {
  display: flex; align-items: center; gap: 24px;
  width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter);
}
/* Divider marks the handover from portfolio to product. */
.rn-mark {
  display: inline-flex; align-items: center; gap: 9px;
  padding-left: 20px; margin-left: -4px;
  border-left: 1px solid var(--line-strong);
}
.rn-mark-name { font-size: 18px; font-weight: 640; letter-spacing: -.022em; color: var(--t-strong); }
/* The glyph matches the wordmark rather than inheriting body text. Without
   this the mark drew at --t-body, whose own alpha multiplied against each
   dot's fill-opacity and pushed the faintest dot under the 3:1 floor for
   non-text contrast. A logo should be full strength in any case. */
.rn-mark svg { color: var(--t-strong); }
.rn-nav-links { margin-left: auto; display: flex; align-items: center; gap: 28px; }
.rn-nav-links a { font-size: 15px; font-weight: 500; letter-spacing: -.01em; color: var(--t-mute); transition: color .2s var(--ease-io); }
.rn-nav-links a:hover { color: var(--t-strong); }

/* Small return path to the portfolio. */
/* Sits at the far left, ahead of the Rounds mark: this page belongs to the
   portfolio, so the way back is the first thing in the bar rather than a
   trailing afterthought. The rule reads left-to-right as origin then product. */
.rn-back {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 12px 0 9px;
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  font-size: 13px; color: var(--t-mute);
  transition: color .2s var(--ease-io), border-color .2s var(--ease-io);
  white-space: nowrap;
}
.rn-back:hover { color: var(--t-strong); border-color: var(--t-strong); }
.rn-back svg { width: 13px; height: 13px; flex: none; }

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

   Asymmetric split: copy on the content grid's left columns, the app window
   starting at the grid seam and bleeding off the right edge of the viewport.
   The crop is deliberate — the window doesn't fit the page. */

.rn-hero {
  position: relative;
  background: var(--paper);
  padding-top: 68px;
  overflow: clip;
}
/* ------------------------------------------------------------ hero mesh

   Two stacked mesh layers rather than one flat wash. The first pass was two
   radial stops painted straight onto the section, which reads as a smudge
   because every blob shares one centre of gravity and nothing moves.

   Here each layer carries three blobs at different sizes and offsets, the two
   layers drift in opposite directions on a very long loop, and the result is
   a field whose shape is never quite the same twice. Colours come from tokens
   so light and dark each get their own weighting: barely-there in light (this
   page rejected pastel once already), genuinely luminous in dark, where an
   unlit background is the thing that looks cheap.

   Transform-only animation on two composited layers, so it costs no repaint,
   and the whole thing sits behind a mask that returns the page to flat paper
   before the copy below it starts. */

.rn-hero-grid {
  position: absolute; inset: 0 0 auto 0;
  height: 1080px;
  pointer-events: none;
  overflow: hidden;
  /* Fades out well before the section ends so the spec ribbon underneath
     still meets a clean, untinted edge. */
  -webkit-mask-image: linear-gradient(to bottom, #000 46%, transparent 92%);
          mask-image: linear-gradient(to bottom, #000 46%, transparent 92%);
}
.rn-hero-grid::before,
.rn-hero-grid::after {
  content: "";
  position: absolute;
  /* Oversized so the drift never exposes an edge. */
  inset: -25%;
}
.rn-hero-grid::before {
  background:
    radial-gradient(46% 38% at 18% 26%, var(--mesh-1), transparent 68%),
    radial-gradient(38% 44% at 78% 18%, var(--mesh-2), transparent 66%),
    radial-gradient(52% 40% at 58% 62%, var(--mesh-3), transparent 70%);
}
.rn-hero-grid::after {
  background:
    radial-gradient(40% 34% at 88% 54%, var(--mesh-4), transparent 66%),
    radial-gradient(34% 40% at 34% 72%, var(--mesh-2), transparent 68%),
    radial-gradient(30% 26% at 62% 12%, var(--mesh-1), transparent 64%);
}
@media (prefers-reduced-motion: no-preference) {
  .rn-hero-grid::before { animation: rn-mesh-a 44s ease-in-out infinite alternate; }
  .rn-hero-grid::after  { animation: rn-mesh-b 52s ease-in-out infinite alternate; }

  /* Pointer parallax. This uses the standalone `translate` property rather
     than `transform`, because `transform` already belongs to the drift
     keyframes above; the two compose instead of one clobbering the other.

     The layers move opposite ways, which is what makes it read as depth
     rather than as the whole background sliding around. The long transition
     is doing the real work: the field trails the cursor by half a second, so
     it feels like something settling rather than something tracking you.
     --rn-mx / --rn-my are written by rounds.js, and default to 0 so this is
     inert until a real mouse shows up. */
  .rn-hero-grid::before {
    translate: calc(var(--rn-mx, 0) * 26px) calc(var(--rn-my, 0) * 18px);
    transition: translate .55s cubic-bezier(.22,.61,.36,1);
  }
  .rn-hero-grid::after {
    translate: calc(var(--rn-mx, 0) * -16px) calc(var(--rn-my, 0) * -11px);
    transition: translate .75s cubic-bezier(.22,.61,.36,1);
  }
}
/* Small amplitudes on purpose: the point is that the field is never static,
   not that the reader watches it move. */
@keyframes rn-mesh-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(4%, -3%, 0) scale(1.12); }
}
@keyframes rn-mesh-b {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(-5%, 4%, 0) scale(1); }
}

/* Single column: centred copy, then the product shot full width beneath it.
   This replaces the asymmetric copy-left / window-bleeding-right split. With
   one message and one asset, the stack lets the shot be seen whole instead of
   cropped, and gives the headline the full measure so it sets at a larger size
   in two lines rather than three in a narrow column. */
.rn-hero-split {
  position: relative;
  display: block;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* Own bottom value, not --sect-pad. That token is 144px and is shared
     with .rn-chapter's much taller section below; used here it left a
     144+28px gap of pure white between the hero and the facts ribbon,
     which read as dead space once the hero shrank to a single recording. */
  padding-block: 72px 48px;
}
.rn-hero-copy { text-align: center; max-width: 760px; margin-inline: auto; }
/* Rhythm widens as it descends: the badge sits close to the headline, the
   lede gets air beneath a 72px display, and the CTAs get more still. A flat
   24px between all three made the lede crowd the headline. */
.rn-hero-copy > * + * { margin-top: 20px; }
.rn-hero-copy .rn-lede { margin-top: 30px; }
.rn-hero-copy .rn-hero-cta { margin-top: 40px; }
.rn-hero-copy h1 { max-width: 17ch; margin-inline: auto; }
.rn-hero-copy .rn-lede {
  /* balance, not pretty. Centred and three lines deep, this block shows its
     rag: `pretty` only protects the last line and left 646/193/356 — a full
     line, a 193px stub, then the second sentence. `balance` evens the widths
     to 400/439/356 and holds that regardless of the measure, so the result
     does not depend on a hand-tuned ch value. */
  text-wrap: balance;
  max-width: 46ch;
  margin-inline: auto;
  line-height: 1.5;
  color: var(--t-body);
}
.rn-hero-cta { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; }

/* Contained, not bled: the whole window is visible, sitting on its own shadow. */
.rn-hero-shot { position: relative; margin-top: 64px; }
.rn-hero-shot .rn-ph {
  border-radius: var(--r-card);
  border-color: var(--line-strong);
  box-shadow: var(--lift);
}


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

   Two screenshots of the same window, one per appearance. Which one shows is
   pure CSS so it is right on the first paint: the default follows the reader's
   own prefers-color-scheme, and an explicit data-shot from the toggle wins over
   it. That ordering matters because without JS the page still shows the
   appearance the reader actually uses.
   -------------------------------------------------------------------------- */

/* No margin reset here: :where() already zeroes figure margins, and declaring
   it again at this point in the file overrode .rn-hero-shot's margin-top and
   collapsed the gap between the CTAs and the screenshot. */
.rn-shot { position: relative; }
.rn-shot-img {
  width: 100%; height: auto; display: block;
  border-radius: var(--r-card);
  box-shadow: var(--lift);
}
/* The interactive hero demo is retired; the hero shows the recording on
   every device now. */

/* The hero recording. The whole hero now, on every device; there is no
   fine-pointer/wide-screen gate to fall back from any more.

   One asset, not a theme pair: this page pins data-rn-theme="light" and
   never offers a toggle, so there is no dark state for a second capture to
   answer to. A rounds-hero-dark.mp4 sits in assets/rounds unused for
   exactly that reason -- it has nothing to pair against on this page.

   It carries `.rn-shot-img` for the radius, the lift and the 100%-width
   rule, and nothing else -- the video's own 1920x1199 keeps the box the
   right shape before a single frame has loaded. */
.rn-hero-video { background: var(--frost); }

/* Reduced motion gets the poster and no playback.

   `autoplay` cannot be withdrawn in CSS, so the guard that pauses it lives in
   rounds.js; this half hides the fact that a paused video is still a video by
   making sure the frame it stops on is the one the poster already showed. */
@media (prefers-reduced-motion: reduce) {
  .rn-hero-video { animation: none; }
}

/* Unconditionally hidden. This page pins one committed look (light, no
   toggle -- see the head comment in index.html), but a leftover
   prefers-color-scheme query here still flipped every screenshot to its
   dark twin for OS-dark-mode visitors, while the rest of the page's chrome
   stayed forced light. That mismatch, light nav and body next to dark
   screenshots, is what this was actually producing for a real slice of
   visitors, not a hidden no-op. data-shot="dark" still forces a specific
   image dark if something ever needs one on purpose. */
.rn-shot-dark { display: none; }
.rn-shot[data-shot="dark"] .rn-shot-light { display: none; }
.rn-shot[data-shot="dark"] .rn-shot-dark  { display: block; }


/* -------------------------------------------------------------- spec ribbon

   Kicker left, facts right, one hairline band — a spec-sheet header, not the
   reference's centred logo bar. */

/* 40px, not 110. The pinned section right after this is a 100vh sticky box
   with its content vertically centred, so its own heading already sits well
   into the viewport rather than flush against the top -- stacking 110px of
   padding on top of that centring was most of the "dead space under the
   hero" complaint, even though the hero itself had already been tightened. */
.rn-spec-bar { background: var(--paper); padding-bottom: 40px; }
.rn-spec-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px 48px; flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-top: 16px;
}
.rn-spec-list { display: flex; flex-wrap: wrap; gap: 8px 48px; }
.rn-spec-list li { display: flex; flex-direction: column; gap: 5px; text-align: left; padding: 4px 0; }
.rn-spec-list .rn-spec-v {
  font-size: 20px; font-weight: 640; letter-spacing: -.02em; color: var(--t-strong);
}
.rn-spec-list .rn-spec-k {
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--t-mute);
}

/* ------------------------------------------------------------------ chapter */

/* Now holds only the spine (the timeline); the intro showcase above it
   (heading + pinned/unpinned screenshot) was removed, along with its
   .rn-showcase/.rn-chapter-head/.rn-showcase-panel rules. */
.rn-chapter { position: relative; background: var(--frost); padding-bottom: var(--sect-pad); overflow: clip; }

/* -------------------------------------------------------------------- spine

   The four cards hang off a vertical line, alternating sides down the page —
   a pipeline, not a grid. Each card carries a node dot on the line. This is
   the composition doing the marketing brief's work: a spreadsheet is a stack
   of rows; a search has joints and branches. */

.rn-spine {
  position: relative;
  /* Matches the hero's own width (var(--max), 1200px) rather than the
     narrower --bento cap. Cards are 50% of this minus a fixed gutter, so
     widening the spine widens every card and its screenshot/video with it. */
  max-width: var(--max);
  margin: 110px auto 0;
}
.rn-spine::before {
  content: "";
  position: absolute; left: 50%; top: -56px; bottom: -48px; width: 2px;
  margin-left: -1px;
  background: linear-gradient(180deg, transparent, var(--spine) 6%, var(--spine) 94%, transparent);
}
.rn-step { position: relative; width: calc(50% - 48px); }
/* The lead card carries a taller shot. Notes is the feature the maker would
   demo first, and it owns the chapter's full-bleed image already; this gives
   it weight in the spine without widening it, which would break the strict
   two-column alternation the connectors depend on. */
.rn-step-lead .rn-card-media .rn-ph { min-height: 300px; }

.rn-step:nth-child(odd)  { margin-right: auto; }
.rn-step:nth-child(even) { margin-left: auto; margin-top: -190px; }
.rn-step + .rn-step:nth-child(odd) { margin-top: 64px; }
/* Node dot, centred on the spine. */
.rn-step::after {
  content: "";
  position: absolute; top: 40px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--acc);
  box-shadow: 0 0 0 5px var(--frost), 0 0 0 6px var(--spine);
}
.rn-step:nth-child(odd)::after  { right: -54px; }
.rn-step:nth-child(even)::after { left: -54px; }
/* Connector from card to node. */
.rn-step::before {
  content: "";
  position: absolute; top: 45px; width: 42px; height: 2px;
  background: var(--spine);
}
.rn-step:nth-child(odd)::before  { right: -42px; }
.rn-step:nth-child(even)::before { left: -42px; }

.rn-card {
  display: flex; flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: var(--lift-sm);
  overflow: hidden;
}
.rn-card-media { padding: 20px 20px 0; }
.rn-card-media .rn-ph { aspect-ratio: 16/10; border-radius: 14px 14px 0 0; border-bottom: 0; }
.rn-card-body { padding: 22px 26px 28px; }
.rn-card-body h3 { font-size: 18px; font-weight: 640; letter-spacing: -.018em; color: var(--t-strong); margin-bottom: 7px; }
.rn-card-body p  { font-size: 15px; line-height: 1.5; color: var(--t-mute); letter-spacing: -.006em; }

/* ------------------------------------------------------------------ privacy

   The trust engine. Full-bleed dark — not the reference's inset rounded
   band — and the ink runs unbroken from here through the CTA and footer:
   the close of the page is one continuous dark statement. */

.rn-priv {
  background: var(--ink);
  color: var(--t-on-dark);
  padding: 140px 0 120px;
}
.rn-priv .rn-mono { color: var(--acc-lit); }   /* 7.17:1 on --ink */
.rn-priv .rn-d2 { color: #fff; }
.rn-priv-head { max-width: 720px; }
.rn-priv-head .rn-mono { display: block; margin-bottom: 18px; }
.rn-priv-head p { margin-top: 20px; color: var(--t-on-dark-mute); font-size: 17px; line-height: 1.55; }
.rn-priv-body {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(40px, 5vw, 72px);
  align-items: center;
  margin-top: 72px;
}
.rn-priv-media .rn-ph { border-radius: var(--r-card); }
.rn-priv-ledger li { border-top: 1px solid var(--line-dark); padding: 24px 0 26px; }
.rn-priv-ledger li:last-child { border-bottom: 1px solid var(--line-dark); }
.rn-priv-ledger h3 { font-size: 16px; font-weight: 600; letter-spacing: -.014em; color: #fff; margin-bottom: 8px; }
.rn-priv-ledger p  { font-size: 14px; line-height: 1.55; color: var(--t-on-dark-mute); }

/* -------------------------------------------------------------- closing CTA

   Continues the dark run. Statement left, actions right — a signature line,
   not the reference's centred dark block. */

.rn-cta { background: var(--ink); color: #fff; padding: 40px 0 130px; }
.rn-cta-inner {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: 40px 48px;
  align-items: center;
  border-top: 1px solid var(--line-dark);
  padding-top: 104px;
}
.rn-cta .rn-d1 { color: #fff; max-width: 14ch; }
.rn-cta-copy p { margin-top: 22px; color: var(--t-on-dark-mute); font-size: 17px; }
.rn-cta-actions {
  justify-self: end;
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
}
.rn-cta .rn-btn-ghost { color: #fff; }
.rn-cta .rn-btn-ghost:hover { color: #fff; opacity: .72; }
.rn-cta-fine { margin-top: 12px; font-size: 13px; color: rgba(235,238,255,.60); }

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

.rn-foot { background: var(--ink); color: var(--t-on-dark-mute); padding: 0 var(--gutter) 56px; }
.rn-foot-inner {
  max-width: var(--max); margin-inline: auto;
  border-top: 1px solid var(--line-dark); padding-top: 40px;
  display: flex; flex-wrap: wrap; gap: 28px 48px; align-items: flex-start;
}
.rn-foot-brand { margin-right: auto; max-width: 300px; }
.rn-foot-brand .rn-mark-name { color: #fff; }
.rn-foot-brand p { margin-top: 12px; font-size: 14px; line-height: 1.6; }
.rn-foot-col { display: flex; flex-direction: column; gap: 11px; }
.rn-foot-col .rn-mono { color: rgba(235,238,255,.5); margin-bottom: 4px; }
.rn-foot-col a { font-size: 14px; color: var(--t-on-dark-mute); transition: color .2s var(--ease-io); }
.rn-foot-facts span:not(.rn-mono) { font-size: 14px; color: var(--t-on-dark-mute); }
.rn-foot-col a:hover { color: #fff; }
.rn-foot-base {
  max-width: var(--max); margin: 44px auto 0;
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 13px; color: rgba(235,238,255,.60);
}

/* ------------------------------------------------------------------ reveals */

/* Gated on .rn-js, which the head script sets before first paint. No JS
   simply means no animation. 18px travel on the sheet curve. */
.rn-js .rn-rise { opacity: 0; transform: translateY(18px); }
.rn-js .rn-rise.is-in {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ease-io), transform .8s var(--ease);
  transition-delay: var(--rn-delay, 0s);
}

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

@media (max-width: 1024px) {
  .rn { --sect-pad: 104px; --r-sect: 32px; }
  .rn-priv-body { grid-template-columns: 1fr; gap: 48px; }
}

/* Tablet. The jump from 1024 straight to 760 left 768px devices running the
   desktop two-column splits at roughly half the width they were designed for
   (a 296px chapter rail, for one). The remaining split layouts stack here;
   phone-specific chrome still waits for 760. */
@media (max-width: 900px) {
  .rn-hero-split { padding-block: 56px 40px; }
}

@media (max-width: 760px) {
  .rn { --gutter: 18px; --sect-pad: 80px; --r-sect: 24px; --r-card: 18px; }
  .rn-nav-links { display: none; }
  .rn-nav-inner { gap: 10px; }
  /* Keep the glyph as the return affordance and drop the words; the footer
     carries a full-width "James Made" link for anyone who needs the label. */
  /* Leads the bar now, so no auto margin. Glyph only — the words don't fit
     beside the product mark at this width, and the footer carries the label. */
  .rn-back { padding: 0; width: 30px; justify-content: center; }
  .rn-mark { padding-left: 14px; }
  .rn-btn-sm { margin-left: auto; }
  .rn-back-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .rn-mark-name { font-size: 17px; }
  .rn-btn-sm { padding: 0 14px; }
  /* The nav marker and the hero marker both read "Coming soon" and, stacked on
     a phone, land within one screen of each other. The hero owns it. */
  .rn-nav .rn-soon { display: none; }

  .rn-hero-grid { height: 760px; }
  /* Hero stacks; the shot keeps a gentler bleed so the crop still reads. */
  .rn-hero-split { padding-block: 40px 64px; }
  .rn-hero-copy h1 { max-width: none; }
  /* The split only ever had padding-left, because on desktop the shot is meant
     to run off the right edge. Stacked, that left every line of copy flush to
     the screen edge with no gutter at all. The gutter belongs on the copy, not
     the split — the shot still needs to bleed. */

  .rn-hero-shot { margin-top: 40px; }
  /* Buttons stack full-width-left; the ghost button's inline padding was
     indenting it out of line with the filled one above it. */
  .rn-hero-cta { flex-direction: column; align-items: stretch; gap: 8px; }
  .rn-hero-cta .rn-btn { justify-content: center; }
  .rn-hero-cta .rn-btn-ghost { justify-content: center; }

  .rn-spec-bar { padding-bottom: 32px; }
  .rn-spec-inner { flex-direction: column; align-items: flex-start; gap: 18px; padding-top: 22px; }
  /* Grid, not flex-wrap. Five facts of uneven width wrapped 4+1 on a phone,
     which reads as an accident rather than a layout. A fixed three-column
     grid gives a deliberate 3+2 with the columns aligned. */
  .rn-spec-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px 16px; width: 100%; }
  .rn-spec-list .rn-spec-v { font-size: 17px; }
  .rn-spec-list .rn-spec-k { font-size: 10px; letter-spacing: .08em; }

  /* Spine collapses to a left rail: line on the left, cards hanging right. */
  .rn-spine { margin-top: 56px; }
  .rn-spine::before { left: 5px; top: -24px; bottom: -24px; }
  .rn-step,
  .rn-step:nth-child(odd),
  .rn-step:nth-child(even) { width: auto; margin: 0 0 0 30px; }
  .rn-step + .rn-step { margin-top: 24px !important; }
  .rn-step::after,
  .rn-step:nth-child(odd)::after,
  .rn-step:nth-child(even)::after { left: -30px; right: auto; top: 28px; width: 10px; height: 10px; box-shadow: 0 0 0 4px var(--frost), 0 0 0 5px var(--spine); }
  .rn-step::before,
  .rn-step:nth-child(odd)::before,
  .rn-step:nth-child(even)::before { left: -20px; right: auto; top: 32px; width: 20px; }

  .rn-priv { padding: 88px 0 72px; }
  .rn-priv-body { margin-top: 48px; gap: 40px; }

  .rn-cta { padding: 24px 0 88px; }
  .rn-cta-inner { padding-top: 72px; }

  .rn-foot-inner { gap: 26px 34px; }
  .rn-foot-brand { max-width: none; flex-basis: 100%; }
}

/* Touch. Every interactive element measured under Apple's 44pt minimum on a
   phone: footer links were 21px tall, the portfolio back glyph 30x30. Gated on
   a coarse pointer so the desktop layout keeps its tighter rhythm — this is a
   hit-area fix, not a styling change, and the visual size of the text is the
   same either way. */
@media (pointer: coarse) {
  .rn-foot-col a,
  .rn-foot-facts span:not(.rn-mono),
  .rn-nav-links a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  /* The column is a vertical list, so a taller row simply spaces it; the gap
     comes off so the rhythm doesn't double up. */
  .rn-foot-col { gap: 0; }
  .rn-foot-col .rn-mono { margin-bottom: 8px; }

  .rn-back { width: 44px; height: 44px; }
  .rn-back svg { width: 15px; height: 15px; }

  /* The mark is a link home; give it the same floor. */
  .rn-mark { min-height: 44px; }

  /* The nav's compact Download pill is the page's primary conversion and was
     the last element under the floor at 38px. */
  .rn-btn-sm { height: 44px; }

  /* Segmented toggle: 32px tall reads fine with a cursor, not with a thumb. */

  /* Hover styling that sticks after a tap reads as a stuck state on touch. */
  .rn-btn:hover { transform: none; }
}

/* Frostier, not just quieter: translucency preferences get near-solid chrome. */
@media (prefers-reduced-transparency: reduce) {
  .rn-nav.is-stuck { background: var(--nav-solid); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .rn-nav.is-dark.is-stuck { background: rgba(10,10,31,.97); }
  .rn-badge { backdrop-filter: none; -webkit-backdrop-filter: none; background: #fff; }
}

@media (prefers-reduced-motion: reduce) {
  .rn-js .rn-rise { opacity: 1; transform: none; transition: none; }
  .rn-btn:hover, .rn-btn:active { transform: none; }
}


/* ============================================================================
   DARK MODE

   The page is token-driven, so dark mode is a palette swap rather than a
   parallel stylesheet. Three states, matching the rest of the site's habit:

     no attribute           follow the reader's OS setting
     data-rn-theme="dark"   forced dark
     data-rn-theme="light"  forced light, even when the OS is dark

   The :not([data-rn-theme="light"]) guard is what makes the third state possible:
   without it the media query would win back over an explicit light choice.
   The dark run (privacy, CTA, footer) was already dark and is left alone; it
   only steps darker so it stays the floor of the page.
   ========================================================================= */

@media (prefers-color-scheme: dark) {
  :root:not([data-rn-theme="light"]) .rn {
  /* Surfaces. The light half of the page inverts to an indigo-black that is
     a step ABOVE the dark run, so the privacy act still reads as the darkest
     moment on the page rather than merging into everything around it. */
  --paper:      #0C0C1A;
  --frost:      #0C0C1A;
  --frost-2:    #12122A;
  --card:       #15152F;
  --ink:        #06060F;   /* dark run stays the floor */
  --ink-2:      #14142E;

  /* Text. Measured on --paper #0C0C1A:
     --t-strong #F4F5FF  17.6:1
     --t-body   .78 white composited -> #C0C3D6  10.7:1
     --t-mute   .60 white composited -> #9497AE   5.9:1  (clears 4.5:1) */
  --t-strong:   #F4F5FF;
  --t-body:     rgba(235,238,255,.78);
  --t-mute:     rgba(235,238,255,.60);
  --t-faint:    rgba(235,238,255,.34);

  /* --acc does NOT move: white-on-indigo is 6.26:1 whatever is behind the
     button. Only accent-as-text moves, to the lit tint (7.4:1 on --paper). */
  --acc-fg:     #8B93FF;

  --cool:       #16162E;
  --cool-2:     #14142C;
  --cool-3:     #17162E;
  --spine:      rgba(139,147,255,.34);

  --line:       rgba(255,255,255,.10);
  --line-strong:rgba(255,255,255,.20);

  /* On an unlit ground the same alphas vanish, so dark carries the mesh at
     roughly twice the weight. Same neutral direction as light: smoke and
     mauve rather than electric indigo, with one bronze note so the dark
     reads as a lit room rather than a screensaver. */
  --mesh-1:     rgba(132,126,168,.20); /* smoked violet */
  --mesh-2:     rgba(166,138,150,.13); /* mauve         */
  --mesh-3:     rgba(104,116,140,.16); /* slate         */
  --mesh-4:     rgba(168,142,116,.10); /* warm bronze   */

  --nav-glass:  rgba(12,12,26,.72);
  --nav-solid:  rgba(12,12,26,.97);
  --ph-bg:      #15152F;
  --ph-hl:      rgba(255,255,255,.06);
  --seg-shadow: 0 1px 2px rgba(0,0,0,.45);

  /* Depth reads as light, not shadow, on a dark ground: the ambient blacks
     go deeper and the spread tightens so edges stay legible. */
  --lift:
    0 1px 2px rgba(0,0,0,.44),
    0 8px 24px rgba(0,0,0,.36),
    0 28px 64px -16px rgba(0,0,0,.60);
  --lift-sm:
    0 1px 2px rgba(0,0,0,.40),
    0 4px 14px -2px rgba(0,0,0,.34);
}
}
:root[data-rn-theme="dark"] .rn {
  /* Surfaces. The light half of the page inverts to an indigo-black that is
     a step ABOVE the dark run, so the privacy act still reads as the darkest
     moment on the page rather than merging into everything around it. */
  --paper:      #0C0C1A;
  --frost:      #0C0C1A;
  --frost-2:    #12122A;
  --card:       #15152F;
  --ink:        #06060F;   /* dark run stays the floor */
  --ink-2:      #14142E;

  /* Text. Measured on --paper #0C0C1A:
     --t-strong #F4F5FF  17.6:1
     --t-body   .78 white composited -> #C0C3D6  10.7:1
     --t-mute   .60 white composited -> #9497AE   5.9:1  (clears 4.5:1) */
  --t-strong:   #F4F5FF;
  --t-body:     rgba(235,238,255,.78);
  --t-mute:     rgba(235,238,255,.60);
  --t-faint:    rgba(235,238,255,.34);

  /* --acc does NOT move: white-on-indigo is 6.26:1 whatever is behind the
     button. Only accent-as-text moves, to the lit tint (7.4:1 on --paper). */
  --acc-fg:     #8B93FF;

  --cool:       #16162E;
  --cool-2:     #14142C;
  --cool-3:     #17162E;
  --spine:      rgba(139,147,255,.34);

  --line:       rgba(255,255,255,.10);
  --line-strong:rgba(255,255,255,.20);

  /* On an unlit ground the same alphas vanish, so dark carries the mesh at
     roughly twice the weight. Same neutral direction as light: smoke and
     mauve rather than electric indigo, with one bronze note so the dark
     reads as a lit room rather than a screensaver. */
  --mesh-1:     rgba(132,126,168,.20); /* smoked violet */
  --mesh-2:     rgba(166,138,150,.13); /* mauve         */
  --mesh-3:     rgba(104,116,140,.16); /* slate         */
  --mesh-4:     rgba(168,142,116,.10); /* warm bronze   */

  --nav-glass:  rgba(12,12,26,.72);
  --nav-solid:  rgba(12,12,26,.97);
  --ph-bg:      #15152F;
  --ph-hl:      rgba(255,255,255,.06);
  --seg-shadow: 0 1px 2px rgba(0,0,0,.45);

  /* Depth reads as light, not shadow, on a dark ground: the ambient blacks
     go deeper and the spread tightens so edges stay legible. */
  --lift:
    0 1px 2px rgba(0,0,0,.44),
    0 8px 24px rgba(0,0,0,.36),
    0 28px 64px -16px rgba(0,0,0,.60);
  --lift-sm:
    0 1px 2px rgba(0,0,0,.40),
    0 4px 14px -2px rgba(0,0,0,.34);
}

/* The screenshot follows the page. Both were independent before: the shot
   keyed off prefers-color-scheme while the page was locked light. Now the
   page theme decides, and the reader's own [data-shot] choice still wins
   over both (those rules are declared later in the file). */
:root[data-rn-theme="dark"] .rn-shot:not([data-shot]) .rn-shot-light { display: none; }
:root[data-rn-theme="dark"] .rn-shot:not([data-shot]) .rn-shot-dark  { display: block; }
:root[data-rn-theme="light"] .rn-shot:not([data-shot]) .rn-shot-light { display: block; }
:root[data-rn-theme="light"] .rn-shot:not([data-shot]) .rn-shot-dark  { display: none; }

/* Theme control. Two buttons in one segmented pill, same component language
   as the screenshot appearance switch so the page has one control idiom. */
.rn-theme {
  display: inline-flex; padding: 3px; margin-left: 4px;
  background: var(--frost-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
}
.rn-theme button {
  appearance: none; border: 0; background: transparent;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: var(--r-pill);
  color: var(--t-mute); cursor: pointer;
  transition: color .2s var(--ease-io), background-color .2s var(--ease-io);
}
.rn-theme button:hover { color: var(--t-strong); }
.rn-theme button[aria-pressed="true"] {
  background: var(--card); color: var(--t-strong);
  box-shadow: var(--seg-shadow);
}
.rn-theme svg { width: 15px; height: 15px; }
.rn-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* Over the dark run the pill inverts with the rest of the bar. */
.rn-nav.is-dark .rn-theme { background: rgba(255,255,255,.06); border-color: var(--line-dark); }
.rn-nav.is-dark .rn-theme button { color: var(--t-on-dark-mute); }
.rn-nav.is-dark .rn-theme button[aria-pressed="true"] { background: rgba(255,255,255,.14); color: #fff; }

@media (max-width: 900px) { .rn-theme { margin-left: 0; } }

/* ============================================================================
   GRAFTED FROM V3
   Three components carried over onto the V1 page: the nav bar, the word-rise
   text effect, and the footer. Everything else here is V1.

   These keep their v3- prefix on purpose. They are the only v3- classes in
   the file, so it stays obvious at a glance which rules came from where and
   what can be lifted back out without touching the V1 system.

   They run on V1's tokens, not V3's. Only the handful V1 has no equivalent
   for are defined below.
   ========================================================================= */
.rn {
  --disp: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Instrument Sans", sans-serif;
  --r-lg: 24px;
  --shadow: 0 1px 2px rgba(10,10,31,.05), 0 30px 70px -30px rgba(10,10,31,.28);
  /* The footer CTA's magenta. V1's --acc is indigo and reads as a link on
     the white panel; this is the one colour the graft brings with it. */
  --ember:   #B8317F;
  --ember-2: #A02A6F;
  /* Brand wordmark only (nav + footer mark) — kept on Bricolage even after
     --disp moved to the system font stack for headings. */
  --brand: "Bricolage Grotesque", "Instrument Sans", -apple-system, BlinkMacSystemFont, sans-serif;
}

/* ---------------------------------------------------------------- nav (V3) */
/* Sticky, not fixed: it sits in the document's own box so it can be pushed
   and clipped normally. The negative margin stops it taking a slot in the
   flow. Two transitions, two jobs: colour for the light-to-dark handover
   V1 already drives through .is-dark, and transform for the hide-on-scroll
   -down. Entry settles with a small overshoot; the exit anticipates
   downward for a beat and then leaves quickly, which is what reads as a pop
   rather than a slide. CSS applies the TARGET state's transition, so the
   exit curve lives on .is-hidden. */
.v3-nav {
  position: sticky; top: 0; margin-bottom: -96px;
  z-index: 100; height: 96px; display: flex; align-items: center;
  color: var(--t-strong);
  transition: color .25s var(--ease-io),
              transform .44s cubic-bezier(.18, .89, .32, 1.06);
}
/* Drawn in: the bar narrows before it goes, so the exit has a wind-up. */
.v3-nav.is-drawn .v3-nav-inner { max-width: calc(var(--max) - 220px); }
.v3-nav.is-hidden {
  transform: translateY(calc(-100% - 24px));
  transition: transform .4s cubic-bezier(.5, -0.32, .74, .42);
}
/* Geometry is set once and never changes with scroll state. Width is the
   only thing that moves, eased long so it reads as the bar collecting
   itself before it leaves. */
.v3-nav-inner {
  display: flex; align-items: center; gap: 20px;
  width: 100%; max-width: var(--max); margin-inline: auto;
  height: 64px; padding-inline: var(--gutter);
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  transition: max-width .42s cubic-bezier(.22, 1, .28, 1),
              background-color .3s var(--ease-io), border-color .3s var(--ease-io),
              backdrop-filter .3s var(--ease-io), box-shadow .3s var(--ease-io);
}
/* The container that was missing: V1's is-stuck toggle was still firing
   (updateNav() never changed), but the glass fill it used to paint lived on
   .rn-nav, a class this bar no longer carries. Ported onto the pill itself
   rather than edge-to-edge like V1's bar, since this nav is a floating pill,
   not a full-width strip. */
.v3-nav.is-stuck .v3-nav-inner {
  background: var(--nav-glass);
  backdrop-filter: saturate(180%) blur(22px);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  border-color: var(--line);
  box-shadow: 0 1px 2px rgba(10,10,31,.04), 0 12px 28px -14px rgba(10,10,31,.22);
}
.v3-nav.is-dark.is-stuck .v3-nav-inner {
  background: rgba(10,10,31,.72);
  border-color: var(--line-dark);
}
@media (prefers-reduced-transparency: reduce) {
  .v3-nav.is-stuck .v3-nav-inner { background: var(--nav-solid); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .v3-nav.is-dark.is-stuck .v3-nav-inner { background: rgba(10,10,31,.97); }
}
.v3-back { display: inline-flex; line-height: 0; opacity: .9; transition: opacity .2s var(--ease-io); }
.v3-back:hover { opacity: 1; }
/* No display on this compound rule. At (0,1,1) it beats the (0,1,0) hides
   below and renders BOTH wordmarks side by side. display belongs on the
   classes that switch. */
.v3-back img { width: 116px; height: auto; }
.v3-mark {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--brand); font-weight: 700; font-size: 19px;
  letter-spacing: -.02em; color: inherit;
}
/* Fixed brand purple, matching the footer mark. Declared directly rather
   than inherited, so .v3-nav.is-dark's colour swap (which every OTHER nav
   element still follows) has nothing to override here: this one thing on
   the bar never changes colour. */
.v3-mark-brand { color: #6B2FB5; }
.v3-nav-right { margin-left: auto; display: inline-flex; align-items: center; gap: 12px; }
.v3-soon {
  display: inline-flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 16px; border: 1.5px solid var(--line-strong);
  border-radius: var(--r-pill); font-size: 14px; font-weight: 500;
  color: var(--t-mute); white-space: nowrap;
}
.v3-soon::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--acc-fg); }
.v3-soon-lg { height: 52px; padding: 0 22px; font-size: 15px; }

/* Over the light hero the dark wordmark is correct; over V1's dark run the
   white one is. V1 already tracks which it is in .is-dark by watching the
   [data-nav-dark] zones, so the graft reuses that rather than hardcoding
   white the way the V3 page could afford to. */
.v3-logo-light { display: block; }
.v3-logo-dark  { display: none; }
.v3-nav.is-dark { color: #FFFFFF; }
.v3-nav.is-dark .v3-logo-light { display: none; }
.v3-nav.is-dark .v3-logo-dark  { display: block; }
.v3-nav.is-dark .v3-soon { border-color: rgba(255,255,255,.5); color: #FFFFFF; }
.v3-nav.is-dark .v3-soon::before { background: var(--acc-lit); }

/* -------------------------------------------------- word rise effect (V3) */
/* Each word is an inline block with its origin on the baseline, rising out
   of a clipped line with a small per-word stagger. The words are split in
   the markup, not by script, so the effect is static and correct with JS
   off rather than absent. */
.v3-line { display: block; overflow: hidden; padding-bottom: .16em; margin-bottom: -.16em; }
.v3-w { display: inline-block; transform-origin: 50% 100%; }
.rn-js [data-words] .v3-w { transform: translateY(118%); }
.rn-js [data-words].in .v3-w {
  transform: none;
  transition: transform .85s var(--ease);
  transition-delay: calc(var(--wi, 0) * 90ms);
}

/* ------------------------------------------------------------- footer (V3) */
/* Its first stop continues V1's --ink dark run rather than cutting against
   it, so the two simply flow into one another. */
.v3-foot {
  position: relative;
  background: linear-gradient(180deg, #0B0322 0%, #1A0847 34%, #3A1470 68%, #5A1A80 100%);
  padding: 12vh var(--gutter) 56px;
}
.v3-foot-panel {
  width: 100%; max-width: var(--max); margin-inline: auto;
  background: #FFFFFF; border-radius: var(--r-lg); box-shadow: var(--shadow);
  padding: 48px 48px 40px; color: var(--t-body);
}
.v3-foot-top { display: flex; justify-content: space-between; gap: 40px; align-items: start; flex-wrap: wrap; }
.v3-foot-panel h2 { color: var(--t-strong); }
/* :not(.v3-foot-mark) is load-bearing. The wordmark IS a <p> inside this
   panel, so the bare descendant rule sits at (0,1,1) and beat
   .v3-foot-mark's (0,1,0) colour: the mark rendered in --t-mute grey no
   matter what the component asked for. Same trap as the doubled wordmarks
   and the doubled hero shot earlier in this file. */
.v3-foot-panel p:not(.v3-foot-mark) { color: var(--t-mute); max-width: 40ch; margin-top: 12px; }
/* This line runs 65 characters; the shared 40ch cap above forced it to wrap
   on every screen, including ones wide enough to hold it on one line. */
.v3-foot-top > div > p:not(.v3-cta-row) { max-width: none; white-space: nowrap; }

@media (max-width: 460px) {
  .v3-foot-top > div > p:not(.v3-cta-row) { white-space: normal; }
}
.v3-foot-links { display: grid; gap: 10px; justify-items: start; }
.v3-foot-links a { font-size: 15.5px; font-weight: 500; color: var(--t-body); transition: color .2s var(--ease-io); }
.v3-foot-links a:hover { color: var(--t-strong); }
.v3-cta-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 30px; }
.v3-btn-acc {
  display: inline-flex; align-items: center; height: 52px; padding: 0 24px;
  border-radius: var(--r-pill); font-size: 15px; font-weight: 600;
  background: #000000; color: #FFFFFF;
  transition: background-color .3s var(--ease-io);
}
.v3-btn-acc:hover { background: #262626; }
.v3-foot-mark {
  display: flex; align-items: center; gap: 18px;
  margin-top: 44px;
  /* The ramp's darkest stop, so the word and the mark are one object.
     8.0:1 on the white panel. */
  color: #6B2FB5;
  font-family: var(--brand); font-weight: 800; letter-spacing: -.035em;
  font-size: clamp(56px, 10.5vw, 150px); line-height: 1;
}
/* Sized off the nav, measured the same way both times: actual font metrics
   (fontBoundingBox / actualBoundingBox from a canvas 2D context), not a
   pixel-scan of rendered text, which is what produced 1.066em and sent the
   glyph past the cap height and past the baseline at large sizes -- visible
   once the wordmark ran at 150px. Measured this way the nav's own glyph
   sits at 0.874x its type size, and that ratio is what carries over. */
.v3-foot-mark svg { width: .874em; height: .874em; flex: none; }
.v3-foot-base {
  width: 100%; max-width: var(--max); margin: 28px auto 0;
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  font-size: 13.5px; color: rgba(255,255,255,.62);
}

@media (max-width: 820px) {
  .v3-nav { height: 72px; margin-bottom: -72px; }
  .v3-nav-inner { height: 56px; gap: 14px; }
  .v3-nav .v3-soon { display: none; }
  .v3-back, .v3-mark { min-height: 44px; display: inline-flex; align-items: center; }
  .v3-back img { width: 96px; }
  .v3-foot { padding-top: 9vh; }
  .v3-foot-panel { padding: 28px 20px; border-radius: 18px; }
  .v3-foot-top { flex-direction: column; gap: 26px; }
  .v3-foot-links a { min-height: 44px; display: inline-flex; align-items: center; }
  .v3-btn-acc { width: 100%; justify-content: center; }
  .v3-foot-mark { margin-top: 30px; gap: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .rn-js [data-words] .v3-w { transform: none; transition: none; }
  .v3-nav, .v3-nav-inner { transition: none; }
}
