/* ============================================================
   LAUNCH7 — BASE
   Reset, document type, landmarks, layout utilities.
   Everything here reads tokens.css. No raw values below this line.
   ============================================================ */

/* ---------- metric-matched fallback ----------
   The webfont is loaded async (see the <head> of every page), so first paint
   is always a system font. This face makes that system font the same size and
   shape as Bricolage Grotesque, so when the real font arrives nothing moves.

   The numbers are measured, not estimated — canvas text metrics, Chrome on
   Windows, 700 weight at 100px:

     Bricolage Grotesque   width 2237.95   ascent 93   descent 27
     Segoe UI              width 2393.80   ascent 108  descent 25

   size-adjust    = 2237.95 / 2393.80        = 93.49%
   ascent-override  = 93 / 0.9349            = 99.48%   (of the adjusted em)
   descent-override = 27 / 0.9349            = 28.88%

   MEASURED ON WINDOWS ONLY. Segoe UI is what this stack resolves to here.
   macOS (SF) and Android (Roboto) fall back to the same face with the same
   numbers, which is closer than no adjustment but is not tuned to them —
   re-measure on those platforms before claiming zero CLS there. */
@font-face{
  font-family:'Bricolage Fallback';
  src:local('Segoe UI'),local('Arial'),local('Helvetica Neue');
  size-adjust:93.49%;
  ascent-override:99.48%;
  descent-override:28.88%;
  line-gap-override:0%;
  font-weight:400 800;
}

*,*::before,*::after{box-sizing:border-box}
*{margin:0}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

body{
  background:var(--bg-page);
  color:var(--text-primary);
  font-family:var(--font-sans);
  font-size:var(--step-body);
  line-height:1.6;
  font-weight:400;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

img,svg,video{display:block;max-width:100%}
img{height:auto}
ul,ol{list-style:none;padding:0}
button,input,select,textarea{font:inherit;color:inherit}
a{color:var(--text-accent);text-decoration:none}
a:hover{text-decoration:underline}
strong,b{font-weight:700}

/* ---------- headings ----------
   One family, weight and size do the work. Tight leading on
   headings, relaxed on body — the whole hierarchy is size +
   weight + colour, never decoration. */
h1,h2,h3,h4{font-weight:700;letter-spacing:-.022em;line-height:1.08;text-wrap:balance}
h1{font-size:var(--step-h1);letter-spacing:-.032em}
h2{font-size:var(--step-h2);letter-spacing:-.026em}
h3{font-size:var(--step-h3);line-height:1.2;letter-spacing:-.014em}
h4{font-size:var(--step-sm);letter-spacing:-.008em}

/* The two-beat headline: setup in ink, payoff in accent, split on a <br>.
   Only ever the SECOND beat, and only where there is a real differentiator
   to isolate — a heading with nothing to contrast stays one monochrome
   declarative. Scoped to headings so it cannot collide with .chip .n.accent. */
h1 .accent,h2 .accent{color:var(--text-accent)}

p{text-wrap:pretty}

/* ---------- focus ----------
   Visible everywhere. Non-negotiable, and never removed by a
   component. 3px at 7.6:1 clears SC 2.4.11 and 1.4.11. */
:focus-visible{outline:3px solid var(--focus-ring);outline-offset:3px;border-radius:2px}
:focus:not(:focus-visible){outline:none}

/* ---------- skip link ---------- */
.skip{
  position:absolute;left:-9999px;top:var(--space-2);z-index:400;
  background:var(--btn-primary-bg);color:var(--btn-primary-text);
  padding:var(--space-3) var(--space-5);border-radius:var(--radius-pill);
  font-weight:600;
}
.skip:focus{left:var(--space-4);text-decoration:none}

/* ---------- layout ---------- */
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--space-5)}
.wrap-narrow{max-width:var(--wrap-narrow)}
.section{padding-block:var(--section-y)}
.section-tint{background:var(--bg-tint)}

/* Section head: eyebrow → h2 → one plain-language subhead.
   Every section on the site opens this way. */
.section-head{max-width:var(--wrap-narrow);margin-bottom:var(--space-7)}
.section-head.center{margin-inline:auto;text-align:center}
.section-head p{
  margin-top:var(--space-4);
  font-size:var(--step-body-lg);
  color:var(--text-secondary);
}

.eyebrow{
  display:block;
  font-family:var(--font-mono);
  font-size:var(--step-eyebrow);
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--text-accent);
  margin-bottom:var(--space-4);
}
.eyebrow-muted{color:var(--text-muted)}

.lede{font-size:var(--step-body-lg);color:var(--text-secondary);max-width:60ch}
.muted{color:var(--text-secondary)}
.small{font-size:var(--step-xs);color:var(--text-muted)}
.mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.measure{max-width:65ch}

