/* =============================================================================
   site-chrome.css  ·  the shared public-site header + footer
   =============================================================================
   ONE source of truth for the site chrome. Extracted VERBATIM (2026-07-27) from
   landing.html, which is the design source of truth: the `sh-` header block and
   the `act9-` footer block used to be inline in that template and are now here,
   consumed by templates/components/site_header.html and site_footer.html.

   Depends on site-tokens.css (link it BEFORE this file).

   CHROME-PRIVATE TOKENS (--sh-z / --sh-space-6 / --sh-space-7)
   ------------------------------------------------------------------------
   Three names the header used from the contract also exist in
   design-system.css with different values:
       --z-header   contract 600   ·  design-system 350
       --space-6    contract 40px  ·  design-system 1.5rem (24px)
       --space-7    contract 64px  ·  design-system 2rem   (32px)
   Public pages built on base.html load BOTH stylesheets and design-system.css
   has to keep winning those names (its spacing utilities are built on them),
   so the header cannot read them directly or it would render one way on / and
   another way on /faq. It reads the mirrors below instead, whose values ARE the
   contract's. (--space-3 is 12px in both, so it is still read directly.)
   Change the contract and these three follow by hand - they are deliberately
   the only hand-copied numbers in the file.
   ============================================================================= */

:root {
  --sh-z:        600;    /* == contract --z-header */
  --sh-space-6:  40px;   /* == contract --space-6  */
  --sh-space-7:  64px;   /* == contract --space-7  */

  /* THE ANCHOR OFFSET (W3.3). The header is `position:fixed`, so an anchor that
     scrolls its target's top edge to scrollY parks the section's first rows
     BEHIND it — measured live at 62px on every `#anchor` on the landing. This
     is the amount every anchor route must hold back, and it is declared HERE,
     at :root, rather than on .site-header (whose own --sh-h is scoped to the
     element and unreadable from a section or from JS). It is the glass
     header's real height, i.e. the height it always has by the time an anchor
     scroll settles: 62px, and 56px on the <=620px bar. Consumers:
       · `scroll-margin-top: var(--sh-anchor)` on every anchor target
         (landing.html) — covers the browser's own hash jump + scrollIntoView
       · headerOffset() in landing.html and site-chrome.js — Lenis and manual
         window.scrollTo do NOT honour scroll-margin, so they subtract this.
     One number, one place: CSS and JS cannot drift. */
  --sh-anchor: 62px;
}
@media (max-width: 620px) { :root { --sh-anchor: 56px; } }

/* =====================================================================
   SITE HEADER  ·  the page's ONE navigation chrome        (namespace sh-)
   ---------------------------------------------------------------------
   Carries the retired landing template's navigation (Features · Pricing ·
   Knowledge Hub · FAQ · About · Contact · Book a Demo, plus Sign In and the
   signup CTA) in this page's design language rather than the old one's.
   Three things define it:

   AURORA-GLASS.  Over the film it is pure content: no plate, no border, the
   hero reads edge to edge behind it. Past ~80% of the first viewport it
   gains a plate (translucent navy + blur + saturate) and an accent hairline,
   and the bar itself compacts 72 -> 62px. The plate is a pseudo-element, so
   the state change is ONE compositable opacity, never a repaint of the bar.

   ONE MARKUP, TWO LAYOUTS.  `.sh-drawer` is `display:contents` on desktop —
   the nav and the actions are direct flex children of the bar — and becomes
   a fixed full-viewport glass sheet at <=1100px. Nothing is duplicated, so
   the desktop bar and the mobile menu can never drift apart.

   BOOT INTERPLAY.  The header must not be visible during the ignition, so
   the scoped script arms `html.sh-armed` the moment it parses and the
   preloader's exit path adds `html.boot-done`: the header rises in WITH the
   wipe. Arming from JS (rather than a plain `opacity:0` default) is what
   keeps it visible on a no-JS render, where there is no preloader either.

   Z: --z-header (600) — over the rail, under the preloader. See the z-bands.
   ===================================================================== */
.site-header {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: var(--sh-z);
  display: flex; align-items: center;
  gap: clamp(16px, 2.2vw, 34px);
  height: var(--sh-h);
  padding: 0 var(--sh-px);
  font-family: var(--font-ui);
  --sh-h: 72px;
  --sh-px: clamp(18px, 5vw, 64px);   /* == the engine topbar's own gutter */
  transition: height var(--dur-3) var(--ease-out);
}
/* the glass plate.
   z-index:0 is LOAD-BEARING, not decoration. The plate is an absolutely
   positioned pseudo-element, so at `z-index:auto` it paints in the
   positioned-descendants phase — i.e. ON TOP of every unpositioned flex
   child of the bar. Caught in QA: with the plate at auto, `.sh-actions`
   (Sign In + the CTA) rendered *underneath* a 72%-opaque blurred pane and
   vanished the moment the header went glassy, while the nav links (which
   happen to be position:relative for their underline) survived. Pinning the
   plate to 0 and every content group to 2 makes the stack explicit instead
   of accidental. */
