/* ========================
   NovaStation — shared site stylesheet
   Consolidated from the original NovaStation_Intro.html /
   NovaStation_Why.html inline styles into a single sheet
   so every page renders identically.
   ======================== */

/* ---- Midnight Sapphire with Steel-Blue Support ---------------------------
   The ONE place a colour is chosen. Everything below this block, and every
   rule in the rest of the sheet, resolves to one of these tokens — there are
   no raw colour literals in component rules.

   Character: sapphire carries the brand and the primary action, steel blue
   carries support, information blue carries recorded/technical state, deep
   navy carries the page. Hierarchy comes from surface and border contrast,
   type scale, and spacing — NOT from gradients or glows. The authentic
   product screenshots stay the most colourful thing on any page.

   ⚠ Four values differ slightly from the approved palette sheet, each for a
   measured accessibility reason and each noted inline. Nothing else moved. */
:root {
  /* Backgrounds — ~80% of the visible page */
  --ns-bg-deep:#050C18;        /* page canvas                              */
  --ns-bg-section:#091323;     /* alternating section band                 */
  --ns-surface:#0E1B30;        /* default surface (card, panel, input)     */
  --ns-surface-raised:#152641; /* raised surface (modal, active row, tile) */

  /* Structure */
  --ns-border-subtle:#293E5C;  /* default surface edge                     */
  --ns-border-strong:#3C577A;  /* raised surface edge, divider emphasis    */
  /* ⚠ Interactive boundaries need 3:1 (WCAG 1.4.11). --ns-border-strong is
     2.05:1 on a raised surface, so controls use this lifted step instead:
     3.00:1 on --ns-surface-raised, 3.90:1 on --ns-bg-deep. Same hue. */
  --ns-border-control:#4E719E;

  /* Typography */
  --ns-text-primary:#F1F5FA;
  --ns-text-secondary:#AAB8C9;
  /* ⚠ #70839B was 3.90:1 on --ns-surface-raised. Lifted to clear 4.5:1 on
     every surface token (4.51 raised / 5.14 surface / 5.83 deep). */
  --ns-text-muted:#7C8EA3;

  /* Primary brand — sapphire. Used for THE action, not for everything. */
  --ns-sapphire:#4F7EDB;
  --ns-sapphire-hover:#6794EC;
  --ns-sapphire-pressed:#3E68C3;
  --ns-sapphire-soft:#142852;
  --ns-sapphire-contrast:#07101A;   /* ink on a sapphire fill — 4.86:1 */
  /* ⚠ Sapphire as *text* is 3.85:1 on a raised surface. Where sapphire has
     to be a word rather than a fill, it uses this one step up (5.07:1). */
  --ns-sapphire-text:#6794EC;

  /* Steel-blue support */
  --ns-steel:#6B90B3;          /* ⚠ #6A8FB3 +1 step: 4.52:1 on raised */
  --ns-steel-hover:#7EA4C8;
  --ns-steel-soft:#172B40;

  /* Informational blue — recorded state, technical metadata, app labels */
  --ns-info-blue:#7FA7D8;
  --ns-info-blue-soft:#182B48;

  /* Semantic. Market truth only — never a marketing accent. */
  --ns-success:#4FAF79;
  --ns-danger:#D96B6B;         /* ⚠ #D86565 +1 step: 4.52:1 on raised */
  --ns-warning:#D19A4A;
  --ns-info:#7FA7D8;
  --ns-success-soft:#10281C;
  --ns-danger-soft:#2A1618;
  --ns-warning-soft:#2A2012;

  /* Depth is neutral. A shadow separates planes; it never tints or glows. */
  --ns-shadow-sm:0 1px 2px rgba(0,0,0,.34);
  --ns-shadow-md:0 8px 20px rgba(0,0,0,.38);
  --ns-shadow-lg:0 18px 44px rgba(0,0,0,.44);

  --ns-focus:var(--ns-sapphire);
  --ns-scrim:rgba(3,7,14,.86);

  /* ---- Legacy aliases -----------------------------------------------------
     Older rules and a handful of inline styles still name these. They are now
     views onto the tokens above rather than colours of their own, so the whole
     sheet re-hues from one place and nothing is left on an old value. */
  --text:var(--ns-text-primary);
  --muted:var(--ns-text-secondary);
  --cyan:var(--ns-sapphire);       /* was #22d6ff — cyan branding is retired */
  --violet:var(--ns-steel);        /* was #8357ff — violet is retired       */
  /* Was a five-stop cyan->violet->pink ramp behind clipped headline text and
     a dozen small fills. Headline gradients are retired; the name survives so
     the fills that used it stay one flat brand colour. */
  --hero-grad:var(--ns-sapphire);
  --max:1240px;
  --nav-offset:64px;

  /* ---- Type roles --------------------------------------------------------
     One descending ramp every section reuses so the eye always lands on the
     heading first, then the intro, then the detail:
       label / eyebrow  ->  H1 / H2  ->  intro lead  ->  body.

     Frontend-Design Pass 1 measured 38 distinct size/weight pairs rendering on
     the homepage alone — many of them 0.3px apart (17.9 / 18.4 / 18.7 / 18.9px
     all doing the job of an <h3>). Differences that small are not a hierarchy;
     they are drift, and they read as sloppiness. Every text element now
     resolves to ONE of the roles below.

     ⚠ Fluid steps use a LOW vw coefficient and a hard max, so type never grows
     surprisingly. The old price used `clamp(3rem, 6vw, 4.6rem)` — a STEEPER
     slope (6vw) than the H1's (4.8vw) AND a higher floor (48px vs 40px), which
     is how the price came to out-shout the headline at EVERY width, mobile
     included. Nothing may outrank --text-display. See --text-price below. */
  --text-display:    clamp(2.5rem, 1.75rem + 2.4vw, 3.75rem);  /* 40 -> 60  H1            */
  --text-section:    clamp(2rem, 1.55rem + 1.9vw, 3.25rem);    /* 32 -> 52  H2            */
  --text-price:      clamp(1.75rem, 1.3rem + 1.55vw, 3rem);    /* 28 -> 43  always < H1   */
  --text-subsection: 1.25rem;                                  /* 20  H3 in prose flow    */
  --text-card-title: 1.0625rem;                                /* 17  H3 inside a card    */
  --text-lead:       clamp(1.02rem, .92rem + .4vw, 1.18rem);   /* 16.3 -> 18.9  intro     */
  --text-body:       1rem;                                     /* 16                      */
  --text-supporting: .9375rem;                                 /* 15  secondary copy      */
  --text-control:    .9375rem;                                 /* 15  buttons             */
  --text-caption:    .875rem;                                  /* 14  figcaptions, notes  */
  --text-code:       .875rem;                                  /* 14  mono / checksums    */
  --text-fine:       .8125rem;                                 /* 13  legal fine print    */
  --text-label:      .75rem;                                   /* 12  uppercase eyebrow   */

  /* Two text weights carry the whole site. 800 was only ever used to shout. */
  --weight-body:400;
  --weight-strong:600;
  --weight-bold:700;

  --leading-tight:1.06;   /* display + section headings */
  --leading-snug:1.35;    /* subsection + card titles   */
  --leading-loose:1.72;   /* long-form legal prose      */

  /* ---- Measure -----------------------------------------------------------
     One cap per CONTENT TYPE — not one cap for everything. Pass 1 measured the
     marketing pages at a healthy 46-62ch while the utility and legal pages
     capped nothing at all and ran to the full container: FAQ answers 81ch,
     Terms/EULA 85ch, quick-start steps 115ch, and the footer fine print 134ch
     on every page. Screenshot captions were the worst at 144ch — WIDER than any
     body copy, when a caption should be the narrowest text on the page. */
  --measure-hero:    46ch;   /* hero lead — concise and controlled       */
  --measure-body:    64ch;   /* section intros and marketing prose       */
  --measure-prose:   72ch;   /* long-form: legal, FAQ answers, support   */
  --measure-caption: 56ch;   /* narrower than body, tied to its image    */
  --measure-fine:    76ch;   /* footer fine print                        */

  /* Back-compat aliases — index.html sets max-width:var(--measure-lead) inline
     on four .demo paragraphs, and several rules still read --fs-*. Aliasing
     keeps this pass to the stylesheet and out of the HTML. */
  --measure-lead:var(--measure-body);

  /* ---- Radius ------------------------------------------------------------
     Two scale steps plus a pill. Pass 1 measured 13 distinct authored radii
     across 12 card classes (0/9/10/12/13/14/15/16/18/20/22/24/28/30px) — the
     residue of five component generations layered on top of each other.
     CONTROLS are tighter than SURFACES; that contrast is the entire system.
     Circles (50%) are a shape, not a scale step, and are left alone. */
  --radius-pill:999px;   /* nav links, avatars — a shape, not a size        */
  --radius-chip:8px;     /* micro-components: chips, tags, marks            */
  --radius-sm:14px;      /* controls: buttons, inputs, code blocks, nav      */
  --radius-md:18px;      /* surfaces: cards, panels, tiles, modals, media    */

  /* ---- Micro-component tier ----------------------------------------------
     Chips, tags, marks and bullet markers are the smallest thing on the page
     and there were six unrelated recipes for them: three radii, four borders,
     two glow treatments and a gradient circle used as a list bullet. At this
     size a 999px radius plus a glow does not read as "premium", it reads as a
     toy — the shape is nearly all corner, and the glow is a bigger visual
     event than the word inside it.

     ONE recipe now, expressed as tokens, applied by .chip and by every
     component that used to invent its own. A tight 8px radius reads as an
     instrument marking rather than a bubble; the border is a true hairline;
     nothing at this tier glows. Emphasis at this size is carried by INK and
     TRACKING, never by fill or shadow — the one exception being the paid-tier
     tag, where a solid fill is the hierarchy. */
  --chip-ink:var(--ns-text-secondary);
  --chip-line:var(--ns-border-subtle);
  --chip-bg:transparent;
  --chip-pad-y:5px;
  --chip-pad-x:9px;
  --chip-tracking:.10em;
  /* Bullet markers are a 9x1.5px rule, not a dot: a rule points at the line it
     belongs to and sits on the text baseline grid, where a circle floats. */
  --marker-w:9px;
  --marker-h:1.5px;
  --marker-ink:var(--ns-steel);
  --marker-ink-quiet:var(--ns-text-muted);

  /* ---- Anchor landing ----------------------------------------------------
     ONE knob for "how far below the top of the viewport does a #hash target
     come to rest". The sticky header is --nav-offset tall and shrinks by
     8px once scrolled, so a target parked at exactly --nav-offset touched
     the header's own hairline; --anchor-gap is the breathing room under it.
     Every scroll-padding-top and scroll-margin-top on the site resolves from
     this token and nothing else, so there is no per-section offset to drift
     and no JavaScript involved in landing a link. */
  --anchor-gap:18px;
  --anchor-offset:calc(var(--nav-offset) + var(--anchor-gap));

  /* ---- Product captures -------------------------------------------------
     Every approved screenshot is the same 2560x1440 landscape frame produced
     by tools/screenshot_tool, so the site has ONE image ratio. --shot-ratio
     states it to the layout engine (which reserves the box before the file
     arrives); --shot-aspect is the same ratio as a bare number, so a height
     cap can be written as the width it implies. */
  --shot-ratio:2560 / 1440;
  --shot-aspect:1.7777778;
  /* Measured height of the path progress DOCK, which sticks below the header
     while the chapters are on screen. Only anchors it covers pay for it; above
     1081px the dock becomes a side rail in its own column and the reservation
     drops to zero. The browser leg re-measures it, so a copy edit that
     re-wraps the dock fails a gate instead of landing a heading behind it. */
  --guide-rail:89px;

  /* ---- Text / media pairing ---------------------------------------------
     A block of copy and the screenshot it explains are ONE composition, and
     four numbers decide whether they read as one: how wide the text runs, how
     wide the image runs, how wide its caption runs, and the gap between the
     two columns. Naming them here is what stops each new section inventing its
     own — the audit found captions running wider than the images they belonged
     to and body copy on three different rails within one page.

       --measure-text    the explanation beside or above an image
       --measure-media   the image column's own cap
       --measure-caption already defined above, and deliberately the narrowest
       --pair-gap        text column -> media column

     Caption width is not re-declared here: a caption is tied to its image and
     must stay the narrowest text in the composition, which --measure-caption
     already says once for the whole site. */
  --measure-text:var(--measure-body);
  --measure-media:720px;
  --pair-gap:clamp(24px, 3.2vw, 44px);

  /* ---- Spacing scale (4px base) -----------------------------------------
     One rhythm the whole site snaps to. The stepped tokens (--space-1..10)
     are for component padding, gaps and intra-card spacing; the fluid
     tokens (clamp) drive the section rhythm, gutters and grids so spacing
     scales smoothly from mobile to ultrawide with no breakpoint pile-up.
     The page's overall breathing room is tunable from two knobs:
     --gutter (horizontal) and --section-y (vertical). */
  --space-1:4px;     /* 2xs */
  --space-2:8px;     /* xs  */
  --space-3:12px;    /* sm  */
  --space-4:16px;    /* md  */
  --space-5:20px;
  --space-6:24px;    /* lg  */
  --space-7:28px;
  --space-8:32px;    /* xl  */
  --space-9:40px;
  --space-10:48px;   /* 2xl */
  --space-11:56px;
  --space-12:64px;   /* 3xl */
  --space-13:80px;
  --space-14:96px;
  --space-15:128px;

  /* ---- Canonical relationship tokens ------------------------------------
     ONE token per recurring spacing relationship; components reference these
     (or a --space-N primitive above), never bespoke px. Equivalent
     relationships therefore resolve to the SAME value site-wide:
        section gap ............. --section-y          (between page sections)
        grid gutter ............. --grid-gap           (cards in a row; both axes)
        card padding ............ --card-pad           (inner pad of every card/panel)
        sub-block / row->block .. --block-gap          (stacked blocks in a section)
        eyebrow -> heading ...... --eyebrow-gap        (label to the h2 it names)
        heading -> intro ........ --heading-gap        (h2 to its lead sentence)
        heading -> content ...... --section-head-gap   (under a section heading)
     These are fluid clamps so the main rhythm scales proportionally from
     mobile to ultrawide in one place; fixed component spacing uses --space-N. */
  --gutter:clamp(20px, 4.2vw, 40px);            /* container side padding  */
  /* Measured across every route at 1440: section-to-section came out at 67-88px
     with most junctions on 72, under the 80-112px band a major boundary needs
     at desktop; at 390 it came out 60-79, straddling the 56-72px mobile band.
     One knob moves both. 3.2vw with a 30px floor and a 46px ceiling lands
     56-60px through phone and tablet and 92px from 1440 up, so the SAME token
     satisfies both targets and nothing else has to be tuned per breakpoint.
     (An earlier pass tightened this 48 -> 40 -> 36 to pull long pages up. The
     measurement above says it went one step too far: at 36 the boundary
     between two sections was no larger than the gap inside one of them.) */
  --section-y:clamp(30px, 3.2vw, 46px);       /* section top/bottom pad */
  --section-head-gap:clamp(24px, 2.6vw, 32px);  /* section head -> content */
  /* The three steps INSIDE a section head, measured on the live site and
     found inverted: the label sat 16px from its heading while the heading
     sat 12px from its own intro, so the eyebrow read as further from the
     thing it names than the sentence that elaborates it. A label is part of
     its heading and binds tight; the intro is the next idea and steps away.
     Fixed, not fluid: these are type relationships at a fixed type size, and
     scaling them with the viewport is what made the section head drift. */
  --eyebrow-gap:var(--space-3);                 /* label -> heading   12px */
  --heading-gap:var(--space-5);                 /* heading -> intro   20px */
  --grid-gap:clamp(16px, 2vw, 24px);            /* card-grid gutter (both axes) */
  --card-pad:clamp(18px, 1.9vw, 26px);          /* standard card / panel padding */
  --panel-pad:clamp(26px, 2.6vw, 34px);         /* large showcase-panel padding  */
  --hero-gap:clamp(26px, 3.4vw, 40px);          /* hero column gap         */
  --stack:clamp(14px, 1.5vw, 20px);             /* vertical stack rhythm   */
  /* Comfortable separation between any two sibling blocks inside a section:
     a card ROW meeting the block directly below it (pricing cards ->
     subscription panel; overview feature cards -> "Scattered tools") AND
     stacked sub-blocks ("Scattered tools" -> "Best for/Not built for" ->
     "The honest framing"). ONE knob so every such junction resolves to the
     same value; larger than the card-grid gutter so the cards' drop-shadows
     can't bleed onto the block below; fluid so it scales with the section
     rhythm. Applied single-sided at each junction (no doubling). */
  --block-gap:clamp(32px, 4vw, 48px);            /* card-row / sub-block separation */
}

* { box-sizing:border-box; }
html { scroll-behavior:smooth; scroll-padding-top:var(--anchor-offset); }
body {
  margin:0;
  color:var(--ns-text-primary);
  font-family:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* One flat canvas. This used to be three stacked radial washes plus two
     fixed corner blooms on ::before/::after — the single loudest source of the
     "AI landing page" read. Sections separate themselves with a flat band and
     a hairline now, which is also what the desktop application does. */
  background:var(--ns-bg-deep);
  min-height:100vh;
  overflow-x:hidden;
}

.shell { position:relative; z-index:1; }

/* ---- Atmosphere ---------------------------------------------------------
   Pass 1 deleted the three stacked washes, the corner blooms and the
   cyan/violet ramp, and left a great deal of empty #050C18 — the right half
   of every hero is dead canvas. This is the depth that fills it: a SKY, not a
   decoration. Isolated points, no lines between them, nothing glowing. The
   asset, and every number in it, is generated and documented by
   tools/build_atmosphere.py; launch-readiness CAT 11 byte-checks it.

   ONE DIAL. --atmos scales the whole system; no layer owns an opacity. The
   four roles are read off structure the markup already has, so no <body> tag
   needed a class:
     .62 hero     marketing pages — they have a .hero
     .20 minimal  reading and form pages — they have a .legal-shell
       0 none     sign-in, account, callback — [data-auth-page]
     .20 minimal  the default, so anything unrecognised lands on restraint
   :has() is the mechanism and the fallback is the point: without it those two
   rules drop and every page renders minimal — less sky, never more.

   Nothing animates. Not a concession to prefers-reduced-motion, the design:
   a still sky is what keeps this from reading as a particle demo. */
:root {
  --atmos:.20;
  --atmos-field:url(../images/atmos-field.svg?v=20260728c);
  --atmos-pool:rgba(79,126,219,.055);  /* one sapphire pool, ~5.5% alpha */
}
body:has(.hero)        { --atmos:.62; }
body:has(.legal-shell) { --atmos:.20; }  /* after .hero on purpose: a page  */
body[data-auth-page]   { --atmos:0; }    /* with both is a reading page     */

/* Viewport-FIXED settles three things: no tile to spot, no change to document
   height (the progression anchor measures it), no repaint while scrolling.
   background-attachment:fixed is shorter and wrong — iOS ignores it and scales
   the image to the whole document box, turning a 1.5px star into a disc on a
   long page. .shell is already position:relative;z-index:1, so a z-index:-1
   child lands above the page canvas and below every element, header included */
.shell::before {
  content:"";
  position:fixed;
  inset:0;
  z-index:-1;
  pointer-events:none;
  background:var(--atmos-field) 50% 0/cover no-repeat;
  opacity:var(--atmos);
}

/* The hero gets two things no other surface does. First a single soft sapphire
   pool at 76%/26% — the empty upper right of every hero here. Headline, lead
   and buttons all sit in the left column. One hue, ~5.5% alpha before the dial:
   depth, not a gradient. */
.hero { position:relative; isolation:isolate; }
.hero::before {
  content:"";
  position:absolute;
  inset:0 0 auto 0;
  height:min(100%, 520px);
  z-index:-1;
  pointer-events:none;
  background:radial-gradient(44% 60% at 76% 26%, var(--atmos-pool), transparent 70%);
  opacity:var(--atmos);
}

/* Second, one arc. Both numbers below are measured, not chosen — rendering all
   eleven hero pages and reading the rightmost ink per row says the corner is
   free from y~240 down, and is NOT free under the header, where a bordered
   eyebrow bar spans the full hero width on three of them. Page-absolute, not
   fixed: a fixed arc would drag over screenshots on scroll. The percentage in
   `right` resolves against .hero's own 1160px box, so it cancels the centring
   gutter exactly and pins the layer to the VIEWPORT edge, which is both where
   an orbit around something off-page belongs and 21px further from the nearest
   ink at 1080. Ends fade in the asset; see tools/build_atmosphere.py. */
@media (min-width:1081px) {
  .hero::after {
    content:"";
    position:absolute;
    top:170px;
    right:calc((100% - 100vw) / 2);
    width:270px;
    height:160px;
    z-index:-1;
    pointer-events:none;
    background:url(../images/atmos-orbit.svg?v=20260728c) 100% 0/270px 160px no-repeat;
    opacity:var(--atmos);
  }
}

/* `cover` on a portrait viewport crops to the vertical middle of the asset,
   which is where the generator's well thinned it out, so at 50% the sky went
   entirely empty; 12% lands on the denser left edge. A phone gets fewer stars
   than a desktop by cropping rather than by fading. */
@media (max-width:720px) {
  .shell::before { background-position:12% 0; }
}
/* More contrast turns the sky off, and so does a forced palette: someone
   running forced colours has said which colours they want. Matching
   specificity rather than !important, so the dial stays readable. */
@media (prefers-contrast:more), (forced-colors:active) {
  body, body:has(.hero), body:has(.legal-shell) { --atmos:0; }
}

.container { max-width:var(--max); margin:0 auto; padding:0 var(--gutter); }
section { padding:var(--section-y) 0; }
/* Anchor landing, site-wide, in ONE declaration: scroll-padding-top on the
   scroller, above.
   scroll-padding-top insets the region a target is aligned into, and
   scroll-margin-top grows the target's own box — so setting BOTH to the offset
   does not make the landing twice as reliable, it makes it twice as far down.
   That is what this file used to do, and every heading came to rest ~100px
   below the header with a slab of dead space above it. One knob now: the
   scroller reserves the header's height, and the browser lands the target
   against it. Measured identical for a direct hash load, a nav click
   (scrollIntoView), a back/forward restore and the mobile drawer, because all
   four resolve the same padding — and unlike a [id] rule it also covers an
   element the browser scrolls to for a reason of its own, such as keyboard
   focus landing on a control tucked under the sticky header.
   A component that genuinely needs to rest lower adds scroll-margin-top for
   the DIFFERENCE only (see .loop-stage), never for the whole offset again. */

/* ---------- Top navigation ---------- */
.nav-wrap {
  position:sticky;
  top:0;
  z-index:60;
  border-bottom:1px solid var(--ns-border-subtle);
  background:var(--ns-bg-deep);
  
  box-shadow:0 12px 34px rgba(0,0,0,.24);
  /* Was overflow:hidden — that clipped nav-links silently when they
     didn't fit on one row at medium widths.  No rounded corners on
     nav-wrap, so nothing visual relied on the clip; removing it lets
     nav-links wrap to a second row instead of disappearing. */
}
.nav {
  max-width:var(--max);
  margin:0 auto;
  padding:var(--space-4) var(--space-6);
  display:grid;
  /* minmax(0, 1fr) lets the nav-links cell shrink below its intrinsic
     content width, which then forces flex-wrap on the nav pills.
     With plain `1fr` the cell expanded to fit content and wrap never
     triggered, so pills overflowed silently. */
  grid-template-columns:auto minmax(0, 1fr) auto;
  gap:var(--space-3) var(--space-5);
  align-items:center;
}
.brand {
  display:flex;
  align-items:center;
  gap:var(--space-4);
  text-decoration:none;
  color:inherit;
  min-width:0;
}
.brand-mark {
  width:46px; height:46px;
  display:grid;
  place-items:center;
  border-radius:var(--radius-sm);
  padding:var(--space-1);
  background:var(--ns-surface);
  border:1px solid var(--ns-border-strong);
  box-shadow:none;
  flex:0 0 auto;
}
.brand-mark img { width:100%; height:100%; object-fit:contain; display:block; }
.brand-copy strong { display:block; font-size:var(--text-card-title); }
.brand-copy span { display:block; color:var(--muted); font-size:var(--text-caption); margin-top:2px; }

.nav-links {
  grid-column:2;
  display:flex;
  flex-wrap:nowrap;              /* single row — the header NEVER wraps to a 2nd line */
  align-items:center;
  justify-content:flex-start;
  gap:var(--space-2);
  padding-bottom:2px;
  min-width:0;
}
.nav-links::-webkit-scrollbar { display:none; }
/* The header's three zones: brand (left) · primary nav · account actions.
   margin-left:auto pushes Sign in and the account actions right without a
   second row. It hung off .nav-download-link, now removed. */
.nav-links a[data-account-nav] { margin-left:auto; }
.nav-links a {
  color:var(--muted);
  text-decoration:none;
  font-size:var(--text-control);
  padding:var(--space-2) var(--space-3);
  border-radius:999px;
  border:1px solid transparent;
  white-space:nowrap;
  /* Named: bare `.2s ease` means `all`, which faded the focus ring up over
     200ms. Same fix as .btn — see "Don't animate the focus ring" below. */
  transition:color .2s ease, border-color .2s ease, background-color .2s ease;
}
.nav-links a:hover, .nav-links a.active {
  color:var(--text);
  border-color:var(--ns-border-strong);
  background:var(--ns-surface-raised);
  box-shadow:none;
}

/* ---------- Hero ---------- */
/* ---------- Hero ----------
   The hero is a MEDIA BAND, not a two-column split.

   Measured in Pass 2B: inside the old 1.03fr/.97fr grid the workstation capture
   rendered 543x293 at 1440 — 27% of native, and the SMALLEST product image on
   the page, while the supporting shots below it ran at 51%. The most persuasive
   asset on the site was its weakest. Widening the media column was tested and
   tops out at 30.6% — a two-column hero structurally cannot beat a full-width
   figure. So the copy sits in a measured column and the capture spans the whole
   container beneath it: 1069x576 = 52%, and now the strongest image on the page.
   (Below 1081px the hero was ALREADY stacked, which is why the defect was
   desktop-only.) */
.hero {
  padding:clamp(36px, 4.4vw, 56px) 0 var(--space-6);
  display:grid;
  grid-template-columns:1fr;
  gap:var(--hero-gap);
  align-items:start;
}
/* :not([hidden]) is load-bearing, not tidiness. A blanket display on a
   container's CHILDREN (0,1,1) outranks the browser's own [hidden] rule
   (0,1,0), so without the guard this line silently resurrects every block a
   module has not filled in yet — which is exactly how an empty 185px frame
   appeared under the /start headline whenever no path was chosen, and on
   every invalid ?goal=. The same guard is on the 1080px stacking rule below.
   Any rule that sets display on `> *` needs it. */
.hero > div { min-width:0; }
.hero > div:not([hidden]) { display:block; }
.hero-copy { max-width:none; }   /* the h1 (26ch) and .lead (--measure-hero) cap themselves */

/* Tier A — the page eyebrow. One per page, so it keeps a frame; the frame is
   what separates it from the section labels below it. Instrument-tier: tight
   radius, hairline, no gradient dot and no glow. The 12x1px rule that replaces
   the dot is the same marker the section labels use, at the same weight — that
   repetition is what makes the two tiers read as one system. */
.eyebrow {
  display:inline-flex;
  align-items:center;
  gap:var(--space-3);
  padding:var(--chip-pad-y) var(--space-3);
  border-radius:var(--radius-chip);
  border:1px solid var(--chip-line);
  background:var(--chip-bg);
  color:var(--ns-text-primary);
  letter-spacing:var(--chip-tracking);
  text-transform:uppercase;
  font-size:var(--text-label);
  font-weight:var(--weight-bold);
}
.eyebrow::before,
.label::before {
  content:"";
  width:12px;
  height:var(--marker-h);
  border-radius:0;
  background:var(--ns-steel);
  flex:none;
}
/* Legal / thanks / support page eyebrows carry an extra top gap to clear the
   sticky nav. Hoisted from a repeated inline style so it lives in one place. */
.legal-shell > .eyebrow { margin-top:var(--space-8); }
/* 404 "shortcut" cards rendered as real links: keep the .card visual + hover
   lift, just drop the anchor underline and inherit the surrounding colour. */
.hero-point-link { text-decoration:none; color:inherit; }
/* ---------- Heading roles ----------
   THE rule of this system: nothing on the page may render larger or heavier
   than the H1. Pass 1 found the price doing exactly that (73.6px/800 against a
   60px/700 H1) at every width. --text-display is the ceiling. */
