/* ===== RESET & BASE ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* PHASE — palette.
   Neutral. Near-white and cool silver on a near-black; no chromatic brand accent at all.

   This replaced the acid lime (#D4FF00) that replaced the original violet. The lime existed to
   avoid looking like every other tool in this space, which it did — but a saturated accent
   fights the glass: translucent panels sample whatever is behind them, and a strong hue behind
   them means every surface picks up a cast and the page reads as tinted rather than as glass.
   Apple's own material is neutral for exactly this reason and takes its colour from content.

   The desktop app followed on 2026-09-06 and shipped as 1.5.0: StylePalette's Mono style carries
   these same values and is the default, so site and .exe are back in step. The hero .app-mockup
   is drawn from that build.

   Everything downstream composes from the three channel triples below, so the whole site
   re-colours from here. */
:root {
  --bg:       #08090C;
  --bg2:      #0F1116;
  --bg3:      #161920;

  /* The accent as bare channels. Everything downstream — this file, account.html and the
     particle backdrop in background.js — composes its own alpha off these three lines rather
     than writing the numbers out, so re-colouring the site is a three-line edit instead of the
     66 hardcoded rgba() it used to be.

     Channels rather than hex because most uses here are translucent, and there is no way to
     take an alpha off a hex custom property. rgba(var(--accent-rgb), 0.2) expands to a plain
     rgba() before the parser sees it, so it is as ordinary as what it replaced. */
  --accent-rgb:  238, 243, 250;
  --accent2-rgb: 172, 185, 202;
  --accent3-rgb: 255, 255, 255;

  --accent:   rgb(var(--accent-rgb));
  --accent2:  rgb(var(--accent2-rgb));
  --accent3:  rgb(var(--accent3-rgb));

  --text:     #F5F7FA;
  --muted:    #7C8593;
  --border:   rgba(var(--accent-rgb), 0.16);
  --card:     #11141A;
  --glow:     0 0 32px rgba(var(--accent-rgb), 0.28);
  --glow-sm:  0 0 16px rgba(var(--accent-rgb), 0.35);
  --font:     'Inter', 'Segoe UI', sans-serif;

  /* ── Liquid glass ──
     A glass panel is four things stacked, and it stops reading as glass if any one is dropped:
       1. a near-transparent tint, so it has a body
       2. backdrop-filter blur + saturate — the saturate is what stops the blurred backdrop
          going grey, and it is the single biggest difference between "glass" and "grey box"
       3. a specular edge: a hairline that is bright along the top and fades by the bottom,
          which is how a real bevel catches light
       4. a soft ambient shadow, so it sits above the page rather than in it

     Three weights. Thin for controls, regular for cards, thick for the nav and anything that
     has to stay legible over arbitrary content scrolling under it. */
  --glass-blur-thin:    14px;
  --glass-blur:         26px;
  --glass-blur-thick:   40px;
  --glass-sat:          180%;

  --glass-tint-thin:    rgba(255, 255, 255, 0.04);
  --glass-tint:         rgba(255, 255, 255, 0.06);
  --glass-tint-thick:   rgba(255, 255, 255, 0.075);

  /* Edge light. Top-left catches, bottom-right falls off — the direction is arbitrary but it
     must be consistent across every panel or the page reads as lit from several suns. */
  --glass-edge:         rgba(255, 255, 255, 0.14);
  --glass-edge-dim:     rgba(255, 255, 255, 0.05);

  /* The centre backdrop field. Cooler and bluer than --accent so the three fields are not all
     the same value — glass wants variation in what is behind it, and three fields in one hue
     compound into a flat wash rather than reading as light. (That was true of the lime palette
     too, where it went olive; the failure mode survives the neutral swap.)
     This is the only chromatic value left on the site, and it is deliberately too dim and too
     diffuse to read as a colour — it exists to give the panels something to refract. */
  --light-rgb:  186, 212, 255;

  --glass-shadow:       0 8px 32px rgba(0, 0, 0, 0.38);
  --glass-shadow-lg:    0 24px 64px rgba(0, 0, 0, 0.46);

  /* Apple's corners are continuous curvature, not circular arcs. CSS has no squircle, but the
     gap narrows a lot at larger radii — hence values well above the 16px this replaces. */
  --radius:      22px;
  --radius-lg:   28px;
  --radius-sm:   14px;
  --radius-pill: 999px;

  /* ── Motion ──
     Two curves only. --ease-glass is the workhorse: a heavy ease-out that starts fast and
     settles long, which is what makes a panel feel like it has mass. --ease-spring overshoots
     slightly and is for things that appear or toggle, never for anything that moves a long
     distance (the overshoot reads as a wobble at scale). */
  --ease-glass:  cubic-bezier(0.32, 0.72, 0, 1);
  --ease-spring: cubic-bezier(0.34, 1.42, 0.64, 1);
  --dur-fast:    0.22s;
  --dur:         0.4s;
  --dur-slow:    0.7s;
  --font-mono:  'JetBrains Mono', 'Consolas', ui-monospace, monospace;
}

html { scroll-behavior: smooth; }

/* ── Background: the lit ground the glass sits on ──
   Fixed layers behind everything:
     -3  .space            flat backdrop, clips the light fields
     -3  .neb.a/.b/.c      three drifting light fields — near-white, silver, and cool blue
     -3  canvas.space-net  the drifting particle network (background.js)
     -1  .noise            film grain over the lot

   A negative stacking index paints behind every in-flow descendant of the root whether or
   not that descendant is itself positioned, which a 0/auto index would not guarantee here
   (some sections below are position:relative, some are plain static blocks).

   This layer changed role in the glass redesign. It used to be pure decoration — two faint
   circles pushed almost entirely off the edge, there to suggest depth. Now every panel on the
   page is translucent and samples what is behind it, so this is the only thing keeping those
   panels from rendering as grey rectangles. Hence three fields rather than two, one of them
   parked mid-viewport where the cards actually are, and alphas roughly 2.5× the old ones.

   Two things learned tuning it, both easy to get wrong again:
     - Three fields in the same hue compound into a flat wash (olive, back when the palette was
       lime). They are deliberately three different values now; glass wants variation behind it.
     - The intensity has to satisfy both paths. Under backdrop-filter the panels' saturate(180%)
       amplifies these considerably, so what looks right through glass looks like nothing at all
       on the opaque reduced-transparency fallback. These values are the compromise. */