.site-header::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: rgba(7, 10, 28, .72);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out);
}
/* the hairline: white at the edges, accent through the middle */
.site-header::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  z-index: 0; pointer-events: none;
  background: linear-gradient(90deg,
    rgba(255,255,255,.05) 0%, var(--accent-line) 50%, rgba(255,255,255,.05) 100%);
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-out);
}
.site-header.is-glass { height: 62px; }
.site-header.is-glass::before,
.site-header.is-glass::after { opacity: 1; }

/* boot interplay — see the block comment */
html.sh-armed .site-header { opacity: 0; transform: translateY(-14px); pointer-events: none; }
html.sh-armed.boot-done .site-header {
  opacity: 1; transform: none; pointer-events: auto;
  transition: opacity var(--dur-5) var(--ease-out),
              transform var(--dur-5) var(--ease-out),
              height var(--dur-3) var(--ease-out);
}

/* ---- brand ---------------------------------------------------------
   pose-emblem.png at 30px, the SAME asset and the same drop-shadow the act4
   terminal badge uses at 31px (BEAT 3 · Emblem). Checked against the other
   two baked poses at header size: pose-front carries ~13% dead margin so it
   renders visibly smaller in the same box, and the three-quarter pose reads
   as a skewed slab once the perspective is under ~40px. The emblem is the
   pose that was baked to survive being small — so the header uses it, and
   the page has ONE small-mark treatment rather than two. */
.sh-brand {
  position: relative; z-index: 2; flex: 0 0 auto;
  display: flex; align-items: center; gap: 9px;
  text-decoration: none; color: var(--ink-1);
}
.sh-brand__mark {
  width: 30px; height: 30px; display: block; object-fit: contain;
  filter: drop-shadow(0 2px 8px rgba(0, 242, 254, .32));
  transition: transform var(--dur-2) var(--ease-out);
}
.sh-brand__name {
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--type-d5); letter-spacing: var(--track-tight);
  line-height: 1;
}
.sh-brand:hover .sh-brand__mark { transform: translateY(-1px) scale(1.05); }

/* every content group sits above the plate — see the ::before note */
.sh-brand, .sh-nav, .sh-actions, .sh-burger { position: relative; z-index: 2; }

/* ---- nav ------------------------------------------------------------ */
.sh-drawer { display: contents; }
.sh-nav {
  display: flex; align-items: center;
  gap: clamp(13px, 1.7vw, 26px);
  margin-inline: auto;                 /* optically centred between the two ends */
}
.sh-nav__link {
  position: relative; white-space: nowrap; text-decoration: none;
  color: var(--ink-3);
  font-size: var(--type-u5); font-weight: 500; line-height: 1;
  padding: 7px 1px;
  transition: color var(--dur-2) var(--ease-out);
}
.sh-nav__link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--grad-accent-x);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform var(--dur-2) var(--ease-out);
}
.sh-nav__link:hover { color: var(--ink-1); }
.sh-nav__link:hover::after { transform: scaleX(1); }
/* W5.6 · POINTER TARGETS IN THE BAR.
   Every control in the header paints shorter than 44px: the nav links are 28
   (14px text + 7px padding), the two action pills 34. The fix has to be a hit
   expander rather than more padding, because padding would push the links'
   ::after underline down with them and would grow the pills' painted capsule.
   ::before is transparent and paints nothing, so the bar looks identical.
     · links  -> 44 tall (-8 block); -6 inline is half the 24px nav gap at
       1440, so two neighbours' zones meet and never overlap.
     · pills  -> 44 tall (-5 block); -5 inline stays inside the 12px
       (--space-3) gap between Sign In and the CTA.
   The links' expander is deliberately BEHIND the underline: ::after is declared
   later, so both pseudos exist without either being clipped. */
.sh-nav__link::before { content: ""; position: absolute; inset: -8px -6px; }
.sh-signin, .sh-cta { position: relative; }
.sh-signin::before, .sh-cta::before { content: ""; position: absolute; inset: -5px; border-radius: var(--r-pill); }
/* .sh-brand is already position:relative (see the brand block above). */
.sh-brand::before { content: ""; position: absolute; inset: -7px -6px; }