h1 {
  margin:var(--space-5) 0 var(--space-5);
  /* Hero headline — a confident, professional lead, not a banner. Sized one
     clear step above the section-H2 ramp so the ladder is legible at a glance. */
  font-size:var(--text-display);
  font-weight:var(--weight-bold);
  line-height:var(--leading-tight);
  letter-spacing:-.03em;
  max-width:26ch;   /* 2 lines at desktop instead of 3 — 64px of the media band's cost, recovered */
}
/* <h3> had NO global rule, which is why it rendered at five different sizes
   (13.1 / 17.9 / 18.4 / 18.7 / 18.9px) depending on whichever component styled
   it last — and why "How access works" ended up a 13.1px caption. Two roles
   now: a prose subsection, and a title inside a card. Both sit ABOVE body. */
h3 {
  font-size:var(--text-subsection);
  font-weight:var(--weight-bold);
  line-height:var(--leading-snug);
  letter-spacing:-.02em;
}
/* Was a five-stop cyan->violet->pink ramp clipped to the headline. Gradient
   text is the single most recognisable "AI landing page" tell, and a headline
   is not where brand colour belongs — it is where SIZE belongs.
   The class is deliberately left with no visual effect rather than deleted:
   twenty pages mark a phrase with it, and in fifteen of them that phrase IS
   the whole subject of the title ("FAQ", "Support", "Risk disclaimer") — a
   phrase styled differently from the two words around it would be noise, not
   hierarchy. Pinning the colour explicitly keeps it from inheriting a coloured
   parent later. If a headline ever needs internal emphasis, it belongs to
   weight or size, not to a second colour.

   Doubled deliberately. `.card span` (0,1,1) out-ranks a bare `.gradient-text`
   (0,1,0), and the auth, 404 and success headlines all sit inside a card — so
   a single class here left the MARKED phrase muted while the words around it
   stayed primary, inverting the emphasis it exists to carry. The old rule
   never hit this because a clipped gradient paints through background-clip
   and ignores `color` entirely. Same double-class trick account.html uses on
   its badges, and for the same reason. */
.gradient-text.gradient-text {
  color:var(--ns-text-primary);
}
/* The one-sentence answer to "what is this?", sitting between the promise
   (the h1) and the terms (.lead). Lead-sized but in PRIMARY text, because a
   visitor who reads exactly one line under the headline has to read this one:
   the product, not the billing. */
.hero-what {
  margin:var(--space-3) 0 0;
  color:var(--ns-text-primary);
  font-size:var(--text-lead);
  font-weight:var(--weight-strong);
  line-height:1.55;
  max-width:46rem;
  text-wrap:balance;
}

.lead {
  margin:0;
  color:var(--muted);
  font-size:var(--text-lead);
  font-weight:var(--weight-body);
  line-height:1.76;
  max-width:46rem;
}

/* ---------- Buttons ---------- */
.hero-actions {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-3);
  margin-top:var(--space-8);
  margin-bottom:var(--space-2);
  width:100%;
}
/* Download trust badges — compact, glanceable spec/source pills beside the
   primary CTA. Deliberately subtle (hairline cyan-tinted pills, muted text)
   so they read as quiet facts, not loud claims, and never imply code-signing. */
.trust-badges {
  list-style:none;
  margin:var(--space-4) 0 0;
  padding:0;
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-2);
}
/* No phone override. The list is three short facts (67 / 114 / 126px) that
   measure 307px + gaps against a 328px rail at 360, so the natural flex row
   holds one line down to 360 and degrades to 2 + 1 at natural widths below
   that. The 2x2 grid that used to live here was written for a FOUR-chip list
   and, with three, only stretched two pills to 175px and stranded the third. */
.trust-badges li {
  /* inline-block (not inline-flex): a flex item trims the whitespace
     between the inner span and the following word, which collapsed
     "7-day Premium trial" -> "7-dayPremium trial". inline-block keeps the
     chip shape AND preserves the normal inline space. */
  display:inline-block;
  padding:var(--chip-pad-y) var(--chip-pad-x);
  border-radius:var(--radius-chip);
  font-size:var(--text-label);
  font-weight:var(--weight-strong);
  letter-spacing:.02em;
  color:var(--chip-ink);
  background:var(--chip-bg);
  border:1px solid var(--chip-line);
  white-space:nowrap;
}
.btn {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:var(--space-2);
  min-height:54px;
  padding:0 var(--space-6);
  border-radius:var(--radius-sm);
  text-decoration:none;
  font-weight:var(--weight-bold);
  font-size:var(--text-control);
  letter-spacing:-.01em;
  border:1px solid transparent;
  cursor:pointer;
  transition:.2s ease;
  font-family:inherit;
}
/* No hover lift and no hover glow. A button that changes fill on hover has
   already answered "is this live?"; moving it as well is the second answer to
   a question asked once. The one motion a button makes is the press scale,
   and it is declared once, with the rest of the press states, in the motion
   foundation block (.btn:active). */
.btn:focus { outline:none; }
.btn:focus-visible {
  outline:3px solid var(--ns-sapphire);
  outline-offset:3px;
}
/* ONE primary action per view owns the only saturated fill on the page. That
   is the whole CTA hierarchy — no glow, no gradient, no second bright button.
   Ink is --ns-sapphire-contrast: #F1F5FA on #4F7EDB measures 3.59:1 and fails
   AA, the dark ink measures 4.86:1. */
.btn-primary {
  color:var(--ns-sapphire-contrast);
  background:var(--ns-sapphire);
  border-color:var(--ns-sapphire);
  box-shadow:none;
}
.btn-primary:hover {
  background:var(--ns-sapphire-hover);
  border-color:var(--ns-sapphire-hover);
}
/* ⚠ The pressed fill is dark enough that the dark ink drops to 3.62:1, so the
   ink inverts for this one state (#F1F5FA on #3E68C3 = 4.83:1). Every button
   state clears AA; neither ink clears it on all three fills. */
.btn-primary:active {
  background:var(--ns-sapphire-pressed);
  border-color:var(--ns-sapphire-pressed);
  color:var(--ns-text-primary);
}
/* Secondary reads as an outline, never as a second fill. */
.btn-secondary {
  color:var(--ns-text-primary);
  border-color:var(--ns-border-control);
  background:transparent;
  box-shadow:none;
}
.btn-secondary:hover {
  background:var(--ns-surface-raised);
  border-color:var(--ns-border-control);
}


.hero-points,
.grid-3,
.grid-2,
.workflow-grid {
  display:grid;
  gap:var(--grid-gap);
  align-items:start;
}
.hero-points, .grid-3 { grid-template-columns:repeat(3, minmax(0,1fr)); }
.grid-2 { grid-template-columns:repeat(2, minmax(0,1fr)); }
.workflow-grid { grid-template-columns:repeat(4, minmax(0,1fr)); }

/* :not([hidden]) — see the note on `.hero > div`. A display set on a
   container's children outranks the browser's [hidden] rule, so without the
   guard any card a module has not filled in yet renders as an empty cell. */
.hero-points > *:not([hidden]),
.grid-3 > *:not([hidden]),
.grid-2 > *:not([hidden]),
.workflow-grid > *:not([hidden]) {
  min-width:0;
  display:flex;
}

/* Two card rows of the same kind stacked together (e.g. a 2×3 benefit grid
   split across two .grid-3 rows) read as ONE group: separate them by the grid
   GUTTER, not the larger block gap, so the rows line up as a single grid. */
.grid-2 + .grid-2,
.grid-3 + .grid-3 { margin-top:var(--grid-gap); }

/* An asymmetric pair, on purpose. /why-novastation sets a 10-item inventory of
   what the product IS beside a 5-item list of what it is NOT, and at equal
   widths those measured 720px against 283px: the right column ran out of
   content two thirds of the way down and left a 437px hole beside the left
   one. Stretching the short panel to match would only make the hole a bordered
   box, so the fix is the column ratio instead. The affirmative list is the
   substance of the page and takes the dominant column; the disclaimer list is
   deliberately the narrower aside. Widening the long panel also shortens it —
   fewer of its items wrap to two lines — and narrowing the short one lengthens
   it, so the two move toward each other from both ends without a single pixel
   of forced height. Gated behind min-width rather than written bare: a media
   query adds no specificity, so a bare `.grid-2.claim-pair` (0,2,0) would beat
   the `.grid-2 { 1fr }` stack (0,1,0) inside the 1080px block and hold two
   columns down to 360, where the pair measured 1878px against 685px and put
   seven list items into runts. Below 1080 the base rule stacks them, and a
   stacked pair has no ratio to balance. */
@media (min-width: 1081px) {
  .grid-2.claim-pair { grid-template-columns:minmax(0, 1.4fr) minmax(0, 1fr); }
}

/* ---------- Equal height: a COMPARISON contract, not a global one ----------
   This rule used to pin align-items:stretch on every card row on the site, and
   its comment described that as a feature — "immune to future per-grid
   overrides". It was: it silently beat three separate attempts to fix the
   emptiness, because it sits later in the cascade than all of them.

   Ragged bottoms are not the defect. EMPTY CARDS are. Stretching a short card to
   match a tall one does not add information, it adds 174px of nothing — measured
   at 33% empty on the Lite includes/excludes panels, 22-39% on the delivery-flow
   steps, and 24% on two of the three feature cards.

   Equal height was kept in exactly one place: a COMPARISON, on the argument that
   the two CTAs have to line up so the reader can scan across them. Measured, they
   never did line up — Premium carries six long bullets against Lite's six short
   ones, so stretching Lite to match only moved its button 180px away from the
   bullets it belongs to, over a void, while the CTAs stayed on different rows
   anyway because the Premium card also carries a price note Lite does not.

   So the exception is retired and the rule is now universal: NOTHING on this site
   stretches a card past its content. The comparison is carried by what actually
   carries it — a two-column grid, matched section order inside each card, and the
   same type ramp on both sides. Natural heights, tops aligned. */
.plan-compare { align-items:start; }


.panel,
.card,
.workflow-step {
  position:relative;
  overflow:hidden;
  background:var(--ns-surface);
  border:1px solid var(--ns-border-subtle);
  box-shadow:0 18px 46px rgba(0,0,0,.35);
  border-radius:var(--radius-md);
  width:100%;
  /* NO min-height:100%. A percentage min-height resolves against the GRID AREA
     (the row height = the tallest sibling), so it re-stretches the card even
     after the container is switched to align-items:start. This was the fourth
     and least obvious of the four mechanisms forcing equal heights. */
  display:flex;
  flex-direction:column;
  justify-content:flex-start;
}
.panel,
.card,
.workflow-step { padding:var(--card-pad); }
/* The corner bloom that used to sit behind every card and panel is gone. A
   180px radial wash in the bottom-right of every surface is decoration that
   reads as depth without carrying any; the 1px border and the surface step
   now do that work on their own. */
/* A card's TITLE is a strong the card itself carries. `.card strong` used to
   block-ify every strong inside a card, so an emphasised phrase in the middle
   of a sentence -- "the <strong>1D chart preview only</strong>, across a core
   set of symbols" -- was thrown onto its own line at title size and left the
   rest of the sentence starting with a comma. Same over-reach, same fix, as
   `.note > strong` further down. Cards that want a labelled row style it
   themselves (see .acct-help in account.html). */
.card h3,
.panel h3,
.card > strong,
.workflow-step h3 {
  display:block;
  font-size:var(--text-card-title);
  letter-spacing:-.03em;
  margin:0 0 var(--space-2);
}
.card p,
.panel p,
.card span,
.section-head p,
.workflow-step p {
  color:var(--muted);
  line-height:1.72;
  margin:0;
}

.workflow-step::before {
  content:attr(data-step);
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:40px;
  height:40px;
  border-radius:12px;
  margin-bottom:var(--space-3);
  /* The one numbered sequence on the site that earns the accent: this IS the
     workflow the product is about. Every other numbered list — delivery,
     access steps — takes the neutral chip below, so a sapphire numeral still
     means "this is the thing" instead of "this is a list". */
  color:var(--ns-sapphire-contrast);
  background:var(--ns-sapphire);
  font-weight:var(--weight-bold);
  position:relative;
  z-index:1;
}


/* A full-bleed rule is structure, not brand. It was a cyan->violet ramp, and a
   flat sapphire line is the same mistake in one colour: it spends the accent on
   a boundary that only needs to be legible. Subtle border does the job. */
.hero-divider {
  height:1px;
  margin:var(--space-1) 0 2px;
  background:var(--ns-border-subtle);
}

/* ---------- Section headings + lists ---------- */
.section-head {
  max-width:880px;
  margin-bottom:var(--section-head-gap);
}
/* --section-head-gap is the ONE gap under a section head. A block that also
   carries a top margin for separation from a card ROW above it — the case
   --block-gap was written for — must not add that margin here as well: the
   two do not collapse to the smaller of the pair, they collapse to the
   LARGER, so the head's own 32px silently became 48px. Measured on the
   homepage (#get-started) and /pricing, the only two places it happens. */
.section-head + .trust-grid,
.section-head + .price-tile {
  margin-top:var(--section-head-gap);
}

/* A section's own padding IS the gap above its first block. A heading or an
   aside that also carries a top margin — correct between siblings inside the
   section — applies it a second time at the top edge, and that is where every
   126-182px outlier in the rhythm audit came from (/faq h2 +40, /support h2
   +40, /how-it-works .note +48). The rule is scoped to the FIRST child, so
   the same element keeps its full margin everywhere else. */
section > :first-child,
.legal-card > :first-child,
.support-section > :first-child {
  margin-top:0;
}
/* The heading's own step down to its intro. Scoped to the section head so a
   heading that is followed directly by content (a card grid, a table, a code
   block) keeps the tighter global h2 margin — the wider step is specifically
   the gap between a heading and the SENTENCE that elaborates it. */
.section-head h1,
.section-head h2,
.section-head h3 {
  margin-bottom:var(--heading-gap);
}
/* Intro lead — one tier below the heading, one tier above supporting copy.
   Capped measure keeps it from spanning as a heavy full-width block. */
.section-head p {
  font-size:var(--text-lead);
  font-weight:var(--weight-body);
  line-height:1.7;
  max-width:var(--measure-lead);
}
/* Tier A' — the section label. It appears eight times on the homepage, and as
   a bordered gradient pill eight times it read as a row of buttons that could
   not be pressed. A label's job is to name the section, not to look clickable,
   so the frame comes off and the marker does the work. Same micro-type, same
   marker, one tier quieter than the eyebrow above it. */
.label {
  display:inline-flex;
  align-items:center;
  gap:var(--space-3);
  margin-bottom:var(--eyebrow-gap);
  padding:0;
  border:0;
  background:none;
  color:var(--ns-text-secondary);
  font-size:var(--text-label);
  font-weight:var(--weight-bold);
  text-transform:uppercase;
  letter-spacing:var(--chip-tracking);
}
h2 {
  margin:0 0 var(--space-3);
  font-size:var(--text-section);
  font-weight:var(--weight-bold);
  line-height:var(--leading-tight);
  letter-spacing:-.04em;
}

.list {
  list-style:none;
  padding:0;
  margin:var(--space-4) 0 0;
  display:grid;
  gap:var(--space-3);
}
.list li {
  position:relative;
  padding-left:var(--space-5);
  color:var(--muted);
  line-height:1.68;
}
/* Tier C — the bullet marker. Was an 8px gradient circle with two glow
   shadows: at body size that is a light source next to every sentence, and
   thirty of them down a page is the single biggest reason the site read as
   playful. A hairline rule instead — it points into the line it introduces,
   it lands on the text baseline, and it does not compete with the words. */
.list li::before {
  content:"";
  position:absolute;
  left:0; top:.82em;
  width:var(--marker-w); height:var(--marker-h);
  border-radius:0;
  background:var(--marker-ink);
}

/* ---------- Pricing ---------- */
.price-tile {
  position:relative;
  /* Clear vertical separation between the three plan cards above and this
     subscription panel (shared --block-gap; the previous --grid-gap was too
     tight and let the cards' shadows bleed onto the panel). Single-sided
     (panel top only; the grid-3 above has no bottom margin) so it never
     doubles. */
  margin-top:var(--block-gap);
  overflow:hidden;
  padding:var(--panel-pad);
  border-radius:var(--radius-md);
  
  border:1px solid var(--ns-border-strong);
  box-shadow:0 24px 80px rgba(0,0,0,.55);
  /* The ~430px empty column had TWO causes, and only fixing both removes it:
       1. align-items:stretch inflated the shorter column to match the taller;
       2. the left column was carrying four fine-print paragraphs and a nested
          "Already subscribed?" card, which made it 945px against a 510px right.
     The fine print and the signpost now sit in full-width rows beneath, so the
     offer (~350px) and the access path (~400px) are naturally close in height —
     and align-items:start lets each end where its content ends. Content-driven,
     not equalised. */
  display:grid;
  grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);
  gap:var(--block-gap) var(--grid-gap);
  align-items:start;
  width:100%;
}
.price-tile > * { min-width:0; }
.price-tile .price-head {
  display:flex; flex-direction:column; gap:var(--space-4);
}
.price-badge {
  align-self:flex-start;
  display:inline-flex;
  align-items:center;
  gap:var(--space-2);
  padding:var(--space-2) var(--space-4);
  border-radius:999px;
  font-size:var(--text-label);
  text-transform:uppercase;
  letter-spacing:.10em;
  color:var(--ns-sapphire-contrast);
  background:var(--hero-grad);
  font-weight:var(--weight-bold);
  box-shadow:none;
}
.price-amount {
  display:flex;
  align-items:baseline;
  gap:var(--space-3);
  line-height:1;
  letter-spacing:-.04em;
}
.price-amount .num {
  /* Was clamp(3rem, 6vw, 4.6rem)/800 with a gradient fill — 73.6px against a
     60px H1 at 1440, and 48px against a 40px H1 at 375. Its 6vw slope was
     STEEPER than the H1's 4.8vw and its floor HIGHER, so it out-shouted the
     headline at every width, on a page whose primary action is a FREE download.
     It now uses --text-price (max 48px: under the 52px H2 ceiling and well under
     the H1) at heading weight, in solid --text. The gradient is reserved for the
     H1's emphasis span, so the gradient MEANS "the message" instead of
     decorating a number. The price is still the largest thing inside its own
     tile — it simply no longer outranks the page. */
  font-size:var(--text-price);
  font-weight:var(--weight-bold);
  color:var(--text);
}
.price-amount .per {
  color:var(--muted);
  font-size:var(--text-body);
}
.price-amount .currency {
  color:var(--muted);
  font-size:var(--text-label);
  letter-spacing:.06em;
  text-transform:uppercase;
}
.price-microcopy {
  color:var(--muted);
  line-height:1.72;
  margin:0;
  font-size:var(--text-body);
  /* balance, not pretty: this is a 3-line caption sitting directly under the
     price, and the generic p rule left it rendering as two full lines plus a
     lone "add-ons." at 360, 390 and 1024. A runt under the one number the
     page exists to state is the most visible orphan on the site. */
  text-wrap:balance;
}
/* Calm caption above the two payment buttons in the pricing tile.
   Frames the choice as a preference, not a sales push. */
.price-cta-intro {
  color: var(--muted);
  font-size:var(--text-supporting);
  font-weight:var(--weight-strong);
  letter-spacing: .005em;
}

.price-cta-row {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-4);
  margin-top:var(--space-5);
}
.price-cta-row .btn { flex:0 1 auto; min-width:260px; }

/* A label that needs inline markup — an em dash bound to its word, the
   [data-price] token — ships inside ONE span. .btn is a flex container,
   so each element child would otherwise become its own flex item:
   blockified, unwrappable, and pushed apart by the button's gap. One
   item, inline children, and the label wraps as a phrase again. */
.btn > .btn-label { min-width:0; }

/* ---------- The plan pair — two plans, one attached trial ----------
   The homepage pricing row was three peer cards: Lite, 7-day trial, Premium.
   Measured at 1440 they ran 315 / 225 / 200px, three different weights with
   no reason a reader could name, and the section read as accidental.

   The cause was the taxonomy, not the spacing. Lite and Premium are PLANS —
   states you stay in until you change them. The trial is temporary ACCESS to
   Premium that expires on its own and is started inside the app. Putting all
   three in one row asserted they were the same kind of thing, and the ragged
   heights were the row disagreeing.

   So the trial moves onto Premium as a band and the row becomes the two
   plans it always was. Note what this does NOT do: nothing is padded, and no
   min-height is set. The cards land close in height because Premium now
   genuinely carries the content that makes it as heavy as Lite — the same
   principle as the equal-height note above, applied to composition instead
   of to a stretch rule. */

/* Full-bleed to the card's edges (the negative inline margins cancel
   --card-pad) so the band reads as part of the card rather than as one more
   paragraph in it. The card's own overflow:hidden and --radius-md clip the
   bottom corners, so the band needs no radius of its own. */
.plan-trial-band {
  margin:var(--space-5) calc(var(--card-pad) * -1) calc(var(--card-pad) * -1);
  padding:var(--space-4) var(--card-pad);
  border-top:1px solid var(--ns-border-subtle);
  background:var(--ns-surface-raised);
}
.plan-trial-band p { font-size:var(--text-supporting); }
.plan-trial-lede {
  margin:0 0 var(--space-2);
  color:var(--ns-text-primary);
  font-weight:var(--weight-strong);
}

/* The lead plan gets ONE step of emphasis: a brighter edge. Not a different
   fill, not a scale, not a ribbon — the two cards have to stay the same kind
   of object or the pair stops reading as a comparison. --ns-border-control is
   the 3:1 boundary token, so the step is visible without being loud. */
.plan-card-lead { border-color:var(--ns-border-control); }

/* The pair keeps BOTH columns down to tablet. A comparison only does its job
   while both plans are on screen at once, and the shared grid breakpoint
   (1080px) is not tuned for a pair — it is tuned for the 3-up and 4-up rows,
   where the third and fourth column genuinely do run out of width first.
   Measured at 768px the two plan cards are 340px each, WIDER than the same
   cards get once they stack on a phone, so collapsing them there costs the
   side-by-side read and buys nothing back. Below 720px — where the container
   gutter tightens and the nav folds to two rows — the stack is correct and
   the base rule takes over again. */
@media (min-width: 721px) {
  .grid-2.plan-duo { grid-template-columns:repeat(2, minmax(0,1fr)); }
  .grid-2.plan-duo > *:not([hidden]) { display:flex; }
}

@media (max-width: 880px) {
  .price-tile {
    grid-template-columns:1fr;
  }
}

/* ---------- Skip-to-content (accessibility) ----------
   Visually hidden until keyboard-focused, then floats above
   the sticky nav so screen-reader / keyboard users can jump
   straight past navigation to the main content. */
/* Visually hidden, still announced. Needed for the copy-to-clipboard live
   region: a copy button carries an aria-label, and an aria-label WINS over the
   element's text — so swapping the button's label to "Copied ✓" is silent for a
   screen-reader user. The confirmation has to live somewhere else. */
.sr-only {
  position:absolute;
  width:1px; height:1px;
  padding:0; margin:-1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

/* Copy succeeded — a green tick alone would be colour-only status, so the
   button's label changes too (site.js). This is scoped to .btn generally; it
   used to exist only inside .post-purchase-actions, so every other copy
   button in the site had no visible success state at all. */
.btn.is-copied {
  color:var(--ns-success);
  border-color:var(--ns-success);
  background:var(--ns-success-soft);
}

/* Parked by its OWN height rather than a guessed -40px. The chip measures 45px
   at the current type scale, so the old constant left a 5px sapphire sliver
   showing at the top of every page at rest — a stray accent on all 18 routes,
   which is exactly the kind of uncontrolled colour this system is meant to
   remove. translateY(-100%) cannot drift when the padding or type scale moves.

   The focus ring is --ns-text-primary, not --cyan. --cyan is now an alias of
   --ns-sapphire, and this chip's background is --hero-grad, which is ALSO an
   alias of --ns-sapphire — so the ring had become sapphire-on-sapphire and
   disappeared. It read as visible only while --cyan was still #22d6ff. A light
   ring is the right answer on a saturated chip: it carries against both the
   sapphire fill and the near-black page showing through outline-offset. */
.skip-link {
  position:absolute;
  top:0;
  left:12px;
  z-index:100;
  padding:var(--space-3) var(--space-4);
  border-radius:var(--radius-sm);
  background:var(--hero-grad);
  color:var(--ns-sapphire-contrast);
  font-weight:var(--weight-bold);
  text-decoration:none;
  box-shadow:none;
  transform:translateY(-100%);
  transition:transform .2s ease;
}
.skip-link:focus {
  transform:translateY(12px);
  outline:3px solid var(--ns-text-primary);
  outline-offset:2px;
}

/* ---------- Footer legal-links row ----------
   Sits below the existing two-card footer grid on every
   page.  Renders as a tidy row of pill-style links —
   Terms, Privacy, Refunds, Disclaimer, FAQ, Contact. */
.footer-legal {
  margin-top:var(--space-5);
  border-radius:var(--radius-md);
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
}
.footer-legal-row {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-2) var(--space-2);
  align-items:center;
}
/* On phones the five legal links + separators + the long support-email chip
   wrapped as 4/4/2/1 — a ragged tail ending in a stranded item. Centering the
   wrap makes every partial row deliberate, and the email chip (the widest
   item by far) takes its own centered closing row instead of breaking a link
   row mid-way. */
@media (max-width:640px) {
  .footer-legal-row { justify-content:center; }
  /* The interpuncts are desktop rhythm; on a phone they turn 5 links into 9
     flex items and make every wrap ragged. Centered links alone wrap cleanly,
     and each keeps a 44px touch row. */
  .footer-legal-row .sep { display:none; }
  .footer-legal-row a {
    display:inline-flex;
    align-items:center;
    min-height:44px;
    padding:0 var(--space-3);
  }
  .footer-legal-row [data-support-email] {
    flex-basis:100%;
    justify-content:center;
    text-align:center;
  }
  .footer-legal-fineprint { text-align:center; margin-inline:auto; }
}
.footer-legal-row a {
  color:var(--muted);
  text-decoration:none;
  padding:2px var(--space-1);
  font-size:var(--text-caption);
  border:0;
  background:transparent;
  transition:color .2s ease;
}
.footer-legal-row a:hover {
  color:var(--text);
}
.footer-legal-row .sep {
  color:var(--muted);
  opacity:.4;
  user-select:none;
}
.footer-legal-fineprint {
  color:var(--muted);
  font-size:var(--text-fine);
  line-height:1.6;
  letter-spacing:.01em;
  opacity:.85;
  max-width:var(--measure-fine);
}
.footer-legal-fineprint a {
  color:inherit;
  text-decoration:underline;
  text-decoration-color:var(--ns-border-strong);
  text-underline-offset:3px;
}

/* ---------- Legal/long-form page typography ----------
   Used by terms.html, privacy.html, refunds.html,
   disclaimer.html, faq.html.  Optimised for reading
   density: narrower text column, generous line-height,
   clear h2/h3 hierarchy, and softer card backgrounds. */
.legal-shell {
  max-width: 880px;
  margin:0 auto;
}
.legal-shell h1 {
  font-size: var(--text-section);
  margin:var(--space-8) 0 var(--space-3);
  letter-spacing: -.04em;
  line-height: 1.05;
  max-width: none;
}
.legal-shell h2 {
  margin-top:var(--space-9);
  font-size:var(--text-subsection);
  letter-spacing: -.02em;
}
.legal-shell h3 {
  margin-top:var(--space-7);
  margin-bottom:var(--space-1);
  font-size:var(--text-card-title);
  color: var(--text);
  letter-spacing: -.01em;
}
.legal-shell p,
.legal-shell li {
  color: var(--muted);
  line-height: var(--leading-loose);
  font-size: var(--text-body);
}
.legal-shell strong { color: var(--text); }
.legal-shell ul, .legal-shell ol {
  padding-left:var(--space-6);
  margin:var(--space-3) 0 var(--space-4);
}
.legal-shell li { margin-bottom:var(--space-2); }
/* `:not(.btn)` because this is the ORDINARY INLINE LINK treatment — primary
   text plus a visible rule — and a button is not an inline link. Unscoped,
   `.legal-shell a` (0,1,1) out-ranks `.btn-primary` (0,1,0), so on the four
   legal-shell pages it was repainting ten CTAs with prose colour and hanging
   a border under them. Scoping it OUT is the fix rather than doubling the
   button class: the rule should never have matched a button in the first
   place, and escalating specificity would leave the same wrong match with a
   louder voice. */
.legal-shell a:not(.btn) {
  color: var(--text);
}
.legal-shell a:not(.btn):hover { border-bottom-color: var(--ns-steel-hover); }
.legal-shell code {
  padding:1px var(--space-2);
  border-radius: 8px;
  background: var(--ns-surface-raised);
  border: 1px solid var(--ns-border-subtle);
  color: var(--text);
  font-size: var(--text-code);
}
.legal-shell .meta {
  margin-top:var(--space-2);
  color: var(--muted);
  font-size:var(--text-supporting);
  letter-spacing: .04em;
  text-transform: uppercase;
  opacity: .75;
}
.legal-shell .legal-card {
  margin-top:var(--space-7);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-subtle);
  box-shadow:0 14px 34px rgba(0,0,0,.34);
}