.space {
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  overflow: hidden;
  background: var(--bg);
}

/* The blur is not decoration, it is what hides the banding. A wide, low-alpha gradient over a
   near-black background steps through very few distinct colours, and each step is broad enough
   to read as a concentric ring. Blurring the element resamples across those steps and the rings
   go.

   These used to be static and nearly invisible (0.09 alpha, parked in two corners). Glass has
   nothing to refract over a flat black page — a panel over dead ground renders as a grey
   rectangle no matter how the material is built. So they are now brighter, larger, and drifting:
   the backdrop has to *carry colour and change* for the blur behind a panel to be worth looking
   at. This is the layer that makes the rest of the page read as glass. */
.neb {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(110px);
  will-change: transform;
  /* mix-blend-mode:screen so overlapping fields add light instead of the topmost one simply
     covering the one beneath. Two fields crossing should be brighter than either alone — that
     brightening is most of what makes the backdrop look lit rather than painted. */
  mix-blend-mode: screen;
}

/* Three fields, not two, and none of them parked fully off-screen: a panel in the middle of the
   page needs something under it too, which the old opposite-corner placement never gave it.

   The drift is slow enough (40-60s) that it never reads as animation — what a visitor notices is
   that the glass looks slightly different on the way back up the page. Transform-only, so it
   stays on the compositor and costs nothing per frame. */
.neb.a {
  width: 820px; height: 820px;
  top: -300px; left: -200px;
  background: radial-gradient(circle,
    rgba(var(--accent-rgb), 0.24) 0%,
    rgba(var(--accent-rgb), 0.08) 40%,
    transparent 70%);
  animation: drift-a 52s var(--ease-glass) infinite alternate;
}

.neb.b {
  width: 780px; height: 780px;
  bottom: -400px; right: -260px;
  background: radial-gradient(circle,
    rgba(var(--accent2-rgb), 0.27) 0%,
    rgba(var(--accent2-rgb), 0.09) 40%,
    transparent 70%);
  animation: drift-b 61s var(--ease-glass) infinite alternate;
}

/* The middle field. Deliberately the dimmest of the three and the slowest — its whole job is to
   stop the centre of a long page going flat, not to be seen. */
.neb.c {
  width: 720px; height: 720px;
  top: 38%; left: 45%;
  background: radial-gradient(circle,
    rgba(var(--light-rgb), 0.14) 0%,
    rgba(var(--light-rgb), 0.05) 42%,
    transparent 72%);
  animation: drift-c 74s var(--ease-glass) infinite alternate;
}

@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(140px, 90px, 0) scale(1.16); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-160px, -70px, 0) scale(1); }
}
@keyframes drift-c {
  from { transform: translate3d(-50%, -50%, 0) scale(0.9); }
  to   { transform: translate3d(-38%, -62%, 0) scale(1.2); }
}

/* Sized in CSS pixels; background.js sets the backing store to match devicePixelRatio. */
.space-net {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Film grain. Its job here is dithering, not texture: it breaks up the banding in the glows
   below by scattering the boundary between one gradient step and the next. Anything strong
   enough to notice as grain is already too strong for that, which is what the white
   alpha-from-noise version this replaces kept getting wrong - white specks over a lit area
   read as dots no matter how far the opacity came down.

   Low-opacity colour noise instead, which averages out to nothing and only does the dithering.
   These are the values on the reference site, unchanged. */
.noise {
  position: fixed;
  inset: 0;
  z-index: -1;
  opacity: 0.04;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  /* `clip` rather than `hidden`, with hidden left as the fallback for older engines.
     overflow-x:hidden makes the element a scroll container, which is why it did not actually
     stop the sideways pan the footer was causing — the viewport still scrolled. `clip` clips
     without creating one, so nothing can pan even if something overflows again later.
     This is a backstop, not the fix: an element wide enough to trigger it is still a bug. */
  overflow-x: hidden;
  overflow-x: clip;
}

.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 24px;
}

/* ===== LIQUID GLASS =====
   One material, applied by adding .glass (plus an optional weight) to a container. Components
   below opt in rather than each rebuilding the stack, so the specular direction, the corner
   radius and the blur weight stay identical everywhere — inconsistency in any of those is what
   makes a glass UI look cheap.

   The two pseudo-elements are load-bearing and do different jobs:
     ::before  the sheen — a broad highlight across the top third, the light *on* the surface
     ::after   the bevel — a 1px gradient hairline, the light caught *by* the edge
   Both are pointer-events:none and inherit the radius, so they never interfere with content or
   round differently from their host.

   Every component that uses this had its ::before/::after checked as free first. */
.glass {
  position: relative;
  background: var(--glass-tint);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  border-radius: var(--radius);
  box-shadow: var(--glass-shadow);
  /* isolate, so a child with its own stacking context cannot land above the sheen. */
  isolation: isolate;
}

.glass > * { position: relative; z-index: 1; }

/* Sheen. Steep falloff — carried much past 40% it stops looking like a highlight and starts
   looking like the panel is simply lighter at the top. */
.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    177deg,
    rgba(255, 255, 255, 0.10) 0%,
    rgba(255, 255, 255, 0.02) 32%,
    rgba(255, 255, 255, 0) 62%
  );
  pointer-events: none;
  z-index: 0;
}

/* Bevel. A gradient border, which CSS has no property for — so it is a filled box masked down
   to its own 1px padding ring. The two masks composite to "border-box minus content-box", i.e.
   exactly the ring. -webkit- prefixes kept: mask-composite is still prefixed in Safari, which
   given the design language is the one browser that must not fall back here. */
.glass::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    145deg,
    var(--glass-edge) 0%,
    var(--glass-edge-dim) 40%,
    rgba(255, 255, 255, 0.02) 62%,
    var(--glass-edge-dim) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
}

/* Weights. */
.glass-thin {
  backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  background: var(--glass-tint-thin);
}

.glass-thick {
  backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
  background: var(--glass-tint-thick);
  box-shadow: var(--glass-shadow-lg);
}

/* Accent-tinted glass, for the one panel per screen that should pull the eye. The tint rides
   on top of the white so the panel still reads as the same material, only lit differently. */