/* ---- actions --------------------------------------------------------- */
.sh-actions { display: flex; align-items: center; gap: var(--space-3); flex: 0 0 auto; }
.sh-signin, .sh-cta {
  text-decoration: none; white-space: nowrap; line-height: 1;
  font-size: var(--type-u5); border-radius: var(--r-pill);
  transition: color var(--dur-2) var(--ease-out),
              background var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
.sh-signin {
  color: var(--ink-2); font-weight: 500; padding: 9px 15px;
  border: 1px solid rgba(255, 255, 255, .14);
}
.sh-signin:hover {
  color: var(--ink-1); border-color: var(--accent-line-hi); background: var(--accent-wash);
}
/* brand law: cyan gradient, DARK ink, never white */
.sh-cta {
  color: #04121a; font-weight: 600; padding: 10px 20px;
  background: var(--grad-accent);
  box-shadow: 0 6px 18px rgba(0, 242, 254, .16);
}
.sh-cta:hover { transform: translateY(-1px); box-shadow: 0 10px 26px var(--accent-glow); }

/* ---- hamburger (mobile only) ---------------------------------------- */
.sh-burger {
  display: none; position: relative; z-index: 2;
  margin-left: auto; width: 42px; height: 42px; padding: 0;
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background: transparent; border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-sm);
  place-items: center; align-content: center; justify-items: center;
  gap: 5px;
  transition: border-color var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.sh-burger span {
  display: block; width: 18px; height: 1.5px; border-radius: var(--r-xs);
  background: var(--ink-1);
  transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out);
}
/* W5.6 · 42x42 painted -> 44x44 hit. One pixel of transparent margin each side;
   the burger is the only control in its corner, so nothing can be shadowed. */
.sh-burger::before { content: ""; position: absolute; inset: -1px; }
.site-header.is-open .sh-burger { border-color: var(--accent-line-hi); background: var(--accent-wash); }
.site-header.is-open .sh-burger span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.site-header.is-open .sh-burger span:nth-child(2) { opacity: 0; }
.site-header.is-open .sh-burger span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* focus is never removed, only re-drawn in the brand accent */
.sh-brand:focus-visible, .sh-nav__link:focus-visible,
.sh-signin:focus-visible, .sh-cta:focus-visible, .sh-burger:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm);
}

/* ---- the drawer  ·  <=1100px  (<=1360px for `is-dense`) --------------
   1100, not 860: the full bar is ~1030px wide with all seven links and both
   actions, so between 860 and 1100 it would be legible but cramped against
   the logo. Everything at or below the breakpoint gets the sheet.

   `is-dense` (see the DENSE VARIANT block below) collapses at 1360 instead,
   because nine section labels are a much wider bar than seven route links.
   The rules for that range are a deliberate COPY of the ones below, in their
   own `min-width: 1101px` query, because a media query's threshold cannot be
   varied by a class and the alternative (a style query, or resetting every
   property back to the desktop layout for `:not(.is-dense)`) is more fragile
   than 25 duplicated lines. EDIT THE TWO BLOCKS TOGETHER. */