/* ========================
   Support page — section hierarchy
   ------------------------
   Scoped to ``.support-shell`` so this only fires on
   /support.html and leaves Terms / Privacy / Refunds /
   Disclaimer typography untouched.

   Goals:
     - Promote each H3 (Manual access verification, Billing,
       Install, Try Lite, Reporting a bug) from "small heading"
       into a clearly scannable section header.
     - Group each H3 + its bullets in a soft glass block so the
       page reads as 5 distinct topics instead of one wall of
       text.
     - Stay inside the existing NovaStation cyan/purple highlight
       system — no new visual language.
   ======================== */
.support-shell .support-topics-heading {
  margin-top:var(--space-10);
  margin-bottom:var(--space-2);
  font-size:var(--text-subsection);
  letter-spacing: -.02em;
  color: var(--text);
  /* Tiny cyan-purple gradient bar under the section meta-heading
     so the "Common topics" entry-point reads as a separator
     between the contact card and the topic blocks. */
  padding-bottom:var(--space-3);
  border-bottom: 1px solid var(--ns-border-subtle);
  position: relative;
}
.support-shell .support-topics-heading::after {
  content: "";
  position: absolute;
  left: 0; bottom: -1px;
  width: 96px; height: 2px;
  background:var(--ns-steel);
  border-radius: 2px;
  box-shadow:none;
}

.support-shell .support-section {
  margin-top:var(--space-7);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-subtle);
  box-shadow:0 14px 32px rgba(0,0,0,.30);
  position: relative;
}
/* Thin cyan/purple gradient bar pinned to the left edge — a
   single visual cue that the block is a major support topic.
   Cheap visual separator, no extra text. */
.support-shell .support-section::before {
  content: "";
  position: absolute;
  left: 0; top: 20px; bottom: 20px;
  width: 3px;
  border-radius: 3px;
  background:var(--ns-border-strong);
}
.support-shell .support-section h3 {
  margin-top:2px;
  margin-bottom:var(--space-3);
  font-size:var(--text-card-title);
  font-weight:var(--weight-bold);
  letter-spacing: -.01em;
  color: var(--text);
  /* Small gradient dot before the heading — same dot the
     pricing feature list uses, scaled down — so the section
     title reads as on-brand without needing an extra pill. */
  display: flex;
  align-items: center;
  gap:var(--space-3);
}
.support-shell .support-section h3::before {
  content: "";
  flex: 0 0 auto;
  width: 10px; height: 10px;
  border-radius: 50%;
  background:var(--ns-steel);
}
.support-shell .support-section ul {
  margin:var(--space-1) 0 var(--space-1);
  padding-left:var(--space-6);
}
.support-shell .support-section li {
  margin-bottom:var(--space-3);
  line-height: 1.72;
}
.support-shell .support-section p {
  line-height: 1.72;
  margin:var(--space-2) 0 var(--space-3);
}

@media (max-width: 640px) {
  .support-shell .support-topics-heading {
    font-size: var(--text-subsection);
    margin-top:var(--space-8);
  }
  .support-shell .support-section h3 {
    font-size: var(--text-card-title);
  }
}

/* ---- Two-column topic band (>=1240px) ---------------------------------
   Five stacked cards of prose ran /support to 3,838px at 1440, and 880px of
   the 1160px content box was all the width any of it ever used. This band
   breaks the topics out of the .legal-shell reading column to the full
   container width. 1160px is exactly --max (1240) minus two 40px gutters —
   the widest the container is ever allowed to be — so a fixed width at this
   breakpoint can never overflow and no vw arithmetic (which would include
   the scrollbar) is needed. Below 1240 nothing changes: one column, one card
   per row, which is what every narrow viewport wants anyway.

   column-count, not grid: the five cards have very unequal heights (the
   activation card is nearly twice the install card), and a row-based grid
   would leave a half-empty last row plus forced gaps wherever a short card
   sits beside a tall one. Multicol balances the two columns by height and
   leaves no empty cell. Reading order is unchanged -- a column box reads top
   to bottom, left column first, which is DOM order.

   The heading and its category index span both columns so they read as the
   way into the band, not as the first item of column one. */
@media (min-width:1240px) {
  /* One width for the page, not two. Breaking only the topics out to the
     full container left the H1 and the two entry cards indented 140px from
     everything below them -- a step that reads as an accident, which is the
     exact class of defect this pass exists to remove. Widening the shell
     instead keeps every left edge on the page aligned, and needs no negative
     margins: 1160px is --max (1240) minus two 40px gutters, the widest the
     container is ever allowed to be. */
  .support-shell { max-width:1160px; }
  .support-shell .support-entry {
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:var(--space-8);
    align-items:start;
  }
  .support-shell .support-topics {
    column-count:2;
    column-gap:var(--space-8);
  }
  .support-shell .support-topics > .support-topics-heading,
  .support-shell .support-topics > .legal-toc {
    column-span:all;
  }
  .support-shell .support-topics .support-section {
    break-inside:avoid;
  }
}
.legal-shell .legal-toc {
  display: flex;
  flex-wrap: wrap;
  gap:var(--space-2);
  margin:var(--space-5) 0 var(--space-2);
  padding:0;
  list-style: none;
}
.legal-shell .legal-toc li { margin:0; }
.legal-shell .legal-toc a {
  display: inline-block;
  padding:var(--space-2) var(--space-3);
  border-radius: 999px;
  font-size:var(--text-caption);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-strong);
  color: var(--muted);
}
.legal-shell .legal-toc a:hover {
  color: var(--text);
  border-color: var(--ns-border-strong);
}

/* ========================
   FAQ CATEGORY RAIL: ONE ELEMENT, TWO SHAPES.
   ------------------------
   43 questions in five groups, and until now the only way to reach the fifth
   was to scroll past the other four. The rail is the same idiom the /start
   paths already use (see "THE RAIL: ONE ELEMENT, TWO SHAPES" below): below
   1081px it is a DOCK — a sticky strip of category chips under the header —
   and at 1081px and up it is a SIDE RAIL in a column of its own.

   One element, not a dock plus a desktop copy: two copies would put the same
   five links and the nav's accessible name into the document twice, and a
   screen reader reading "FAQ categories" twice cannot tell which is live.

   The chips are plain in-page anchors, so every category is deep-linkable and
   the whole thing works with scripting off — which is also why the <details>
   items underneath are left exactly as they are. The counts are part of the
   link text on purpose: "About the product 17" tells a reader how much is
   behind the chip before they spend a tap on it.
   ======================== */
:root {
  /* What the dock covers, so a category heading can clear it. Measured, not
     guessed: one row of --text-caption chips at --space-2 padding inside a
     --space-2 rail measures 59px at 360, 390, 768 and 1080 alike — the dock
     is a fixed-height strip because its chips scroll instead of wrapping. */
  --faq-rail-h:59px;
}

.faq-rail {
  /* Flush under the header for the same reason the path dock is: a gap
     between two sticky surfaces shows the page sliding through it. */
  position:sticky; top:var(--nav-offset); z-index:40;
  margin:var(--space-6) 0 var(--space-2);
  padding:var(--space-2);
  border-radius:var(--radius-sm);
  border:1px solid var(--ns-border-subtle);
  background:var(--ns-bg-deep);
}
/* The dock has no room for a label beside five chips, but the nav is named by
   it — so it stays in the accessibility tree rather than being display:none'd
   out of it, and the side rail below prints it. */
.faq-shell .faq-rail-title {
  position:absolute; width:1px; height:1px;
  margin:-1px; padding:0; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}
.faq-shell .faq-rail-list {
  display:flex;
  gap:var(--space-2);
  margin:0; padding:0;
  list-style:none;
  /* The chips scroll sideways rather than wrapping to a second row: a dock
     that changes height as the viewport narrows changes what --faq-rail-h has
     to reserve, and every category heading lands wrong by the difference. */
  overflow-x:auto;
  overflow-y:hidden;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.faq-shell .faq-rail-list::-webkit-scrollbar { display:none; }
.faq-shell .faq-rail-list li { margin:0; flex:none; }
.faq-shell .faq-rail-list a {
  display:inline-flex; align-items:center; gap:6px;
  padding:var(--space-2) var(--space-3);
  border-radius:999px;
  border:1px solid var(--ns-border-strong);
  background:var(--ns-surface);
  color:var(--muted);
  font-size:var(--text-caption);
  white-space:nowrap;
  border-bottom-color:var(--ns-border-strong);
}
.faq-shell .faq-rail-list a:hover,
.faq-shell .faq-rail-list a:focus-visible {
  color:var(--text);
  border-color:var(--ns-border-control);
  border-bottom-color:var(--ns-border-control);
}
.faq-rail-count {
  color:var(--ns-text-tertiary);
  font-variant-numeric:tabular-nums;
  font-size:var(--text-label);
  font-weight:var(--weight-strong);
}
/* A heading the dock sticks over has to land below the DOCK, not below the
   header. The site-wide scroll-padding-top already pays for the header and
   STACKS with this, so only the extra is added here. Scoped to the FAQ's own
   category sections: widening the global offset would push every anchor on
   every other page down by the height of a dock that is not there. */
.faq-body-col > section[id] {
  scroll-margin-top:calc(var(--space-2) + var(--faq-rail-h));
}

/* ---- The side rail ------------------------------------------------------
   Same 1081px as the /start rail, and for the same reason: it is the width at
   which a 248px column plus --pair-gap comes out of the page and still leaves
   the answers a full reading measure. The shell widens by exactly the column
   it gained (880 -> 1180 at --pair-gap's 44px ceiling), so the answers keep
   the ~880px they were typeset at instead of paying for the navigation. */
@media (min-width:1081px) {
  .faq-shell {
    max-width:1180px;
    display:grid;
    grid-template-columns:minmax(0, 248px) minmax(0, 1fr);
    column-gap:var(--pair-gap);
    align-items:start;
  }
  /* The page head belongs to both columns. Spanning it keeps the h1 at the
     full width it is sized for and lets the rail start level with the first
     category, rather than beside a title it does not navigate. */
  .faq-shell > .eyebrow,
  .faq-shell > h1,
  .faq-shell > .meta { grid-column:1 / -1; }

  .faq-rail {
    margin:0;
    align-self:start;
    /* --space-4 below the header, not flush against it: with a column of its
       own the rail no longer forms a continuous surface with the header, and
       sitting flush would read as an extension of it. */
    top:calc(var(--nav-offset) + var(--space-4));
    padding:var(--space-5) var(--space-4);
  }
  /* With a column of its own the rail can say what it is — the one thing the
     dock has to leave to the nav's accessible name. */
  .faq-shell .faq-rail-title {
    position:static; width:auto; height:auto;
    margin:0 0 var(--space-3); clip-path:none;
    color:var(--muted);
    font-size:var(--text-label); font-weight:var(--weight-strong);
    letter-spacing:var(--chip-tracking); text-transform:uppercase;
  }
  .faq-shell .faq-rail-list {
    flex-direction:column;
    gap:var(--space-1);
    overflow:visible;
  }
  /* Read vertically the chips become rows: full width, count pushed to the
     right edge so the five numbers line up as a column of their own. */
  .faq-shell .faq-rail-list a {
    justify-content:space-between;
    width:100%;
    border-radius:var(--radius-sm);
    border-color:transparent;
    background:transparent;
    padding:var(--space-2) var(--space-3);
    white-space:normal;
  }
  .faq-shell .faq-rail-list a:hover,
  .faq-shell .faq-rail-list a:focus-visible { background:var(--ns-surface); }
  /* In its own column the rail covers nothing, so the anchor reservation goes
     back to the site-wide header offset. */
  .faq-body-col > section[id] { scroll-margin-top:0; }
}

/* ---------- FAQ accordion (no JS — uses <details>) ---------- */
.faq-list {
  display:grid;
  gap:var(--space-3);
  margin-top:var(--section-head-gap);
}
.faq-item {
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  background:var(--ns-surface);
  box-shadow:0 12px 30px rgba(0,0,0,.28);
  overflow:hidden;
}
.faq-item > summary {
  cursor:pointer;
  list-style:none;
  padding:var(--space-5) var(--space-6);
  font-weight:var(--weight-strong);
  color:var(--text);
  /* Two-column grid: the question owns the flexible track, the +/× indicator
     owns a fixed end track — so the icon is pinned to the SAME right inset in
     every row (the row's padding), regardless of question length or card
     width, and the two boxes can never collide. (Was flex space-between with
     a 72ch measure cap on the row itself — on wide homepage cards the row
     shrank to the cap and the icon floated ~430px inward.) */
  display:grid;
  grid-template-columns:minmax(0, 1fr) auto;
  align-items:center;
  gap:var(--space-3);
  font-size:var(--text-body);
  letter-spacing:-.01em;
}
.faq-item > summary::-webkit-details-marker { display:none; }
.faq-item > summary::after {
  content:"+";
  font-size:var(--text-subsection);
  font-weight:var(--weight-bold);
  color:var(--muted);
  transition:transform .2s ease, color .2s ease;
}
.faq-item[open] > summary::after {
  transform:rotate(45deg);
  color:var(--ns-text-primary);
}
.faq-item > .faq-body {
  padding:0 var(--space-6) var(--space-5);
  color:var(--muted);
  line-height:1.74;
}
.faq-item > .faq-body :first-child { margin-top:0; }
.faq-item > .faq-body :last-child  { margin-bottom:0; }

/* ---------- Quick-start specific ---------- */

/* A note is an aside, not an announcement. It was a cyan-to-violet wash with a
   blue rim, which is more emphasis than the copy inside it ever carries. It is
   the default surface now; the leading <strong> is what marks it as a note. */
.note {
  margin-top:var(--block-gap);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border:1px solid var(--ns-border-subtle);
  color:var(--muted);
  line-height:1.72;
}
/* Only the note's LEADING strong is a title. This used to be `.note strong`,
   which block-ified EVERY <strong> inside a note — so an inline label like
   "<strong>Official site:</strong> novastationpro.com" was forced onto its own
   line with the value orphaned beneath it, and an emphasised phrase
   mid-sentence broke out as an isolated line. The child combinator
   keeps the title behaviour while letting nested emphasis stay inline. */
.note > strong { color:var(--text); display:block; margin-bottom:var(--space-1); }
.note strong { color:var(--text); }
/* .warn is the one note variant that carries semantic meaning, so it is the
   one that gets colour: amber, on the amber-tinted surface, and nowhere else. */
.note.warn {
  background:var(--ns-warning-soft);
  border-color:var(--ns-warning);
}

/* ---------- Footer ---------- */
.footer-brand {
  display:flex;
  align-items:center;
  gap:var(--space-4);
  margin-bottom:var(--space-4);
}
.footer-brand img {
  width:56px; height:56px; object-fit:contain;
  border-radius:var(--radius-sm);
  background:var(--ns-bg-section);
  padding:var(--space-2);
  border:1px solid var(--ns-border-subtle);
}
.fine-print {
  margin-top:var(--space-4);
  color:var(--muted);
  line-height:1.72;
  font-size:var(--text-supporting);
}

/* ---------- Responsive ---------- */
@media (max-width: 1080px) {
  .hero-points,
.grid-3,
.grid-2,
.workflow-grid { grid-template-columns:1fr; }
  .hero > div:not([hidden]),
.hero-points > *:not([hidden]),
.grid-3 > *:not([hidden]),
.grid-2 > *:not([hidden]),
.workflow-grid > *:not([hidden]) { display:block; }
}
@media (max-width: 720px) {
  .container, .nav { padding-left:var(--space-4); padding-right:var(--space-4); }
  /* h1 sizing now comes from --text-display (a controlled 2.4vw ramp with a
     40px floor). The old override here used a 9vw slope — the kind of
     formula that makes type jump unpredictably between breakpoints. */
  /* Mobile nav: keep the brand on the top row, drop the nav-links
     onto a second full-width row so they wrap cleanly on small screens. */
  .nav {
    grid-template-columns:1fr;
    align-items:center;
    row-gap:var(--space-3);
  }
  .brand        { grid-column:1; grid-row:1; }
  .nav-links    { grid-column:1; grid-row:2; justify-content:flex-start; }
    .hero { gap:var(--space-7); }
  .hero-actions { width:100%; gap:var(--space-3); margin-top:var(--space-7); margin-bottom:var(--space-3); }
  .hero-actions .btn { flex:1 1 240px; min-width:0; }
  .hero-points { margin-top:var(--space-6); }
  .nav-links {
    width:100%;
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    padding:2px 2px var(--space-1);
    margin-top:2px;
    background:var(--ns-bg-deep);
    border-top:1px solid transparent;
  }
}


/* ========================
   Thank-you / next-steps page + reusable step list
   ------------------------ */
.thanks-shell { max-width: 720px; }

/* ---- /activate composition (>=1240px) ---------------------------------
   Scoped to .activate-shell, never to .thanks-shell, because /success shares
   that class and is a one-column receipt with nothing to lay out beside
   anything.

   /activate has three things to say and was saying them in one 720px column
   1,038px tall before the troubleshooting even started: five one-line steps,
   a capture of the screen those steps describe, and two closing notes. The
   steps and the capture belong to each other, so they go side by side; the
   notes are the answer to "it didn't work", so they stay under, as a pair
   rather than as two more full-width bands.

   1160px is --max (1240) minus two 40px gutters -- the widest the container
   is ever allowed to be -- so widening the shell here can never overflow at
   this breakpoint. Below it every wrapper is an ordinary block and the page
   is byte-for-byte the layout it has always had. */
@media (min-width:1240px) {
  .activate-shell { max-width:1160px; }
  .activate-shell .activate-flow-grid {
    display:grid;
    /* The steps are five short lines; the capture is the evidence. The
       0.62 : 1 split gives the list the ~430px it actually needs and spends
       the rest on the thing worth looking at. */
    grid-template-columns:minmax(0,.62fr) minmax(0,1fr);
    gap:var(--space-8);
    align-items:start;
  }
  .activate-shell .activate-flow-grid > .feature-shot { margin-top:0; }
  .activate-shell .activate-flow-steps > :first-child { margin-top:0; }
  .activate-shell .activate-aside-pair {
    display:grid;
    /* Six troubleshooting entries beside a one-paragraph note is not a 50/50
       split. Measured at 1240/1440/1920: an even pair ends 453px apart, and
       that gap is the last thing on the page before the footer. Giving the
       long card the width it needs and the note a side-panel column closes
       the delta to 206px -- 1.5:.5 would close it to 96px, but at a 282px
       note column and with the list at exactly the 72ch prose cap. */
    grid-template-columns:minmax(0,1.35fr) minmax(0,.65fr);
    gap:var(--space-8);
    /* start, not stretch: stretching the short card to match would be the
       empty space this pass is under orders not to manufacture. The ratio
       above shortens the difference instead of padding it. */
    align-items:start;
  }
}
.thanks-lead {
  color: var(--muted);
  font-size:var(--text-lead);
  line-height: 1.72;
  margin:var(--space-2) 0 var(--space-7);
  max-width: 60ch;
}

/* "Payment received" badge revealed by query-string detection on
   /thank-you. Purely visual reassurance for customers arriving from
   the Stripe / PayPal post-payment redirect. */
.thanks-paid-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap:var(--space-3) var(--space-4);
  margin:var(--space-5) 0 var(--space-2);
  padding:var(--space-4) var(--space-5);
  border-radius:var(--radius-md);
  background:var(--ns-success-soft);
  border: 1px solid var(--ns-success);
  color: var(--text);
  font-size:var(--text-supporting);
  line-height: 1.55;
  box-shadow:0 14px 30px rgba(0,0,0,.28);
}
.thanks-paid-banner strong {
  color: var(--ns-success);
  letter-spacing: .04em;
  text-transform: uppercase;
  font-size:var(--text-label);
  padding:var(--space-1) var(--space-3);
  border-radius: 999px;
  background: var(--ns-success-soft);
  border: 1px solid var(--ns-success);
}
.thanks-paid-banner span { color: var(--muted); }

/* ========================
   Post-purchase support — inline chip + thank-you card
   ------------------------ */

/* "Already subscribed? — Send Setup Details", under the Subscribe buttons in
   the LEFT payment column. It sits there rather than under the feature list
   because the feature column runs taller, which left a dead gap below the
   buttons; this closes it and gives the payment column an end.

   Question as text, action as a real .btn-secondary: the split is what makes
   it read as a component rather than a wordy button, and .btn-secondary keeps
   it below Subscribe in the CTA hierarchy without inventing a third style.
   The manual activation steps live in #delivery, not here. */
.purchase-helper-card {
  /* Was a card nested inside the price tile — a card inside a card has no
     hierarchy left to spend. It is now a quiet row on the tile itself. */
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:var(--space-5);
  padding-top:var(--block-gap);
  border-top:1px solid var(--ns-border-subtle);
}
.purchase-helper-title {
  margin:var(--space-1) 0 0;
  font-size:var(--text-card-title);
  font-weight:var(--weight-bold);
  letter-spacing: -.01em;
  color: var(--text);
  line-height: 1.2;
}
.purchase-helper-body {
  margin:0;
  max-width: 46ch;            /* comfortable measure in the narrower
                                 left column, not stretched edge-to-edge */
  color: var(--muted);
  font-size:var(--text-supporting);
  line-height: 1.6;
}
/* CTA — uses the same dark-glass body as .btn-secondary, with a
   brighter cyan border + soft cyan hover glow so it reads as "this
   is the next step" rather than a flat link, while still sitting
   below the Stripe / PayPal primary buttons in the hierarchy.
   Left-hugging (align-self:flex-start) and sized to its label so it
   lines up with the card's left-aligned pill, title, and body; it
   stretches to full width only on the narrowest phones. */
.purchase-helper-btn {
  align-self:flex-start;     /* hug the left, sized to its label */
  margin-top:var(--space-3);
  min-height: 46px;
  padding:0 var(--space-6);
  font-size:var(--text-supporting);
  font-weight:var(--weight-bold);
  letter-spacing: -.005em;
  color: var(--text);
  background:var(--ns-surface-raised);
  border: 1px solid var(--ns-border-control);
  box-shadow:0 10px 24px rgba(0,0,0,.34);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap:var(--space-3);
  white-space: nowrap;
  transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.purchase-helper-btn:hover {
  transform: translateY(-1px);
  border-color: var(--ns-border-control);
  box-shadow:0 18px 36px rgba(0,0,0,.42);
}
.purchase-helper-btn span[aria-hidden] {
  color: var(--ns-steel-hover);
  font-size:var(--text-body);
  line-height: 1;
  transition: transform .2s ease;
}
.purchase-helper-btn:hover span[aria-hidden] {
  transform: translateX(4px);
}

/* Responsive: at <880px the .price-tile flips to a single column,
   so the stack becomes price → notes → buttons → legal → this card
   → included features.  The card keeps its compact left-aligned
   desktop styling; we only give it a touch more separation from the
   legal line above and let the button go full-width on the narrowest
   phones for easier tapping. */
@media (max-width: 880px) {
  .purchase-helper-card {
    margin-top:var(--space-5);
  }
}
@media (max-width: 640px) {
  .purchase-helper-btn {
    align-self:stretch;
    width: 100%;
    justify-content: center;
    white-space: normal;
  }
}

/* Premium "Send your purchase details" card on /thank-you.
   Reuses the visual language of .price-tile (radial glass,
   gradient glow) so the post-purchase experience reads as
   first-class, not as an afterthought. */

/* Visible "What to include" checklist — replaces the previous
   card-lead paragraph + invisible template fields. Each row shows
   one piece of information the customer should put in their email.
   Bordered container so it reads as a distinct content block; small
   gradient dot per row keeps the cyan/magenta accent rhythm. */

/* "About the machine code" note — quiet info callout that sits between
   the copy buttons and the template preview. Subtle cyan tint signals
   it's helpful context, not a required action. */

/* Collapsible template preview — quiet by default, expands to show
   the exact subject + body the Copy / Email-with-template buttons
   use. Keeps the card compact for customers who don't need to read
   the template, while remaining honest about what gets sent. */

.thanks-flow-heading {
  margin:var(--space-8) 0 var(--space-3);
  font-size:var(--text-subsection);
  letter-spacing: -.015em;
  color: var(--text);
}

/* ========================
   Early-release note
   ------------------------
   A subtle, on-brand card used to communicate that NovaStation
   is an active early release and to invite bug reports.
   Designed to feel secondary — never as loud as a warning
   banner — and to sit comfortably below primary content on
   the thank-you and support pages.
   ======================== */
.early-access-note {
  margin-top:var(--block-gap);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-subtle);
  box-shadow:0 10px 28px rgba(0,0,0,.22);
  color: var(--muted);
  font-size:var(--text-supporting);
  line-height: 1.65;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap:var(--space-2) var(--space-4);
}
.early-access-note .early-access-tag {
  display: inline-flex;
  align-items: center;
  gap:var(--space-2);
  padding:var(--space-1) var(--space-3);
  border-radius: 999px;
  background: var(--ns-sapphire-soft);
  border: 1px solid var(--ns-sapphire-soft);
  color: var(--text);
  font-size:var(--text-label);
  font-weight:var(--weight-strong);
  letter-spacing: .12em;
  text-transform: uppercase;
  flex: 0 0 auto;
}
.early-access-note .early-access-tag::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--ns-steel);
}
.early-access-note p {
  margin:0;
  flex: 1 1 320px;
}
.early-access-note a {
  color: var(--text);
}
.early-access-note a:hover { border-bottom-color: var(--ns-steel-hover); }

/* Footer variant of the early-release note — sits with the
   risk-disclaimer line in .footer-legal and inherits the same
   muted size; we just lighten it a touch so it reads as a
   secondary, ambient note rather than fresh content. */
.footer-legal .footer-legal-fineprint + .footer-legal-fineprint {
  margin-top:var(--space-2);
  opacity: .72;
}

/* ========================
   Manual delivery flow — Pay → Send → Verify → Deliver
   ------------------------
   Lives on the homepage under #delivery and again on the
   /thank-you page as the lead post-purchase explanation.
   Reuses the .thanks-steps visual rhythm but lays out as a
   4-column flow on desktop so the four steps read as a
   single horizontal process rather than a stack.  Drops
   back to a single column on tablet / mobile.
   ======================== */
.delivery-flow {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap:var(--grid-gap);
  list-style: none;
  padding:0;
  margin:0;
  counter-reset: delivery;
  align-items:start;   /* steps end where their copy ends */
}
.delivery-flow > li:not([hidden]) {
  position: relative;
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-subtle);
  box-shadow:0 16px 38px rgba(0,0,0,.34);
  color: var(--muted);
  line-height: 1.65;
  display: flex;
  flex-direction: column;
  gap:var(--space-3);
}
.delivery-flow > li::before {
  content: attr(data-step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 13px;
  /* Neutral step chip — see .workflow-step::before for why the accent is not
     spent here. Ordinal, size and position carry the sequence; the numeral
     only has to be readable. */
  color: var(--ns-text-primary);
  background: var(--ns-surface-raised);
  border: 1px solid var(--ns-border-control);
  font-weight:var(--weight-bold);
  font-size:var(--text-caption);
  letter-spacing: .02em;
  box-shadow:none;
}
.delivery-flow > li strong {
  display: block;
  color: var(--text);
  font-size:var(--text-card-title);
  letter-spacing: -.01em;
}
.delivery-flow > li span {
  display: block;
  color: var(--muted);
  line-height: 1.68;
  font-size:var(--text-supporting);
}
.delivery-flow > li code {
  padding:1px var(--space-2);
  border-radius: 8px;
  background: var(--ns-surface-raised);
  border: 1px solid var(--ns-border-subtle);
  color: var(--text);
  font-size: var(--text-code);
}
.delivery-flow > li a {
  color: var(--text);
}
.delivery-flow > li a:hover { border-bottom-color: var(--ns-steel-hover); }

@media (max-width: 1080px) {
  .delivery-flow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .delivery-flow { grid-template-columns: 1fr; }
}

/* How it works — balanced 3-column grid (six steps => two even rows)
   instead of the shared 4-up flow, which left card 05 alone on row 2
   with a large empty gap.  Scoped to #how-it-works so every other
   .delivery-flow list keeps its existing layout.  max-width keeps the
   cards from sprawling on wide screens; the 2-up / 1-up breakpoints
   mirror the shared component so tablet and mobile are unchanged. */
#how-it-works .delivery-flow {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 1040px;
  margin-inline: auto;
}
@media (max-width: 1080px) {
  #how-it-works .delivery-flow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  #how-it-works .delivery-flow { grid-template-columns: 1fr; }
}