.glass-accent {
  background:
    linear-gradient(150deg, rgba(var(--accent-rgb), 0.11), rgba(var(--accent-rgb), 0.02) 55%),
    var(--glass-tint);
}
.glass-accent::after {
  background: linear-gradient(
    145deg,
    rgba(var(--accent-rgb), 0.55) 0%,
    rgba(var(--accent-rgb), 0.14) 42%,
    rgba(255, 255, 255, 0.03) 66%,
    rgba(var(--accent-rgb), 0.10) 100%
  );
}

/* Lift on hover. Panels rise toward the light, so the sheen and the bevel both strengthen with
   the shadow — moving the shadow alone reads as the panel sliding rather than lifting. */
.glass-hover {
  transition:
    transform var(--dur) var(--ease-glass),
    box-shadow var(--dur) var(--ease-glass),
    background var(--dur) var(--ease-glass);
}
.glass-hover:hover {
  transform: translateY(-6px);
  box-shadow: var(--glass-shadow-lg);
  background: var(--glass-tint-thick);
}
.glass-hover::before,
.glass-hover::after { transition: opacity var(--dur) var(--ease-glass); }
.glass-hover:hover::before { opacity: 1.0; }

/* ── Fallbacks ──
   No backdrop-filter (older Firefox with the flag off, some embedded webviews): the tint alone
   over a dark page is close to invisible and the panel loses its boundary entirely. Fall back to
   an opaque card, which is what the site looked like before this redesign and is a perfectly
   respectable floor. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass, .glass-thin, .glass-thick {
    background: rgba(17, 20, 26, 0.92);
    border: 1px solid var(--border);
  }
  .glass-accent {
    background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.08), rgba(17, 20, 26, 0.94) 60%);
  }
}

/* Reduced transparency is an accessibility setting, not a preference about taste: people who
   turn it on find text over busy translucent backgrounds genuinely hard to read. Honour it by
   making the panels opaque and dropping the blur — layout untouched, only the material changes.

   Note for anyone testing this: Windows 11 with Settings ▸ Personalisation ▸ Colours ▸
   "Transparency effects" switched off reports this query, and so does macOS with Reduce
   Transparency on. On those machines the whole glass treatment correctly renders as flat cards,
   which is easy to mistake for the CSS being broken. It is not — check the media query first.

   The background light fields deliberately stay. The setting is about text sitting on
   translucent surfaces; these sit behind everything and never have content over them, and
   removing them left the page looking unfinished rather than more legible. Their drift is
   already stopped by the reduced-motion block above, which is the query that governs it. */
@media (prefers-reduced-transparency: reduce) {
  .glass, .glass-thin, .glass-thick, .glass-accent {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: #12151C;
    border: 1px solid var(--border);
  }
  .glass-accent {
    background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.09), #12151C 62%);
    border-color: rgba(var(--accent-rgb), 0.4);
  }
  /* The sheen is a highlight on a translucent surface; on an opaque card it is just a lighter
     stripe across the top. The bevel stays — it is what keeps the card edges defined. */
  .glass::before { display: none; }

  .btn-ghost, .btn-outline {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: #161920;
  }
  .hero-badge, .nav.is-stuck .nav-inner, .buybar {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .nav.is-stuck .nav-inner, .buybar { background: #12151C; }
}

/* ===== TYPOGRAPHY ===== */
.section-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}

.section-title {
  font-size: clamp(28px, 4vw, 42px);
  font-weight: 800;
  line-height: 1.15;
  color: var(--text);
  margin-bottom: 16px;
}

/* Second line of the hero title: brushed metal - dark steel, a white highlight across the middle,
   dark steel again - with a soft glow behind it (2026-09-23). The flat accent it replaces read as
   a second paragraph colour rather than as the loudest words on the page. Inline-block so the
   clipped gradient spans this line only, and a little bottom padding so descenders are not cut. */
.hero-title-accent {
  display: inline-block;
  padding-bottom: 0.06em;
  background: linear-gradient(135deg, #8594A8 0%, #FFFFFF 46%, #E4EBF4 56%, #71809A 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 30px rgba(var(--accent-rgb), 0.34));
}

/* ===== BUTTONS =====
   Pills, because a capsule is the shape this design language uses for anything you press.

   Only the secondary buttons are glass. The primary CTA stays a solid accent fill: it is the
   one element on the page that must never lose contrast against whatever happens to scroll
   behind it, and a translucent "Buy Now" over a bright nebula is exactly the failure mode.
   It gets the specular treatment on top of the fill instead, so it still belongs to the set. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 13px 30px;
  border-radius: var(--radius-pill);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.2px;
  text-decoration: none;
  cursor: pointer;
  border: none;
  white-space: nowrap;
  isolation: isolate;
  transition:
    transform var(--dur-fast) var(--ease-spring),
    box-shadow var(--dur-fast) var(--ease-glass),
    background var(--dur-fast) var(--ease-glass),
    color var(--dur-fast) var(--ease-glass);
}

/* Shared sheen across every variant, so a glass button and a filled one are lit alike. */
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(178deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0) 52%);
  pointer-events: none;
  /* -1, not 0. Every button's label is a bare text node, so the `.btn > *` rule below cannot
     lift it — at z-index 0 the sheen painted a 22% white wash straight over the label, which
     cost real contrast on .btn-primary where dark text sits on a near-white fill.
     A negative index inside this element's own stacking context (isolation:isolate above)
     paints after the background and before the inline content, which is exactly right. */
  z-index: -1;
  transition: opacity var(--dur-fast) var(--ease-glass);
}
/* Still needed for variants whose content is wrapped in elements rather than bare text. */
.btn > * { position: relative; z-index: 1; }

/* Press. Scale rather than a downward translate — the button is a physical object being pushed
   into the surface, and it should compress toward its own centre, not slide. */
.btn:active { transform: scale(0.965); transition-duration: 0.09s; }

.btn-primary {
  background: linear-gradient(160deg, var(--accent3) 0%, var(--accent) 42%, var(--accent2) 100%);
  color: #0A0B0D;
  box-shadow:
    0 4px 20px rgba(var(--accent-rgb), 0.32),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.4);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow:
    0 10px 34px rgba(var(--accent-rgb), 0.5),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.5);
}

/* True glass. The bevel is a real gradient ring here (same technique as .glass::after), which
   is why these carry no `border` — a flat 1px border would read as a sticker on top of it. */
.btn-ghost,
.btn-outline {
  background: var(--glass-tint);
  backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  box-shadow:
    var(--glass-shadow),
    inset 0 0 0 1px var(--glass-edge-dim),
    inset 0 1px 0 0 var(--glass-edge);
}