@media (max-width: 1100px) {
  .sh-burger { display: grid; }
  .sh-drawer {
    position: fixed; inset: 0; z-index: 1;
    display: flex; flex-direction: column; justify-content: center;
    gap: var(--sh-space-6);
    padding: calc(var(--sh-h) + var(--sh-space-6)) var(--sh-px) var(--sh-space-7);
    background: rgba(6, 9, 24, .94);
    backdrop-filter: blur(22px) saturate(140%);
    -webkit-backdrop-filter: blur(22px) saturate(140%);
    opacity: 0; pointer-events: none; visibility: hidden;
    transition: opacity var(--dur-3) var(--ease-out),
                visibility 0s linear var(--dur-3);
  }
  .site-header.is-open .sh-drawer {
    opacity: 1; pointer-events: auto; visibility: visible;
    transition: opacity var(--dur-3) var(--ease-out);
    /* the sheet is the whole viewport, so this IS the touch lock */
    touch-action: none; overscroll-behavior: contain;
  }
  .sh-nav {
    flex-direction: column; align-items: flex-start;
    gap: clamp(14px, 2.6vh, 24px); margin: 0;
  }
  /* stacked links are DISPLAY type, and rise on the contract's stagger */
  .sh-nav__link {
    font-family: var(--font-display); font-weight: 500;
    font-size: var(--type-u1); letter-spacing: var(--track-display);
    color: var(--ink-1); padding: 2px 1px;
    /* W5.6 · in the sheet the links are already display-sized (24-32px tall) and
       the stack gap bottoms out at 14px, so the bar's -8px expander would make
       two neighbours' zones overlap. -4px keeps every link comfortably past 44
       with 6px of clear air between zones. */
    opacity: 0; transform: translateY(18px);
    transition: opacity var(--dur-4) var(--ease-out),
                transform var(--dur-4) var(--ease-out),
                color var(--dur-2) var(--ease-out);
  }
  .sh-actions {
    flex-direction: column; align-items: stretch;
    width: min(360px, 100%); gap: var(--space-3);
    opacity: 0; transform: translateY(18px);
    transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
    transition-delay: calc(7 * var(--stagger-unit));
  }
  .site-header.is-open .sh-nav__link,
  .site-header.is-open .sh-actions { opacity: 1; transform: none; }
  .site-header.is-open .sh-nav__link {
    transition-delay: calc(var(--stagger-lead) + var(--i, 0) * var(--stagger-unit));
  }
  /* W5.6 · measured in the sheet at 390x844: the link box is 28px tall (--type-u1
     clamps to its 24px floor below ~833px wide, plus 2px padding) on a pitch that
     bottoms out at 42px (28 + the gap's 14px floor, reached at ~538px viewport
     height, i.e. inside the 200%-zoom case). -7px is therefore the largest
     expander that cannot make two neighbours' zones overlap: 42px tall, up from
     28. The AAA 44 is unreachable in the sheet without respacing it. */
  .sh-nav__link::before { inset: -7px -6px; }
  .sh-signin, .sh-cta { text-align: center; padding: 15px 20px; font-size: var(--type-u4); }
  /* The rail needs no hiding rule: it lives at --z-rail (500) and the whole
     header — sheet included — is one stacking context at --z-header (600). */
}
@media (max-width: 620px) { .site-header { --sh-h: 62px; } .site-header.is-glass { height: 56px; } }

/* =====================================================================
   DENSE VARIANT  ·  `.site-header.is-dense`      (opt-in, landing only)
   ---------------------------------------------------------------------
   2026-08-03 · Glass Lane's skeleton V1 makes the landing's top bar a
   section-anchor menu over all NINE sections (Mirror Ai / Research
   Intelligence / Tailored Research / Experience / The Team / Powered By /
   FAQ / Subscribe / Socials). That is 94 characters of nav, against the
   ~55 the bar was designed around, and it does not fit next to the brand
   lockup and Sign In at the default step and gap.

   OPT-IN ON PURPOSE. The class is rendered only when a caller passes
   `header_dense` (templates/landing.html does; nothing else does), so the
   header on every other public page is byte-for-byte what it was. Two
   changes, both scoped to the class:

     1. The desktop bar gets a tighter gap and the --type-m2 step (12px)
        instead of --type-u5 (14px). Confined to `min-width: 1361px`, which
        is the only range where a dense header HAS a desktop bar - without
        that guard it would also shrink the drawer sheet's display-sized
        links, which are 24-32px by design.
     2. The drawer breakpoint moves 1100 -> 1360.

   KNOWN GAP: site-chrome.js closes an open sheet on a
   `matchMedia('(max-width: 1100px)')` change, so a dense header resized
   ACROSS 1360 with the sheet open keeps it open until the next close. The
   sheet stays fully operable (burger, Escape, link click all still close
   it) and site-chrome.js is outside this workpackage's scope; noted here
   so it is found rather than rediscovered.
   ===================================================================== */
@media (min-width: 1361px) {
  .site-header.is-dense .sh-nav { gap: clamp(12px, 1.25vw, 22px); }
  .site-header.is-dense .sh-nav__link { font-size: var(--type-m2); }
}
/* the 1100px drawer block, re-scoped to the dense header's wider range.
   KEEP IN SYNC with `@media (max-width: 1100px)` above. */