/* ========================
   "What to expect" trust strip
   ------------------------
   Compact 4-up row of honest trust facts (billing, local-first
   data, cancellation, real-human support).  Reuses the panel
   visual language but shorter and quieter than .card so the
   strip reads as a "fine print at a glance" element rather
   than a marketing claim.
   ======================== */

/* ========================
   Pricing right-column "Included with subscription" heading
   ------------------------
   Tiny uppercase heading sitting above .price-features.
   Keeps the value list visually distinct from the price/CTA
   column so customers immediately see the right column is the
   "what you get" answer.
   ======================== */
.price-features-wrap {
  /* NO min-height:100%. That was half of the stretch problem — it forced the
     column to the tile's full height no matter how much content it held. */
  display:flex;
  flex-direction:column;
  gap:var(--space-5);
}
/* This heads the four-step "how you actually get access" sequence — the real
   onboarding on this page. It was styled as a 13.1px uppercase kicker with a
   dot: SMALLER than the 16px body copy it introduces. It is a heading, so it is
   typeset as one. The dot left with the kicker styling — a heading that needs a
   bullet in front of it is not reading as a heading. */
.price-included-head {
  margin:0 0 var(--space-3);
  padding:0 0 var(--space-3);
  font-size: var(--text-card-title);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
  letter-spacing: -.01em;
  text-transform: none;
  color: var(--text);
  border-bottom: 1px solid var(--ns-border-subtle);
}

/* ========================
   "How NovaStation fits into your workflow" — guided product tour
   ------------------------
   Replaces the "we should add screenshots" idea with a clean
   5-step card flow.  No real screenshots — just numbered cards
   describing each step of the routine the customer will follow
   inside the app.  Auto-fits the available width:
     wide  → 5 columns
     mid   → 3 columns (3 + 2 wrap)
     narrow → 2 columns
     mobile → 1 column
   No new hex codes — every colour resolves through the
   existing accent / panel / border tokens.
   ======================== */

/* ========================
   "Why this matters" — three-card grounded value section
   ------------------------
   Visual variant of the existing .grid-3 + .card pair with
   a slightly lifted accent badge per card so the reader's eye
   lands on the value name first.
   ======================== */

/* ========================
   "What you receive after subscribing" — receive panel pair
   ------------------------
   Two-column receive grid that reuses the .panel / .list
   visual rhythm so the section feels of-a-piece with the
   Features and Built-for blocks above it.  No new chrome —
   the receive-grid alias just gives the section a stable
   selector so verifiers / future edits target it directly.
   ======================== */

/* ========================
   "Current product focus" — quiet active-improvement strip
   ------------------------
   Single small card with a "Active development" label tag
   and a list of in-flight focus areas.  Designed to read
   as confident maintenance notes — not as "this is broken".
   ======================== */

/* ========================
   NovaStation Lite — added 2026-05-16
   Lite is intentionally visually quieter than Premium.  Lite
   uses the calmer ``.btn-secondary`` chrome; Premium keeps the
   accent-gradient ``.btn-primary`` chrome.  Plan comparison
   cards live on the same panel surface but Lite has a subdued
   border + Premium has the accent-tinted border + glow.
   ======================== */

/* Tertiary button — for the third hero CTA ("View features"). */

/* Hero hint strip under the action buttons — tells users
   what the secondary Lite button actually downloads. */
/* "FREE" supporting info block under the hero CTAs (home + Lite page).
   A quiet, padded glass chip (same language as .support-callout / .note) so it
   reads as an intentional block rather than loose text squeezed between the
   buttons and the cards. The FREE pill sits top-left of the copy; the copy is
   one flex item that wraps cleanly within a comfortable measure. */
.hero-lite-hint {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex-wrap: wrap;
  max-width: 600px;
  margin: var(--space-5) 0 var(--space-8);   /* ~28px below the CTA row, ~32px above the cards */
  padding: var(--space-5);                    /* ~20px internal breathing room */
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid var(--ns-border-subtle);
  color: var(--muted);
  font-size:var(--text-supporting);
  line-height: 1.7;
}
.hero-lite-hint .hero-lite-hint-text { flex: 1 1 260px; min-width: 0; }
.hero-lite-hint .lite-pill { flex: 0 0 auto; margin-top: 2px; }
/* The hint's own bottom margin owns the gap down to the hero point-cards, so
   the cards' default (mobile) top margin isn't stacked on top of it. */
.hero-lite-hint + .hero-points { margin-top: 0; }
/* Keep short, must-not-break phrases (price, "no card required") on one line.
   Used only on short spans — never the whole paragraph. */
.nowrap { white-space: nowrap; }
/* Mobile: tighten the chip's spacing/padding a touch so it scales down cleanly.
   Placed after the base rule so it actually wins at <=720px (same specificity). */
@media (max-width: 720px) {
  .hero-lite-hint { margin: var(--space-4) 0 var(--space-6); padding: var(--space-4); }
}
/* Tier B — the chip. Plan tags, the Lite pill and the inline Premium tag all
   used to be pills at three different paddings; they are one component now.
   The paid tier keeps a solid fill because that IS the hierarchy — it is the
   only place on the site where a micro-component is allowed to shout — but it
   loses its drop shadow, which at 11px was doing nothing but blurring. */
.lite-pill,
.plan-tag-lite,
.plan-tag-premium,
.plan-tag,
.card-tag {
  display: inline-flex;
  align-items: center;
  padding:var(--chip-pad-y) var(--chip-pad-x);
  border-radius:var(--radius-chip);
  font-size:var(--text-label);
  font-weight:var(--weight-bold);
  letter-spacing:var(--chip-tracking);
  text-transform: uppercase;
  white-space: nowrap;
}
.lite-pill,
.plan-tag-lite {
  color:var(--chip-ink);
  background:var(--chip-bg);
  border: 1px solid var(--chip-line);
}
.plan-tag-premium,
.card-tag {
  color: var(--ns-sapphire-contrast);
  background: var(--hero-grad);
  border: 1px solid transparent;
}
/* Small inline "Premium" tag for feature-card headings (Signal Twin). */
.card-tag {
  margin-left: var(--space-2);
  padding: 3px 8px;
  vertical-align: middle;
}

/* Plan compare row — Lite (left) + Premium (right) above the
   existing detailed Premium ``.price-tile``.  Premium card is
   visually dominant; Lite is calmer. */
.plan-compare {
  display: grid;
  gap:var(--grid-gap);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.plan-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap:var(--space-4);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border: 1px solid transparent;
  box-shadow:none;
}
.plan-card-premium {
  border-color: var(--ns-border-strong);
  /* Galaxy treatment: cyan glow top-right + a subtle violet/magenta
     glow bottom-left form the brand's cyan↔purple diagonal.  Kept at
     low opacity so it reads as premium depth, never neon. */
  
  box-shadow:none;
}
.plan-card-head h3 {
  margin:var(--space-2) 0 var(--space-1);
  font-size:var(--text-card-title);
  color: var(--text);
}
.plan-tagline {
  margin:0;
  color: var(--muted);
  font-size:var(--text-supporting);
  line-height: 1.55;
}
.plan-price {
  display: flex;
  align-items: baseline;
  gap:var(--space-3);
  margin-top:var(--space-2);
}
.plan-price-num {
  font-size: var(--text-subsection);
  font-weight: var(--weight-bold);
  color: var(--text);
  letter-spacing: -.01em;
}
/* The Premium price was gradient-clipped text. A price is already the largest
   number on the card; colouring it as well says the same thing twice, and the
   Lite price sitting beside it in plain white made the pair look like two
   different components. Both prices are now the same object. */
.plan-price-per {
  color: var(--muted);
  font-size:var(--text-supporting);
}
/* The amount and its interval are ONE price, so they are ONE flex item. Split
   across two, the container's 12px gap landed on both sides of the slash and
   the plan cards on /pricing and /lite printed "$14.99 / month" — the exact
   shape the homepage orphan-slash defect wrapped out of, and not the canonical
   "$14.99/month" every other surface uses. The wrapper closes the gap and, with
   nowrap, gives the two-element price the same atomic guarantee [data-price]
   already gives the single-element one. USD stays outside it: it qualifies the
   price rather than being part of it, and the gap it keeps is the gap the
   middot used to draw by hand. Shared by both price compositions — the plan
   cards' .plan-price and the tile's .price-amount — because the defect and
   the fix are identical in each, and one class means a future change to how a
   price binds cannot land on one of them and miss the other. */
.price-token {
  display:inline-flex;
  align-items:baseline;
  white-space:nowrap;
}
.plan-features {
  list-style: none;
  padding:0;
  margin:var(--space-1) 0 var(--space-2);
  display: flex;
  flex-direction: column;
  gap:var(--space-2);
}
.plan-features li {
  position: relative;
  padding-left:var(--space-5);
  color: var(--muted);
  font-size:var(--text-supporting);
  line-height: 1.55;
}
.plan-features li::before {
  content: "";
  position: absolute;
  left: 0; top: .78em;
  width:var(--marker-w); height:var(--marker-h);
  border-radius: 0;
  background:var(--marker-ink);
}
/* Premium's feature markers were sapphire — one accent repeated eleven times
   down a card, which is exactly where an accent stops meaning anything. Steel
   still separates them from Lite's neutral markers without competing with the
   plan's CTA, which is the one thing on the card that should be sapphire. */
.plan-card-premium .plan-features li::before {
  background: var(--ns-steel);
}
/* Lite "this feature is NOT included" bullets — quieter dot
   plus a muted strike-through-feel via lower opacity.  Keeps
   the visual hierarchy: included features are bright dots,
   excluded features are clearly de-emphasised. */
.plan-card-lite .plan-features li.plan-feature-no {
  color: var(--ns-text-muted);
}
.plan-card-lite .plan-features li.plan-feature-no::before {
  background:var(--marker-ink-quiet);
}
/* NOT margin-top:auto. Pushing the CTA to the bottom of a stretched card is
   what opened a 180px hole between the last Lite bullet and its button: the
   reader's eye had to cross a void to get from the offer to the action, and
   the two stopped reading as one card. The button now follows its bullets. */
.plan-cta {
  align-self: stretch;
  text-align: center;
}
.plan-foot {
  margin:0;
  color: var(--muted);
  font-size:var(--text-caption);
  line-height: 1.6;
  opacity: .9;
}

@media (max-width: 720px) {
  .plan-compare { grid-template-columns: 1fr; }
}

/* Three-up: Lite · Trial · Premium, trial centre — it is the option taken
   BETWEEN the other two. Scoped to #premium-unlocks, not a modifier class, so
   /lite.html's two-card .plan-compare is untouched. */
#premium-unlocks .plan-compare {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1080px) {
  #premium-unlocks .plan-compare { grid-template-columns: 1fr; }
}
/* Emphasised the one way this system emphasises a card: the strong border
   the paid plan already carries. No new colour, shadow or transform. */
.plan-card-trial {
  border-color: var(--ns-border-strong);
}

/* Access-flow split — Lite (free, instant) on the left,
   Premium (manual verification) on the right.  Premium keeps
   the existing ``.delivery-flow`` ordered-list inside, so the
   four-step manual flow is unchanged. */

/* ========================
   "Try NovaStation Lite First" section — added 2026-05-16.
   Dedicated section that elevates Lite to a top-level
   homepage surface (sits directly above #pricing).  Visual
   weight stays calmer than the Premium pricing tile below —
   Lite is a visible secondary path, Premium remains the
   dominant paid product.
   ======================== */


/* ========================
   NovaStation — interactivity & polish layer            (added 2026-06-05)
   ------------------------
   Phase 2 (interactive sections) + Phase 3 (animation & polish).
   HARD RULE: no new base colours. Every value below resolves to an
   existing token (--cyan, --blue, --violet, --magenta, --text, --muted, the
   --bg ramp, --hero-grad) or to an rgba() of those exact hexes already used
   throughout this sheet. The deep-space feel is extended, never replaced.
   The list is spelled out rather than globbed. Globbed, the --bg entry ended
   in an asterisk followed by a slash, and that sequence CLOSES A COMMENT: the
   :root block below was swallowed as one invalid rule, leaving --border,
   --border-bright, --border-strong, --glass and --ease-out undefined
   site-wide. A var() that resolves to nothing makes its whole declaration
   unset, so ~20 borders and transitions silently did nothing. Never write a
   glob that ends in an asterisk immediately before a slash in this file.
   ======================== */

:root {
  /* Derived tokens — aliases of values already used literally above, so the
     new components stay byte-for-byte on-palette and future edits are central. */
  --border:var(--ns-border-subtle);        /* the panel border used everywhere */
  --border-bright:var(--ns-border-strong); /* eyebrow / label border           */
  /* --radius-* now live in the ROOT token block at the top of this sheet, so
     there is exactly one place to change them. --radius-lg (24px) is GONE: a
     third surface step is what let the price tile and the old logo stage drift
     to 28px while .card sat at 24px and .trust-card at 22px. Surfaces get
     --radius-md; controls get --radius-sm. */
  --ease-out:cubic-bezier(.22,.61,.36,1);
}

.hero-stage-col { width:100%; }

/* ---------- App screenshots (real product captures) ----------
   Frames the real NovaStation captures (hero chart, RSI chart, alerts) in the
   deep-space style: rounded corners, a faint cyan/violet border-glow, and the
   panel shadow the old demo stage used. The captures already carry the app's
   own window chrome, so the frame stays minimal. The native aspect is kept
   (object-fit:contain, no forced ratio) so wide/tall captures never stretch;
   the intrinsic width/height attributes reserve the box before the bitmap
   decodes, so there is no layout shift while it loads. */
.hero-shot, .feature-shot {
  position:relative;
  margin:0;
  width:100%;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:var(--space-3);
}
.feature-shot { margin-bottom:var(--block-gap); }
/* …but not in a loop stage: .loop-stages already gaps by --block-gap, so this
   doubled it. The margin stays for the pages with no gapped parent. */
.loop-stage > .feature-shot { margin-bottom:0; }
/* --shot-cap is the tier's width cap, declared on the FIGURE so the image and
   its caption read the same number. A caption wider than the image it labels
   stops looking attached to it; one property overridden in one place per
   breakpoint is what stops the two drifting apart. */
.hero-shot    { --shot-cap:min(100%, calc(680px * var(--shot-aspect)),
                               calc(72vh * var(--shot-aspect))); }   /* thesis */
.feature-shot { --shot-cap:min(100%, calc(420px * var(--shot-aspect)),
                               calc(48vh * var(--shot-aspect))); }   /* supporting */
.hero-shot-img, .feature-shot-img {
  display:block;
  /* The capture scales DOWN to fit its tier's cap while keeping its native
     aspect (no stretch, no upscale past native), and it starts on the same
     rail as the words that explain it so the figure hugs the image with no
     letterbox bars.
     The width is DEFINITE and the ratio is stated, which is what makes the box
     exist before the bytes do. This used to read width:auto + aspect-ratio:auto
     with the cap expressed as a max-height: in a column flex box that shrinks
     an undecoded image to nothing, so each of the six loop captures occupied
     2px until it loaded and then jumped to 384 — the homepage grew by ~2000px
     under the reader as they scrolled. Same rendered size, measured identical
     at 390/768/1280/1440; the difference is that it is now reserved. Each tier
     states its cap as a max-WIDTH derived from the same ratio, so the cap and
     the reservation can never disagree. */
  width:100%;
  height:auto;
  /* THE RAIL. Image and caption share one centred rail inside the figure.
     Left-aligning the capture put the caption on the left edge of an image up
     to 200px narrower than its figure, which reads as a stray line rather than
     a label. Centring both keeps them a unit at every width; the caption is
     capped at the image's width below. .feature-shot is a COLUMN flex
     container, so align-self decides this, not margin-inline. */
  align-self:center;
  margin-inline:auto;
  aspect-ratio:var(--shot-ratio);
  object-fit:contain;
  border-radius:var(--radius-md);
  /* THE EDGE. A bright rim so the capture has a boundary, a dark ring under it
     so it sits ON the page rather than in it, and one shadow. No halo. */
  border:1px solid var(--ns-border-strong);
  background:var(--ns-bg-section);
}
/* IMAGE HIERARCHY (Pass 2B).  The hero carries the product proof at full scale;
   the demo captures are SUPPORTING evidence in a sequence and are sized below
   it on purpose. Scale is the differentiator — 52% vs 42% at 1440 — so the
   reader can tell the thesis from its footnotes without a single label. */
/* The caps are the same numbers they always were — 680/72vh and 420/48vh —
   turned into the width they imply, so a definite width can carry them. They
   are read from --shot-cap on the figure, which the caption reads too. */
.hero-shot-img, .feature-shot-img { max-width:var(--shot-cap); }
/* The PANEL tier (Pass 4) is gone as of 2026-07-24. It existed because several
   captures were PORTRAIT windows whose content was small UI text, and the
   supporting cap rendered them too narrow to read. Every approved capture is
   now the same 2560x1440 landscape frame produced by tools/screenshot_tool, so
   the two-tier hero/supporting scale above is the whole system again and close
   reading is carried by "View full size" (the lightbox opens the same file at
   full resolution). Nothing referenced .feature-shot-img--panel after the
   swap, so the rule was removed rather than left as dead CSS. */
/* Measured at 144ch in Pass 1 — wider than any body copy on the page, because
   nothing capped them and they inherit the full container. A caption is the
   NARROWEST text on a page: it is a label for the image above it, not prose. */
.hero-shot-note, .feature-shot-note {
  /* A caption belongs to the image above it: same rail, never wider — min()
     takes whichever of the reading measure and the image cap is smaller. This
     is the ONLY centred text on these pages; prose stays left-aligned, because
     centring it costs the reader a new left edge on every line. */
  margin:0;
  align-self:center;
  margin-inline:auto;
  max-width:min(var(--measure-caption), var(--shot-cap));
  text-align:center;
  /* A caption is 1-2 lines at a 56ch cap, and the second was landing as a
     runt: "mixed low-sample result.", "a winning one.", "product itself."
     figcaption is neither p nor li, so the site-wide balance/pretty rules
     never reached it. */
  text-wrap:balance;
  font-size:var(--text-caption);
  line-height:1.5;
  color:var(--muted);
  opacity:.8;
}
/* THE CENTRED CAPTURE IS A CONTRACT, not an accident (checked 2026-09-05).
   The post-1.2.2 polish pass measured the homepage at 1440 -- prose at x=140,
   747px capture starting at x=347 -- and moved the figure onto the prose rail
   to close what looked like a 207px stray indent. It is not stray. The
   composition is heading-left, lead-left, capture centred below, and it is
   pinned in rendered geometry by two WEBSITE BLOCKERs:
   verify_start_paths.py and verify_visual_intersections.py assert "every
   caption is centred on its image", "every caption sets its text centred", and
   "#proof-stage: the capture is centred in its section" (imgMid == secMid).
   Left-aligning the figure fails all three. Do not re-try it: re-aiming those
   gates to match the change would be redesigning the page, not polishing it. */

/* .shot-badge is GONE: an uppercase pill shouted over the caption it qualified.
   The words are prose in the sentence now ("on recorded data", "a simulated
   practice account") — still defined by the band below, and one sentence to a
   screen reader. --chip-bg / --chip-line stay: six other users. */

/* ---------- Homepage product proof: copy ABOVE its capture ----------
   These three blocks were briefly built as .grid-2 (card beside figure), which
   is the one composition this site does not allow: prose in the column next to
   a screenshot reads as an illustration of the layout rather than of the
   sentence, and at <=1080px the grid's display:block on its children also
   flattened the figure's own column flex, so each caption sat hard against its
   image with no gap at all. Stacked, the figure keeps its flex gap at every
   width and the reading order is the one the rest of the site uses: heading,
   copy, IMAGE, caption. */
.proof-stage {
  display:flex;
  flex-direction:column;
  gap:var(--space-4);
  margin-bottom:var(--block-gap);
}
.proof-stage:last-of-type { margin-bottom:0; }
.proof-stage h3 { margin:0; max-width:var(--measure-body); }
.proof-stage > p {
  margin:0;
  max-width:var(--measure-prose);
  color:var(--muted);
  line-height:1.72;
}
.proof-stage strong { color:var(--text); }
/* .proof-stage already gaps by --space-4; the figure's own trailing margin
   would double it under the caption. */
.proof-stage > .feature-shot { margin-bottom:0; }

/* Disclosure band: one home for what those words mean and the boundaries
   covering every capture above. .note surface tier; prose capped to the
   reading measure. */
.disclosure-band {
  margin-top:var(--block-gap);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border:1px solid var(--ns-border-subtle);
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
}
.disclosure-band p {
  margin:0;
  max-width:var(--measure-prose);
  color:var(--muted);
  font-size:var(--text-caption);
  line-height:1.7;
}
.disclosure-band strong { color:var(--text); }

/* ---------- Signature moment #1: hero showcase ----------
   The hero screenshot is the page's "wow": an animated aurora blooms
   behind it, and a cursor-driven 3D tilt (vars set by site.js) gives the
   frame depth. Pure transform/opacity; disabled under reduced-motion and
   on touch (no fine pointer). */
/* overflow-x:clip keeps the aurora ::before glow (inset:-14% on the sides)
   from extending past the viewport and creating a few px of horizontal page
   scroll at full-width breakpoints. Horizontal only, so the glow still blooms
   above/below the frame; clip (not hidden) + a non-sticky-ancestor element
   means the sticky nav is unaffected. */
/* A dark app on a dark page has no edge — that, not size alone, is why the
   capture "dissolved". The old answer was a conic-gradient halo behind it
   (.hero-shot::before), which is decoration standing in for a boundary: it
   glows AROUND the image without ever defining where the image stops. Deleted.
   The boundary is now a real one — a bright 1px rim, a dark separator ring that
   lifts it off the background, and a single controlled shadow. */
.hero-shot { overflow-x:clip; }
.hero-shot-img {
  position:relative;
  z-index:1;
}

/* ---------- "One connected workspace" — mission-control panel ----------
   Replaces the old scattered-windows before/after toy. A central NovaStation
   hub with the six workflow tools wired into it. Pure CSS/HTML, fully STATIC
   (no animation -> calm under prefers-reduced-motion by construction) and
   mobile-first: a hub over a 2-column node grid on phones that becomes a
   hub-in-the-centre "command centre" from 720px up. Literal palette values on
   purpose — the derived-token :root block does not resolve on this page (the
   same reason the accordion uses a literal easing). */

/* Heading block */

/* Diagram — MOBILE-FIRST: hub on top, 6 nodes in a 2-column grid below. */

/* Hub — the focal "command centre" card. */

/* Node — one workflow tool. */
/* Bottom-row tools lean violet so the top (read/analyse) and bottom
   (monitor/practise/review) halves read as two families feeding one hub. */

/* Mobile connector hint — a short line from the hub down into the grid. */

/* ---- Wider screens: hub becomes a centre band with 3 tools above and 3
        below, each wired to the hub by a short connector stub. ---- */

/* ---------- Interactive workflow stepper ----------
   .stepper is the first block after the section heading, so the heading's
   --section-head-gap owns the gap above it (no separate top margin). */
/* Detail box: sized to its content (one heading + one sentence) so it reads
   as intentional rather than a large floaty panel. Tighter radius/padding,
   a grounded shadow, a min-height that only prevents step-to-step jump, and a
   capped line length. */

/* ---------- Pricing "what's included" groups (always visible) ----------
   Flattened from click-to-expand disclosures — the included-features list is
   core buying info, so every group stays open under a plain subhead. */

/* ---------- FAQ preview "read more" ---------- */
.faq-more { margin-top:var(--block-gap); }

/* ---------- Scroll-progress bar ---------- */
.scroll-progress {
  position:fixed;
  top:0; left:0; right:0;
  height:3px;
  z-index:80;
  background:transparent;
  pointer-events:none;
}
.scroll-progress span {
  display:block;
  height:100%;
  width:100%;
  transform:scaleX(0);
  transform-origin:0 50%;
  /* Ground already covered, so it takes the colour the guided rail gives a
     completed step: steel, not the CTA accent. */
  background:var(--ns-steel);
  will-change:transform;
}

/* The deep-space ambience layer that used to live here is removed — see the
   "Ambient starfield: REMOVED" note further down. */

/* ---------- Sticky nav: shrink + stronger blur on scroll ---------- */
.nav-wrap {
  transition:background .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.nav-wrap .nav { transition:padding .25s var(--ease-out); }
.nav-wrap.scrolled {
  background:var(--ns-bg-deep);
  box-shadow:0 14px 40px rgba(0,0,0,.4);
  border-bottom-color:var(--ns-border-strong);
}
.nav-wrap.scrolled .nav { padding-top:var(--space-2); padding-bottom:var(--space-2); }

/* ---------- Micro-interactions: gentle hover lift + brand glow ---------- */.card,
.panel,
.hero-shot {
  transition:transform .25s var(--ease-out), box-shadow .25s var(--ease-out), border-color .25s var(--ease-out);
}.hero-points .card:hover,
#what-it-does .card:hover,
.panel:hover {
  border-color:var(--border-bright);
  box-shadow:0 26px 60px rgba(0,0,0,.42);
}

/* ========================
   Motion foundation (2026-06-06) — unified hover system, animated link
   underline, fine grain overlay, and scroll-parallax on the space layers.
   All GPU-friendly (transform / opacity / box-shadow only) and all
   neutralised by the reduced-motion block below.
   ======================== */

/* ---- Unified button hover: lift (already set) + brand-colored glow,
        crisp press state ---- */
/* The button states themselves live with .btn-primary / .btn-secondary above;
   this later block only carries the shared press motion. */
.btn:active { transform:translateY(0) scale(.985); }


/* These were two stacked gradient background layers faking an underline that
   wiped in from the left. An ordinary inline link in body copy should just be
   underlined — the real text-decoration properties give the same read, follow
   the text baseline through line wraps, and cost no paint layers. */
.legal-shell a:not(.btn),
.delivery-flow > li a,
.early-access-note a,
.vscan-link {
  border-bottom:0;
  padding-bottom:0;
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:3px;
  text-decoration-color:var(--ns-border-control);
  transition:color .2s ease, text-decoration-color .2s ease;
}
.legal-shell a:not(.btn):hover,
.legal-shell a:not(.btn):focus-visible,
.delivery-flow > li a:hover,
.delivery-flow > li a:focus-visible,
.early-access-note a:hover,
.early-access-note a:focus-visible,
.vscan-link:hover,
.vscan-link:focus-visible {
  color:var(--ns-text-primary);
  text-decoration-color:var(--ns-steel-hover);
  text-decoration-thickness:2px;
}

/* A navigation pill is not prose. The rule above turns every link in these
   shells into underlined body text — right for a link inside a sentence,
   wrong for the chips, which have shipped on /terms, /privacy and /eula with
   a rule drawn through the label and the label itself pushed to the top of
   the pill: that rule zeroes padding-bottom, leaving 8px above the text and
   none below. The chips restate their own box, and they do it HERE, one rule
   later, rather than at their own definitions upstream — those carry the same
   specificity and would lose on source order. */
.legal-shell .legal-toc a,
.faq-shell .faq-rail-list a {
  padding-bottom:var(--space-2);
  text-decoration:none;
}

/* ---- Fine grain/noise overlay (very low opacity) ----
        Static SVG turbulence tiled once over the deep-space background;
        adds richness without motion.  Sits below all content (z-index:0,
        the shell is z-index:1) and never intercepts pointer events. */
html::before {
  content:"";
  position:fixed;
  inset:0;
  z-index:0;
  pointer-events:none;
  opacity:var(--fx-noise-opacity, .035);
  mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size:160px 160px;
}

/* .spotlight was a radial light that tracked the pointer across a section's
   background; the JS that fed it --mx/--my went in an earlier pass and the
   light went with this one. The class survives on one section (#pricing) and
   is referenced by the section-divider selector below, so the positioning
   contract stays (its content is still lifted above it). The divider rule
   further down no longer skips .spotlight either: that exclusion existed to
   stop a hairline landing on a section that already had its own treatment,
   and now #pricing gets the same separator as every other section. */
.spotlight { position:relative; }
.spotlight > * { position:relative; z-index:1; }

/* ========================
   Reduced-motion — disable / neutralise every animation added above.
   ======================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior:auto; }
  .scroll-progress span { transition:none; }
  [data-reveal] { opacity:1 !important; transform:none !important; transition:none !important; }
  .nav-wrap, .nav-wrap .nav { transition:none; }.card,
.panel,
.hero-shot,
.btn { transition:none !important; }.hero-points .card:hover,
#what-it-does .card:hover,
.btn:hover { transform:none !important; }
  /* Motion-foundation + signature-moment additions (2026-06-06) */
  .btn:active { transform:none !important; }
  .legal-shell a,
.delivery-flow > li a,
.early-access-note a { transition:none !important; }
  .hero-shot-img { transform:none !important; transition:none !important; }
}