.btn-ghost { color: var(--accent3); }
.btn-ghost:hover {
  transform: translateY(-2px);
  background: rgba(var(--accent-rgb), 0.14);
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.4),
    inset 0 0 0 1px rgba(var(--accent-rgb), 0.32),
    inset 0 1px 0 0 rgba(var(--accent-rgb), 0.5);
}

.btn-outline { color: var(--text); }
.btn-outline:hover {
  transform: translateY(-2px);
  color: var(--accent3);
  background: var(--glass-tint-thick);
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.4),
    inset 0 0 0 1px rgba(var(--accent-rgb), 0.28),
    inset 0 1px 0 0 var(--glass-edge);
}

.btn-nav {
  background: linear-gradient(160deg, var(--accent3) 0%, var(--accent) 42%, var(--accent2) 100%);
  color: #0A0B0D;
  padding: 10px 24px;
  font-size: 14px;
  box-shadow:
    0 2px 14px rgba(var(--accent-rgb), 0.32),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.4);
}
.btn-nav:hover {
  transform: translateY(-2px);
  box-shadow:
    0 8px 26px rgba(var(--accent-rgb), 0.5),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.5);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .btn-ghost, .btn-outline { background: rgba(22, 25, 32, 0.9); }
}

/* ===== NAV =====
   A floating capsule rather than a full-width bar welded to the top edge. The point of the
   material is that you can see the page moving underneath it, and a bar that spans edge to edge
   with a border on the bottom hides that — it reads as chrome. Detached, with page visible down
   both sides, it reads as an object resting above the content.

   .nav is a transparent, pointer-events:none gutter so the capsule can be centred and inset
   without the full-width strip swallowing clicks either side of it. */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  padding: 16px 24px;
  pointer-events: none;
  transition: padding var(--dur) var(--ease-glass);
}

.nav-inner {
  pointer-events: auto;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 12px 0 26px;
  height: 62px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: var(--radius-pill);
  transition:
    height var(--dur) var(--ease-glass),
    background var(--dur) var(--ease-glass),
    box-shadow var(--dur) var(--ease-glass);
}

/* Scrolled state, set by script.js. The capsule tightens and the glass thickens — over the hero
   there is little behind it and it can afford to be light, but once real content is passing
   underneath it has to hold legibility, which is what the heavier blur buys. */
.nav.is-stuck { padding: 10px 24px; }
.nav.is-stuck .nav-inner {
  height: 54px;
  background: var(--glass-tint-thick);
  backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
  box-shadow: var(--glass-shadow-lg);
}

@media (max-width: 480px) {
  .nav { padding: 10px 12px; }
  .nav-inner { padding: 0 8px 0 18px; height: 56px; }
}

.nav-links {
  display: flex;
  gap: 28px;
}

.nav-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  transition: color 0.2s ease;
}

.nav-links a:hover { color: var(--text); }

@media (max-width: 860px) {
  .nav-links { display: none; }
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 20px;
}

.nav-link {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 500;
  transition: color 0.18s ease;
}

.nav-link:hover {
  color: var(--accent);
}

@media (max-width: 480px) {
  .nav-actions { gap: 12px; }
  .nav-link { display: none; }   /* the footer link covers small screens */
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}

/* ── Wordmark ──
   No glyph. The square-wave mark it replaces was a good idea badly served by its execution: it
   was inlined nine times across six files, drawn at 20×14 so it went to mush beside 17px type,
   and its lime drop-shadow fought every glass surface it sat on.

   Type alone instead. Wide tracking is the whole identity here — at 800 weight the letterforms
   are heavy enough that the space between them becomes the distinctive part, and it scales from
   a favicon to a hero without redrawing anything.

   The trailing letter-spacing is trimmed off the right so the word stays optically centred in
   its flex row; without that the tracking hangs as a phantom space after the E. */
.logo-text {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 4px;
  margin-right: -4px;
  text-transform: uppercase;
  color: var(--text);
  transition: letter-spacing var(--dur) var(--ease-glass), color var(--dur) var(--ease-glass);
}

.nav-logo:hover .logo-text {
  letter-spacing: 5.5px;
  margin-right: -5.5px;
  color: var(--accent);
}

.logo-dim {
  color: var(--muted);
  font-weight: 600;
}

/* ===== HERO ===== */
.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 120px 24px 80px;
  overflow: hidden;
}

/* One centred column since 2026-09-23: the pitch, then the product under it. Side by side, the
   headline had to share the width with the mockup and could never be big enough to lead. */
.hero-inner {
  position: relative;
  width: 100%;
  max-width: 1180px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 64px;
}

/* A soft light behind the headline, so the largest words on the page sit on the brightest spot. */
.hero-inner::before {
  content: '';
  position: absolute;
  top: -40px;
  left: 50%;
  width: min(980px, 100%);
  height: 560px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, rgba(var(--accent-rgb), 0.075), transparent 64%);
  pointer-events: none;
  z-index: -1;
}

.hero-content {
  position: relative;
  text-align: center;
  max-width: 860px;
}

/* ── Marquee strip ──
   The track is duplicated in the markup, so translating it exactly -50% lands the copy on
   the original's starting position and the loop is seamless. width:max-content keeps the
   items on one line regardless of viewport. */
.marquee {
  position: relative;
  z-index: 2;
  overflow: hidden;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  background: rgba(15, 17, 22, 0.5);
  padding: 18px 0;
}
.mq-row {
  display: flex;
  width: max-content;
  animation: mq-scroll 34s linear infinite;
}
.mq-track {
  display: flex;
  gap: 44px;
  /* Trailing padding equal to the gap, so a track's width includes the space that follows it.
     Without this the -50% shift lands half a gap short and the loop visibly stutters. */
  padding-right: 44px;
  font-family: var(--font-mono);
  font-size: 14px;
  /* --text, not --muted. The items used to be a bold name plus a muted description; with the
     descriptions gone there is nothing left to sit at the quieter level, and a strip of muted
     names alone reads as disabled rather than as secondary. The .mq-track b and .mq-track .s
     rules that carried the old two-tone treatment went with them. */
  color: var(--text);
  font-weight: 500;
  white-space: nowrap;
}
@keyframes mq-scroll { to { transform: translateX(-50%); } }

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: rgba(var(--accent-rgb), 0.10);
  backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur-thin)) saturate(var(--glass-sat));
  box-shadow:
    inset 0 0 0 1px rgba(var(--accent-rgb), 0.24),
    inset 0 1px 0 0 rgba(var(--accent-rgb), 0.4),
    0 4px 18px rgba(0, 0, 0, 0.3);
  color: var(--accent3);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.2px;
  padding: 8px 20px;
  border-radius: var(--radius-pill);
  margin-bottom: 32px;
}
.hero-badge-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.85);
}