@media (min-width: 1101px) and (max-width: 1360px) {
  .site-header.is-dense .sh-burger { display: grid; }
  .site-header.is-dense .sh-drawer {
    position: fixed; inset: 0; z-index: 1;
    display: flex; flex-direction: column; justify-content: center;
    gap: var(--sh-space-6);
    padding: calc(var(--sh-h) + var(--sh-space-6)) var(--sh-px) var(--sh-space-7);
    background: rgba(6, 9, 24, .94);
    backdrop-filter: blur(22px) saturate(140%);
    -webkit-backdrop-filter: blur(22px) saturate(140%);
    opacity: 0; pointer-events: none; visibility: hidden;
    transition: opacity var(--dur-3) var(--ease-out),
                visibility 0s linear var(--dur-3);
  }
  .site-header.is-dense.is-open .sh-drawer {
    opacity: 1; pointer-events: auto; visibility: visible;
    transition: opacity var(--dur-3) var(--ease-out);
    touch-action: none; overscroll-behavior: contain;
  }
  .site-header.is-dense .sh-nav {
    flex-direction: column; align-items: flex-start;
    gap: clamp(14px, 2.6vh, 24px); margin: 0;
  }
  .site-header.is-dense .sh-nav__link {
    font-family: var(--font-display); font-weight: 500;
    font-size: var(--type-u1); letter-spacing: var(--track-display);
    color: var(--ink-1); padding: 2px 1px;
    opacity: 0; transform: translateY(18px);
    transition: opacity var(--dur-4) var(--ease-out),
                transform var(--dur-4) var(--ease-out),
                color var(--dur-2) var(--ease-out);
  }
  .site-header.is-dense .sh-actions {
    flex-direction: column; align-items: stretch;
    width: min(360px, 100%); gap: var(--space-3);
    opacity: 0; transform: translateY(18px);
    transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
    transition-delay: calc(7 * var(--stagger-unit));
  }
  .site-header.is-dense.is-open .sh-nav__link,
  .site-header.is-dense.is-open .sh-actions { opacity: 1; transform: none; }
  .site-header.is-dense.is-open .sh-nav__link {
    transition-delay: calc(var(--stagger-lead) + var(--i, 0) * var(--stagger-unit));
  }
  .site-header.is-dense .sh-nav__link::before { inset: -7px -6px; }
  .site-header.is-dense .sh-signin,
  .site-header.is-dense .sh-cta { text-align: center; padding: 15px 20px; font-size: var(--type-u4); }
}

/* Scroll lock while the sheet is open. Three belts, because each covers a
   different input: JS stops Lenis (wheel + keys on the smooth path), the
   sheet takes `touch-action:none` (the finger, which Lenis leaves native),
   and the root goes overflow:hidden (everything else, incl. reduced motion
   where there is no Lenis at all).
   The SHORTHAND is deliberate. This page sets `html,body{overflow-x:clip}`
   to keep position:sticky alive for the act5 orbit, and an `overflow-y`
   longhand written against that resolves back to clip rather than to a
   lock. Both axes at hidden is unambiguous. On the Lenis path this rule
   never actually wins — Lenis's own `.lenis.lenis-stopped{overflow:clip}`
   (0,2,0) outranks it and does the locking itself — which is exactly why
   this belt exists for the path where Lenis is not there at all. In force
   only while the sheet is up, so sticky has nothing to do meanwhile.
   Verified with a real PageDown at 390px: scrollY held at 0. */
html.sh-locked { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .site-header, .site-header::before, .site-header::after,
  .sh-brand__mark, .sh-nav__link, .sh-nav__link::after,
  .sh-signin, .sh-cta, .sh-burger, .sh-burger span,
  .sh-drawer, .sh-actions,
  html.sh-armed.boot-done .site-header { transition: none !important; }
  .sh-cta:hover { transform: none; }
  .site-header.is-open .sh-nav__link { transition-delay: 0s; }
  @media (max-width: 1100px) {
    .sh-nav__link, .sh-actions { opacity: 1; transform: none; }
    .sh-drawer { opacity: 0; }        /* visibility still gates it */
  }
  /* same, for the range where only a dense header is in its sheet */
  @media (min-width: 1101px) and (max-width: 1360px) {
    .site-header.is-dense .sh-nav__link,
    .site-header.is-dense .sh-actions { opacity: 1; transform: none; }
    .site-header.is-dense .sh-drawer { opacity: 0; }
  }
}

/* ---------------------------------------------------------------------------
   PER-PAGE VARIANTS  (added by the unification WP - not present on landing)
   ---------------------------------------------------------------------------
   `is-solid` is the state every page EXCEPT the landing starts in: there is no
   hero film underneath, so the header is glass from the first paint. It is the
   same visual as `is-glass`, expressed as its own class so it needs no JS and
   cannot flash transparent before a scroll handler runs. site-chrome.js skips
   the scroll sync entirely on a solid header.
   --------------------------------------------------------------------------- */
.site-header.is-solid { height: 62px; }
.site-header.is-solid::before,
.site-header.is-solid::after { opacity: 1; }
@media (max-width: 620px) { .site-header.is-solid { height: 56px; } }

/* Active page. Same treatment as :hover, held on - the nav never invents a
   second highlight vocabulary. aria-current="page" is on the same element. */