/* ========================
   Copy-only support-email chips (2026-06-10)
   ------------------------
   Every [data-support-email] element is converted by site.js into a
   copy-to-clipboard chip — no support CTA on the site opens a mail
   client.  Steel address with an underline, a copy glyph, and a
   sapphire-soft fill on hover/focus.  The
   .copy-toast span carries the visible "Support email copied."
   confirmation.  Inline-safe: chips sit inside prose, footers, and
   contact cards without breaking text flow (overflow-wrap for long
   addresses on mobile).
   ======================== */
.email-copy-chip {
  /* Positioning context for the floating .copy-toast child so the
     "copied" confirmation can be absolutely positioned over the chip
     and never reflow the surrounding paragraph. */
  position: relative;
  color: var(--ns-steel-hover);
  text-decoration: underline;
  text-decoration-color: var(--ns-steel);
  text-underline-offset: 3px;
  cursor: pointer;
  border-radius: 6px;
  padding: 0 2px;
  transition: color .15s ease, background .15s ease, box-shadow .15s ease;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.email-copy-chip::after {
  content: "⧉";
  font-size: .78em;
  margin-left: .35em;
  opacity: .8;
  color: var(--violet);
}
.email-copy-chip:hover,
.email-copy-chip:focus-visible {
  /* Web rebuild Pass 11: the lighter steel. --ns-steel on sapphire-soft is
     4.31:1, under the 4.5:1 body-text floor; --ns-steel-hover is 5.52:1.
     The focus ring is the one set further down this file, not suppressed. */
  color: var(--ns-steel-hover);
  background: var(--ns-sapphire-soft);
  box-shadow:none;
}
.email-copy-chip:active {
  background: var(--ns-sapphire-soft);
}
/* 44x44, at every pointer type (2026-07-26).
   The touch floor further down this file gave the chips a 44px height only
   under `pointer: coarse`. That was the right question for a finger and the
   wrong one for everybody else: on a mouse, on a trackpad, and for anyone
   aiming with a head pointer or a tremor, a 24px-tall control is the same
   24px-tall control. WCAG 2.5.5 does not ask what kind of pointer is in use.

   inline-flex keeps the chip in the text flow — it still sits inside a
   sentence, a footer row and a contact card without breaking the line — while
   giving it a real box to hit. min-width is met by the address itself in every
   instance on the site and is stated anyway, because a future short label must
   not be allowed to shrink below the floor. The "copied" toast is absolutely
   positioned over this box, so confirming a copy still cannot reflow a single
   line of the paragraph around it. */
.email-copy-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}

.legal-shell a.email-copy-chip,
.legal-shell a.email-copy-chip:hover,
.early-access-note a.email-copy-chip,
.delivery-flow > li a.email-copy-chip {
  border-bottom: 0;
}

/* Floating "Support email copied." confirmation.  Absolutely positioned
   over the chip (its position:relative parent) so it is OUT of the text
   flow: showing or hiding it cannot change the paragraph's height, reflow
   the wrap, or leave a blank gap.  Sits just above the chip as a small
   glass pill, fades in/out, and never intercepts clicks. */
.copy-toast {
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  z-index: 6;
  padding: 3px 9px;
  border-radius: 8px;
  background: var(--ns-bg-section);
  border: 1px solid var(--ns-border-control);
  box-shadow:0 8px 22px rgba(0,0,0,.40);
  font-size:var(--text-caption);
  font-weight:var(--weight-strong);
  line-height: 1.3;
  color: var(--ns-success);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .18s ease, transform .18s ease;
  pointer-events: none;
}
.copy-toast.is-visible { opacity: 1; transform: translateY(0); }
.copy-toast.is-error {
  color: var(--ns-warning);
  white-space: normal;
  width: max-content;
  max-width: 220px;
}

@media (max-width: 640px) {
  /* Keep the chip an easy tap target inside mobile prose. */
  .email-copy-chip { padding: 2px 4px; }
}
@media (prefers-reduced-motion: reduce) {
  .email-copy-chip, .copy-toast { transition: none !important; }
}

/* ========================
   Homepage refresh (2026-06-21) — visual-proof screenshot strip, Signal Twin
   spotlight, Premium CTA row, and the "How access works" step list. Reuses the
   existing tokens (--space-N, --cyan, --hero-grad, --card-pad, --grid-gap …) so
   it inherits the site's rhythm, theme, and reduced-motion handling. No
   load-bearing animation is introduced here.
   ======================== */

/* ---- Visual-proof screenshot strip ("Built like a workstation") ----
   The two chart captures above the strip are REAL; these tiles are honest,
   clearly-labelled PREVIEW placeholders until the per-feature screenshots are
   dropped in (see the TODO filenames in index.html). They are styled to read
   as intentional frames, never as fabricated product screenshots. */

/* (The Signal Twin spotlight's .st-grid lived here until 2026-07-26. It gave
   Stage 4 a two-column composition while the other six loop stages stacked,
   so its capture rendered at roughly half the width of every other capture in
   the same list and started on its own rail. The stage uses the same markup as
   its neighbours now, and the grid went with it.) */

/* ---- Premium CTA row + access steps ---- */
.access-steps {
  list-style:none;
  counter-reset:acc;
  margin:0;
  padding:0;
  display:flex;
  flex-direction:column;
  gap:var(--space-5);
}
.access-steps li {
  counter-increment:acc;
  position:relative;
  padding-left:var(--space-9);
  color:var(--muted);
  line-height:1.6;
  font-size:var(--text-supporting);
}
.access-steps li strong { color:var(--text); }
.access-steps li::before {
  content:counter(acc);
  position:absolute; left:0; top:-2px;
  width:28px; height:28px; border-radius:9px;
  display:grid; place-items:center;
  font-size:var(--text-label); font-weight:var(--weight-bold);
  /* Neutral step chip — matches .delivery-flow > li::before. */
  color:var(--ns-text-primary); background:var(--ns-surface-raised);
  border:1px solid var(--ns-border-control);
}
.note-inline { margin-top:var(--space-5); }

/* Long unbroken tokens in inline <code> must WRAP on narrow screens
   instead of forcing horizontal overflow. */
code { overflow-wrap:anywhere; word-break:break-word; }

/* ========================
   Trust / download-verification section (#get-started) — 2026-06-21 polish.
   Two MATCHED .trust-card panels (previously a .note + a .panel, which read
   as unbalanced), a stacked label->value info list, and a clearly separated
   risk line. Premium dark glass aesthetic; reuses the site tokens. Stacks
   below laptop width.
   ======================== */
.trust-grid {
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:clamp(20px, 2.4vw, 32px);
  margin-top:var(--block-gap);          /* breathing room under the heading */
}
.trust-card {
  position:relative;
  display:flex;
  flex-direction:column;
  gap:var(--space-5);
  /* --panel-pad, not a third clamp. This read clamp(22px,2.4vw,34px): same
     34px max, drifted floor — a third padding tier. See the --radius-lg note. */
  padding:var(--panel-pad);
  border-radius:var(--radius-md);
  background:var(--ns-surface);
  border:1px solid var(--ns-border-subtle);
  box-shadow:0 22px 54px rgba(0,0,0,.40);
  overflow:hidden;
}
/* .trust-card::before carried the same corner bloom as .card and went with it.
   `> *` keeps its stacking context: harmless, and the card still positions. */
.trust-card > * { position:relative; z-index:1; }
.trust-card-head { margin:0; font-size:var(--text-card-title); letter-spacing:-.02em; color:var(--text); }

/* ══ DOWNLOAD SAFETY + VERIFICATION PANEL (lite.html #verify) ════════════
   Was a six-item bullet list: labels and values ran together, the signing
   notice wrapped badly, the checksum was an afterthought, and the card was
   tall and narrow with most of its width unused. It is now the three
   questions a cautious person actually asks, in order:
       A. What am I getting?        → fact grid   (.fact-grid)
       B. Why is Windows warning me? → notice      (.dl-note)
       C. How do I check it?         → checksum + numbered steps
   Everything below is built from the existing tokens; no new scale. */
.dl-verify {
  margin-top:var(--block-gap);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-strong);
  background:var(--ns-surface);
}
.dl-verify-title { margin:0; font-size:var(--text-card-title); line-height:1.35; color:var(--text); }
.dl-verify-lead {
  margin:var(--space-2) 0 0;
  color:var(--muted); line-height:1.7;
}

/* A — release facts. Six facts, so the column count is pinned to divisors of six
   (1 / 2 / 3). auto-fit was tempting but it packed five across at 1160px and
   orphaned "Code signing" alone on a second row. min-width:0 on the cell is what
   stops a long value (the installer filename) from blowing its track out. */
.fact-grid {
  display:grid;
  grid-template-columns:1fr;
  gap:var(--space-5) var(--space-6);
  margin:var(--space-6) 0 0;
  padding:var(--space-5) 0 0;
  border-top:1px solid var(--border);
}
@media (min-width:520px) { .fact-grid { grid-template-columns:repeat(2, minmax(0, 1fr)); } }
@media (min-width:900px) { .fact-grid { grid-template-columns:repeat(3, minmax(0, 1fr)); } }
.fact { min-width:0; display:flex; flex-direction:column; gap:3px; }
.fact dt {
  margin:0;
  font-size:var(--text-label); font-weight:var(--weight-bold); letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted);
}
.fact dd {
  margin:0; color:var(--text); font-size:var(--text-body); line-height:1.5;
  overflow-wrap:anywhere;
}
.fact dd code {
  font-family:ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size:var(--text-code);
}
/* B — signing notice. Reuses the amber caution already in the palette
   (see .note.warn) so this is the system's existing warning voice. */
.dl-note {
  margin-top:var(--space-6);
  padding:var(--space-4) var(--space-5);
  border-radius:var(--radius-sm);
  border-left:3px solid var(--ns-warning);
  background:var(--ns-warning-soft);
}
.dl-note-title { margin:0 0 var(--space-1); font-weight:var(--weight-bold); color:var(--text); }
.dl-note p { margin:0; color:var(--muted); line-height:1.7; }
.dl-note p + p { margin-top:var(--space-2); }

/* C — verification */
.dl-verify-sub {
  margin:var(--space-8) 0 0;
  font-size:var(--text-lead); line-height:1.4; color:var(--text);
}
.dl-verify-why { margin:var(--space-2) 0 0; color:var(--muted); line-height:1.7; }
/* The SHA label (.dl-label) and its hash/Copy row (.checksum-row) are gone from
   here too: /lite#verify now shows the same .vfp fingerprint receipt the
   homepage does, which carries its own tag and its own 44px Copy control. */

.dl-steps { margin:var(--space-6) 0 0; padding-left:1.3rem; color:var(--muted); }
.dl-steps li { margin:0 0 var(--space-4); line-height:1.7; }
.dl-steps li:last-child { margin-bottom:0; }
.dl-steps strong { color:var(--text); }
.dl-steps .checksum-cmd { margin-top:var(--space-2); }

.dl-mismatch {
  margin:var(--space-6) 0 0;
  padding:var(--space-4) var(--space-5);
  border-radius:var(--radius-sm);
  border-left:3px solid var(--ns-danger);
  background:var(--ns-danger-soft);
  color:var(--muted); line-height:1.7;
}
.dl-mismatch strong { color:var(--text); }

/* ── SHA-256 checksum (download-trust block) ─────────────────────
   64 unbroken hex characters, so it must be allowed to break mid-token — it is
   one "word" to the line breaker, and left alone it forces a horizontal
   scrollbar on a phone. (Where it sits in a grid cell — .vfact dd — that cell
   carries the matching min-width:0, without which a grid child refuses to
   shrink below its content width at all.)
   The checksum is the thing the user is actually here to compare, so it reads
   as a block they can scan — not a tiny inline snippet. */
.checksum {
  display:block;
  padding:var(--space-3) var(--space-4);
  border-radius:var(--radius-sm);
  border:1px solid var(--border);
  background:var(--ns-bg-deep);
  font-family:ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size:var(--text-code);
  line-height:1.75;
  color:var(--text);
  overflow-wrap:anywhere;
  word-break:break-all;
  /* One click/tap selects the whole hash — nobody should be hand-dragging
     across 64 characters to compare a checksum. */
  -webkit-user-select:all;
  user-select:all;
}
/* The verification command. `white-space:pre` + overflow-x scrolled it inside its
   own box — which kept the PAGE from overflowing but showed the reader a command
   truncated mid-flag ("…-Alg") with no scroll affordance on a phone. pre-wrap
   wraps it at the space before -Algorithm instead, so the whole command is
   visible; break-word only kicks in if a single token is wider than the line.
   Still selectable and copyable as the exact original text. */
.checksum-cmd {
  display:block;
  margin-top:var(--space-2);
  padding:var(--space-3) var(--space-4);
  border:1px solid var(--border);
  border-radius:var(--radius-sm);
  background:var(--ns-bg-deep);
  font-family:ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size:var(--text-code);
  line-height:1.7;
  color:var(--text);
  white-space:pre-wrap;
  word-break:normal;
  overflow-wrap:break-word;
}

/* (The signed-installer / SmartScreen notice used to be .trust-notice here. It
   is now the third .vmod module on the homepage and the .dl-note on /lite, so
   both pages tell that story in one voice.) */

/* Right card — looser is/isn't rows + separated disclaimer */
.isnt-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--space-5); }
.isnt-list li { position:relative; display:flex; flex-direction:column; gap:3px; padding-left:var(--space-6); }
/* This list is what the product is NOT. Marking it in the brand accent was
   the clearest possible mismatch between colour and meaning. */
.isnt-list li::before {
  content:""; position:absolute; left:0; top:.5em;
  width:9px; height:9px; border-radius:3px; background:var(--ns-text-muted);
  box-shadow:none;
}
.isnt-term { font-size:var(--text-card-title); font-weight:var(--weight-bold); color:var(--text); letter-spacing:-.01em; }
.isnt-desc { color:var(--muted); font-size:var(--text-supporting); line-height:1.65; }
.trust-disclaimer {
  margin:var(--space-2) 0 0;
  padding-top:var(--space-5);
  border-top:1px solid var(--ns-border-subtle);
  color:var(--muted); font-size:var(--text-supporting); line-height:1.6;
}

/* Responsive: stack the two cards below laptop width. */
@media (max-width: 900px) {
  .trust-grid { grid-template-columns:1fr; }
}

/* ========================
   VERIFY-YOUR-DOWNLOAD — premium modular pass (2026-07-17).
   The old homepage card packed the filename, version, size, platform, signer,
   the 64-char hash, a Copy button, a PowerShell command AND two footnotes into
   ONE label/value list — a dump of facts with no hierarchy, the exact "trust
   info competes visually" problem. It is now three grouped modules a cautious
   person reads in order:
       THE FILE .............. what you're about to run        (.vfacts)
       CHECK THE FINGERPRINT . the SHA-256 + its Copy control   (.vfp)
   Post-1.2.2 positioning pass: the homepage keeps only the fingerprint, so
   .vfacts (the file's own facts) is now a /changelog module and the .vmod
   group-heading rules left with the markup that used them. /lite carries the
   whole procedure.
   /lite owns the PowerShell walkthrough and the SmartScreen note in its own
   safety box now, so the .vcmd and .vnote modules were deleted with the
   markup that used them (post-1.2.2 positioning pass). The homepage keeps the
   published checksum and links to /lite for the rest. Built entirely from existing tokens. The fingerprint block is the
   single loud element; everything around it stays quiet.
   ======================== */
.vintro { margin:0; color:var(--muted); line-height:1.65; max-width:60ch; }

/* 1 · THE FILE — compact label/value facts. */
.vfacts { margin:0; padding:0; display:grid; gap:var(--space-3); }
.vfact { display:grid; grid-template-columns:7.5rem 1fr; gap:var(--space-4); align-items:baseline; }
.vfact dt { margin:0; font-size:var(--text-caption); color:var(--muted); }
.vfact dd { margin:0; min-width:0; color:var(--text); font-size:var(--text-body); line-height:1.5; overflow-wrap:anywhere; }
.vfact-mono { font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace; font-size:var(--text-code); }
.vfact-ok { color:var(--ns-success); font-weight:var(--weight-strong); white-space:nowrap; }

/* 2 · CHECK THE FINGERPRINT — the panel's one bold element: a signed-manifest
   "receipt" holding the hash, its Copy control, and the verify command. */
.vfp {
  border:1px solid var(--border-bright);
  border-radius:var(--radius-sm);
  background:var(--ns-bg-deep);
  padding:var(--space-4);
  box-shadow:none;
}
.vfp-top { display:flex; align-items:center; justify-content:space-between; gap:var(--space-3); margin-bottom:var(--space-3); }
.vfp-tag { font-size:var(--text-label); font-weight:var(--weight-bold); letter-spacing:.1em; text-transform:uppercase; color:var(--ns-info-blue); }
.vfp-top .btn { min-height:44px; padding:10px 16px; }  /* 38px missed the 44px
                                            touch floor — this is the one copy
                                            control a phone user actually needs */
.vfp .checksum { border:0; background:none; padding:0; letter-spacing:.02em; }
.vfp-foot { margin:var(--space-3) 0 0; color:var(--muted); font-size:var(--text-caption); line-height:1.6; max-width:var(--measure-prose); }

/* The independent-scan link sits in the receipt caption and takes the prose-link
   treatment below; only its icon alignment and tap target are its own. */
.vscan-link { display:inline-flex; align-items:center; gap:.4em; min-height:44px; }

@media (max-width:480px) {
  .vfact { grid-template-columns:1fr; gap:2px; }
  .vfp-top { flex-wrap:wrap; }
  .vfp-top .btn { width:100%; }
}

/* (The .shot-figure / .shot-figure-img / .shot-figure-cap tier lived here.
   It was the third screenshot frame on the site, added in 2026-06-22 for the
   #strategy 3-up, the #alerts 2-up and the Signal Twin spotlight. Those first
   two sections were rebuilt on .feature-shot long ago, and on 2026-07-26 the
   Signal Twin spotlight followed, which left the tier with nothing to style.
   Two screenshot frames — .hero-shot for the page-opening capture and
   .feature-shot for captures inside a story — is the whole system now, plus
   .path-hero-shot / .chapter-shot on the guided paths.) */

/* ========================
   Screenshot fullscreen / lightbox viewer (2026-06-22)
   ------------------------
   Every product screenshot (.hero-shot-img / .feature-shot-img /
   .path-shot-img) is a button (wired in site.js) that opens a dark,
   centered, viewport-contained overlay. A hover/focus "View full size" pill
   signals it is interactive. The overlay reuses the already-loaded WebP, so
   nothing extra is downloaded.
   ======================== */
.is-zoomable { cursor:zoom-in; }
.is-zoomable:focus-visible {
  outline:3px solid var(--ns-sapphire);
  outline-offset:4px;
}

/* "View full size" affordance pill — pinned to each screenshot frame's top-
   right, revealed on hover/keyboard focus. The figures are position:relative
   (hero-shot / feature-shot already are; the two path frames are set below). */
.path-hero-shot, .chapter-shot { position:relative; }
.hero-shot::after,
.feature-shot::after,
.path-hero-shot::after,
.chapter-shot::after {
  content:"View full size";
  position:absolute; top:12px; right:12px; z-index:4;
  padding:6px 12px; border-radius:999px;
  font-size:var(--text-label); font-weight:var(--weight-strong); letter-spacing:.01em; white-space:nowrap;
  color:var(--text); background:var(--ns-bg-section);
  border:1px solid var(--ns-border-strong);
  box-shadow:0 6px 18px rgba(0,0,0,.4);
  opacity:0;
  transition:opacity .2s ease, transform .2s ease;
  pointer-events:none;
}
.hero-shot:hover::after, .hero-shot:focus-within::after,
.feature-shot:hover::after, .feature-shot:focus-within::after,
.path-hero-shot:hover::after, .path-hero-shot:focus-within::after,
.chapter-shot:hover::after, .chapter-shot:focus-within::after {
  opacity:1; transform:translateY(0);
}
/* Touch devices have no hover — keep a faint persistent hint so users know
   the screenshot is tappable. */
@media (hover: none) {
  .hero-shot::after, .feature-shot::after,
  .path-hero-shot::after, .chapter-shot::after {
    opacity:.85; transform:none;
  }
}

/* Scroll lock while the viewer is open. */
html.lightbox-open { overflow:hidden; }

.lightbox {
  position:fixed; inset:0; z-index:1000;
  display:flex; align-items:center; justify-content:center;
  padding:clamp(16px, 4vw, 56px);
  background:var(--ns-bg-deep);
  
  
  animation:lightbox-fade .18s ease-out;
}
.lightbox[hidden] { display:none; }
.lightbox-stage {
  margin:0; max-width:100%; max-height:100%;
  display:flex; flex-direction:column; align-items:center; gap:var(--space-4);
}
.lightbox-img {
  display:block;
  width:auto; height:auto;
  max-width:min(1500px, 96vw);
  max-height:86vh;
  object-fit:contain;
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-strong);
  box-shadow:0 40px 120px rgba(0,0,0,.7);
  background:var(--ns-bg-deep);
}
.lightbox-cap {
  margin:0; color:var(--muted); font-size:var(--text-supporting); line-height:1.5;
  text-align:center; max-width:64ch;
}
.lightbox-cap[hidden] { display:none; }
.lightbox-close {
  position:absolute; top:clamp(12px,2.2vw,26px); right:clamp(12px,2.2vw,26px);
  width:46px; height:46px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  font-size:var(--text-subsection); line-height:1; font-family:inherit;
  color:var(--text); cursor:pointer;
  background:var(--ns-bg-section);
  border:1px solid var(--ns-border-strong);
  box-shadow:0 10px 30px rgba(0,0,0,.5);
  transition:transform .2s ease, border-color .2s ease, background .2s ease;
}
.lightbox-close:hover {
  transform:scale(1.06);
  border-color:var(--ns-border-control);
  background:var(--ns-surface-raised);
}
.lightbox-close:focus-visible {
  outline:3px solid var(--ns-sapphire);
  outline-offset:3px;
}
@keyframes lightbox-fade { from { opacity:0; } to { opacity:1; } }
@media (prefers-reduced-motion: reduce) {
  .lightbox { animation:none; }
  .hero-shot::after, .feature-shot::after,
  .path-hero-shot::after, .chapter-shot::after { transition:none; }
}


/* ========================
   VISUAL POLISH PASS (2026-06-26) — depth, glass, glow & motion elevation.
   ------------------------
   VISUAL-ONLY: no content, copy, link, layout or structural change. Every
   colour resolves to an existing brand token / rgba of a hex already used
   in this sheet (no new base colours). All motion is transform / opacity /
   box-shadow only and is fully neutralised by the reduced-motion block at
   the end. This layer builds ON the existing signature moments (aurora,
   hero tilt, cursor spotlight, scroll-reveal) — it never replaces them.
   ======================== */


.card,
.panel,
.workflow-step,
.plan-card-lite,
.trust-card,
.purchase-helper-card {
  border-color:var(--fx-card-border, var(--ns-border-subtle));
}


.card,
.panel,
.workflow-step,
.plan-card,
.trust-card,
.price-tile {
  transition:box-shadow .26s var(--ease-out), border-color .26s var(--ease-out);
}
/* Hover was a 38px sapphire halo plus a 26px steel halo around the card — a
   coloured outer glow, which is the single loudest tell of the look this pass
   removes, and it told the reader nothing the cursor had not already said.
   The border brightening one step is the whole hover state now. */
.card:hover,
.panel:hover,
.workflow-step:hover,
.plan-card:hover,
.trust-card:hover {
  border-color:var(--ns-border-control);
  box-shadow:var(--ns-shadow-md);
}

/* ---- 3 · Screenshot frame — the captures are the most colourful thing on
        the page and they must stay that way, so nothing here paints ON them.
        The frame was a lit glass edge, a cyan ring and two coloured halos;
        what a dark capture on a dark page actually needs is an edge. One
        border, one honest drop shadow, nothing inside the image. The path
        frames are NOT in this group: .path-hero-shot / .chapter-shot carry
        their own frame in the guided-path block, and stacking both would put
        two rings on one image. ---- */
.hero-shot-img, .feature-shot-img{
  border-color:var(--ns-border-control);
  box-shadow:0 28px 74px rgba(0,0,0,calc(.52 * var(--fx-shadow-strength, 1)));
}
.feature-shot:hover .feature-shot-img{
  box-shadow:0 36px 92px rgba(0,0,0,.58);
}
.feature-shot-img{ transition:transform .35s var(--ease-out), box-shadow .35s var(--ease-out); }

/* ---- 4 · The diagonal light-sweep that crossed every button on hover is
        gone. overflow:hidden stays: .btn.is-copied swaps its label in place
        and the clip keeps a long one from spilling the pill. ---- */
.btn{ position:relative; overflow:hidden; }

/* ---- 5 · The Premium plan card had a breathing gradient border-ring over a
        glow, and the "Recommended" tag had a glow of its own. The card is
        distinguished by being recommended, priced and CTA'd — it does not
        also need to pulse. isolation stays so its tag stacks correctly. ---- */
.plan-card-premium{ isolation:isolate; }

/* ---- 6 · The Signal Twin section carried four drifting "ghost path" curves
        painted by an inline SVG whose stroke was a cyan -> violet -> magenta
        ramp. Encoded inside a data URI, those three hexes were invisible to a
        colour audit, and decorative light-trails behind a section are the
        clearest surviving tell of the look this pass removes. Gone: the real
        Signal Twin screenshot below it is the section's visual. ---- */

/* ---- 7 · Workflow / step polish — a soft cyan halo on the numbered badge
        so the step sequence reads as a lit progression; the per-card lift
        (block 2) supplies the active-card emphasis. ---- */
.workflow-step::before{
  box-shadow:none;
}

/* ---- 8 · Navigation — a centred gradient underline grows under the
        hovered / current nav pill, layered over its existing glow. The
        free ::after keeps the pill background untouched. ---- */
.nav-links a{ position:relative; }
.nav-links a::after{
  content:""; position:absolute; left:50%; bottom:3px;
  width:58%; height:2px; border-radius:2px;
  transform:translateX(-50%) scaleX(0); transform-origin:center;
  background:var(--ns-sapphire);
  box-shadow:none;
  transition:transform .28s var(--ease-out); pointer-events:none;
}
.nav-links a:hover::after, .nav-links a.active::after{ transform:translateX(-50%) scaleX(1); }

/* ---- 9 · Section dividers — a hairline at the top of each content section
        that follows another. It was a cyan/violet ramp fading at both ends;
        the fade was the whole point, so the rule keeps its centred, capped
        width and takes the subtle border colour. A separator is structure.
        .spotlight is no longer excluded — see the note on that class above. */
.container > section{ position:relative; }
.container > section + section::before{
  content:""; position:absolute; left:50%; top:0; transform:translateX(-50%);
  width:min(440px, 58%); height:1px; pointer-events:none;
  background:var(--ns-border-subtle);
}

/* ---- 12 · Reduced motion — neutralise every animation / transition added
        in this pass (mirrors the existing reduced-motion contract). ---- */
@media (prefers-reduced-motion: reduce){
  .nav-links a::after{ transition:none !important; }

.feature-shot-img,
.workflow-step,
.plan-card,
.trust-card,
.price-tile { transition:none !important; }

.feature-shot:hover .feature-shot-img,
.card:hover,
.panel:hover,
.workflow-step:hover,
.plan-card:hover,
.trust-card:hover { transform:none !important; }
}

/* ========================
   SHARED EFFECT SYSTEM + CROSS-BREAKPOINT INTENSITY  (2026-06-28)
   ------------------------
   WEBSITE-ONLY visual + performance pass. No content, copy, pricing, legal,
   link or layout change. ONE set of tokens drives every ambient effect so
   desktop, tablet and mobile render the SAME visual language at three
   intensities — lighter on smaller screens, never a different effect. The
   effects above read these tokens; the breakpoints below simply re-declare
   them, so the whole system scales from one place. All motion is
   transform / opacity / blur and stays neutralised by reduced-motion.
   ======================== */
:root{
  /* Master ambient dial — 1 = full desktop richness. The glow and aurora
     opacities that used to derive from it are gone; what remains is the
     scroll-reveal tier and the grain overlay. */
  --fx-intensity:1;

  /* The one ambient texture that survives: fine grain over flat navy, which
     keeps a large dark surface from banding. It is not a colour. */
  --fx-noise-opacity:.035;                  /* fine-grain overlay opacity */

  /* Surfaces. */
  --fx-card-border:var(--ns-border-subtle); /* resting card edge          */
  --fx-shadow-strength:1;                    /* screenshot-frame depth     */

  /* Scroll-reveal (shared by every [data-reveal]). */
  --fx-reveal-distance:22px;                /* entry travel distance      */
  --fx-reveal-duration:.72s;                /* entry transition duration  */
  --fx-reveal-delay:90ms;                   /* per-item stagger (mirrored in JS) */
}