/* Black weight and tight tracking: at this size the headline is the design. */
.hero-title {
  font-size: clamp(44px, 8.4vw, 88px);
  font-weight: 900;
  line-height: 1.06;
  letter-spacing: -0.025em;
  color: #fff;
  margin-bottom: 30px;
}

/* One bold line between the headline and the detail: the fact a buyer checks first. */
.hero-claim {
  font-size: clamp(18px, 2vw, 22px);
  font-weight: 700;
  color: #A7B0BD;
  margin: 0 0 10px;
}

.hero-sub {
  font-size: clamp(16px, 1.6vw, 19px);
  color: #8C96A4;
  max-width: 600px;
  margin: 0 auto 40px;
  line-height: 1.7;
}

.hero-buttons {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
}

.btn-hero {
  gap: 10px;
  padding: 16px 34px;
  font-size: 16px;
  font-weight: 700;
}
.btn-primary.btn-hero {
  box-shadow:
    0 0 32px rgba(var(--accent-rgb), 0.38),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.45);
}
.btn-primary.btn-hero:hover {
  box-shadow:
    0 0 46px rgba(var(--accent-rgb), 0.55),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.5);
}
.btn-arrow {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--dur-fast) var(--ease-spring);
}
.btn-hero:hover .btn-arrow { transform: translateX(3px); }

/* Hero stats strip. Three verifiable numbers, spaced generously so the row reads as a
   punctuation mark on the pitch rather than a fourth heading. The label under each figure
   is monospace + uppercase + wide tracking so the eye reads NUMBER first, context second. */
.hero-stats {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: 44px;
  margin: 48px auto 0;
  padding: 24px 0 0;
  border-top: 1px solid var(--border);
  max-width: 560px;
  flex-wrap: wrap;
}
.hero-stats li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.stat-n {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.stat-n i {
  font-style: normal;
  font-size: 14px;
  font-weight: 700;
  color: var(--muted);
  margin-left: 2px;
}
.stat-l {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  font-family: var(--font-mono);
}
@media (max-width: 480px) {
  .hero-stats { gap: 28px; }
  .stat-n { font-size: 22px; }
}

/* ===== FEATURES ===== */
.features {
  padding: 100px 0;
  position: relative;
}

.features .container {
  text-align: center;
}

/* Flex rather than auto-fit grid. At five cards the grid put four on a row — 4×230 + 3×20 fits
   the 1100px container, 5×230 + 4×20 does not — and stretched the fifth across the full width on
   its own. Flex wraps to a centred 3 + 2 instead, which reads as deliberate. */
.features-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  margin-top: 52px;
}

.feature-card {
  /* Three per row, sized as an exact third rather than left to grow. flex-grow would let the
     wrapped row of two stretch wider than the row of three above it (353 against 337), which
     reads as a mistake. min-width, not just the basis, because shrink otherwise compresses the
     cards to whatever fits instead of wrapping — that put four back on a row at 248px each. */
  flex: 0 1 calc((100% - 40px) / 3);
  min-width: 300px;
  /* Material comes from .glass in the markup; this rule keeps only layout. Splitting them
     that way is the point of the primitive — a card should not be able to drift out of step
     with the rest of the page's glass by editing one component. */
  padding: 32px 24px;
  text-align: left;
}

.feature-badge {
  position: absolute;
  top: 20px;
  right: 20px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  padding: 3px 9px;
  border-radius: 6px;
}