.sh-nav__link.is-active { color: var(--ink-1); }
.sh-nav__link.is-active::after { transform: scaleX(1); }

/* Content offset. The landing's hero is designed to run UNDER the header, so
   landing never gets this class; every other page does, and reserves exactly
   the solid header's height. */
body.has-site-header { padding-top: 62px; }
@media (max-width: 620px) { body.has-site-header { padding-top: 56px; } }

/* Minimal variant (sign-in): brand + nothing else, so the burger must go too,
   or a <=1100px viewport shows a hamburger that opens an empty sheet. With the
   nav gone, its `margin-inline:auto` goes with it — the actions have to claim
   the free space themselves or they sit glued to the wordmark. */
/* `!important` here is not specificity laziness — it is the only thing that
   beats another sheet's `!important`. responsive.css carries a blanket
   accessibility rule:
       @media (max-width: 767px) { button, … { display: inline-flex !important } }
   which matches .sh-burger (a <button>) and outranks any unflagged declaration
   no matter how specific. The result was a hamburger visible at <=767px on
   every minimal header, opening nothing at all: below 1100px the minimal
   drawer is `display: contents`, so there is no sheet to toggle. Live on
   /signin before this line, and it would have shipped to all six onboarding
   pages with the chrome migration. Blast radius is exactly the minimal
   variant; the full header still gets its burger from the 1100px rule. */
.site-header.is-minimal .sh-burger { display: none !important; }
.site-header.is-minimal .sh-actions { margin-left: auto; }
/* …and with no burger there is nothing to open the sheet, so below the drawer
   breakpoint the minimal header stays a row: the drawer is un-sheeted rather
   than left as an invisible full-viewport panel holding the only CTA. */
@media (max-width: 1100px) {
  .site-header.is-minimal .sh-drawer {
    position: static; display: contents;
    background: none; backdrop-filter: none; -webkit-backdrop-filter: none;
    opacity: 1; visibility: visible; pointer-events: auto; padding: 0;
    transition: none;
  }
  .site-header.is-minimal .sh-actions {
    flex-direction: row; align-items: center; width: auto;
    opacity: 1; transform: none; transition: none; transition-delay: 0s;
  }
  .site-header.is-minimal .sh-signin,
  .site-header.is-minimal .sh-cta { padding: 9px 15px; font-size: var(--type-u5); }
}

/* =============================================================================
   ACT9 SHARED PRIMITIVES + SITE FOOTER                     (namespace act9-)
   =============================================================================
   W6.7 · THIS IS NOW THE SINGLE SOURCE for the act9 primitives.

   Until this pass, six rule pairs existed TWICE: once here, re-scoped under
   `.act9-footer`, and once inline in templates/landing.html for that page's
   pricing + final-CTA sections. Identical declarations, two files, and the
   pairs had to be edited in lockstep — the scoped tokens, the universal
   box-sizing, `.act9-wrap`, `.act9-reveal`, `.act9-reveal.is-in`, `.act9-rise`
   / `.act9-reveal.is-in .act9-rise`, and the reduced-motion override further
   down. The footer scoping was the only thing keeping them apart, and it bought
   nothing: the classes are used by exactly two live templates, this file's
   companion components/site_footer.html and landing.html, and they want the
   same values.

   So the selectors below simply name all three hosts, the landing's inline
   copies are gone, and `.act9-wrap` / `.act9-reveal` / `.act9-rise` are
   declared unscoped (they only ever appear inside one of the three). Cascade is
   unchanged: this file is a <head> stylesheet, so landing.html's remaining
   inline act9 rules still come later and still win where they deliberately
   override — e.g. `.act9-final-inner`'s narrower max-width over `.act9-wrap`.

   REVEAL: `.act9-reveal` starts at opacity 0 and is switched on by the act9
   IntersectionObserver, which only exists on the landing page. Everywhere else
   site_footer.html renders WITHOUT the reveal classes (footer_reveal=false), so
   the footer is simply there. See the component for the flag.
   ============================================================================= */
/* 2026-07-31 · #act9-socials added — the landing's new "Connect with Mirror Ai"
   band (Tanya, skeleton v1). These tokens are SCOPED, not :root, so a new act9
   host that is missing from this selector gets no --act9-t1/-t3/-hair at all and
   every colour in it falls back to the UA default, i.e. link-blue text on navy.
   Any future act9- section must be added here and to the box-sizing list below.
   The section itself lives in templates/landing.html; only the token scope is
   shared, which is why the change lands in this file and bumps its ?v=.
   Same pass: `#act9-closing` -> `.act9-closing`. That band was split into two
   sibling sections (#act9-faq and #act9-subscribe) which both wear the class, so
   keying the scope off the class covers both and covers the next one for free —
   an id list is exactly the thing someone forgets to extend, and the failure mode
   is silent link-blue text. */