/* ---- Tablet (<=1080px): the same effects, one notch lighter. Ambient
        animations still play — tablets handle them comfortably. ---------- */
@media (max-width:1080px){
  :root{
    --fx-intensity:.85;
    --fx-reveal-distance:16px;
    --fx-reveal-duration:.62s;
    --fx-reveal-delay:70ms;
  }
}

/* ---- Phone / small (<=720px): lightest tier + the heavy-layer perf cuts.
        Keeps the galaxy identity but removes the per-frame compositor work
        that made scrolling into the next section feel laggy on touch. ----- */
@media (max-width:720px){
  :root{
    --fx-intensity:.6;
    --fx-noise-opacity:0;
    --fx-shadow-strength:.82;
    --fx-reveal-distance:12px;
    --fx-reveal-duration:.5s;
    --fx-reveal-delay:50ms;
  }

  /* Grain overlay used mix-blend-mode:soft-light on a fixed full-screen
     layer, forcing a backdrop read-back on every paint — a real scroll cost
     on mobile for a texture that is imperceptible at this size. Drop it. */
  html::before{ display:none; }
}

/* ---- Touch devices: never rely on hover. Stop a tapped card / screenshot
        from being stranded in its lifted (transform) hover state, so taps
        feel stable. Desktop hover polish is completely unchanged. --------- */
@media (hover:none){.card:hover,
.panel:hover,
.workflow-step:hover,
.plan-card:hover,
.trust-card:hover,
.hero-points .card:hover,
#what-it-does .card:hover,
.hero-shot:hover .hero-shot-img,
.feature-shot:hover .feature-shot-img {
    transform:none !important;
  }
}

/* ========================
   RESPONSIVE NAV + MOBILE FLOW  (2026-06-30)
   ------------------------
   WEBSITE-ONLY. Collapses the desktop pill nav into a compact mobile header
   (logo · Try Free · hamburger) with a grouped slide-down menu at <=960px.
   Progressive enhancement: the hamburger + drawer are gated on `html.js`
   (the drawer needs JS); without JS the inline links stay reachable, so the
   hamburger is never a dead control. CSS-first, GPU-friendly (opacity /
   transform only), reduced-motion-safe, and iPhone/Android safe-area aware.
   ======================== */

/* Notch / cutout: pad the sticky header's top edge by the safe-area inset
   (needs <meta viewport viewport-fit=cover>, added to every page). */
.nav-wrap { padding-top: env(safe-area-inset-top, 0px); }

/* Mobile-only header actions (compact CTA + hamburger). Sit in the nav's
   3rd grid column; hidden on desktop and whenever JS is absent. */
/* The primary action is a BUTTON at every width. It used to be display:none
   above 1080px, so the widest, most common desktop view was the only one with
   no button-styled CTA in the header — the download link sat at the same visual
   weight as "Features". */
.nav-actions { grid-column:3; display:flex; align-items:center;
               gap:var(--space-3); justify-self:end; }
.nav-cta {
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 var(--space-4); border-radius:var(--radius-sm);
  font-weight:var(--weight-bold); font-size:var(--text-control); letter-spacing:-.01em; text-decoration:none;
  color:var(--ns-sapphire-contrast); background:var(--hero-grad); white-space:nowrap;
  box-shadow:none; transition:transform .15s ease;
}
.nav-cta:active { transform:translateY(1px); }
.nav-cta:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }
.nav-toggle {
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; padding:0; cursor:pointer; color:var(--text);
  background:var(--ns-surface-raised);
  border:1px solid var(--ns-border-strong); border-radius:var(--radius-sm);
}
.nav-toggle:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:2px; }
.nav-toggle-box { position:relative; width:20px; height:14px; display:block; }
.nav-toggle-bar, .nav-toggle-box::before, .nav-toggle-box::after {
  content:""; position:absolute; left:0; right:0; height:2px; border-radius:2px;
  background:currentColor;
  transition:transform .25s var(--ease-out), opacity .18s var(--ease-out),
             top .25s var(--ease-out);
}
.nav-toggle-box::before { top:0; }
.nav-toggle-bar { top:6px; }
.nav-toggle-box::after { top:12px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-box::before { top:6px; transform:rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar { opacity:0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-box::after { top:6px; transform:rotate(-45deg); }

/* The drawer + backdrop — not rendered until the mobile breakpoint (+JS). */
.mobile-menu, .mobile-menu-backdrop { display:none; }
.mobile-menu-inner {
  display:flex; flex-direction:column; gap:var(--space-6);
  max-width:560px; margin:0 auto;
}
.mobile-menu-label {
  margin:0 0 var(--space-2); padding:0 var(--space-2);
  font-size:var(--text-label); font-weight:var(--weight-bold); letter-spacing:.12em; text-transform:uppercase;
  color:var(--muted);
}
.mobile-menu-links { display:flex; flex-direction:column; gap:2px; }
.mobile-menu-links a {
  display:flex; align-items:center; min-height:48px;
  padding:var(--space-2) var(--space-4); border-radius:var(--radius-sm);
  color:var(--text); text-decoration:none; font-size:var(--text-supporting); font-weight:var(--weight-strong);
  border:1px solid transparent;
  transition:background .15s ease, border-color .15s ease;
}
.mobile-menu-links a:active { background:var(--ns-surface-raised); }
@media (hover:hover){
  .mobile-menu-links a:hover {
    background:var(--ns-surface-raised);
    border-color:var(--ns-border-strong);
  }
}
.mobile-menu-links a:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:2px; }

/* ---- Collapse to the hamburger at <=1080px (JS only) ----
   The full 8-link nav only fits one clean row at ~1040px+, so collapse a
   touch above that (1080, matching the hero/grid breakpoint): 1081px+ keeps
   the full pill nav (one row, no wrap); 1024 landscape + tablet + phones get
   the menu. Prevents the 8th link wrapping to a 2nd row at 1024. */
@media (max-width:1080px){
  html.js .nav-links { display:none; }
  html.js .nav-actions { display:flex; }

  /* Compact header (~58px tall, within the 56-64px target).
     Pin the 3-column grid (brand · spacer · actions) so it OVERRIDES the
     legacy <=720 single-column nav — otherwise nav-actions (grid-column:3)
     would land in an implicit column and force horizontal scroll. */
  html.js .nav {
    grid-template-columns:auto minmax(0,1fr) auto;
    row-gap:0;
    padding-top:var(--space-2); padding-bottom:var(--space-2);
  }
  html.js .brand { grid-column:1; grid-row:1; }
  html.js .nav-actions { grid-column:3; grid-row:1; }
  html.js .brand-mark { width:38px; height:38px; border-radius:var(--radius-sm); }
  html.js .brand-copy span { display:none; }   /* keep the name, drop subtitle */

  /* Slide-down drawer: fixed panel below the header, full width, scrollable,
     bottom-safe-area padded. Animated via .is-open (opacity + transform). */
  html.js .mobile-menu {
    display:block; position:fixed; left:0; right:0;
    top:var(--nav-h, 58px); bottom:0; z-index:55;
    padding:var(--space-6) var(--gutter)
            calc(var(--space-9) + env(safe-area-inset-bottom, 0px));
    overflow-y:auto; -webkit-overflow-scrolling:touch;
    background:var(--ns-bg-deep);
    border-top:1px solid var(--ns-border-subtle);
    /* The drawer's slide is NAVIGATION STATE — purpose 1 of the three motion
       purposes that survive Pass 2B. (The hover-lift sweep briefly took this out
       with the decorative transforms; it is not decorative.) */
    opacity:0; visibility:hidden; transform:translateY(-8px);
    transition:opacity .22s var(--ease-out), transform .22s var(--ease-out),
               visibility .22s;
  }
  html.js .mobile-menu.is-open { opacity:1; visibility:visible; transform:none; }
  html.js .mobile-menu-backdrop {
    display:block; position:fixed; inset:0; z-index:50;
    background:var(--ns-bg-deep); opacity:0; visibility:hidden;
    transition:opacity .22s ease, visibility .22s;
  }
  html.js .mobile-menu-backdrop.is-open { opacity:1; visibility:visible; }
}

/* ---- Auth pages (login / account): keep the functional nav-cta reachable
        on DESKTOP too. ----
   The generic nav hides the whole .nav-actions cell above the mobile
   breakpoint because marketing CTAs live in the page body. But the auth
   pages repurpose that cell for a REQUIRED action with no body equivalent —
   "Sign out" on /account, "Back to site" on /login. Without this override a
   signed-in DESKTOP user (>1080px) has no visible way to sign out. Show the
   cell + its CTA on desktop; the hamburger stays mobile-only (the desktop
   pill nav already exposes every link the drawer would). */
@media (min-width:1081px){
  /* Stated FIRST and explicitly, not left to fall out of the general rule below:
     on an AUTH page the account actions (Sign out) MUST be reachable on desktop,
     and the hamburger that would duplicate them must not be.
     verify_website_responsive_nav holds us to this, and it reads the block from
     the opening brace — so this requirement leads. */
  html.js body[data-auth-page] .nav-actions { display:flex; }
  html.js body[data-auth-page] .nav-toggle  { display:none; }

  /* The desktop pill row already exposes every link the drawer would, so the
     hamburger is phone/tablet only — but the CTA button now stays at EVERY width
     (Pass 1: above 1080px the header had no button-styled CTA at all). */
  .nav-toggle { display:none; }
}

/* Lock background scroll while the drawer is open. */
html.menu-open, html.menu-open body { overflow:hidden; }

/* ---- Public account entry ("Sign in" <-> "My account") ----
   Static anchors marked [data-account-nav] (one desktop pill, one drawer
   row) that assets/js/account-nav-*.js relabels from the Supabase session.
   Layout-shift control: the desktop pill reserves the WIDER "My account"
   label width up front, so the Sign in -> My account swap never shifts the
   header horizontally or changes its height. No plan badge renders in the
   header any more — plan status lives in the /account dashboard (this is
   what fixed the signed-in header wrapping to a second row and jumping
   height once auth resolved). */
/* The reserved width must be >= the rendered "My account" pill, or the swap
   still moves the header - which is exactly what a returning visitor sees
   when a bfcache restore or a cross-tab sign-in relabels an already-painted
   page. Measured with Inter loaded: 110px here, 104px in the compressed
   range below. verify_account_nav_visual.py flips the label in place and
   asserts nothing moves, so these two numbers cannot silently drift. */
.nav-links a[data-account-nav] { min-width:110px; text-align:center; }
/* Narrow-desktop guard: between the collapse breakpoint and ~1200px, compress
   the pills a touch so the single nowrap row always fits comfortably. */
@media (min-width:1081px) and (max-width:1199px){
  .nav-links { gap:var(--space-1); }
  .nav-links a { font-size:var(--text-control); padding:var(--space-2) 9px; }
  .nav-links a[data-account-nav] { min-width:104px; }
}

/* ---- Hero: compress on phones so copy + the primary CTA lead, and the
        product screenshot never dominates the first screen. ---- */
@media (max-width:640px){
  .hero { padding-top:var(--space-6); }
  .hero-copy .lead { font-size:var(--text-body); line-height:1.62; }
  /* Override the CAP, not the image — the caption reads the same property, so
     the two can never disagree at this breakpoint. */
  .hero-shot    { --shot-cap:min(100%, calc(300px * var(--shot-aspect)),
                                 calc(40vh * var(--shot-aspect))); }
  .feature-shot { --shot-cap:min(100%, calc(380px * var(--shot-aspect)),
                                 calc(48vh * var(--shot-aspect))); }
  .hero-shot-note { font-size:var(--text-caption); }
  .hero-actions { margin-top:var(--space-5); }
  .trust-badges { gap:var(--space-1) var(--space-2); margin-top:var(--space-3); }
}
@media (max-width:400px){
  .nav-cta { padding:0 var(--space-3); font-size:var(--text-control); }
}

/* Reduced motion — no slide / morph, instant show. */
@media (prefers-reduced-motion: reduce){
  .mobile-menu, .mobile-menu-backdrop,
  .nav-toggle-bar, .nav-toggle-box::before, .nav-toggle-box::after { transition:none !important; }
}


/* ══════════════════════════════════════════════════════════════════════════
   SYSTEM LAYER — accessibility + interaction contracts.
   Appended last so it wins over the per-component rules it standardises.
   Every rule here closes a hole found by a real-browser audit, not a taste call.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 1. One focus ring, everywhere ────────────────────────────────────────
   There were twelve per-component focus rules (three colours, four offsets) and
   the two most-used interactive surfaces on the site had NONE: the desktop nav
   links and every FAQ <summary>. Keyboard users fell back to the UA ring, which
   is nearly invisible on this dark background. One rule, applied to anything
   focusable, and the components keep their own only where they genuinely differ.
   The block that sat here was DEAD: FOCUS TREATMENT below repeats the selector
   list at equal specificity and, being later, won every property. One floor,
   stated once, there; `details` went with it. */
:root { --focus-ring: var(--ns-focus); }

/* The copy chips deliberately suppressed the outline in favour of a box-shadow
   ring; that is fine visually but it left them with no outline at all if the
   shadow is overridden. Give them the standard ring back. */
.email-copy-chip:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }

/* ── 2. Disabled buttons must LOOK disabled ───────────────────────────────
   There was no :disabled styling anywhere in the codebase. A submit button
   mid-request (checkout, sign-in) was pixel-identical to a live one, so the only
   feedback for "we're working on it" was… nothing. */
.btn:disabled,
.btn[aria-disabled="true"],
button:disabled {
  opacity: .55;
  cursor: not-allowed;
  transform: none;
  box-shadow:none;
  filter: saturate(.6);
}
.btn:disabled:hover,
button:disabled:hover { transform: none; }

/* ── 3. Touch targets ─────────────────────────────────────────────────────
   Footer legal links and the inline email chips rendered ~20px tall — half the
   practical minimum for a thumb. Pad them out on touch-sized viewports only, so
   the desktop footer keeps its tight rhythm.

   The floor here was 40px, chosen before the site settled on 44. That mattered
   because `.footer-legal-row a` (0,2,1) outranks `.email-copy-chip` (0,1,0) and
   sits later in the file, so the footer's support-email chip was being pulled
   BACK DOWN to 40px on every viewport under 560px that a coarse pointer had not
   been detected on — a mouse, a trackpad, a narrow desktop window. 44 is the
   floor the rest of the file already agrees on, including the `pointer: coarse`
   block below, which set 44 for these exact selectors. */
@media (max-width: 560px) {
  .footer-legal-row a,
.footer-legal-fineprint a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .footer-legal-row { gap: var(--space-1) var(--space-3); }
}

/* ── 4. Reduced motion — the catch-all ────────────────────────────────────
   Nine separate @media blocks already covered the big set-pieces, but they
   missed the things people actually trigger: the FAQ chevron still rotated, the
   footer links still lifted, the nav CTA still pressed, the lightbox close still
   scaled, and two arrows still slid on hover. A blanket rule is the only way to
   be sure a new component is covered by default rather than by remembering. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }


                                    .nav-cta:active,
.lightbox-close:hover,
.purchase-helper-btn span[aria-hidden] { transform: none !important; }
}

/* ── 5. Links must be visible AND distinguishable ─────────────────────────
   site.css had NO base `a { color }` — only an allowlist (.legal-shell a,
   .support-callout a, …). Every prose link outside that allowlist therefore
   rendered in the BROWSER DEFAULT blue (#0000EE) on a near-black background:
   measured 2.16:1, against the 4.5:1 WCAG 1.4.3 AA needs. Thirteen links on the
   homepage and three on /lite were effectively unreadable.

   A bare `a` selector (0,0,1) is deliberately weak: every component that styles
   its own links (.btn, .nav-links a, .footer-links a, .legal-shell a …) already
   wins on specificity, so this only reaches the links nothing else claimed.

   This is EVERY unclaimed link on the site, so it is exactly the rule that must
   not be sapphire: the accent belongs to the primary CTA, and a hundred prose
   links wearing it is how the accent stops being read as one. Steel is the
   supporting-link colour, and it measures 5.84:1 on the canvas — sapphire's
   4.38:1 on a surface would have failed 1.4.3 AA in any case. */
a { color: var(--ns-steel-hover); }

/* Colour alone is not enough: steel against the surrounding --muted body text
   is under the 3:1 that WCAG 1.4.1 requires before colour may be the sole
   distinguishing cue. Unclassed links inside prose get an underline.
   :not([class]) targets exactly the links no component has styled. */
p a:not([class]),
li a:not([class]),
dd a:not([class]) {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

/* ── 6. The focus ring must survive on form fields ────────────────────────
   login.html's inline style sets `outline: none` on :focus, which beat the
   site-wide ring. What remained was a 1px border change plus a glow measured at
   1.42:1 against the field — under the 3:1 an indicator needs to count. Restore
   the ring; the border/glow stay as an additional cue. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid var(--focus-ring) !important;
}

/* ── 7. Don't animate the focus ring ──────────────────────────────────────
   `.btn { transition: .2s ease }` expands to `transition-property: all`, which
   fades outline-color in over 200ms — a focus indicator that arrives late is a
   focus indicator you can miss while tabbing quickly. */
.btn { transition-property: transform, box-shadow, border-color, background-color, color; }

/* ── 8. Touch targets: gate on the POINTER, not the viewport width ────────
   The earlier fix was `@media (max-width:560px)`, which is the wrong axis. It
   left two holes a real-browser audit found:
     * an iPad in portrait (768px) reverted the footer legal links to 20px tall;
     * the in-body .email-copy-chip (264 x 24px) was never covered at all.
   `pointer: coarse` asks the actual question — "is this being operated by a
   finger?" — and so covers tablets and touch laptops at any width, while leaving
   the mouse-driven desktop footer at its tight rhythm. */
/* Pass 4 raised this floor from 40px to 44px. 40 cleared WCAG 2.5.8 (AA wants
   24) but sat under the 44x44 of WCAG 2.5.5 and the Apple HIG — and a measured
   sweep found the two clusters a phone user aims at MOST were sitting exactly
   there: the whole footer nav + legal row at 40px, and the loop rail's stage
   chips at 42px. The brand link below was already 44 for precisely this reason;
   the rest of the site now agrees with it. Coarse pointers only, so the
   mouse-driven desktop rhythm is untouched. Inline links inside a sentence are
   exempt under 2.5.8 and are deliberately NOT targeted here. */
@media (pointer: coarse) {
  .footer-nav a { display:inline-flex; align-items:center; min-height:44px; }
  .footer-legal-row a,
.footer-legal-fineprint a,
.email-copy-chip,
.note a,
.legal-shell .legal-toc a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* Height alone is not a target. "FAQ" (34px) and "EULA" (41px) are the two
     shortest labels in the legal row, and both were narrower than the finger
     aiming at them even at a full 44px tall. They are standalone nav links in
     a separated row, not links inside a sentence, so the 2.5.8 inline
     exemption does not cover them. Width only — the height above stands. */
  .footer-legal-row a { min-width:44px; justify-content:center; }
  .footer-legal-row { gap: var(--space-1) var(--space-3); }
  /* NOTE: the loop rail is NOT fixed from here. This "system layer" is only
     appended-last relative to the components that existed when it was written;
     Pass 3 added the loop CSS BELOW it, so a `.loop-rail .loop-link` rule here
     is same-specificity and LOSES to the base rule further down the file. The
     rail's floor is therefore set at its own base rule. Anything added below
     this block is outside the system layer's reach — check, don't assume. */
  /* The brand/logo link measured 38px — two short of a comfortable target. */
  .nav .brand { min-height: 44px; }
}

/* ── 9. No orphaned last lines ────────────────────────────────────────────
   Measured: an h3 whose final line was the single word "why.", and a button
   whose final line was "steps". `balance` distributes a short run of lines
   evenly instead of leaving one word stranded; `pretty` does the same job for
   body copy. Both degrade to normal wrapping where unsupported, so this is a
   free improvement rather than a dependency. */
h1, h2, h3, h4, summary,
.btn, .nav-cta,
.section-head p, .lead { text-wrap: balance; }
/* `text-wrap` is a longhand of the `white-space` SHORTHAND, so the line
   above is also what governs whether a button label wraps: it resets
   text-wrap-mode to `wrap` and out-ranks the earlier `.btn` rule by
   order. Button labels wrap, balanced. That is deliberate — a nowrap
   label overflows a 360px viewport sideways, where a balanced two-line
   label just reads as two lines. A label carrying inline markup needs
   to be ONE flex item for this to apply to it; see .btn > .btn-label. */
p, li { text-wrap: pretty; }

/* THE PRICE IS ONE WORD. Measured on the live homepage: `balance` split
   "Free to start. $14.99 / month for the rest." after the amount and opened
   line two with a stranded "/ month". The price was three break opportunities
   wide (two spaces plus the slash's own), so every wrapping feature on the
   site — balance, pretty, plain reflow — could take one of them.

   Two halves to the fix and both are needed. The COPY half is canonical form
   "$14.99/month" (config.js PRICE_FULL_DISPLAY, hydrated into every
   [data-price] span), which removes the two spaces. This is the other half:
   browsers still offer a break after a slash — that is how a long URL wraps —
   so the token says so itself. Scoped to the token, never to its heading: a
   nowrap on the H2 would push the whole headline off a 360px viewport. */
[data-price] { white-space:nowrap; }

/* A three-line button is a layout accident, not a design. Give the label back
   the room the 24px side padding was taking at phone widths. */
@media (max-width: 400px) {
  .btn { padding: 0 var(--space-4); }
}

/* ── 10. Cards should not be mostly empty ─────────────────────────────────
   Fixed GLOBALLY now (see "CONTENT-DRIVEN HEIGHTS" above): the container's
   align-items and the item's min-height/align-self were BOTH forcing equal
   heights, and both are gone. The scoped override that used to live here —
   #get-started .trust-grid, #value .grid-2, #benefits .grid-3 — is redundant,
   and two of those three IDs no longer exist on any page. */


/* ========================
   TEXT MEASURE — one cap per CONTENT TYPE  (Frontend-Design Pass 2A)
   ------------------------
   Pass 1 measured two different typographic standards on one site. The
   marketing pages capped their measure and read well (home 54ch, why 46ch,
   lite 62ch). The utility and legal pages capped NOTHING and ran to the full
   1240px container:

       FAQ answers ........ 81ch      Terms / EULA ....... 85ch
       Support ............ 78ch      Privacy ............ 83ch
       quick-start steps .. 115ch     footer fine print .. 134ch

   That single difference is most of why the utility pages "felt disconnected"
   from the marketing pages — not the colour, not the cards. They were typeset
   to a different standard.

   .legal-shell is the shared wrapper for /faq, /support, /privacy, /terms,
   /eula, /refunds and /disclaimer, so ONE cap corrects SEVEN routes. Long-form
   prose gets the roomier --measure-prose (72ch), not the marketing 64ch: legal
   text should be readable, not artificially narrow.

   Code, checksums and command lines are deliberately EXEMPT — they must stay
   exact and selectable, and they already wrap/scroll safely on their own.
   ======================== */
.legal-shell p,
.legal-shell li,
.legal-shell dd,
.legal-shell .faq-body,
.support-shell .support-section p,
.support-shell .support-section li {
  max-width:var(--measure-prose);
}
/* quick-start's numbered walkthrough ran at 115ch — the worst measure on the
   site. These lists are instructions; they are read line by line. */
.access-steps li,
.delivery-flow > li,
.dl-steps li,
.list li {
  max-width:var(--measure-prose);
}

.trust-disclaimer {
  max-width:var(--measure-caption);
}
.fine-print { max-width:var(--measure-prose); }   /* footer blurb: prose, not a caption */

/* Never cap machine truth — a SHA-256 must stay one exact, selectable string. */
.checksum,
.checksum-cmd,
code, pre {
  max-width:none;
}

/* The homepage carries a FAQ *preview* that is not inside .legal-shell, so the
   shell's measure cap never reached it — its answers still ran at 110ch. The
   FAQ body is prose wherever it appears. The SUMMARY is deliberately NOT
   capped: it is the accordion ROW (question + indicator grid) — capping it
   detached the +/× icon from the card's right edge on wide homepage cards
   (Pass 7 fix). Questions are short; the answers are the prose that needs
   the measure. */
.faq-body p,
.faq-body li {
  max-width:var(--measure-prose);
}
/* Card and step body copy. In a 3-up or 4-up row these blocks are already
   narrow, so the cap is inert; it engages only in the single-column state
   between 721 and 1080px, where a card spans the whole container and its
   paragraph measured 89ch (95ch for a Premium feature line). The cap is
   --measure-prose rather than the marketing 64ch because the text is inside
   a bordered box: ending a line far short of a visible edge reads as a
   mistake, and 72ch lands just inside the card's own padding. */
.card > p,
.card > ul li,
.workflow-step > p,
.plan-features li {
  max-width:var(--measure-prose);
}

/* The download page's safety copy: the signed-installer/SmartScreen notice and
   the checksum-mismatch warning both ran past 105ch. These are the two
   paragraphs a nervous user actually reads before running a downloaded
   installer. */
.dl-note p,
.dl-mismatch,
.dl-verify-why,
.dl-verify-lead {
  max-width:var(--measure-prose);
}

/* ========================
   FOCUS TREATMENT — one ring, and nothing can opt out of it
   ------------------------
   Pass 2A measured every focusable element on every page and found two inline
   links inside the FAQ answers ("the comparison above", "pricing & access")
   with NO visible focus indicator at all — a keyboard user could tab onto them
   and have no idea where they were. Rather than patch those two, the ring is
   now a FLOOR: every focusable thing inherits it, and a component that wants a
   different treatment overrides it with its own (higher-specificity) rule.
   Radius follows the control step so the ring hugs whatever it is ringing.
   ======================== */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
details:focus-visible,
[tabindex]:focus-visible {
  outline:3px solid var(--focus-ring, var(--ns-sapphire));
  outline-offset:2px;
  border-radius:var(--radius-sm);
}

/* ---- The closing chapter ----
   It was a full section-head (eyebrow + H2 + a paragraph that repeated the
   hero's trust badges verbatim) plus two buttons, one of which duplicated the
   plan card's. A closer is one sentence and one action. */
.closer > div:not([hidden]) {
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:var(--space-6);
  padding-top:var(--section-y);
  border-top:1px solid var(--ns-border-subtle);
}
.closer h2 { margin:0; max-width:20ch; }

/* ---- Footer ----
   The marketing footer used to be two big cards, 19 links (two of them
   duplicates), and a bulleted restatement of the whole value proposition —
   a fourth pitch, at the end of the page. /account already shipped the right
   answer: a brand line, the links you actually need, the legal row, done. */
.footer {
  padding:var(--section-y) 0 var(--space-9);
  margin-top:var(--section-y);
  border-top:1px solid var(--ns-border-subtle);
  display:flex;
  flex-direction:column;
  gap:var(--space-6);
}
.footer-nav {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-2) var(--space-5);
}
/* Ten links wrapped raggedly at almost every width (4/2/3/1 on phones, 9/1 at
   1024) — always ending in a stranded link. Two deliberate treatments:
   a tidy two-column grid on phones (10 links = five full rows, no orphan
   possible), and a centered wrap above that (a short last row sits centered,
   which reads as intentional, not leftover). */
@media (max-width:640px) {
  .footer-nav {
    display:grid;
    grid-template-columns:repeat(2, minmax(0, 1fr));
    gap:var(--space-1) var(--space-4);
  }
}
@media (min-width:641px) and (max-width:1280px) {
  /* Ten links are exactly five columns by two rows — full rows, no orphan.
     Above 1280px the single flex row genuinely fits and needs no help. */
  .footer-nav {
    display:grid;
    grid-template-columns:repeat(5, minmax(0, auto));
    justify-content:start;
    gap:var(--space-2) var(--space-5);
  }
}
.footer-nav a {
  color:var(--muted);
  text-decoration:none;
  font-size:var(--text-supporting);
  border-bottom:1px solid transparent;
}
.footer-nav a:hover { color:var(--text); border-bottom-color:var(--ns-border-control); }
.footer .footer-brand { align-items:center; gap:var(--space-4); }
.footer .footer-brand img { width:40px; height:40px; }
.footer .footer-brand strong { display:block; color:var(--text); }
.footer-legal { border:0; padding:0; background:none; box-shadow:none; }

/* ---- Pricing tile rows (Pass 2B) ----
   The post-purchase signpost and the legal fine print span BOTH columns, below
   the offer and the access path. Keeping them out of the left column is what
   let the two columns balance without a single word of filler. */
.purchase-helper-card,
.price-legal { grid-column:1 / -1; }
.purchase-helper-copy { flex:1 1 420px; min-width:0; }
.purchase-helper-btn { flex:0 0 auto; }
.price-legal {
  margin:0;
  padding-top:var(--space-5);
  border-top:1px solid var(--ns-border-subtle);
  color:var(--muted);
  font-size:var(--text-fine);
  line-height:1.7;
  max-width:var(--measure-prose);
  opacity:.9;
}
.price-cta-intro { margin:var(--space-3) 0 0; }
.price-microcopy strong { color:var(--text); }

/* ---- Ambient starfield: REMOVED ----
   A cyan/violet aurora wash plus two generated dot fields sat fixed behind
   every page. It is the particle-network background this pass is here to
   remove, and it was the layer doing the most to make a market workstation
   read as a launch page. The three rules, the .space-fx container, its two
   markup instances (index, account) and the parallax branch that drove them
   are gone; nothing else referenced them. */

/* ========================
   SECTION HIERARCHY (Pass 2B)
   ------------------------
   Pass 1: eight of nine chapters opened with the SAME block — a glowing eyebrow,
   a 52px H2, a muted paragraph — and only one had any background. Nine equal
   announcements; no way to tell a major beat from a minor one.

   The differentiator is COMPOSITION, not decoration. No new colours, no new
   gimmick per section — chapters alternate OPEN and BOXED, and the closing beats
   drop their chrome entirely:

     hero .......... OPEN   full-bleed media band
     how-it-works .. BOXED  the loop, on cards
     demo .......... OPEN   editorial: narrow copy, wide evidence
     features ...... BOXED  three cards
     plans ......... BOXED  a comparison + the trial bridge (a NOTE, not a card)
     trust ......... OPEN   ← was two heavy cards; it is prose and a data list
     pricing ....... BOXED  the one spotlight tile
     closer ........ OPEN   one line, one action, a hairline rule
     footer ........ OPEN   a full stop
   ======================== */

/* TRUST — an open chapter. It was two large cards, which gave a section whose
   job is to SLOW YOU DOWN the same visual weight as the sales chapters. The
   verification table keeps a quiet surface (it is data, and the checksum needs
   a container); the "what this is and isn't" column loses its box — it is prose
   and should read as prose. */
#get-started .trust-grid > .trust-card:last-child {
  background:none;
  border:0;
  box-shadow:none;
  padding:0;
  /* Pass 8C: kill the INVISIBLE rounded clipping box too. The base card
     keeps overflow:hidden + an 18px radius; with the chrome stripped and
     padding at 0 the content sat flush against that unseen boundary, so
     the rounded corner shaved the heading edge and the bullet glows were
     cut at the left. An open column has nothing legitimate to clip. */
  overflow:visible;
  border-radius:0;
  /* Pass 8C centred this column against a verify card that was ~350px
     taller. It is the TALLER column now, so the centring lives on the grid
     (see .trust-grid { align-items:center }) and this declaration only
     restates it for the child that used to own it. Below the 980px stacking
     breakpoint the columns stack and alignment is inert. */
  align-self:center;
}
#get-started .trust-grid > .trust-card:last-child::before { display:none; }
/* (Pass 8C put the Copy-checksum control on its own row inside the old SHA
   value cell. The fingerprint receipt owns that layout now — .vfp-top is a
   flex row with the tag on the left and the button on the right — so the
   override is gone with the cell it was correcting.) */