/* utility grids — the only three column patterns the site uses */
.grid{display:grid;gap:var(--space-5)}
@media(min-width:640px){.grid-2{grid-template-columns:repeat(2,1fr)}}
@media(min-width:900px){.grid-3{grid-template-columns:repeat(3,1fr)}}
@media(min-width:640px){.grid-4{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1000px){.grid-4{grid-template-columns:repeat(4,1fr)}}

.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}

/* ---------- scroll reveal ----------
   Opt-in per element. Under reduced-motion the class is inert and
   content is simply present — see site.js, which never adds it. */
.js .rv{opacity:0;transform:translateY(14px);transition:opacity var(--dur-slow) var(--ease),transform var(--dur-slow) var(--ease)}
.js .rv.on{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){
  .js .rv{opacity:1;transform:none;transition:none}
}

/* ============================================================
   MOTION
   ------------------------------------------------------------
   Everything here is decoration. Nothing in this block creates,
   positions, or reveals content that is otherwise unreachable —
   strip the whole section and every page still reads correctly.

   Two rules, both non-negotiable:

   1. Reduced motion wins. Every animation below is either inside
      a `not (prefers-reduced-motion: reduce)` query or has an
      explicit reduce override. The rest of this stylesheet
      already honours that preference; new motion must not be the
      exception that breaks it.
   2. Durations come from the tokens (--dur-fast 160ms /
      --dur-base 240ms / --dur-slow 320ms). The product claims to
      be fast. A 500ms transition argues the opposite, so nothing
      here exceeds --dur-slow.
   ============================================================ */

/* ---------- page transitions ----------
   Cross-document view transitions. Chromium-only at time of
   writing (this includes Brave and Edge); Safari and Firefox
   simply ignore the whole at-rule and navigate instantly, which
   is the pre-existing behaviour. There is no JS fallback on
   purpose — a scripted fade that intercepts clicks can strand a
   visitor on a blank page if anything throws, and an instant
   page load is not a broken experience.

   The nav is given its own transition name so it is captured
   separately and holds still while the page body swaps. */
@view-transition { navigation: auto; }

::view-transition-old(root) {
  animation: l7-vt-out var(--dur-fast) var(--ease) both;
}
::view-transition-new(root) {
  animation: l7-vt-in var(--dur-base) var(--ease) both;
}

.site-nav { view-transition-name: l7-nav }

@keyframes l7-vt-out { to { opacity: 0 } }
@keyframes l7-vt-in  { from { opacity: 0; transform: translateY(8px) } }

/* Reduced motion keeps the transition machinery but removes the movement,
   which resolves to an ordinary instant navigation. Written as an override
   rather than by wrapping the at-rule in a negated media query, both to
   match how the rest of this stylesheet handles the preference and because
   `@view-transition` nested inside `@media` is thinner ground than a plain
   top-level declaration. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(*) { animation: none !important }
}

/* ---------- staggered reveal ----------
   site.js numbers the immediate children of a revealing group so
   a row of cards arrives in sequence rather than as one slab.
   Capped in JS so a long list never builds a visible queue. */
.js .rv { transition-delay: calc(var(--rv-i, 0) * 55ms) }
@media (prefers-reduced-motion: reduce) {
  .js .rv { transition-delay: 0s }
}

/* ---------- press feedback ----------
   The site had hover states but nothing on activation, so clicks
   registered as page loads rather than as button presses. One
   pixel of travel is enough to feel mechanical; anything more
   reads as sloppy. Pointer-only — :active on touch fires during
   scroll and makes lists feel twitchy. */
@media (hover: hover) and (pointer: fine) {
  .btn, .link-arrow, .card-hover {
    transition: transform var(--dur-fast) var(--ease),
                box-shadow var(--dur-fast) var(--ease);
  }
  .btn:active { transform: translateY(1px) scale(.995) }
  .card-hover:active { transform: translateY(0) }
}
@media (prefers-reduced-motion: reduce) {
  .btn:active, .card-hover:active { transform: none }
}

/* ---------- focus ----------
   Keyboard focus should be as considered as hover. Never
   animates opacity or size — a focus ring that fades in is a
   focus ring you can miss. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--l7-blue);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ---------- nav underline ----------
   Runs on a pseudo-element so it never reflows the text. */
@media (hover: hover) and (pointer: fine) {
  .nav-links a { position: relative }
  .nav-links a::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -2px;
    height: 2px;
    background: var(--l7-blue);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-base) var(--ease);
  }
  .nav-links a:hover::after,
  .nav-links a[aria-current="page"]::after { transform: scaleX(1) }
}
@media (prefers-reduced-motion: reduce) {
  .nav-links a::after { transition: none }
}