.act9-footer,
#act9-pricing, #act9-final, .act9-closing, #act9-socials {
  --act9-cyan: var(--accent);
  --act9-blue: var(--accent-2);
  --act9-ink: #04121a;
  --act9-t1: var(--ink-1);
  --act9-t2: var(--ink-2);
  --act9-t3: var(--ink-3);
  --act9-t4: var(--ink-4);
  --act9-hair: rgba(255,255,255,.08);
  box-sizing: border-box;
}
.act9-footer *,
#act9-pricing *, #act9-final *, .act9-closing *, #act9-socials * { box-sizing: border-box; }

.act9-wrap { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 24px; }

/* reveal (landing only - see the block comment) */
.act9-reveal {
  opacity: 0;
  transform: translateY(52px);
  transition: opacity var(--dur-6) var(--ease-out), transform var(--dur-6) var(--ease-out);
  will-change: opacity, transform;
}
/* `will-change: auto` once the reveal has played. Two reasons: it releases the
   compositor layer the hint reserved, and — the reason it is not optional — a
   live `will-change: transform` keeps the element a containing block AND a
   stacking context for `position: fixed` descendants FOREVER, even after
   opacity/transform have settled back to their identity values. The disclaimer
   modal used to be a descendant here and was trapped by exactly that. */
.act9-reveal.is-in { opacity: 1; transform: none; will-change: auto; }
.act9-rise {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--dur-5) var(--ease-out), transform var(--dur-5) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger-unit));
}
.act9-reveal.is-in .act9-rise { opacity: 1; transform: none; }

/* ===== FOOTER ===== */
.act9-footer {
  background: linear-gradient(180deg, #080b1e 0%, #05060f 100%);
  padding: 80px 0 48px;
  border-top: 1px solid var(--act9-hair);
  font-family: var(--font-ui);
}
.act9-foot-grid {
  display: grid;
  grid-template-columns: 1.7fr repeat(4, 1fr);
  gap: 40px;
  padding-bottom: 52px;
  border-bottom: 1px solid var(--act9-hair);
}
.act9-wordmark { font-family: var(--font-display); font-size: var(--type-d5); font-weight: 600; letter-spacing: var(--track-tight); color: var(--act9-t1); }
.act9-wordmark-ai {
  background: var(--grad-accent);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.act9-foot-blurb { font-size: var(--type-u6); line-height: var(--lh-body); color: var(--act9-t3); margin: 16px 0 22px; max-width: 300px; }
.act9-socials { display: flex; gap: 10px; }
.act9-social {
  position: relative;                        /* W5.6 · anchors the hit expander */
  display: inline-flex; align-items: center; justify-content: center;
  /* 2026-07-30 · 34 -> 44px (Tanya, 28 Jul: enlarge the footer social icons for
     visibility). The pill now meets the 44px tap target on its own; the ::before
     expander below is kept because the pitch grew with it — see the note there. */
  width: 44px; height: 44px;
  border-radius: var(--r-pill);
  border: 1px solid var(--act9-hair);
  color: var(--act9-t3);
  transition: color var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out), background var(--dur-2) var(--ease-out);
}
.act9-social:hover {
  color: var(--act9-ink);
  background: var(--grad-accent);
  border-color: transparent;
  transform: translateY(-2px);
}
/* W5.6 — the pills paint 44x44 on a 54px pitch (44 + the 10px flex gap), so
   -5px on every side lands exactly on 54x54 with the neighbours' zones touching
   but never overlapping. Nothing is repainted. (Was 34 on a 44px pitch before
   the 2026-07-30 size bump; the relationship is unchanged, both numbers grew by
   the same 10px, and the pill alone already clears 44x44 now.) */
.act9-social::before { content: ""; position: absolute; inset: -5px; border-radius: var(--r-pill); }
/* W5.4 — the footer's links and social pills carried NO focus rule at all, so
   they fell through to Chromium's UA ring: rgb(16,16,16) drawn on a #05060f
   footer, i.e. invisible. The site's own accent ring, same treatment the header
   has had since the chrome was unified. */
.act9-foot-col a:focus-visible,
.act9-social:focus-visible,
.act9-ent-link:focus-visible,
.act9-foot-legal a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm);
}

/* W5.10 — these were <h4> under the page's <h2>, a skipped level on every
   public page (landing: h2 final CTA -> h4 Platform; /contact: h2 -> h4).
   Promoted to <h3> in components/site_footer.html; the selector moves with it.
   Nothing about the rendered type changes — the size comes from --type-u6. */