/* The closing chapters need less air above them than a new argument does. */
#download-cta { padding-top:0; }
/* (The `#faq-preview .section-head p { display:none }` that used to live here
   is gone — the paragraph it hid was deleted from the HTML instead. Hiding
   copy with CSS still ships it.) */

/* .trust-grid is a card row like the others — content-driven, not stretched.
   (Removing the Download CTA from the verification card left it 42% empty
   under a stretch it never needed.)

   CENTRED, not start-aligned. `start` plus a centred :last-child encoded which
   column happened to be taller; the post-1.2.2 pass moved the installer facts
   and the Copy control to /lite and inverted that, leaving the shorter verify
   card pinned to the top with the imbalance all at the bottom. Centring the row
   is the same intent without the assumption: whichever column is taller sets
   the height, the other one is centred against it. */
.trust-grid { align-items:center; }

/* ========================
   THE LOOP SPINE  (Frontend-Design Pass 3)
   ------------------------
   The seven stages are not a widget ON the page — they ARE the page's product
   half. The rail is an INDEX, not a progress meter: it says "you are reading
   stage 4", nothing more. No percentage, no fill-behind, no completion state,
   no auto-advance. Marking where you are is navigation; telling you how much
   you have "completed" would be a game.

   Two treatments, one markup:
     >=1081px  a sticky index column beside the stages
     < 1081px  a wrapping row of jump chips above them (no cramped sidebar,
               no carousel, no horizontal scroll)

   Everything below reuses the existing token system — no new radii, no new
   type sizes, no new weights.
   ======================== */
.loop {
  display:grid;
  grid-template-columns:1fr;
  gap:var(--block-gap);
  margin-top:var(--section-head-gap);
}

/* ---- the rail ----
   Below the desktop breakpoint the rail is a GRID, never a naturally-wrapping
   flex row. Natural wrapping degraded by viewport: 3/3/1 at 414-480px,
   3/2/2 at 375-390px, 2/2/2 with a stranded Review at 320-360px — Stage 7
   always looked like a leftover. The layout is now deliberate at every width:
     <560px    two columns — 2/2/2, then Review CENTERED across both columns
               at one column's width (the loop closes in the middle, on
               purpose — it reads as the loop's full stop, not an orphan)
     560-1080  a 24-track grid — four chips (span 6 each) on row one, three
               chips on row two starting at track 4, so row two sits centered
               under row one: an intentional 4 + 3
     >=1081    the sticky vertical index column (unchanged, further below) */
.loop-rail ol {
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:var(--space-2);
}
.loop-rail li { min-width:0; }
.loop-rail li:nth-child(7) {
  grid-column:1 / -1;
  display:flex;
  justify-content:center;
}
.loop-rail li:nth-child(7) .loop-link {
  width:calc(50% - var(--space-2) / 2);   /* exactly one column's width */
}
@media (min-width:560px) and (max-width:1080px) {
  .loop-rail ol { grid-template-columns:repeat(24, minmax(0, 1fr)); }
  .loop-rail li { grid-column:span 6; }
  .loop-rail li:nth-child(5) { grid-column:4 / span 6; }  /* centers row two */
  .loop-rail li:nth-child(7) { display:block; grid-column:span 6; }
  .loop-rail li:nth-child(7) .loop-link { width:100%; }
}
.loop-rail .loop-link {
  display:flex;
  align-items:center;
  width:100%;
  gap:var(--space-3);
  min-height:44px;                       /* WCAG 2.5.5 / Apple HIG target size.
                                            Measured at 42px in Pass 4 — the rail
                                            IS how a phone user walks the loop. */
  padding:var(--space-2) var(--space-4) var(--space-2) var(--space-2);
  border-radius:var(--radius-pill);
  border:1px solid var(--ns-border-subtle);
  color:var(--muted);
  text-decoration:none;
  font-size:var(--text-supporting);
  transition:color .18s var(--ease-out), border-color .18s var(--ease-out);
}
.loop-n {
  display:grid;
  place-items:center;
  flex:none;
  width:24px; height:24px;
  border-radius:var(--radius-pill);
  background:var(--ns-border-subtle);
  color:var(--muted);
  font-size:var(--text-label);
  font-weight:var(--weight-bold);
  font-variant-numeric:tabular-nums;
  transition:background .18s var(--ease-out), color .18s var(--ease-out);
}
.loop-rail .loop-link:hover { color:var(--text); border-color:var(--ns-border-control); }

/* The ONE workflow emphasis. Text, marker, border — nothing that moves. */
.loop-rail .loop-link.is-active {
  color:var(--text);
  border-color:var(--ns-border-control);
}
.loop-rail .loop-link.is-active .loop-n {
  background:var(--hero-grad);
  color:var(--ns-sapphire-contrast);
}

/* ---- the stages ---- */
.loop-stages {
  list-style:none;
  margin:0;
  padding:0;
  display:flex;
  flex-direction:column;
  gap:var(--block-gap);
}
.loop-stage {
  /* A stage is a tall block with its own step marker on top, so it lands with
     a little more air than a bare heading. The shared offset is already
     reserved by scroll-padding-top on the scroller; this adds ONLY the extra
     step, so the two never double up. */
  scroll-margin-top:var(--space-3);
  display:flex;
  flex-direction:column;
  gap:var(--space-4);
}
.loop-step {
  display:flex;
  align-items:center;
  gap:var(--space-3);
  margin:0;
  font-size:var(--text-label);
  font-weight:var(--weight-bold);
  letter-spacing:.09em;
  text-transform:uppercase;
  color:var(--muted);
}
.loop-stage h3 { margin:0; max-width:var(--measure-body); }
.loop-stage > p { margin:0; color:var(--muted); line-height:1.72; max-width:var(--measure-prose); }
.loop-stage strong { color:var(--text); }

/* "Why it matters" — the line that earns the stage its place. A left rule, not
   a card: this system says "explanation" with a rule and "offer" with a box. */
.loop-why {
  border-left:2px solid var(--ns-border-control);
  padding-left:var(--space-5);
  color:var(--muted);
}
.loop-why em { color:var(--text); font-style:normal; font-weight:var(--weight-strong); }

/* ---- the chapter break: the rail stops, commerce begins, nothing is numbered ---- */
.loop-close {
  margin-top:var(--block-gap);
  padding-top:var(--block-gap);
  border-top:1px solid var(--ns-border-subtle);
  display:flex;
  flex-direction:column;
  gap:var(--space-4);
}
.loop-close h3 { margin:0; }
.loop-close > p { margin:0; color:var(--muted); line-height:1.72; max-width:var(--measure-prose); }
.loop-close-note {
  color:var(--faint, var(--muted));
  font-size:var(--text-supporting);
  opacity:.8;
}
.loop-close-actions {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-4);
  margin-top:var(--space-2);
}

/* ---- desktop: the rail becomes a sticky index beside the stages ---- */
@media (min-width:1081px) {
  .loop {
    grid-template-columns:190px minmax(0, 1fr);
    gap:var(--space-10);
    align-items:start;
  }
  .loop-rail {
    position:sticky;
    top:calc(var(--nav-offset) + var(--space-6));
  }
  .loop-rail ol { display:flex; flex-direction:column; gap:0; }
  .loop-rail li { position:relative; }
  /* Reset the sub-desktop grid's Review treatment: in the vertical index the
     seventh entry is a normal row like the other six. */
  .loop-rail li:nth-child(7) { display:block; }
  .loop-rail li:nth-child(7) .loop-link { width:100%; }
  /* the connector — a hairline, drawn between the markers, ending at Review */
  .loop-rail li:not(:last-child)::before {
    content:"";
    position:absolute;
    left:20px; top:34px; bottom:-4px;
    width:1px;
    background:var(--ns-border-subtle);
  }
  .loop-rail .loop-link {
    border:0;
    border-radius:var(--radius-sm);
    padding:var(--space-2);
    min-height:44px;   /* same target floor as the mobile chips — the rail is
                          still tappable on touch laptops and small desktops */
  }
  .loop-rail .loop-link:hover { background:var(--ns-border-subtle); border-color:transparent; }
  .loop-rail .loop-link.is-active { background:var(--ns-border-subtle); border-color:transparent; }
  /* .loop-close no longer lives in this grid (Pass 7): as grid row 2 it sat
     inside the sticky rail's travel range and the rail slid into it. */
}

/* The active state is a STATE CHANGE, not an animation. Under reduced motion it
   flips instantly — there is nothing to ease. */
@media (prefers-reduced-motion: reduce) {
  .loop-rail .loop-link, .loop-n { transition:none !important; }
}

/* (The loop used to re-align its captures here. It no longer needs to: starting
   on the text rail is what .hero-shot-img / .feature-shot-img and their notes
   do everywhere now, because every capture on the site sits under left-aligned
   prose. One rule instead of a special case — see THE RAIL above.) */

/* A compact pointer to the loop from the supporting routes. A LINK, never a
   second copy of the spine — the homepage owns that story, and telling it twice
   is what Pass 1 found wrong with the page in the first place. */
.lite-loop-link {
  margin:var(--space-5) 0 0;
  color:var(--muted);
  font-size:var(--text-supporting);
  max-width:var(--measure-body);
}

/* ========================
   RESPONSIVE RHYTHM PASS (2026-07-17) — narrow-header compression.
   ------------------------
   At 320-430px the header's three fixed-size pieces (brand mark + wordmark,
   Download Free CTA, menu button) overflowed their row: the wordmark painted
   under the CTA and clipped mid-letter ("NovaS…"). Nothing is removed —
   the brand, the CTA and the menu all stay — the pieces scale FLUIDLY
   instead: tighter grid gaps, a slightly smaller mark, a viewport-scaled
   wordmark and CTA. The ellipsis on the wordmark is a last-resort guard for
   extreme text zoom, not something a default-font phone should ever hit.
   ======================== */
@media (max-width:480px) {
  html.js .nav { column-gap:var(--space-3); }
  html.js .brand { gap:var(--space-2); }
  html.js .brand-mark { width:34px; height:34px; }
  html.js .brand-copy { min-width:0; }
  html.js .brand-copy strong {
    font-size:clamp(.88rem, 4.4vw, var(--text-card-title));
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
  }
  html.js .nav-actions { gap:var(--space-2); }
  html.js .nav-cta {
    min-height:44px;
    padding:0 clamp(10px, 3.4vw, 16px);
    font-size:clamp(.82rem, 3.8vw, var(--text-control));
  }
}
/* The final notch (320-360px): the full wordmark must render un-ellipsized
   with the CTA and the 44px menu button beside it. Every piece gives a few
   pixels — none is removed. */
@media (max-width:360px) {
  html.js .nav { padding-left:var(--space-3); padding-right:var(--space-3); column-gap:var(--space-2); }
  html.js .brand { gap:6px; }
  html.js .brand-mark { width:30px; height:30px; }
  html.js .brand-copy strong { font-size:.84rem; }
  html.js .nav-cta { padding:0 9px; font-size:.8rem; }
}

/* ========================
   PLATFORM-RANKED CALLS TO ACTION + "START WITH NOVASTATION"  (2026-07-25)
   ------------------------
   NovaStation is a Windows desktop application. Until now the site said that
   in words while its buttons said something else: the loudest thing on every
   page, at every width, on every device, was an installer a phone or a Mac
   cannot run — and on a phone the only route to the customer account was
   three taps deep inside a hamburger.

   Nothing is hidden here and no destination is removed. The SAME actions are
   RE-RANKED for who is looking:

     plat-windows   the installer leads (it works on this machine)
     plat-alt       Start Here leads; the Windows download steps back to a
                    quiet third action + the requirement note
     plat-unknown   Windows-EXPLICIT wording, download still primary. We do
                    not know this device is unsupported, so we do not treat it
                    as unsupported — but we never imply it is supported either.
     no JavaScript  identical to unknown. This is why the honest wording is
                    the BASE state and plat-windows is the override: the
                    fail-safe direction is the default, not a fallback.

   <=720px overrides all of the above with the same start-first ranking, on
   the grounds that no phone runs a .exe whatever its OS reports.

   THE CLASS IS DECORATION. It ranks buttons and picks wording. It gates no
   download, decides no entitlement, and is read by no security path.
   ======================== */

/* ---- Header: the third slot ---------------------------------------------
   Under 1080px the header is Logo | Account | Menu. Account is a FIXED
   landmark — same word, same place, signed in or out (account-nav-*.js
   re-points it at /account for a signed-in visitor but leaves the label
   alone; a control that renames itself is one people stop trusting to be
   where they left it). 44x44 minimum, visible focus, never overlapping the
   wordmark or the menu button. */
.nav-account {
  display:none;                       /* desktop: the pill nav owns the account link */
  align-items:center; justify-content:center; gap:var(--space-2);
  min-width:44px; min-height:44px; padding:0 var(--space-3);
  border-radius:var(--radius-sm);
  color:var(--text); text-decoration:none; white-space:nowrap;
  font-size:var(--text-control); font-weight:var(--weight-strong);
  background:var(--ns-surface-raised);
  border:1px solid var(--ns-border-strong);
}
.nav-account:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:2px; }
.nav-account:active { background:var(--ns-surface-raised); }
.nav-account-icon { flex:none; display:block; }
.nav-account.is-authed { border-color:var(--ns-border-control); }

/* ---- Header CTA ranking -------------------------------------------------
   ONE marketing CTA in the header, so nothing is left to rank. It was five,
   with this block picking which pair each platform saw — hiding CTA overload
   rather than reducing it.

   body:not([data-auth-page]) scopes both rules to the marketing header:
   /login and /account reuse .nav-cta in that same cell for Sign out / Back to
   site, which must stay visible at every width. */
body:not([data-auth-page]) .nav-actions .nav-cta { display:none; }

@media (min-width:1081px){
  body:not([data-auth-page]) .nav-actions .nav-cta-win { display:inline-flex; }
}

/* Compact header: no marketing button at all — Logo | Account | Menu. */
@media (max-width:1080px){
  html.js .nav-account { display:inline-flex; }
}
/* Very narrow phones: the label collapses to the icon. The accessible name
   stays "Account" (aria-label in the markup), so nothing is lost to a screen
   reader — only to the 320px viewport. */
@media (max-width:360px){
  html.js .nav-account { min-width:44px; width:44px; padding:0; }
  html.js .nav-account-text {
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
  }
}

/* ---- Body CTA ranking (hero + closers) ----
   One primary action each; `.cta-start` / `.hero-start-link` are gone with
   their rules. The requirement note is UNCONDITIONAL now — it needed
   platform-v*.js before, so a JS-blocked Mac visitor got no warning. */
.platform-note {
  margin:var(--space-3) 0 0;
  font-size:var(--text-supporting);
  color:var(--muted);
  /* Same cap as .hero-terms below, for the same reason and by the same
     measurement: the two are one pair of fact lines set at supporting size,
     and uncapped this one ran to 123ch at 1920 beside a sibling held at 64ch.
     Two adjacent paragraphs at different measures read as two columns. */
  max-width:var(--measure-body);
}
/* The hero's commercial summary: free tier, trial, price. Deliberately the
   SAME size and colour as .platform-note — it is a fact line, not an offer,
   and giving it its own visual weight would turn a pricing anchor into a
   third call to action beside two buttons the page has already ranked. The
   only thing lifted out of the muted flow is the price itself, because that
   is the token a scanning visitor is looking for.
   Middle-dot separators wrap as normal text; the price span is nowrap so
   "$14.99 / month" can never break across two lines mid-figure. */
.hero-terms {
  margin:var(--space-2) 0 0;
  font-size:var(--text-supporting);
  color:var(--muted);
  /* --measure-body, not --measure-hero: this line is set at supporting size,
     so the hero's 46ch cap renders far narrower than the .lead above it and
     the block wrapped mid-clause ("starts inside the / app"). 64ch at this
     size lands just inside the lead's own rag, so the two paragraphs read as
     one column instead of two different ones. */
  max-width:var(--measure-body);
}
.hero-terms [data-price] {
  color:var(--text);
  font-weight:var(--weight-strong);
  white-space:nowrap;
}
.platform-note a,
.hero-email-alt a { color:var(--text); text-decoration:underline; text-underline-offset:3px; }
.platform-note a:focus-visible,
.hero-email-alt a:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* Platform classes no longer hide, move or restyle any body CTA: removing a
   visitor's only action with no replacement is a dead end. */

/* ---- The hero email form: the ONE platform-ranked CTA left ---------------
   Where a .exe cannot run, the button becomes a way to send the download page
   somewhere it can. A swap, not hiding: the form keeps its own "On Windows?"
   link, and every state shows button or form, never neither or both. The
   mobile reveal is WIDTH-ONLY, so a JS-blocked phone still gets it. */
.hero-email {
  display:none;
  width:100%;
  max-width:34rem;
  margin-top:var(--space-8);
  margin-bottom:var(--space-2);
}
.hero-email-label {
  display:block;
  margin:0 0 var(--space-2);
  font-size:var(--text-label);
  font-weight:var(--weight-strong);
  color:var(--text);
}
/* At 320px the children cannot share a line; 16rem of flex-basis each makes
   them wrap full-width instead of leaving a 90px input beside a button. */
.hero-email-row {
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-3);
}
.hero-email-input {
  flex:1 1 16rem;
  min-width:0;
  min-height:54px;
  padding:0 var(--space-4);
  border-radius:var(--radius-sm);
  border:1px solid var(--ns-border-strong);
  background:var(--ns-surface-raised);
  color:var(--text);
  font-family:inherit;
  font-size:var(--text-control);
}
.hero-email-input:focus-visible {
  outline:3px solid var(--ns-sapphire);
  outline-offset:2px;
}
/* .btn is nowrap for one-word actions; this five-word label would overflow
   320px sideways, so it wraps. */
.hero-email-submit {
  flex:1 1 16rem;
  min-width:0;
  white-space:normal;
  text-align:center;
  line-height:1.3;
  padding:var(--space-3) var(--space-5);
}
.hero-email-purpose,
.hero-email-alt {
  margin:var(--space-3) 0 0;
  font-size:var(--text-supporting);
  color:var(--muted);
}
/* Status line: empty until there is something true to say, and the WORDS
   carry the outcome — colour only agrees. */
.hero-email-status {
  margin:var(--space-3) 0 0;
  font-size:var(--text-supporting);
  font-weight:var(--weight-strong);
  color:var(--muted);
}
.hero-email-status:empty { display:none; }
.hero-email[data-state="sent"] .hero-email-status { color:var(--ns-success); }
.hero-email[data-state="error"] .hero-email-status { color:var(--ns-danger); }

/* macOS / Linux / iOS / Android, any width: button out, form in. */
html.plat-alt .hero-email { display:block; }
html.plat-alt .hero-actions .cta-download { display:none; }

/* Any phone/small-tablet viewport, whatever the OS reports and whether or
   not a script ran. No handheld runs a .exe. */
@media (max-width:720px){
  .hero-email { display:block; }
  .hero-actions .cta-download { display:none; }
}

/* ========================
   THE ENTRY PANEL — account and download, ranked four ways.
   ------------------------
   One component, used in exactly three places: the /start hero, each path
   hero, and each path's completion panel. Seven anchors ship in the markup and
   any one state reveals a primary, a secondary and the always-present tertiary
   Quick Start. Every .btn-primary precedes every .btn-secondary in the DOM, so
   the visual hierarchy and the tab order agree in all four states without a
   single `order` declaration — reordering with flexbox would desynchronise
   them, and a keyboard visitor would meet the buttons in a sequence nobody
   designed.

     base / unknown / no-JS   Download for Windows  ·  Create an Account
     plat-windows             Download NovaStation  ·  Create an Account
     plat-alt desktop         Create an Account     ·  View Windows Download
     mobile, iOS, Android     Create an Account     ·  Windows download details

   The download is never removed and never blocked — only its wording and its
   rank change. The unknown state is the BASE, in Windows-explicit words: we do
   not know the device is unsupported, so we do not treat it as unsupported,
   but we never imply it is supported either.
   ======================== */
.entry-panel {
  margin:var(--space-6) 0 0;
  padding:var(--panel-pad);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  
}
.entry-panel-compact { margin:var(--space-5) 0 0; }

/* The /start utility row. Same panel, no frame: it sits UNDER the five cards
   and must not compete with them, so it drops the border, gradient and most of
   the padding and keeps only the actions and the one truth line. Framed and
   placed above, it put seven anchors and three notes between the question the
   page asks and the five answers to it. */
.entry-panel-utility {
  margin:var(--space-6) 0 0;
  padding:var(--space-5) 0 0;
  border:0; border-top:1px solid var(--ns-border-subtle);
  border-radius:0;
  background:none;
}
.entry-panel-utility .entry-actions { gap:var(--space-2) var(--space-4); }
.entry-panel-utility .start-platform-note { margin-top:var(--space-4); }
.entry-actions {
  display:flex; flex-wrap:wrap; align-items:center;
  gap:var(--space-3) var(--space-4);
}
.entry-actions .btn { min-height:44px; }

/* Base state — unknown platform and no-JS alike: the Windows-explicit download
   leads, the account follows as the secondary. */
.entry-download,
.entry-account-lead,
.entry-view-win,
.entry-view-details { display:none; }

/* Both DESKTOP rankings live inside the same width query rather than fighting
   the phone rules on specificity: a rule that cannot match at a width cannot
   lose to one that does. `html.plat-windows .entry-download` outranks a bare
   `.entry-download` on specificity, so left outside the query it would have put
   a .exe button back on a Windows tablet at 390px. */
@media (min-width:721px){
  /* Windows: the platform is established, so the label drops "for Windows". */
  html.plat-windows .entry-download-win { display:none; }
  html.plat-windows .entry-download { display:inline-flex; }

  /* macOS / Linux desktop: no installer to run here, so the account leads and
     the Windows download steps back to a secondary — still one click away. */
  html.plat-alt .entry-download,
  html.plat-alt .entry-download-win,
  html.plat-alt .entry-account-second { display:none; }
  html.plat-alt .entry-account-lead,
  html.plat-alt .entry-view-win { display:inline-flex; }
}

/* Phones and tablets at ANY width. A device that cannot run the installer is
   offered the account and the download DETAILS page, not a .exe button. */
@media (max-width:720px){
  .entry-download,
  .entry-download-win,
  .entry-account-second,
  .entry-view-win { display:none; }
  .entry-account-lead,
  .entry-view-details { display:inline-flex; }
}
html.plat-ios .entry-download,
html.plat-ios .entry-download-win,
html.plat-ios .entry-account-second,
html.plat-ios .entry-view-win,
html.plat-android .entry-download,
html.plat-android .entry-download-win,
html.plat-android .entry-account-second,
html.plat-android .entry-view-win { display:none; }
html.plat-ios .entry-account-lead,
html.plat-ios .entry-view-details,
html.plat-android .entry-account-lead,
html.plat-android .entry-view-details { display:inline-flex; }

/* The account anchor is the header's own session-aware control. Both labels
   ship in the markup and CSS picks one off the .is-authed class that
   account-nav-*.js sets, so the wrong word cannot flash and no new
   authentication code exists to get this wrong. */
.entry-account-label[data-account-state="in"] { display:none; }
.entry-account.is-authed .entry-account-label[data-account-state="out"] { display:none; }
.entry-account.is-authed .entry-account-label[data-account-state="in"] { display:inline; }

/* The supporting sentence. ONE of them, in every platform state. Three
   branched notes (.entry-note-win / -alt / -touch) carried two facts, and the
   macOS/Linux branch never stated the Lite truth outright — the visitor least
   likely to install today was the only one never told an account is optional.
   A sentence true everywhere needs no branch and survives no-JS. */
.start-platform-note { display:block; }

/* The third action. Present in every state and never competing: it is a place
   to read, offered beside two places to act. */
.btn-tertiary {
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 var(--space-4);
  border-radius:var(--radius-sm);
  color:var(--muted); text-decoration:none; white-space:nowrap;
  font-size:var(--text-control); font-weight:var(--weight-strong);
  border:1px solid transparent;
  transition:color .2s ease, border-color .2s ease;
}
@media (hover:hover){
  .btn-tertiary:hover { color:var(--text); border-color:var(--ns-border-strong); }
}
.btn-tertiary:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* ---- Goal selector (homepage + /start) ---------------------------------- */
.goal-list {
  list-style:none; margin:0; padding:0;
  display:grid; gap:var(--space-3);
  grid-template-columns:repeat(2, minmax(0, 1fr));
}
.goal-list > li:not([hidden]) { display:flex; min-width:0; }
.goal-card {
  position:relative;
  display:flex; flex-direction:column; gap:6px; width:100%;
  padding:var(--space-4) var(--space-8) var(--space-4) var(--space-5);
  border-radius:var(--radius-md);
  text-decoration:none;
  background:var(--ns-surface);
  border:1px solid var(--ns-border-subtle);
  box-shadow:none;
  transition:border-color .2s ease, background .2s ease;
}
.goal-card-icon { display:flex; color:var(--ns-steel); }
.goal-card-title {
  color:var(--text); font-weight:var(--weight-bold);
  font-size:var(--text-supporting); letter-spacing:-.01em;
}
.goal-card-benefit { color:var(--muted); font-size:var(--text-caption); line-height:1.6; }
/* Stage tag tier: the quietest of the micro-components. A row of stage names
   is a caption about structure, not five separate badges — boxing each one
   would make the card louder than the choice it describes. */
.goal-card-stages {
  margin-top:2px;
  color:var(--ns-text-secondary);
  font-size:var(--text-label); letter-spacing:.05em;
}
.goal-card-arrow {
  position:absolute; top:var(--space-4); right:var(--space-5);
  color:var(--ns-steel); font-size:1rem; line-height:1;
}
/* The fifth path is not a leftover. "Explore the complete workflow" is the
   superset of the other four, so it closes the grid as a full-width band
   rather than sitting beside an empty cell. One column below 641px, where
   the band is simply the last card. */