.badge-hc { background: rgba(var(--accent-rgb), 0.2); color: var(--accent3); }
.badge-ss { background: rgba(var(--accent2-rgb), 0.2); color: #DCE4EF; }
.badge-ab { background: rgba(var(--accent3-rgb), 0.15); color: #FFFFFF; }
/* Was violet (#a78bfa on rgba(109,40,217,…)) — a leftover the rebrand missed, and the only
   off-palette colour left on the page. */
.badge-st { background: rgba(var(--accent2-rgb), 0.14); color: #C6D0DE; }
.badge-sd { background: rgba(var(--accent-rgb), 0.16); color: var(--accent); }

.feature-icon {
  font-size: 28px;
  margin-bottom: 16px;
  filter: drop-shadow(0 0 6px rgba(var(--accent-rgb), 0.5));
}

.feature-card h3 {
  font-size: 18px;
  font-weight: 700;
  margin-bottom: 10px;
  color: var(--text);
}

.feature-card p {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.65;
}

/* ===== FEATURE CLIPS =====
   The card carries a looping preview of the macro running. Clips are lazy: nothing is fetched
   until the card is near the viewport, and playback stops the moment it leaves — five autoplaying
   videos on a phone is a real cost, and this page is mostly met on a phone.

   .no-clip is set by script.js when a clip is absent or fails to load, and hides the player in
   favour of the badge. That is the shipping state until the footage exists. */
.fc { padding-top: 0; overflow: hidden; }

.fc-media {
  position: relative;
  margin: 0 -24px 20px;          /* bleed to the card edges, past its 24px padding */
  aspect-ratio: 16 / 10;
  background: #0A0C10;
  border-bottom: 1px solid var(--border);
  overflow: hidden;
}

.fc-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.fc-video.is-ready { opacity: 1; }
.fc.no-clip .fc-video { display: none; }

/* Name of the macro currently playing, over the clip. */
.fc-chip {
  position: absolute;
  top: 12px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(8, 9, 12, 0.82);
  backdrop-filter: blur(6px);
  border: 1px solid var(--border);
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}
.fc-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}
.fc.no-clip .fc-chip { display: none; }

/* The clip is a control: click or tap to play and pause. */
.fc-media[role="button"] { cursor: pointer; }
.fc-media:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Shown while a clip is paused - never started under reduced motion, or paused by the visitor. */
.fc-play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 58px;
  height: 58px;
  margin: -29px 0 0 -29px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(8, 9, 12, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow:
    0 6px 24px rgba(0, 0, 0, 0.45),
    inset 0 0 0 1px rgba(255, 255, 255, 0.16),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.28);
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
.fc-play svg { width: 24px; height: 24px; margin-left: 3px; fill: var(--text); }
.fc.is-paused:not(.no-clip) .fc-play { opacity: 1; transform: none; }
.fc-media:hover .fc-play { transform: scale(1.06); }

.fc-fallback {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 13px;
  color: var(--muted);
  background:
    radial-gradient(circle at 50% 45%, rgba(var(--accent-rgb), 0.07) 0%, transparent 65%),
    #0A0C10;
}
.fc.no-clip .fc-fallback { display: flex; }
/* The badge is absolutely positioned in the card by default; inside the fallback it sits inline. */
.fc-fallback .feature-badge { position: static; }


/* ===== WHY PHASE ===== */
.why {
  padding: 100px 0;
}

/* One column since the mockup moved up to the hero - the list is the whole section now. */
.why-inner {
  max-width: 760px;
}

.why-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 28px;
  margin-top: 36px;
}

.why-list li {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

/* Numbered markers instead of the old emoji icons (🎯 ⚙️ 🔒 📦).
   The emoji set was the strongest "generated by an LLM" tell on the page: they render
   inconsistently across OSes, none of them actually depict what the point beside them
   says, and they read as filler. A tabular-number 01–04 in accent is doing the same
   ordering job without borrowing meaning from a random pictogram. */
.why-num {
  flex-shrink: 0;
  min-width: 34px;
  font-family: 'JetBrains Mono', 'Consolas', ui-monospace, monospace;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1px;
  color: var(--accent);
  padding-top: 3px;
  font-variant-numeric: tabular-nums;
}

.why-list strong {
  display: block;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 4px;
}

.why-list p {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.6;
}

.hero-preview {
  display: flex;
  justify-content: center;
  width: 100%;
}

/* ── App mockup ──
   The stand-in for a real screenshot until the footage is ready, so it has to keep pace with
   the app or it quietly starts advertising a product that no longer exists. Redrawn 2026-09-06
   against 1.5.0: chromeless titlebar, a floating tab capsule across the top, and the Crystal
   tab's four macros in a 2x2. It replaced a left sidebar and two cards, one of which was still
   called "Anchor Burst" a rename ago.

   Names and descriptions inside it are copied from MainWindow.xaml rather than written for the
   page. If the app's macros change again, this block is the thing to check. */
.app-mockup {
  /* A query container, so the tab capsule below can respond to the width the mockup actually
     has rather than to the viewport's. Those two come apart badly here: between roughly 520px
     and 1000px the hero is two-column, so the viewport is wide while this box is at its
     narrowest, and a viewport-keyed rule left the capsule overflowing by 30px. */
  container-type: inline-size;
  background: var(--bg2);
  border-radius: var(--radius);
  width: 100%;
  max-width: 480px;
  overflow: hidden;
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  /* The app's own face, not the pixel one. Jersey 15 used to be scoped here to read as
     "Minecraft thing" at a glance, but the shipping UI is Segoe UI throughout — a mockup whose
     job is to show the product should not be the one thing on the page that misreports it.
     var(--font) matches closely enough at this size. */
  font-family: var(--font);
}

.app-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: rgba(0, 0, 0, 0.3);
  border-bottom: 1px solid rgba(var(--accent-rgb), 0.12);
}

.app-logo-sm {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--accent3);
}
.app-dots { display: flex; gap: 6px; }
.app-dots span {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: rgba(var(--accent-rgb), 0.22);
}

.app-body {
  padding: 12px 12px 10px;
}

/* The floating tab capsule, same object as in the app: inset from the edges rather than a
   full-width strip, so the window's own background shows down both sides of it. */
.app-tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px 6px;
  margin-bottom: 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.12);
}
.app-tabs span {
  padding: 5px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}
.app-tabs span.is-active {
  color: var(--text);
  font-weight: 600;
  background: rgba(255, 255, 255, 0.09);
}
.app-tab-sep {
  width: 1px;
  height: 14px;
  margin: 0 5px;
  background: rgba(255, 255, 255, 0.12);
}

.app-section {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  margin: 0 2px 10px;
}

.app-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* A row, not a column. The app's macro card is badge, then name over a one-line description,
   then the power button on the right — the old stacked Keybind/Delay rows are a panel that only
   appears once a card is expanded, so showing them collapsed misrepresented the UI. */
.app-card {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 11px;
  background: rgba(255, 255, 255, 0.035);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.app-card.is-on {
  background: rgba(255, 255, 255, 0.06);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.16),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.2);
}

.app-card-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;   /* or the description's ellipsis never triggers inside a flex row */
  flex: 1;
}

.app-badge {
  flex: none;
  font-family: var(--font-mono);
  font-size: 8.5px;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  padding: 4px 6px;
  border-radius: 6px;
  letter-spacing: 0.4px;
}

.app-card-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.app-card-sub {
  font-size: 10px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The app's power toggle: a ring that fills in when the macro is armed. */
.app-power {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.18);
}
.app-power.is-on {
  background: rgba(255, 255, 255, 0.12);
  box-shadow:
    inset 0 0 0 1.5px rgba(255, 255, 255, 0.55),
    0 0 10px rgba(255, 255, 255, 0.25);
}

.app-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 12px;
  padding-top: 9px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 9.5px;
  color: var(--muted);
}
.app-status-live::before {
  content: '';
  display: inline-block;
  width: 5px; height: 5px;
  border-radius: 50%;
  margin-right: 5px;
  background: var(--text);
  vertical-align: middle;
}

@media (max-width: 520px) {
  .app-panel { grid-template-columns: 1fr; }
}

/* Themes and Settings are the first thing to go when the capsule runs out of room — they are
   the least interesting part of the shot, and dropping them beats letting the row wrap or spill.
   Keyed to the mockup's own width: the six tabs measure ~344px and the body adds 24px of
   padding, so below 380px they cannot all fit however wide the window is.
   Without container query support this rule simply does not apply and all six show, which is
   the behaviour that shipped before — a graceful floor rather than a broken one. */
@container (max-width: 380px) {
  .app-tabs span:nth-last-child(-n+2),
  .app-tab-sep { display: none; }
}

/* ===== WHAT'S INSIDE ─ per-macro depth =====
   Category cards with chip lists. Same card silhouette as the features section above so
   the two read as one system, not two. Chip is the smallest reasonable unit of information
   here — a buyer wants to see 'yes, the knob I care about is in there' more than they want
   a paragraph explaining it. */