.act9-foot-col h3 {
  font-size: var(--type-u6); letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--act9-t4); margin: 0 0 18px; font-weight: 600;
}
.act9-foot-col ul { list-style: none; margin: 0; padding: 0; }
.act9-foot-col li { margin: 0 0 12px; }
.act9-foot-col a {
  position: relative;                        /* W5.6 · anchors the hit expander */
  font-size: var(--type-u5); color: var(--act9-t2); text-decoration: none;
  transition: color var(--dur-2) var(--ease-out);
}
.act9-foot-col a:hover { color: var(--act9-cyan); }
/* W5.6 — a footer link's text box is 18px tall and the rows sit on a 30px
   pitch (18 + the 12px li margin). 30px is therefore the ceiling: a taller zone
   would overlap the neighbour above and make the wrong link clickable. This
   takes every footer link from 18px to the full 30px row, which clears WCAG
   2.5.8's 24px minimum outright; the 44px of 2.5.5 (AAA) is not reachable here
   without respacing the footer, which is a visual change. */
.act9-foot-col a::before { content: ""; position: absolute; inset: -6px -4px; }

.act9-foot-bottom { padding-top: 28px; }
.act9-foot-legal p { margin: 0 0 6px; }
.act9-copy { font-size: var(--type-u6); color: var(--act9-t3); }
.act9-abn { font-family: var(--font-mono); font-size: var(--type-m2); color: var(--act9-t4); }
.act9-disclaimer { font-family: var(--font-mono); font-size: var(--type-m2); color: var(--act9-t4); }

/* ---- footer responsive (verbatim from the act9 stylesheet) ---------------- */
@media (max-width: 1000px) {
  .act9-foot-grid { grid-template-columns: 1fr 1fr; gap: 32px 40px; }
  .act9-foot-brand { grid-column: 1 / -1; }
}
@media (max-width: 620px) {
  .act9-foot-grid { grid-template-columns: 1fr 1fr; gap: 28px 24px; }
}
@media (max-width: 400px) {
  .act9-foot-grid { grid-template-columns: 1fr; }
}

/* W6.7 · the reduced-motion override is the sixth de-duplicated pair: the same
   three !important declarations used to sit here (footer-scoped) AND inline in
   landing.html (unscoped, for pricing + final CTA). One copy now, unscoped, so
   all three act9 hosts are covered from here. landing.html keeps only the part
   that is genuinely its own — `.act9-tier, .act9-cta, .act9-ent-link span`. */
@media (prefers-reduced-motion: reduce) {
  .act9-reveal, .act9-rise {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .act9-social { transition: none !important; }
}

/* Compact variant: brand blurb + link columns dropped, legal line only. For
   flow pages that need a footer for legal reach but no navigation. */
.act9-footer.act9-footer--compact { padding: 40px 0 32px; }
.act9-footer.act9-footer--compact .act9-foot-bottom { padding-top: 0; }

/* =============================================================================
   SKIP LINK  ·  .sh-skip                                            (W5.13)
   =============================================================================
   /faq, /contact, /privacy-policy, /terms-of-service and /signin shipped with
   no bypass block at all: a keyboard visitor had to tab the whole shared header
   (brand + seven nav links + two actions, ten stops) on every one of them
   before reaching any page content. WCAG 2.4.1.

   The landing and the onboarding steps each already carry their own `.skip-link`
   (landing.html's inline block and onboarding.css). Deliberately a DIFFERENT
   class name so those two are untouched — this file loads before onboarding.css
   and a shared `.skip-link` here would be silently re-specified there.

   Off-screen by transform (not display:none / visibility:hidden) so it stays in
   the tab order; it slides in on :focus, which is the state a keyboard user is
   in when they need it. z-index sits above the header's own band.
   ============================================================================= */
.sh-skip {
  position: fixed; top: 0; left: 0; z-index: calc(var(--sh-z) + 1);
  padding: 12px 20px;
  font-family: var(--font-ui); font-size: var(--type-u5); font-weight: 600;
  color: #04121a; background: var(--grad-accent);
  border-radius: 0 0 var(--r-sm) 0; text-decoration: none;
  transform: translateY(-110%);
  transition: transform var(--dur-2) var(--ease-out);
}
.sh-skip:focus, .sh-skip:focus-visible {
  transform: translateY(0);
  outline: 2px solid #fff; outline-offset: -4px;
}
@media (prefers-reduced-motion: reduce) { .sh-skip { transition: none; } }