.goal-list > li:last-child { grid-column:1 / -1; }
@media (min-width:641px){
  .goal-list > li:last-child .goal-card {
    flex-direction:row; align-items:center; flex-wrap:wrap;
    column-gap:var(--space-5); row-gap:4px;
    padding-right:var(--space-5);
  }
  .goal-list > li:last-child .goal-card-benefit { flex:1 1 220px; }
  .goal-list > li:last-child .goal-card-stages { margin-top:0; }
  .goal-list > li:last-child .goal-card-arrow {
    position:static; margin-left:auto;
  }
}
@media (hover:hover){
  .goal-card:hover { border-color:var(--ns-border-control); background:var(--ns-surface-raised); }
}
.goal-card:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }
/* There is no "selected" card any more. A path is a URL now, so choosing one
   is a navigation rather than a state this page has to render — which is why
   the chooser can be a chooser and nothing else. */
@media (max-width:640px){
  .goal-list { grid-template-columns:1fr; }
}

/* The prose that sits under the access strip on a path page. Capped at the
   same measure as every other explanation on these pages, so the three
   surfaces do not each run to a different rail. */
.access-line { margin:0; max-width:var(--measure-text); color:var(--muted); line-height:1.72; }

/* THE ACCESS STRIP. Three derived lines, not a comparison table: what this
   path costs, in the vocabulary of this path's own stages. Derived from the
   stage mapping in the module, so it cannot disagree with the focus path
   printed a screen above it. */
.access-strip {
  display:flex; flex-direction:column; gap:var(--space-3);
  margin:0 0 var(--space-4);
  padding:var(--space-4) var(--space-5);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  background:var(--ns-surface);
}
.access-strip[hidden] { display:none; }
.access-row {
  margin:0; min-width:0;
  display:flex; flex-wrap:wrap; align-items:baseline; gap:4px var(--space-4);
}
.access-row-label {
  flex:none; color:var(--muted);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
}
.access-row-value {
  min-width:0; color:var(--text);
  font-size:var(--text-supporting); font-weight:var(--weight-strong);
}

.journey-line {
  margin:0 0 var(--space-5); color:var(--muted);
  font-size:var(--text-supporting); line-height:1.7; max-width:72ch;
}
.journey-line[hidden] { display:none; }
.goal-truths {
  list-style:none;
  margin:var(--space-4) 0 0; padding:0;
  display:flex; flex-direction:column; gap:var(--space-2);
  color:var(--muted); font-size:var(--text-supporting); line-height:1.7;
  max-width:72ch;
}
.goal-truths[hidden] { display:none; }
.goal-truths li { position:relative; padding-left:var(--space-5); }
.goal-truths li[hidden] { display:none; }
.goal-truths li::before {
  content:""; position:absolute; left:0; top:.74em;
  width:var(--marker-w); height:var(--marker-h);
  background:var(--marker-ink);
}

/* ========================
   THE STARTING PATHS  —  /start and /start/<path>  (2026-07-26)
   ------------------------
   /start is a CHOOSER and nothing else: a hero, the account/download actions,
   five cards, one sentence about the loop. It holds no chapters, no dormant
   progress rail and no empty result markup, because a page that ships the
   furniture of a state it is not in is a page that has to hide it.

   Each of the five paths is its own URL with its own static HTML, generated
   from tools/start_paths.py. What the stylesheet adds is the composition: a
   hero, a reading rail, chapters that pair copy with the capture it explains,
   and a completion panel that gains emphasis once the page has actually been
   read.
   ======================== */

/* ---- Shared: the seven-stage loop rail ----------------------------------
   Used twice. On /start it names the whole loop with nothing emphasised —
   the page has not been told what you want yet. On a path page it marks that
   path's chapters, under "Where this path fits". The on/off distinction is
   carried by a left rule and ink weight as well as hue, and each chip states
   its own status in words for assistive technology; a row of seven chips has
   no room to print that status seven times over. */
.path-loop { padding-top:var(--space-6); }
.path-loop-label {
  margin:0; color:var(--muted);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
}
.loop-chips {
  list-style:none; margin:var(--space-4) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--space-2);
}
.loop-chip {
  padding:var(--chip-pad-y) var(--chip-pad-x);
  border-radius:var(--radius-chip);
  border:1px solid var(--chip-line);
  border-left-width:3px;
  border-left-color:var(--ns-border-subtle);
  background:var(--chip-bg);
  color:var(--ns-text-secondary);
  font-size:var(--text-label); letter-spacing:.04em;
}
.loop-chip.is-on {
  border-left-color:var(--cyan);
  color:var(--text); font-weight:var(--weight-strong);
  background:var(--ns-sapphire-soft);
}
.loop-chip-state {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.path-loop-note {
  margin:var(--space-4) 0 0; max-width:var(--measure-text);
  color:var(--muted); font-size:var(--text-supporting); line-height:1.72;
}
.path-loop-note a { color:var(--text); text-decoration:underline; text-underline-offset:3px; }
.path-loop-note a:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* ---- /start: the chooser ------------------------------------------------ */
.start-hero { display:block; }
.start-hero-head { min-width:0; }

/* ---- A dedicated path page ---------------------------------------------- */
/* Ambient light, moving slowly enough that it reads as depth rather than as
   animation. One surface per page carries it — the hero — and reduced motion
   parks it. */
/* overflow-x:clip for the same reason .hero-shot carries it: the ambient light
   field below bleeds -6% into the side gutters, and below ~412px that bleed is
   wider than --gutter, so the field reached past the viewport and the whole
   path page scrolled sideways by a handful of pixels. Horizontal only, so the
   field still blooms above the hero; clip (not hidden) leaves the y axis
   visible and creates no scroll container, so nothing sticky is affected. */
.path-hero {
  position:relative; isolation:isolate;
  display:block;
  overflow-x:clip;
}
/* ONE COLUMN, EVERY WIDTH. As a two-column grid above 961px this put the
   explanation beside the screenshot instead of under it, and left an empty
   half-column whenever the copy ran short. Source order — eyebrow, path name,
   headline, copy, capture, caption, Begin path — is now the rendered order at
   every width, so reading order and visual order are the same object. */
.path-hero-head { min-width:0; }
/* .path-hero::before was two slowly drifting radial washes behind the path
   headline, animated on a 32s loop. Nothing under it needed lighting. */

.path-name {
  margin:var(--space-3) 0 0;
  color:var(--text); font-weight:var(--weight-bold);
  font-size:var(--text-card-title); letter-spacing:-.01em;
}
.path-hero h1 { margin-top:var(--space-2); }
.path-hero .lead { max-width:var(--measure-hero); }

/* The progress preview: what this path contains, before any of it is read.
   It is a description, not a state — the live rail lives further down and
   starts at zero. */
.path-preview {
  margin:var(--space-5) 0 0;
  display:flex; flex-wrap:wrap; align-items:baseline; gap:4px var(--space-4);
}
.path-preview-count {
  flex:none;
  padding:var(--chip-pad-y) var(--chip-pad-x); border-radius:var(--radius-chip);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
  color:var(--chip-ink); background:var(--chip-bg); border:1px solid var(--chip-line);
}
.path-preview-stages {
  min-width:0; color:var(--ns-text-secondary);
  font-size:var(--text-label); letter-spacing:.05em;
}

/* ---- Screenshot frames --------------------------------------------------
   One recipe for every capture on these pages: a hairline, an inner highlight
   and a single deep shadow. width/height and the inline aspect-ratio reserve
   the box before the bytes arrive, so nothing here can shift the page. */
/* Figure and caption share one centre line. margin-inline:auto on both is what
   makes "directly below" mean directly below, not under the left edge. */
.path-hero-shot, .chapter-shot {
  margin:var(--space-6) auto 0; max-width:var(--measure-media);
}
.path-shot-img {
  display:block; width:100%; height:auto;
  margin-inline:auto;
  aspect-ratio:var(--shot-ratio);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  box-shadow:none;
}
/* The figure is capped at --measure-media and the image fills it, so capping
   the caption at the reading measure keeps it narrower at every width. */
.path-shot-cap {
  margin:var(--space-3) auto 0; max-width:var(--measure-caption);
  text-align:center;
  color:var(--muted); font-size:var(--text-caption); line-height:1.66;
}

/* ---- Begin path ---------------------------------------------------------
   Step six of the image composition: the next action, under the caption, on
   the capture's centre line. The hero has exactly one action — download and
   account moved to the completion panel, where a reader who has been through
   the path is deciding what to do about it. */
.path-begin {
  margin:var(--space-6) 0 0;
  display:flex; justify-content:center;
}
.path-begin-link { gap:var(--space-2); }
.path-begin-link span { transition:transform .2s ease; }
@media (hover:hover){
  .path-begin-link:hover span { transform:translateX(3px); }
}

/* ========================
   THE READING RAIL — how far through THIS PAGE you are.
   ------------------------
   Not app progress, not account progress, not trading progress and not Nova
   Journey progress. It resets on refresh and is stored nowhere: there is no
   cookie, no storage call and no request in start-path-*.js.

   It ships painted at its honest starting state, so a browser with no
   IntersectionObserver — or no JavaScript at all — keeps a correct static rail
   rather than an empty bar.
   ======================== */
.path-scope { position:relative; }

/* A heading the rail sticks over has to land below the RAIL, not below the
   header. --guide-rail is the rail's own reserved height; --space-2 is the gap
   it sticks at. The site-wide scroll-padding-top already accounts for the
   header and STACKS with this, so only the extra is added here — adding the
   header offset again would land every chapter a full header too low. Only
   anchors the rail actually covers pay for it: widening the global offset
   would push #first, #access and #continue down by the height of a rail that
   is nowhere near them. */
.path-scope [id] { scroll-margin-top:calc(var(--space-2) + var(--guide-rail)); }

/* ========================
   THE RAIL: ONE ELEMENT, TWO SHAPES.
   ------------------------
   Below 1081px it is a DOCK — a sticky bar under the header carrying the
   current chapter, the three numbers, the compact map and the next move. At
   1081px and up it is a SIDE RAIL in its own column, with room for the path's
   name and the map read vertically.

   One element, not two: a second copy would put the live region, the group's
   accessible name and every node's state into the document twice, and a
   screen reader cannot know which to believe. The base is the dock.
   ======================== */
.path-rail {
  /* Flush under the header, not floating 8px below it: in that band the
     chapters scrolled through a gap between two sticky surfaces, which reads
     as a rendering fault. The 8px moved into the anchor reservation below, as
     breathing room under the dock for the heading a Continue link lands. */
  position:sticky; top:var(--nav-offset); z-index:40;
  margin:0 0 var(--space-6);
  display:flex; align-items:center; flex-wrap:wrap;
  gap:var(--space-2) var(--space-4);
  padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-sm);
  border:1px solid var(--ns-border-subtle);
  background:var(--ns-bg-deep);
  
  transition:border-color .4s ease;
}
.path-rail[data-complete="true"] { border-color:var(--ns-border-control); }

/* Row one of the dock: which stage, then the three numbers. Two facts, not
   one — "Chapter 3 of 4" is a coordinate, "Gate Inspector" is a place. */
.path-rail-head {
  flex:1 1 100%; min-width:0;
  display:flex; flex-wrap:wrap; align-items:baseline;
  gap:2px var(--space-3);
}
/* The path's name is established by the <h1> two screens up; in a dock this
   narrow, repeating it costs a row. The side rail has a column and prints it. */
.path-rail-name { display:none; margin:0; }
.path-rail-now {
  margin:0; flex:none; min-width:0;
  color:var(--text); font-weight:var(--weight-bold);
  font-size:var(--text-control); letter-spacing:-.01em;
}
.path-rail-count {
  margin:0; flex:none; color:var(--muted);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
}
.path-rail[data-complete="true"] .path-rail-count { color:var(--text); }

/* Row two: the map, then the move. The map takes the slack and the action
   keeps its own width, so at 320px they still share one row. 44px tall
   because it is a real target — the height the dock is built around. */
.path-rail-next {
  flex:none;
  display:inline-flex; align-items:center; gap:6px;
  min-height:44px; padding:0 var(--space-3);
  border-radius:var(--radius-sm);
  color:var(--text); text-decoration:none;
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:.01em;
  border:1px solid var(--ns-border-strong);
  background:var(--ns-surface-raised);
  transition:border-color .2s ease, background .2s ease;
}
.path-rail-next span { transition:transform .2s ease; }
@media (hover:hover){
  .path-rail-next:hover {
    border-color:var(--ns-border-control);
    background:var(--ns-surface-raised);
  }
  .path-rail-next:hover span { transform:translateX(3px); }
}
.path-rail-next:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* Change path lives at the foot of the page in every layout; the side rail
   repeats it so a reader three chapters into the wrong path can leave without
   scrolling past the rest of it. */
.path-rail-change { display:none; margin:0; }
.path-rail-change a {
  color:var(--muted); font-size:var(--text-label);
  text-decoration:underline;
  text-decoration-color:var(--ns-border-strong);
  text-underline-offset:3px;
  border-radius:var(--radius-sm);
}
.path-rail-change a:hover { color:var(--text); text-decoration-color:var(--ns-border-control); }
.path-rail-change a:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* The map is the brief's own diagram: node, connector, node. The connector
   after a node that has been read fills from the left, which is the progress
   line filling BETWEEN nodes rather than a bar floating above them. */
.path-map {
  position:relative;
  list-style:none; margin:0; padding:0 0 10px;
  display:flex; flex:1 1 96px; min-width:0; align-items:center;
  gap:var(--space-2);
}
/* Colour ownership in the rail: what is DONE is steel, what is CURRENT is
   sapphire, what is AHEAD is the subtle border. One accent, on one step —
   the eye lands on where you are, not on how far you have come. */
.path-map::before {
  content:""; position:absolute; left:0; bottom:0;
  height:2px; width:var(--path-fill, 0%); border-radius:2px;
  background:var(--ns-steel);
  transition:width .55s cubic-bezier(.4,0,.2,1);
}
.path-node {
  position:relative;
  flex:1 1 0; min-width:0;
  display:flex; align-items:center; gap:6px;
  color:var(--ns-text-muted);
  font-size:var(--text-label); letter-spacing:.01em;
}
/* The connector. background-size is the animated property, so the fill grows
   from the left edge rather than fading in. The fill layer is a two-identical-
   stop linear-gradient: that is CSS's only way to hand background-size a flat
   colour, and it paints no ramp — the track colour underneath it is the
   background-color, which background-size cannot reach. */
.path-node::after {
  content:""; flex:1 1 auto; min-width:10px; height:2px; margin-left:4px;
  border-radius:2px;
  background-color:var(--ns-border-subtle);
  background-image:linear-gradient(var(--ns-steel), var(--ns-steel));
  background-repeat:no-repeat;
  background-size:0% 100%;
  transition:background-size .5s cubic-bezier(.4,0,.2,1);
}
.path-node:last-child::after { display:none; }
.path-node[data-state="viewed"]::after { background-size:100% 100%; }
.path-node-dot {
  flex:none; width:1.1ch; text-align:center; line-height:1;
  color:var(--ns-steel);
}
.path-node-name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The state word is the same fact the glyph carries. Printed seven times over
   it would triple the rail's height, so it goes to the accessible layer and
   the glyph — a real character, not a colour — carries it on screen. */
.path-node-state {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.path-node[data-state="current"] { color:var(--text); font-weight:var(--weight-strong); }
.path-node[data-state="current"] .path-node-dot { color:var(--ns-sapphire-text); }
.path-node[data-state="viewed"] { color:var(--text); }
.path-node[data-state="viewed"] .path-node-dot { color:var(--ns-steel); }

/* In the DOCK the map is dots only: the names drop to the accessible layer,
   where a screen reader still reads every one. Seven names fit no width the
   dock occupies, and the browser's answer is truncation — worse than a dot,
   because a dot is honestly a marker while half a word is a name that failed.
   The current stage is named in full one row above, every chapter names its
   own, and the side rail prints all seven. */
@media (max-width:1080px){
  .path-node-name {
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
  }
}
/* Three rows at every width from 360px up, which is why --guide-rail is ONE
   number for the dock band. Below 356px (measured) the stage name and the
   numbers stop sharing a line and it gains a fourth. Reserving the taller
   value from 359px down over-reserves by a line between 356 and 359 — the
   safe direction, since an anchor landing low is a cosmetic 17px and one
   landing high is a heading behind the dock. 359 rather than 355 so the
   boundary sits next to a device width, not a font metric. */
@media (max-width:359px){
  :root { --guide-rail:106px; }
}

/* ---- The side rail ------------------------------------------------------
   1081px is where a 248px column plus --pair-gap comes out of the reading
   column and still leaves the chapters wider than --measure-media. Not a
   device width — the width at which this stops costing line length.

   The rail sticks inside its own grid area, so it travels the chapters' height
   and stops with them, beside the text rather than over it. Hence the anchor
   reservation drops to ZERO: a rail in its own column covers nothing, and
   still paying --guide-rail would land every heading a rail's height low. */
@media (min-width:1081px){
  .path-scope {
    display:grid;
    grid-template-columns:minmax(0, 248px) minmax(0, 1fr);
    column-gap:var(--pair-gap);
    align-items:start;
  }
  .path-scope [id] { scroll-margin-top:0; }

  .path-rail {
    margin:0;
    align-self:start;
    top:calc(var(--nav-offset) + var(--space-4));
    flex-direction:column; flex-wrap:nowrap; align-items:stretch;
    gap:var(--space-4);
    padding:var(--space-5) var(--space-4);
  }
  .path-rail-head { flex:none; display:block; }
  /* With a column of its own the rail can say which path this is, which is the
     one thing the dock has to leave to the hero. */
  .path-rail-name {
    display:block;
    color:var(--muted);
    font-size:var(--text-label); font-weight:var(--weight-strong);
    letter-spacing:var(--chip-tracking); text-transform:uppercase;
  }
  .path-rail-now {
    display:block; margin:6px 0 4px;
    /* One step above the dock's --text-control, not two: the rail names the
       chapter, it does not compete with the chapter headings beside it. */
    font-size:var(--text-lead); line-height:var(--leading-snug);
  }

  /* The map read downwards. The connector stops being a flex sibling and
     becomes a line drawn down the dot column — the same fact, rotated — so
     background-size swaps its axes to grow top-down. */
  .path-map {
    flex:none;
    flex-direction:column; align-items:stretch;
    gap:0; padding:0;
  }
  .path-map::before { display:none; }
  .path-node {
    flex:none;
    align-items:flex-start;
    gap:var(--space-3);
    padding-bottom:var(--space-4);
  }
  .path-node:last-child { padding-bottom:0; }
  /* Node to node, edge to edge: the line runs from just under one dot to the
     top of the next, so the map reads as one connected path rather than a
     column of dots with stubs between them. */
  .path-node::after {
    position:absolute;
    left:calc(.55ch - 1px); top:calc(.9em + 3px); bottom:-1px;
    flex:none; width:2px; min-width:0; height:auto; margin:0;
    background-image:linear-gradient(var(--ns-steel), var(--ns-steel));
    background-size:100% 0%;
    transition:background-size .5s cubic-bezier(.4,0,.2,1);
  }
  .path-node[data-state="viewed"]::after { background-size:100% 100%; }
  /* 248px is enough for any stage name the generator can print, so nothing
     needs truncating and the ellipsis rule comes off. */
  .path-node-name { white-space:normal; overflow:visible; text-overflow:clip; }

  .path-rail-next { align-self:flex-start; }
  .path-rail-change { display:block; }
}

/* ---- Chapters -----------------------------------------------------------
   ONE COLUMN, EVERY WIDTH. Source order IS the rendered order: chapter number,
   stage, outcome headline, explanation, what to look for, capture, caption,
   takeaway, Continue. As a two-column grid above 961px this put a screenshot's
   explanation BESIDE it, leaving the caption on a different rail from what it
   labels, and a short chapter left a tall empty media column. One column costs
   a scroll and buys one centre line for heading, copy and caption. */
.chapter-list {
  display:flex; flex-direction:column; gap:var(--block-gap);
}
.chapter {
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  background:var(--ns-bg-section);
  transition:border-color .3s ease, background .3s ease, opacity .5s ease;
}
/* The rise itself. On the article this would move the anchor target; on its
   children it is purely visual. */
.chapter > * { transition:transform .5s ease; }
.chapter-head { min-width:0; }
.chapter-eyebrow {
  margin:0; display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-3);
}
.chapter-count {
  padding:var(--chip-pad-y) var(--chip-pad-x); border-radius:var(--radius-chip);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
  color:var(--chip-ink); background:var(--chip-bg); border:1px solid var(--chip-line);
}
.chapter-stage {
  color:var(--ns-text-secondary);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
}
.chapter-title {
  margin:var(--space-3) 0 0; max-width:var(--measure-text);
  color:var(--text); font-weight:var(--weight-bold);
  /* --text-subsection is the scale's "H3 in prose flow" slot. This read
     --text-sub, which is not a token: the declaration was dropped and the
     chapter's outcome headline rendered at 16px, the same size as the
     paragraph under it, with only weight to separate them. */
  font-size:var(--text-subsection); letter-spacing:-.02em; line-height:var(--leading-snug);
}
.chapter-copy { min-width:0; margin-top:var(--space-4); }
.chapter-lead {
  margin:0; max-width:var(--measure-text);
  color:var(--muted); line-height:var(--leading-loose);
}
.chapter-look {
  margin:var(--space-4) 0 0; max-width:var(--measure-text);
  padding-left:var(--space-4);
  border-left:2px solid var(--ns-border-strong);
}
.chapter-look-label {
  margin:0; color:var(--muted);
  font-size:var(--text-label); font-weight:var(--weight-strong);
  letter-spacing:var(--chip-tracking); text-transform:uppercase;
}
.chapter-look-copy {
  margin:6px 0 0; color:var(--ns-text-secondary);
  font-size:var(--text-supporting); line-height:1.7;
}
/* The takeaway and Continue follow the capture, so they share its centre line:
   text that explains, captions or FOLLOWS an image sits on the image's rail.
   The copy above stays left-aligned — it is a paragraph. Capping the takeaway
   at the caption measure stops a centred sentence running wider than the
   caption above it and breaking the column the two of them make. */
.chapter-take {
  margin:var(--space-5) auto 0; max-width:var(--measure-caption);
  text-align:center;
  color:var(--text); font-weight:var(--weight-strong);
  font-size:var(--text-supporting); line-height:1.65;
}
.chapter-move { margin:var(--space-5) 0 0; display:flex; justify-content:center; }
.chapter-next {
  display:inline-flex; align-items:center; gap:var(--space-2);
  min-height:44px; padding:0 var(--space-4);
  border-radius:var(--radius-sm);
  color:var(--text); text-decoration:none;
  font-size:var(--text-control); font-weight:var(--weight-strong);
  border:1px solid var(--ns-border-strong);
  background:var(--ns-surface-raised);
  transition:border-color .2s ease, background .2s ease;
}
.chapter-next span { transition:transform .2s ease; }
@media (hover:hover){
  .chapter-next:hover { border-color:var(--ns-border-control); background:var(--ns-surface-raised); }
  .chapter-next:hover span { transform:translateX(3px); }
}
.chapter-next:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* Chapter states. The current chapter gains a left rule and a brighter edge;
   a chapter already read settles back without losing an ounce of contrast —
   nothing here dims text you have read. */
.chapter.is-current {
  border-color:var(--ns-border-control);
  box-shadow:inset 3px 0 0 0 var(--cyan);
  background:var(--ns-surface);
}
.chapter.is-read { border-color:var(--ns-border-subtle); }

/* The reveal. Opted in by start-path-*.js only once the observer that drives
   it is known to exist — a reveal that can strand content is not a reveal, it
   is a bug waiting for a browser without the API.

   The rise moves the chapter's CONTENTS, never the chapter itself. A transform
   on the article shifts its border box, and the browser reads that box when it
   works out where a "Continue to Inspect" link should land: with every chapter
   below the fold offset by 12px, the scroll was computed against positions
   that stopped being true the moment the reveal ran, and the next chapter's
   heading came to rest ~28px high — behind the sticky rail. Moving the
   children keeps the same motion and leaves the anchor geometry fixed. */
.chapter-list[data-reveal="on"] .chapter { opacity:.72; }
.chapter-list[data-reveal="on"] .chapter > * { transform:translateY(12px); }
.chapter-list[data-reveal="on"] .chapter.is-inview { opacity:1; }
.chapter-list[data-reveal="on"] .chapter.is-inview > * { transform:none; }

/* ---- Do this first ------------------------------------------------------ */
.first-move {
  margin:0; max-width:var(--measure-text);
  padding:var(--card-pad);
  border-radius:var(--radius-md);
  border:1px solid var(--ns-border-subtle);
  border-left:3px solid var(--cyan);
  background:var(--ns-surface);
  color:var(--text); font-size:var(--text-lead); line-height:1.6;
}

/* ---- The completion panel ----------------------------------------------- */
/* The panel is the page's closing action in both states. Its padding and its
   border box exist before completion, so the frame arriving is pure paint: it
   cannot move a single pixel of the page. */
.path-close {
  position:relative; overflow:hidden;
  padding:var(--space-5);
  border:1px solid transparent;
  border-radius:var(--radius-md);
  transition:border-color .4s ease, background .4s ease;
}
.path-close.is-complete {
  border-color:var(--ns-border-control);
  background:var(--ns-surface);
}
/* A translucent light used to sweep across the completion panel once. Without
   its gradient it is a solid sapphire pane sliding over the text, and even
   restored it is a reward animation the finished-state copy already gives.
   Completion now reads through .path-close.is-complete's border and status
   line — see the .is-complete rule above. */
.path-close-status {
  margin:0 0 var(--space-4);
  color:var(--text); font-weight:var(--weight-strong);
  font-size:var(--text-lead); letter-spacing:-.01em;
}
.path-close-status[hidden] { display:none; }
.path-close-copy {
  margin:0; max-width:var(--measure-text);
  color:var(--muted); line-height:var(--leading-loose);
}

/* ---- Change starting path ----------------------------------------------- */
/* A link, not the five-card chooser again: on a path page the chooser is the
   thing you already used. */
.path-change {
  margin:var(--space-6) 0 var(--section-y);
  color:var(--muted); font-size:var(--text-supporting);
}
.path-change a {
  color:var(--text);
  text-decoration:underline;
  text-decoration-color:var(--ns-border-strong);
  text-underline-offset:3px;
  border-radius:var(--radius-sm);
}
.path-change a:hover { text-decoration-color:var(--ns-border-control); }
.path-change a:focus-visible { outline:3px solid var(--ns-sapphire); outline-offset:3px; }

/* ---- Micro-interactions -------------------------------------------------
   Hover is a hint, not an event. Every lift here is 2px and every transition
   is under a quarter-second; a card that jumps is a card that reads as a
   button on a games console. */
@media (hover:hover){
  .goal-card { transition:border-color .2s ease, background .2s ease, transform .2s ease, box-shadow .2s ease; }
  .goal-card:hover {
    transform:translateY(-2px);
    box-shadow:none;
  }
  .goal-card-arrow { transition:transform .2s ease; }
  .goal-card:hover .goal-card-arrow { transform:translateX(3px); }
  .access-strip { transition:border-color .25s ease; }
  .access-strip:hover { border-color:var(--ns-border-strong); }
}
/* The entry CTA used to sweep a white sheen across itself on hover. A metallic
   highlight is exactly the treatment this pass removes, and the button already
   answers a hover with its own fill step. Removed. */

/* ========================
   REDUCED MOTION
   ------------------------
   Everything above that MOVES is decoration on top of a state that is already
   readable without it. Under a reduced-motion preference the states remain and
   the movement does not: the progress line is drawn at its final width rather
   than growing into it, chapters are simply present rather than arriving, and
   the completion sweep and the ambient drift do not run at all.
   ======================== */
@media (prefers-reduced-motion: reduce){
  .goal-card,
  .chapter,
  .chapter-next,
  .chapter-next span,
  .goal-card-arrow,
  .access-strip,
  .path-rail,
  .path-rail-next,
  .path-rail-next span,
  .path-begin-link span,
  .path-close { transition:none; }
  .path-map::before,
  .path-node::after { transition:none; }
  .chapter > * { transition:none; }
  .chapter-list[data-reveal="on"] .chapter { opacity:1; }
  .chapter-list[data-reveal="on"] .chapter > * { transform:none; }
  @media (hover:hover){
    .goal-card:hover { transform:none; }
    .chapter-next:hover span { transform:none; }
    .path-rail-next:hover span { transform:none; }
    .path-begin-link:hover span { transform:none; }
    .goal-card:hover .goal-card-arrow { transform:none; }
  }
}