.inside {
  padding: 20px 0 100px;
}

.inside-head {
  text-align: center;
  max-width: 640px;
  margin: 0 auto 44px;
}
.inside-head .section-title { margin-bottom: 12px; }
.inside-sub {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.6;
}

.inside-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
@media (max-width: 860px) {
  .inside-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 520px) {
  .inside-grid { grid-template-columns: 1fr; }
}

.mod-card {
  border-radius: var(--radius-sm);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mod-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.mod-name {
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}
/* Sits where dqrkis puts a count ("14 modules"). Here it's the initials the app uses on the
   real card badge, so the mockup and this section share the same identifier vocabulary. */
.mod-count {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.1em;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
}

.chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chips li {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  padding: 4px 9px;
  border-radius: 6px;
  letter-spacing: 0.02em;
}
/* One or two chips per card promoted — the ones the buyer would care most about (sub-tick,
   tick-floor numbers, the cycle rate). Otherwise the wall of neutral chips flattens. */
.chips li.chip-hot {
  color: var(--accent3);
  background: rgba(var(--accent-rgb), 0.10);
  border-color: rgba(var(--accent-rgb), 0.28);
}

/* ===== PRICING ===== */
.pricing {
  padding: 100px 0;
}

.pricing .container {
  text-align: center;
}

.pricing-sub {
  color: var(--muted);
  font-size: 16px;
  margin-bottom: 52px;
}

.pricing-grid {
  display: flex;
  gap: 24px;
  justify-content: center;
  flex-wrap: wrap;
  align-items: stretch;
}

.price-card {
  border-radius: var(--radius-lg);
  padding: 40px 36px;
  width: 300px;
  /* A bare width:300px does not shrink, so inside the container's 272px of usable width on a
     320px phone the card hung 4px past the right edge and let the page pan. Long-standing —
     it predates the glass redesign, and bites any viewport under about 348px. */
  max-width: 100%;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* The featured card takes .glass-accent in the markup for its tint and lit bevel. What is left
   here is the part the primitive cannot know about: it sits forward of its neighbour. */
.price-card-featured {
  box-shadow:
    var(--glass-shadow-lg),
    0 0 60px -12px rgba(var(--accent-rgb), 0.28);
}
.price-card-featured:hover { transform: translateY(-10px); }

.price-popular {
  position: absolute;
  top: -13px;
  left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%);
  color: #0A0B0D;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 4px 14px;
  border-radius: 99px;
  white-space: nowrap;
}

.price-tier {
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 1px;
}

.price-amount {
  font-size: 52px;
  font-weight: 800;
  color: var(--text);
  line-height: 1;
}

.price-period {
  font-size: 18px;
  font-weight: 500;
  color: var(--muted);
}

/* Launch pricing. Deliberately not a struck-through "was €6": these have never sold at €6, and a
   crossed-out price you never charged is misleading advertising — the EU Omnibus rules require a
   displayed prior price to be one you actually used. A promise about the future is both honest and
   a better reason to buy today. */
.price-launch {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
  /* The card is a flex column with a 24px gap; this pulls the line back up so it reads as part of
     the price rather than as a floating third element. */
  margin-top: -13px;
}

.price-launch span {
  color: var(--accent);
  font-weight: 700;
}

.price-features {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}

.price-features li {
  font-size: 14px;
  color: var(--muted);
}

.price-features li::first-letter { color: var(--accent); }

.price-card .btn { width: 100%; }

/* ===== FAQ ===== */
.faq {
  padding: 100px 0;
}

.faq-inner {
  max-width: 760px;
}

.faq .section-title { margin-bottom: 48px; }

.faq-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.faq-item {
  border-radius: var(--radius);
  /* overflow:hidden would clip the sheen and bevel to the content box and square off their
     corners during the open/close height transition. The panel body is clipped on .faq-a
     instead, which is the only thing that actually needs it. */
  transition: background var(--dur) var(--ease-glass), box-shadow var(--dur) var(--ease-glass);
}

.faq-item:has(.faq-q[aria-expanded="true"]) {
  background: var(--glass-tint-thick);
  box-shadow: var(--glass-shadow-lg);
}
.faq-item:has(.faq-q[aria-expanded="true"])::after {
  background: linear-gradient(
    145deg,
    rgba(var(--accent-rgb), 0.42) 0%,
    rgba(var(--accent-rgb), 0.10) 45%,
    rgba(255, 255, 255, 0.02) 70%,
    rgba(var(--accent-rgb), 0.08) 100%
  );
}

.faq-q {
  width: 100%;
  background: none;
  border: none;
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 600;
  padding: 20px 24px;
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  transition: color 0.2s;
}

.faq-q:hover { color: var(--accent3); }

.faq-q::after {
  content: '+';
  font-size: 22px;
  font-weight: 400;
  color: var(--accent);
  flex-shrink: 0;
  transition: transform 0.25s ease;
}

.faq-q[aria-expanded="true"]::after {
  transform: rotate(45deg);
}

.faq-a {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
}

/* No max-height here — script.js sets it inline from the panel's own scrollHeight. The 300px
   cap this replaces clipped the longest answer on any viewport below ~414px. */
.faq-a.open {
  padding-bottom: 20px;
}

.faq-a p {
  padding: 0 24px;
  font-size: 14px;
  color: var(--muted);
  line-height: 1.7;
}

/* ===== FOOTER ===== */
.footer {
  border-top: 1px solid var(--border);
  padding: 40px 0;
}

.footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px;
}

/* wrap, not nowrap. Nine links at 24px gaps measure 572px, so on any phone this row ran off
   the right edge — it took the footer's own scroll width to 596px, which propagated all the way
   up and let the whole document pan sideways about 240px. The wordmark above it was pushed off
   screen with it. Centred because .footer-inner is already column-and-centred below 768px, and
   a wrapped row that stays left-aligned reads as a mistake next to it. */
.footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px 24px;
}

.footer-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  transition: color 0.2s;
}

.footer-links a:hover { color: var(--accent3); }

.footer-copy {
  color: var(--muted);
  font-size: 13px;
}

/* ===== RESPONSIVE ===== */

/* Two columns need roughly 1100px to hold the mockup at a legible size next to the pitch.
   Below that they stack and the copy goes back to centred, which is what it was written for. */
@media (max-width: 960px) {
  .hero {
    min-height: auto;
    padding: 130px 24px 72px;
  }

  .hero-inner { gap: 48px; }
}

@media (max-width: 768px) {
  .app-mockup {
    width: 100%;
    max-width: 340px;
  }

  .footer-inner {
    flex-direction: column;
    text-align: center;
  }

  .feature-card {
    flex-basis: calc((100% - 20px) / 2); /* two per row on tablet */
    min-width: 240px;
  }
}

/* ── Mobile: cost of the glass ──
   The desktop material is roughly twenty live backdrop-filter layers. Each one is a separate
   readback-blur-composite of what is behind it, every frame it changes, and the cost scales
   with the blur radius — on a mid-range phone that is the difference between smooth scrolling
   and visible stutter.

   Redefining the tokens on :root is enough: every .glass rule composes from them, so the whole
   material gets cheaper here without a single component being restyled or looking different in
   kind. Roughly halved, which is still plenty of blur at phone viewing distance. */
@media (max-width: 720px) {
  :root {
    --glass-blur-thin:  8px;
    --glass-blur:      14px;
    --glass-blur-thick: 20px;
  }

  /* The light fields are the other half of the bill. At desktop sizes these are three ~900px
     circles carrying a 110px blur and pinned into their own compositor layers by will-change —
     on a 360px viewport that is three textures several times larger than the screen, held for
     the life of the page. Smaller, softer, and no longer force-promoted; the animation still
     promotes them while it runs, which is the only time it is worth paying for. */
  .neb {
    filter: blur(70px);
    will-change: auto;
  }
  .neb.a { width: 460px; height: 460px; top: -170px; left: -130px; }
  .neb.b { width: 430px; height: 430px; bottom: -190px; right: -120px; }
  .neb.c { width: 400px; height: 400px; }
}

@media (max-width: 480px) {
  .feature-card {
    flex-basis: 100%;
    min-width: 0; /* or the 240px floor above overflows a 375px phone */
  }

  .pricing-grid {
    flex-direction: column;
    align-items: center;
  }

  .hero-buttons {
    flex-direction: column;
    align-items: center;
  }
}

/* ── Touch targets ──
   Keyed on the pointer, not the width: a small window on a desktop still has a mouse, and a
   large tablet still has fingers. The macro chips sat at 36px and the footer links at about 18,
   both under the 44px both Apple and Google ask for.

   Padding rather than height, so nothing moves: the chips keep their size and gain hit area
   around them, and the footer links get a larger box without the row growing (the negative
   margin gives the extra padding back to the layout). */
@media (pointer: coarse) {
  .footer-links a {
    padding: 12px 4px;
    margin: -12px -4px;
  }

  .faq-q { min-height: 56px; }
}

/* ── Reduced motion ──
   orbFloat, arrowBounce and dotPulse all run `infinite`, so for a visitor who has asked their
   OS for less motion they never stop. Collapse every animation and transition to a single
   instant pass — `both`/final-state fills still apply, so nothing ends up invisible.
   The particle canvas and scroll reveals are handled separately in script.js. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Breadcrumbs (2026-08-10)

   Subpages only. The home page is the root of the trail, so a crumb there would
   be a single item pointing at itself.
   ───────────────────────────────────────────────────────────────────────────── */
.crumbs { margin: 0 0 22px; }
.crumbs ol {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: 0.85rem; color: var(--muted);
}
.crumbs li { display: flex; align-items: center; gap: 8px; }
/* The separator is generated rather than written into the markup, so it never
   ends up in the accessible name of the link beside it. */
.crumbs li + li::before { content: "/"; color: var(--border); }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs [aria-current="page"] { color: var(--text); }

/* ─────────────────────────────────────────────────────────────────────────────
   Sticky buy bar — phones only (2026-08-10)

   The hero CTA is gone after the first screen and the nav button is small at
   this width, so past the fold there is nothing to tap. Shown by script.js only
   once the hero has scrolled out, so it never sits on top of the button it is
   standing in for.

   Hidden outright above 720px: on a desktop the nav bar is always in view and a
   second permanent CTA is just clutter.
   ───────────────────────────────────────────────────────────────────────────── */
.buybar { display: none; }

@media (max-width: 720px) {
  .buybar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    /* Floating capsule, inset from all three edges, rather than a slab welded to the bottom
       of the viewport. Same reasoning as the nav: the material only reads as glass if the page
       is visibly moving past it, which an edge-to-edge bar with a hard top border prevents.
       The safe-area inset is still honoured, just as margin now instead of padding. */
    left: 14px; right: 14px;
    bottom: calc(14px + env(safe-area-inset-bottom));
    padding: 12px 12px 12px 22px;
    border-radius: var(--radius-pill);
    background: var(--glass-tint-thick);
    backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
    -webkit-backdrop-filter: blur(var(--glass-blur-thick)) saturate(var(--glass-sat));
    box-shadow:
      var(--glass-shadow-lg),
      inset 0 0 0 1px var(--glass-edge-dim),
      inset 0 1px 0 0 var(--glass-edge);
    /* 110% of its own height no longer clears the viewport now that it is inset from the
       bottom — the offset has to cover the gap too, or it parks in view. */
    transform: translateY(calc(100% + 22px));
    transition: transform var(--dur) var(--ease-glass);
  }
  .buybar[hidden] { display: flex; }          /* animate out rather than vanish */
  .buybar.is-up { transform: translateY(0); }

  .buybar-text { display: flex; flex-direction: column; line-height: 1.25; }
  .buybar-text strong { color: var(--text); font-size: 0.98rem; font-weight: 700; }
  .buybar-text span { color: var(--muted); font-size: 0.8rem; }

  .buybar-btn { padding: 11px 22px; font-size: 0.92rem; white-space: nowrap; }

  /* Clearance so the footer's last line is not sitting under the bar. 96px was measured against
     the old flush-to-the-bottom bar and a single-line link row; the bar is now a capsule inset
     14px from the bottom, and the links wrap to three rows on a 320px screen, which put the
     copyright underneath it. Sized off the bar's own geometry plus the safe-area inset so it
     stays correct on a notched phone. */
  .footer { padding-bottom: calc(112px + env(safe-area-inset-bottom)); }
}

@media (max-width: 720px) and (prefers-reduced-motion: reduce) {
  .buybar { transition: none; }
}
