/* ============================================================
   DETAILBYTE — marketing site stylesheet
   ============================================================
   The tokens, ambient background and .v- primitives below are
   lifted from the product's own design system
   (VELORO REBUILD/public/assets/css/detailbyte-core.css) so the
   site and the app read as one brand. Keep them in sync: if the
   accent ramp moves in the app, move it here too.

   Everything under "SITE LAYOUT" onwards is site-only and has no
   counterpart in the app.
   ============================================================ */

/* ============================================================
   TOKENS — from detailbyte-core.css :root
   ============================================================ */
:root{
  color-scheme:dark;

  /* ---- Deep obsidian-navy surfaces ---- */
  --v-bg-0:#06080f;
  --v-bg-1:#090D16;
  --v-bg-2:#111827;
  --v-glass:rgba(17,24,39,.84);
  --v-glass-2:rgba(21,30,48,.72);

  /* ---- Hairlines ---- */
  --v-line:rgba(31,41,55,.85);
  --v-line-strong:rgba(51,65,85,.9);

  /* ---- Accent ramp ----
     Racing Blue is the fill; Neon Blue is the brighter secondary tint used
     for glows, badges and hover states. Accent TEXT keeps the original
     bright tint — #0052FF on near-black is legible enough for text but
     #3da9ff reads better at small sizes and still reads as part of the same
     ramp. Never set body or link text to var(--accent). */
  --accent:#0052FF;
  --accent2:#1F6CFF;
  --accent-text:#3da9ff;
  --glow:31,108,255;

  /* ---- HUD accents ----
     Additive, semantic only — crimson/amber never stand in for the core
     accent, they mark a genuine alert/highlight telemetry state so "one
     glow colour per element" still holds. */
  --hud-crimson:#ff2a5f;
  --hud-amber:#ffb800;

  /* ---- Glow presets ----
     Restrained on purpose. Glow marks the one important thing on a
     screen; once several elements glow, none of them read as
     emphasised. One glowing CTA per viewport. */
  --v-glow-sm:0 0 0 1px rgba(var(--glow),.12), 0 4px 14px rgba(var(--glow),.10);
  --v-glow-md:0 0 0 1px rgba(var(--glow),.16), 0 8px 22px rgba(var(--glow),.14);
  --v-glow-lg:0 0 0 1px rgba(var(--glow),.22), 0 14px 36px rgba(var(--glow),.18);
  /* --shadow-rgb is --v-bg-0 spelled as an rgb triplet, not literal black —
     a shadow this close to its own surface colour still reads as "this
     object's own cast shadow", where flat black read as a generic
     drop-shadow filter with no relationship to what's under it. Used
     wherever a box-shadow on the site was a plain black alpha value;
     .hero-bg-visual and the showcase frame's own already-navy-adjacent
     shadows were left as literals — they were composed against other
     colour layers deliberately, not a flat black. */
  --shadow-rgb:6,8,15;
  --v-shadow:0 18px 44px rgba(var(--shadow-rgb),.5);

  /* ---- Geometry / motion ---- */
  --v-r-sm:6px; --v-r-md:8px; --v-r-lg:8px; --v-r-pill:999px;
  --v-ease:cubic-bezier(.2,.7,.2,1);

  /* ---- Type ---- */
  --head-font:'Plus Jakarta Sans',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --body-font:'Inter',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --v-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --text-main:#e7e7ec;
  --text-bright:#f4f4f5;
  --text-muted:#9a9aa6;
  --text-dim:#8f8f9a;

  /* ---- Site layout ----
     Raised from 1180px (21 Aug 2026) — the site read as boxed, with a lot
     of dead space on both sides on an actual desktop monitor. Long-form
     text still has its own narrower caps (.lede's 58/62ch, .faq's 820px,
     .doc-body's 74ch) so paragraphs don't stretch into unreadably long
     lines — only the overall page/grid width grew. */
  --shell:1640px;
  --gutter:clamp(18px,4vw,34px);
  --section-y:clamp(58px,8vw,104px);
  --header-h:68px;

  /* ---- Site-only additions (no counterpart in detailbyte-core.css —
     don't fold these into the "keep in sync with the app" block above) ---- */

  /* CTA-safe gradient endpoint. .v-cta's background gradient runs
     var(--accent) -> this, NOT var(--accent2) — white button text on
     accent2 (Neon Blue) measures only 4.52:1, too tight a margin at the
     gradient's brightest end once anti-aliasing is factored in. This
     endpoint measures 7.70:1. accent2 itself stays untouched everywhere
     else (glows, badges, decorative fills) where text never sits directly
     on top of it. */
  --cta-grad-end:#0047c7;
  --text-on-accent:#fff;

  /* Spacing scale — 4px base. Nothing on the site referenced a spacing
     token before this; padding/gap/margin were one-off literals per
     component. New/touched components should use these; existing
     components are not being mass-migrated in one pass. */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-8:32px; --sp-10:40px; --sp-14:56px; --sp-18:72px;

  /* Type scale — collapses the ~15 half-px body sizes (8-15.5px) that had
     accumulated with no shared token down to a fixed set. */
  --fs-caption:11px;
  --fs-label:12.5px;
  --fs-body-sm:14px;
  --fs-lg:18px;
  --fs-h3:clamp(19px,2.2vw,22px);
  --fs-h2:clamp(26px,4vw,42px);   /* covers cta-band(26-38) + feature-block(24-34) + section-head(28-42) */

  /* White-on-dark surface tint scale — names the values that already
     recur exactly elsewhere in the file (rgba(255,255,255,.03/.06/.09/
     .14/.22) appeared 15 times as literals). Only exact matches were
     migrated to these tokens; the other one-off alpha values the audit
     found were left as literals rather than rounded to the nearest
     step, to avoid quietly shifting a hand-tuned look. */
  --tint-1:rgba(255,255,255,.03);
  --tint-2:rgba(255,255,255,.06);
  --tint-3:rgba(255,255,255,.09);
  --tint-4:rgba(255,255,255,.14);
  --tint-5:rgba(255,255,255,.22);

  /* HUD accent triplets — --glow's bare "R,G,B" pattern applied to the
     other two HUD colours, so their alpha-varying uses (six were
     re-spelled as raw literals instead of the token) can route through
     rgba(var(--hud-crimson-rgb),alpha) the same way. */
  --hud-crimson-rgb:255,42,95;
  --hud-amber-rgb:255,184,0;
}

/* ============================================================
   RESET
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html{
  background:var(--v-bg-1);
  scrollbar-gutter:stable;
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  /* Anchor targets must clear the sticky header. */
  scroll-padding-top:calc(var(--header-h) + 18px);
}
body{
  margin:0;
  background:transparent;
  color:var(--text-main);
  font-family:var(--body-font);
  font-size:16px;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
  background-color:var(--v-bg-1);
}
img,svg{max-width:100%;height:auto;display:block}
a{color:var(--accent-text);text-decoration:none;transition:color .18s var(--v-ease)}
a:hover{color:var(--text-bright);text-decoration:underline}
button{font:inherit;color:inherit}

h1,h2,h3,h4{
  font-family:var(--head-font);
  font-weight:700;
  letter-spacing:.01em;
  text-transform:uppercase;
  color:var(--text-bright);
  line-height:1.08;
  margin:0;
  /* Orphaned single words on a headline's last line read as an accident,
     not a deliberate line break — support is wide enough now (Chrome/Edge
     114+, Safari 17.4+, Firefox 121+) that this is a plain quality-of-life
     win with no fallback needed: unsupported browsers just keep today's
     plain wrap. */
  text-wrap:balance;
}
p{margin:0}
.mono{font-family:var(--v-mono)}

/* Mono sub-heading label — was a 190-character inline style="..." block
   copy-pasted verbatim on every feature-block sub-section (features.html:
   202, 261, 309, 799, 831). .mt-lg is the "not the first one in its
   section" variant, which needed extra top margin inline before. */
.h3-mono-label{
  font-size:16px;margin:0 0 16px;
  letter-spacing:.06em;text-transform:uppercase;
  font-family:var(--v-mono);color:var(--text-dim);font-weight:600;
}
.h3-mono-label.mt-lg{margin-top:34px}

/* ============================================================
   AMBIENT BACKGROUND — deep space vignette + twin red glows
   Pure CSS, no images, no extra requests.
   ::before = vignette, ::after = glows. Defined once, here.
   ============================================================ */
body::before,
body::after{content:"";position:fixed;pointer-events:none;z-index:-1}

body::before{
  inset:0;
  background:radial-gradient(130% 100% at 50% 0%,transparent 35%,rgba(0,0,0,.50) 100%);
}

/* Intensified 17 Aug 2026 — a third spot and higher opacity so the deeper
   glass cards below (see .card) actually have something to refract, same
   reasoning as the licensing panel's own frozen-glass pass this session:
   "dark card" vs "glass card" is entirely down to whether there's ambient
   light behind it worth showing through. Stays red/black — intensifying the
   existing palette, not opening it up to a new one. */
body::after{
  inset:-15% -10% auto -10%;height:78vh;
  background:
    radial-gradient(46% 58% at 18% 0%,rgba(var(--glow),.11),transparent 70%),
    radial-gradient(42% 54% at 84% 4%,rgba(var(--glow),.09),transparent 72%),
    radial-gradient(38% 50% at 50% 55%,rgba(var(--glow),.05),transparent 74%);
  filter:blur(12px);
}

/* A slow horizontal scan-line sweep behind the hero only — the "gaming HUD"
   layer the ambient blobs above don't cover on their own. Kept to the hero
   specifically (not page-wide) so it reads as one deliberate accent rather
   than a busy background on every page; .hero already has overflow:hidden
   from its own rule further down, which is what keeps this clipped to its
   box instead of bleeding into document.documentElement.scrollWidth — see
   README.md's own note on why that matters here. */
.hero::after{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  inset:0;
  background:repeating-linear-gradient(
    0deg,
    transparent 0,transparent 3px,
    rgba(var(--glow),.06) 3px,rgba(var(--glow),.06) 4px
  );
  mask-image:linear-gradient(180deg,transparent,#000 30%,#000 70%,transparent);
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 30%,#000 70%,transparent);
}
@media (prefers-reduced-motion:no-preference){
  .hero::after{animation:vScanDrift 9s linear infinite}
}
@keyframes vScanDrift{0%{background-position:0 0}100%{background-position:0 40px}}

/* Ambient signals — sparse, soft points of light drifting across every
   page, not the diagonal two-colour tick marks this used to be. Those hard-
   blinked (steps(2,end) is a flat on/off toggle) and paired a stray amber
   tone against the brand's blue, which read as a glitch/flash rather than
   a deliberate signal at this size. One colour, a smooth breathing fade
   instead of a blink, no rotation needed now that it's a dot, not a line. */
.ambient-signal{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none}
.ambient-signal span{
  position:absolute;display:block;width:4px;height:4px;border-radius:50%;
  background:var(--accent2);opacity:.16;
  box-shadow:0 0 12px 2px rgba(var(--glow),.55);
}
.ambient-signal span:nth-child(1){left:6%;top:18%}
.ambient-signal span:nth-child(2){left:16%;top:72%;width:3px;height:3px;opacity:.10}
.ambient-signal span:nth-child(3){left:31%;top:10%;width:3px;height:3px;opacity:.12}
.ambient-signal span:nth-child(4){right:11%;top:20%;width:5px;height:5px}
.ambient-signal span:nth-child(5){right:4%;top:64%;width:3px;height:3px;opacity:.11}
.ambient-signal span:nth-child(6){right:27%;top:82%;opacity:.13}
.ambient-signal span:nth-child(7){left:48%;top:46%;width:3px;height:3px;opacity:.08}
.ambient-signal span:nth-child(8){right:43%;top:7%;opacity:.09}
body > :not(script):not(.ambient-signal){position:relative;z-index:1}
@media (prefers-reduced-motion:no-preference){
  .ambient-signal span{animation:vPacketDrift 14s ease-in-out infinite,vSignalBreathe 5.2s ease-in-out infinite}
  .ambient-signal span:nth-child(2n){animation-duration:19s,6.4s;animation-delay:-7s,-1.6s}
  .ambient-signal span:nth-child(3n){animation-duration:23s,7.1s;animation-delay:-13s,-3s}
}
@keyframes vPacketDrift{0%{margin-top:-5vh;margin-left:-2vw}50%{margin-top:8vh;margin-left:3vw}100%{margin-top:18vh;margin-left:7vw}}
@keyframes vSignalBreathe{0%,100%{opacity:.35;transform:scale(1)}50%{opacity:1;transform:scale(1.6)}}
@media (max-width:640px){.ambient-signal span:nth-child(3),.ambient-signal span:nth-child(6),.ambient-signal span:nth-child(7),.ambient-signal span:nth-child(8){display:none}}
@media (prefers-reduced-motion:no-preference){
  body::after{animation:vAmbient 22s ease-in-out infinite alternate}
}
@keyframes vAmbient{0%{transform:translate3d(0,0,0)}100%{transform:translate3d(0,26px,0) scale(1.05)}}

/* ============================================================
   GRAIN — a fixed, near-invisible noise layer over the whole page
   ============================================================
   Every dark surface on the site (body, .card, .section) is otherwise a
   perfectly smooth vector fill — fine at small sizes, but a flat colour
   covering a whole viewport reads as digital/sterile. One SVG feTurbulence
   data URI (no image request), tiled small and repeated, sitting on
   html::after rather than body's own ::before/::after (both already used
   above for the vignette and glow blobs). mix-blend-mode:overlay means it
   only ever perturbs existing luminance by a few percent — it can't
   darken or lighten a flat colour outright — so .035 opacity stays
   genuinely subtle rather than reading as visible speckle. Static: a
   texture, not a moving thing, so no prefers-reduced-motion guard needed.
   z-index:3 sits above page content (z-index:1) and the header (100) is
   still above that — this never has to compete with anything readable. */
html::after{
  content:"";position:fixed;inset:0;z-index:3;pointer-events:none;
  opacity:.035;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:160px 160px;
}

/* ============================================================
   SCROLLBARS + FOCUS
   ============================================================ */
*{scrollbar-width:thin;scrollbar-color:rgba(var(--glow),.34) transparent}
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,rgba(var(--glow),.42),rgba(var(--glow),.22));
  border-radius:var(--v-r-pill);
  border:2px solid transparent;
  background-clip:padding-box;
}
*::-webkit-scrollbar-thumb:hover{
  background:linear-gradient(180deg,rgba(var(--glow),.68),rgba(var(--glow),.40));
  background-clip:padding-box;
}

:focus-visible{
  outline:2px solid rgba(var(--glow),.75);
  outline-offset:2px;
  border-radius:6px;
}

.skip-link{
  position:absolute;left:-9999px;top:0;z-index:200;
  padding:12px 18px;border-radius:0 0 var(--v-r-sm) 0;
  background:var(--v-bg-2);border:1px solid rgba(var(--glow),.35);
  color:var(--text-bright);font-weight:600;
}
.skip-link:focus{left:0;text-decoration:none}

/* ============================================================
   PRIMITIVES — from detailbyte-core.css, v- prefixed
   ============================================================ */

/* Eyebrow pill — bordered, accent, softly glowing */
.v-eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  padding:7px 14px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.28);
  background:rgba(var(--glow),.07);
  color:var(--accent-text);
  font-family:var(--v-mono);
  font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  box-shadow:inset 0 0 18px rgba(var(--glow),.05);
}

.v-accent-text{color:var(--accent-text)}

/* Short accent underline beneath a title */
.v-rule-accent{
  width:46px;height:3px;margin:14px 0 0;border:none;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--glow),.55);
}
.v-rule-accent.is-center{margin-left:auto;margin-right:auto}

/* Icon containers.
   The icons inside are stroke SVGs drawn with stroke="currentColor", so they
   take the accent from the tile. Colour emoji were tried first and are the
   reason for the note: emoji are bitmap glyphs, they ignore `color` entirely,
   so a blue calendar sat inside a red tile. Keep icons as SVG. */
.v-icon-tile{
  display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:11px;flex:0 0 auto;
  background:rgba(var(--glow),.10);
  border:1px solid rgba(var(--glow),.22);
  color:var(--accent-text);font-size:17px;line-height:1;
}
.v-icon-ring{
  display:inline-flex;align-items:center;justify-content:center;
  width:78px;height:78px;border-radius:50%;flex:0 0 auto;
  background:radial-gradient(circle at 50% 40%,rgba(var(--glow),.16),transparent 70%);
  border:1px solid rgba(var(--glow),.30);
  color:var(--accent-text);font-size:30px;
  box-shadow:inset 0 0 26px rgba(var(--glow),.14),0 0 26px rgba(var(--glow),.14);
}
.v-icon-tile svg{width:19px;height:19px;display:block}
.v-icon-ring svg{width:32px;height:32px;display:block}

/* Check rows */
.v-check-list{display:grid;gap:8px;margin:0;padding:0;list-style:none}
.v-check-row{
  display:flex;align-items:flex-start;gap:11px;
  padding:9px 12px;border-radius:var(--v-r-sm);
  background:rgba(255,255,255,.028);
  border:1px solid var(--v-line);
}
.v-check{
  display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;border-radius:50%;flex:0 0 auto;margin-top:1px;
  background:rgba(var(--glow),.14);
  border:1px solid rgba(var(--glow),.40);
  color:var(--accent-text);font-size:12px;font-weight:800;line-height:1;
  box-shadow:0 0 12px rgba(var(--glow),.26);
}
.v-check-label{flex:1;min-width:0;font-size:14px;font-weight:500;color:var(--text-main)}
.v-check-label strong{font-weight:600;color:var(--text-bright)}

/* Stat tiles */
/* padding-top gives the "Starting price" tile's .v-scribble-circle (bleeds
   -14px above its own box) clearance from .section's own overflow:hidden —
   the stat section above sits at padding-top:0 (deliberately, to avoid a
   doubled gap under the showcase), so without this the circle's top arc
   was being clipped flush against the section edge and never read as a
   full circle. */
.v-stat-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:12px;
  padding-top:18px;
}
.v-stat{
  display:block;padding:14px 16px;border-radius:var(--v-r-md);
  background:rgba(255,255,255,.028);
  border:1px solid var(--v-line);
}
.v-stat-label{
  display:block;font-family:var(--v-mono);
  font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--text-dim);margin-bottom:7px;
}
.v-stat-value{
  display:block;font-family:var(--head-font);
  font-size:27px;font-weight:700;line-height:1.05;color:var(--text-bright);
  text-transform:uppercase;
  font-variant-numeric:tabular-nums;
}
.v-stat.is-accent .v-stat-value{color:var(--accent-text);text-shadow:0 0 22px rgba(var(--glow),.26)}
/* A short phrase shouldn't be forced onto one line like a figure. */
.v-stat.is-text .v-stat-value{font-size:17px;line-height:1.35}

/* ---- Human-craft counter-trend accents ----
   Deliberately rare (3 spots on the whole homepage) and strictly
   decorative annotation, never the system of record for real UI copy,
   buttons or pricing figures — see the hand-drawn marks themselves for
   why each one is aria-hidden or not. .section's own overflow:hidden
   (above, shared rule) caps how far these can bleed, but the ~14px here
   sits well inside a section's own padding, never near that edge. */
.v-scribble-target{position:relative}
.v-scribble-circle{
  position:absolute;inset:-14px -10px -20px -10px;
  width:calc(100% + 20px);height:calc(100% + 34px);
  fill:none;stroke:var(--accent-text);stroke-width:3;
  stroke-linecap:round;stroke-linejoin:round;
  opacity:.85;pointer-events:none;
}
.v-handnote{
  position:absolute;bottom:-22px;right:2px;
  font-family:'Kalam',cursive;font-weight:700;font-size:14px;
  color:var(--accent-text);transform:rotate(-4deg);
  pointer-events:none;white-space:nowrap;
}
/* Side-anchored variant — for a target with real copy sitting close
   underneath it (the pricing card's price-amount has its price-sub
   paragraph only 6px below), where the default below-anchored note would
   overlap that text. Sits beside the circle at mid-height instead. */
.v-handnote-side{
  bottom:auto;right:auto;top:50%;left:100%;
  margin-left:10px;transform:translateY(-50%) rotate(-3deg);
}
/* In-flow variant — a real signed-off aside after real copy (the origin
   story), not an absolutely-positioned mark tucked against one small
   stat/badge. Not aria-hidden and not pointer-events:none: this one
   carries genuine content, styled as a handwritten note rather than
   decorated the way the other two purely-decorative marks are. */
.v-handnote-signoff{
  position:static;margin-top:16px;
  font-size:17px;transform:rotate(-1.2deg);transform-origin:left center;
  display:inline-block;pointer-events:auto;white-space:normal;
}
/* Self-contained within one .step-num circle, not spanning across grid
   cells to a neighbour — a cross-card connecting arrow would need exact
   pixel positioning that breaks the moment .grid-4 reflows to fewer
   columns at narrower widths. This stays correct at every breakpoint.
   Centred directly beneath the badge as a hand-drawn underline stroke —
   an earlier version sat off to the side as a small detached flick, which
   read as an accidental stray mark with no visible relationship to the
   badge rather than a deliberate annotation, once actually seen live.
   Underlining the number it belongs to reads unambiguously as "on
   purpose" the way the off-side version never did. --accent2, not
   --accent-text (the circle's own border colour), so it still reads as a
   different kind of mark rather than an extension of the badge itself. */
.v-scribble-underline{
  position:absolute;left:50%;top:100%;
  transform:translateX(-50%);margin-top:5px;
  width:28px;height:11px;
  fill:none;stroke:var(--accent2);stroke-width:2;
  stroke-linecap:round;opacity:.7;pointer-events:none;
}

/* ============================================================
   SURFACES — glass cards with a bright top hairline
   ============================================================ */
/* "Frozen glass" pass, 17 Aug 2026 — more translucent fill so the ambient
   glow field behind (body::after, above) actually shows through and gets
   refracted, plus a stronger blur so it reads as frosted rather than just
   dark. Same move the licensing panel's own admin CSS made this session;
   kept here in the two files' shared token/primitive layer so the site and
   the app stay visually one product. Degrades to a solid, still-legible
   card on anything that doesn't support backdrop-filter. */
.card{
  position:relative;
  /* --spot-o defaults to 0 (invisible, free at rest) — site.js sets
     --spot-x/--spot-y/--spot-o on mousemove for a fine-pointer visitor,
     so this radiates from wherever the cursor actually is rather than a
     fixed point. A fourth background layer, same reasoning as the rim
     light below it: applies to every .card automatically, no extra
     element, and paints on top since it's listed first. */
  --spot-x:50%;--spot-y:50%;--spot-o:0;
  /* Liquid-glass rim light as a second background layer, not a pseudo-
     element — .v-hud-card::after already owns ::after (the hover-glow
     ring) on most cards on the site, and a competing .card::after would
     just lose the cascade there silently. A background layer applies
     everywhere .card does, no matter which other classes ride along,
     and needs no extra element. Static, not animated — a light source
     refracting through the glass edge, not another moving thing. */
  background:radial-gradient(240px circle at var(--spot-x) var(--spot-y),rgba(var(--glow),var(--spot-o)),transparent 62%),
    radial-gradient(120% 90% at 8% -6%,rgba(255,255,255,.09),transparent 42%),
    linear-gradient(180deg,rgba(22,26,33,.62),rgba(14,15,20,.74));
  border:1px solid rgba(var(--glow),.16);
  border-radius:var(--v-r-lg);
  box-shadow:0 12px 30px rgba(var(--shadow-rgb),.32),0 0 0 1px rgba(var(--glow),.03),inset 0 0 24px rgba(var(--glow),.025);
  backdrop-filter:blur(18px) saturate(150%);
  -webkit-backdrop-filter:blur(18px) saturate(150%);
  padding:clamp(20px,3vw,28px);
  transition:box-shadow .22s var(--v-ease),border-color .22s var(--v-ease);
}
/* Bright hairline across the top edge — the "lit rim". Slow animated drift
   (17 Aug 2026) so it reads as live rather than a static highlight —
   background-size wider than the element so the gradient has room to move
   without ever showing a hard edge. */
.card::before{
  content:"";position:absolute;left:16px;right:16px;top:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.26),transparent);
  background-size:220% 100%;
  pointer-events:none;
}
@media (prefers-reduced-motion:no-preference){
  .card::before{animation:vHairlineDrift 6s ease-in-out infinite}
}
@keyframes vHairlineDrift{0%,100%{background-position:0% 0}50%{background-position:100% 0}}
.card:hover{
  border-color:rgba(var(--glow),.20);
  box-shadow:0 18px 34px rgba(var(--shadow-rgb),.38),0 0 18px rgba(var(--glow),.07);
}
/* Static cards — nothing to hover-reveal, so they keep one resting shadow. */
.card.is-static:hover{border-color:var(--v-line);box-shadow:var(--v-shadow)}

/* A card that IS a link (the docs index) must not inherit the global
   a:hover underline — text-decoration paints through every descendant, so
   the whole card would underline at once. The border and glow are the
   hover affordance instead. */
a.card,a.card:hover{text-decoration:none;color:inherit}

/* Featured card — accent rim, not a floodlight. Background gradient is
   blue-tinted (cooler than .card's neutral grey) rather than .card's plain
   values, so "featured" still reads as distinct — it used to carry a warm
   reddish tint (rgba(28,20,25,...)) left over from the pre-rebrand red
   palette, missed during the earlier red→blue pass since it's a literal
   RGB triplet, not one of the accent hex constants that pass searched for. */
.v-featured{
  position:relative;overflow:hidden;
  border-color:rgba(var(--glow),.30);
  background:linear-gradient(180deg,rgba(18,24,36,.66),rgba(10,13,20,.8));
  box-shadow:var(--v-glow-lg),var(--v-shadow);
}
.v-featured:hover{
  border-color:rgba(var(--glow),.30);
  box-shadow:var(--v-glow-lg),var(--v-shadow);
}
.v-featured::before{
  background:linear-gradient(90deg,transparent,rgba(var(--glow),.55),transparent);
  background-size:220% 100%;
}
/* Corner accent as a soft radial glow, not a clipped/cut shape — a clipped
   corner near real card content reads as broken or missing content rather
   than a design flourish (the exact lesson the licensing panel's own
   .card-tech learned and documented this session). This never removes a
   pixel of the card, so it can't be mistaken for data loss. */
.v-featured::after{
  content:"";position:absolute;top:0;right:0;width:170px;height:130px;
  pointer-events:none;
  background:radial-gradient(circle at 100% 0%,rgba(var(--glow),.22),transparent 70%);
}

/* ============================================================
   GLOW-CARD — hover effect for the cards a visitor actually chooses
   between (the pricing cards). A neon trace climbs both the left and
   right edges from the bottom on hover/focus, meeting as a brightened top
   rim — real <span> children (.v-glow-edge x2, .v-glow-rim), not ::before/
   ::after, since .v-featured already uses both of those for its own top
   hairline and corner glow. Same component as the webstore's store.css
   (17 Aug 2026) — ported here so a visitor sees one consistent brand
   language whether they're picking a plan on the marketing site or at
   checkout. Respects prefers-reduced-motion throughout.
   ============================================================ */
.v-glow-card{position:relative;overflow:hidden}
.v-glow-edge{
  position:absolute;top:0;bottom:0;width:2px;border-radius:2px;pointer-events:none;z-index:2;
  background:linear-gradient(180deg,transparent,var(--accent2) 65%,var(--accent-text));
  box-shadow:0 0 14px 2px rgba(var(--glow),.7);
  transform-origin:bottom;transform:scaleY(0);opacity:0;
  transition:transform .5s var(--v-ease),opacity .3s var(--v-ease);
}
.v-glow-edge.is-left{left:0}
.v-glow-edge.is-right{right:0}
.v-glow-rim{
  position:absolute;top:0;left:18px;right:18px;height:1px;pointer-events:none;z-index:2;
  background:linear-gradient(90deg,transparent,var(--accent2),transparent);
  opacity:0;transition:opacity .4s var(--v-ease) .12s;
}
.v-glow-card:hover .v-glow-edge,.v-glow-card:focus-within .v-glow-edge{transform:scaleY(1);opacity:1}
.v-glow-card:hover .v-glow-rim,.v-glow-card:focus-within .v-glow-rim{opacity:1}
@media (hover:none){.v-glow-card:hover .v-glow-edge{transform:scaleY(1);opacity:.6}}
@media (prefers-reduced-motion:reduce){
  .v-glow-edge,.v-glow-rim{transition:opacity .2s}
  .v-glow-card:hover .v-glow-edge,.v-glow-card:focus-within .v-glow-edge{transform:none;opacity:.7}
}

/* ============================================================
   BUTTONS — ONE primary CTA
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:13px 24px;min-height:46px;
  border-radius:var(--v-r-pill);
  border:1px solid var(--v-line-strong);
  background:rgba(255,255,255,.04);
  color:var(--text-bright);
  font-family:var(--head-font);
  font-size:15px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  cursor:pointer;text-decoration:none;white-space:nowrap;
  transition:border-color .22s var(--v-ease),background .22s var(--v-ease),transform .22s var(--v-ease);
}
.btn:hover{
  text-decoration:none;
  border-color:rgba(255,255,255,.20);
  background:rgba(255,255,255,.07);
  transform:translateY(-1px);
}
/* :active was unused anywhere on the site — no button, dot or arrow had
   any pressed-state feedback. A small scale-down (the standard pattern
   for this) on every .btn variant, including the CTA gradients, which
   otherwise still have their :hover lift applied on press. */
.btn:active{transform:scale(.97)}
.btn:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}
@media (hover:none){.btn:hover{transform:none}}
@media (prefers-reduced-motion:reduce){.btn:hover{transform:none}}
.v-cta{
  /* var(--cta-grad-end), not var(--accent2) — see the token's own comment
     in :root. accent2 alone drops white-text contrast below a safe margin. */
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));
  border:1px solid rgba(var(--glow),.40);
  border-radius:8px;
  color:var(--text-on-accent);
  box-shadow:0 6px 18px rgba(var(--glow),.22),inset 0 1px 0 rgba(255,255,255,.16);
  transition:box-shadow .22s var(--v-ease),transform .22s var(--v-ease);
}
.v-cta:hover{
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));
  border-color:rgba(var(--glow),.55);
  box-shadow:0 12px 34px rgba(var(--glow),.44),0 0 24px rgba(var(--glow),.24),inset 0 1px 0 var(--tint-5);
  transform:translateY(-1px);
}
@media (hover:none){.v-cta:hover{transform:none}}
@media (prefers-reduced-motion:reduce){.v-cta:hover{transform:none}}
.btn-sm{padding:9px 18px;min-height:44px;font-size:13.5px}
.btn-row{display:flex;flex-wrap:wrap;gap:12px}

/* ============================================================
   SITE LAYOUT
   ============================================================ */
.shell{
  width:100%;max-width:var(--shell);
  margin-inline:auto;
  padding-inline:var(--gutter);
}
/* Bottom padding runs slightly ahead of top — a section whose content sits
   dead-centre between two equal gaps reads as slightly top-heavy once
   there's an eyebrow/heading stack at the top and looser copy at the
   bottom; a few extra percent of breathing room below corrects for that
   optically without being a change anyone consciously notices. */
.section{padding-block:var(--section-y) calc(var(--section-y) * 1.08)}

.section-head{margin-bottom:clamp(28px,4vw,44px)}
.section-head.is-center{text-align:center}
.section-head.is-center .v-eyebrow{margin-bottom:16px}
.section-head h2{font-size:clamp(28px,4.2vw,42px);margin-top:14px}
.section-head .lede{
  margin-top:16px;max-width:62ch;
  font-size:clamp(15.5px,1.6vw,17px);color:var(--text-muted);
}
.section-head.is-center .lede{margin-inline:auto}

.lede{color:var(--text-muted)}
/* Was inline style="...font-size:14.5px..." repeated 40 times across
   index/features/pricing/contact/dilution-calculator — the smaller
   secondary-paragraph size used everywhere a .lede sits inside a card
   rather than under a section heading. */
.lede-sm{font-size:14.5px}

/* ---- Header ----
   A gradient hairline (the same accent-ramp bar used on email templates and
   card tops) replaces the old flat 1px border, and the whole bar responds to
   scroll (site.js adds .is-scrolled) — tighter, more opaque, a visible
   shadow — so the header reads as a live instrument rather than a static
   strip. The nav itself moved from loose floating links to one bordered
   capsule (segmented-control shape), still entirely pill-radius so it
   matches every other pill on the site rather than introducing a new shape
   language of its own. */
.site-header{
  position:sticky;top:0;z-index:100;
  background:rgba(11,14,18,.82);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border-bottom:1px solid var(--v-line);
  transition:background .3s var(--v-ease),box-shadow .3s var(--v-ease),backdrop-filter .3s var(--v-ease);
}
.site-header::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;
  background:linear-gradient(90deg,transparent,var(--accent2) 18%,var(--accent-text) 50%,var(--accent2) 82%,transparent);
  opacity:.55;
}
/* Liquid Glass, textbook use case ("navigation... system-aligned chrome") —
   the nav is the one surface on the site closest to a real OS material:
   deepens blur+saturate once there's real content behind it to lens,
   instead of a flat opacity/shadow change alone. */
.site-header.is-scrolled{
  background:rgba(9,11,15,.94);
  box-shadow:0 12px 30px rgba(var(--shadow-rgb),.36);
  -webkit-backdrop-filter:blur(22px) saturate(160%);backdrop-filter:blur(22px) saturate(160%);
}
.header-inner{
  position:relative;
  display:flex;align-items:center;gap:18px;
  min-height:var(--header-h);
}
.brand{display:flex;align-items:center;flex:0 0 auto}
.brand img{height:56px;width:auto;transition:height .25s var(--v-ease)}
.brand:hover{text-decoration:none}
.site-header.is-scrolled .brand img{height:46px}

.nav{
  margin-left:auto;display:flex;align-items:center;gap:2px;
  padding:5px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line);
  background:rgba(255,255,255,.025);
}
.nav a{
  padding:8px 14px;border-radius:var(--v-r-pill);
  color:var(--text-muted);
  font-size:14px;font-weight:500;
  transition:color .18s var(--v-ease),background .18s var(--v-ease),border-color .18s var(--v-ease);
}
.nav a:hover{color:var(--text-bright);background:var(--tint-2);text-decoration:none}
.nav a:active{background:var(--tint-4)}
.nav a[aria-current="page"]{color:var(--accent-text);background:rgba(var(--glow),.10)}

/* The dilution calculator is a free standalone tool, not a site section —
   giving it its own accent-bordered pill (instead of blending into the flat
   link row) signals "try this, no signup" the way the other five links
   shouldn't have to. */
.nav a.is-tool{
  display:inline-flex;align-items:center;gap:6px;
  color:var(--accent-text);
  border:1px solid rgba(var(--glow),.28);
  background:rgba(var(--glow),.07);
}
.nav a.is-tool:hover{background:rgba(var(--glow),.14);border-color:rgba(var(--glow),.45)}
.nav a.is-tool svg{width:14px;height:14px;flex:0 0 auto}
.nav a[aria-current="page"].is-tool{background:rgba(var(--glow),.16);border-color:rgba(var(--glow),.5)}

.header-cta{flex:0 0 auto}

.nav-toggle{
  display:none;
  margin-left:auto;
  width:44px;height:44px;flex:0 0 auto;
  align-items:center;justify-content:center;
  border-radius:var(--v-r-sm);
  border:1px solid var(--v-line-strong);
  background:rgba(255,255,255,.04);
  cursor:pointer;
}
.nav-toggle span{
  display:block;width:18px;height:2px;border-radius:2px;
  background:var(--text-bright);position:relative;
}
.nav-toggle span::before,.nav-toggle span::after{
  content:"";position:absolute;left:0;width:18px;height:2px;border-radius:2px;
  background:var(--text-bright);
}
.nav-toggle span::before{top:-6px}
.nav-toggle span::after{top:6px}

@media (max-width:900px){
  .nav-toggle{display:inline-flex}
  .nav,.header-cta{
    display:none;
  }
  .site-header.is-open .nav{
    display:flex;flex-direction:column;align-items:stretch;gap:2px;
    order:3;width:100%;margin:0;
    padding:10px 0 16px;
    border:0;border-top:1px solid var(--v-line);
    border-radius:0;background:none;
  }
  .site-header.is-open .nav a{padding:12px 14px;font-size:15.5px;border-radius:var(--v-r-sm)}
  .site-header.is-open .nav a.is-tool{justify-content:flex-start}
  .site-header.is-open .header-cta{
    display:flex;order:4;width:100%;padding-bottom:16px;
  }
  .site-header.is-open .header-cta .btn{width:100%}
  .header-inner{flex-wrap:wrap}
}

/* ---- Footer ---- */
.site-footer{
  position:relative;overflow:hidden;
  border-top:1px solid var(--v-line);
  background:rgba(5,5,7,.7);
  /* Bottom padding is generous on purpose: .v-to-top is fixed at
     left:18px/bottom:18px on every page, and the footer is the one
     section guaranteed to sit at the true bottom of the document — without
     this clearance the button sits directly on top of the copyright line
     once the page is scrolled all the way down. */
  padding-block:clamp(48px,6vw,72px) 84px;
  margin-top:var(--section-y);
}
/* Same gradient hairline the header carries, mirrored — the two ends of
   the page read as one system instead of the footer just being "where the
   colour stops". A faint mesh echo (reusing the hero's own blue ramp, far
   dimmer) keeps it from feeling like a flat grey slab underneath. */
.site-footer::before{
  content:"";position:absolute;left:0;right:0;top:-1px;height:1px;
  background:linear-gradient(90deg,transparent,var(--accent2) 18%,var(--accent-text) 50%,var(--accent2) 82%,transparent);
  opacity:.4;
}
.site-footer::after{
  content:"";position:absolute;z-index:0;pointer-events:none;
  inset:-20% -10% auto -10%;height:120%;
  background:radial-gradient(30% 60% at 12% 0%,rgba(31,108,255,.06),transparent 70%),
    radial-gradient(24% 50% at 88% 10%,rgba(0,82,255,.05),transparent 72%);
  filter:blur(26px);
}
.site-footer > .shell{position:relative;z-index:1}

/* Narrow first: the brand takes a full row and the link columns flow beneath
   it. The previous `minmax(220px,1.4fr) repeat(auto-fit,minmax(150px,1fr))`
   had a 220+150+gap floor of ~400px, which no track could shrink below — so
   the footer pushed every page into horizontal overflow on a phone. */
.footer-grid{
  display:grid;gap:34px 26px;
  grid-template-columns:repeat(auto-fit,minmax(min(150px,100%),1fr));
}
.footer-brand{grid-column:1/-1}
@media (min-width:780px){
  .footer-grid{grid-template-columns:1.6fr repeat(3,minmax(0,1fr))}
  .footer-brand{grid-column:auto}
}
.footer-brand img{height:46px;margin-bottom:16px}
.footer-brand p{font-size:14px;color:var(--text-muted);max-width:34ch;line-height:1.6}
.footer-col h3{
  display:flex;align-items:center;gap:8px;
  font-size:12.5px;letter-spacing:.16em;color:var(--text-dim);
  font-family:var(--v-mono);font-weight:600;margin-bottom:16px;
}
.footer-col h3::before{
  content:"";width:12px;height:1px;flex:0 0 auto;
  background:linear-gradient(90deg,var(--accent2),transparent);
}
.footer-col ul{list-style:none;margin:0;padding:0;display:grid;gap:11px}
.footer-col a{
  display:inline-flex;align-items:center;gap:0;
  color:var(--text-muted);font-size:14px;
  transition:color .18s var(--v-ease),gap .18s var(--v-ease);
}
.footer-col a:hover{color:var(--text-bright);gap:7px}
.footer-col a::before{
  content:"";width:0;height:1px;
  background:var(--accent2);
  transition:width .18s var(--v-ease);
}
.footer-col a:hover::before{width:10px}
.footer-base{
  margin-top:40px;padding-top:22px;
  border-top:1px solid var(--v-line);
  display:flex;flex-wrap:wrap;gap:10px 20px;justify-content:space-between;
  align-items:center;
  font-size:13px;color:var(--text-dim);
}

/* ============================================================
   HERO
   ============================================================ */
/* Trimmed 21 Aug 2026 — the hero (especially with .hero-hud-frame's own
   padding added on top) was tall enough that the CTA buttons sat below
   the fold on a typical laptop screen, needing a scroll to even see
   them. Less top/bottom section padding, and see the h1/lede/btn-row
   margins below for the rest of the trim. */
.hero{padding-block:clamp(32px,5vw,64px) clamp(28px,4vw,48px)}
.hero-inner{max-width:820px}

/* .is-split overrides the single-column max-width with a two-column grid.
   Base .hero-inner rule above still applies to any hero markup reused
   without .is-split. Ratio widened from 1.05fr/1fr — a narrower text
   column was wrapping the h1 onto an extra line, adding real height for
   no benefit; the panel column shrank to match (see .hero-bg-visual). */
.hero-inner.is-split{
  max-width:var(--shell);
  display:grid;
  grid-template-columns:minmax(0,1.3fr) minmax(0,.85fr);
  align-items:center;
  gap:clamp(28px,4vw,56px);
}
.hero h1{
  font-size:clamp(38px,7vw,74px);
  margin-top:14px;
  /* 1.06 was tight enough that the gradient-clipped accent span
     (background-clip:text) could crop the descender off a letter like the
     "g" in "guessing" — the gradient fill only paints within the glyph's
     own line box, and a line-height this tight doesn't leave that box
     enough room below the baseline. 1.14 keeps the same punchy, tight
     headline feel without cutting descenders. */
  line-height:1.14;
  letter-spacing:0;
}
.hero .lede{
  margin-top:14px;max-width:58ch;
  font-size:clamp(16px,1.9vw,19px);
}
.hero .btn-row{margin-top:20px}
.hero-note{
  margin-top:12px;font-size:13.5px;color:var(--text-dim);
}

/* Gradient-wash emphasis span inside the H1 — white fading to the bright
   accent tint across the emphasised words, plus a soft drop-shadow glow so
   it reads as lit rather than just recoloured. background-clip:text needs a
   transparent fill colour to show the gradient through the glyphs; the glow
   uses filter:drop-shadow (not box-shadow, which a fill:transparent element
   has none of) so it follows the actual letterforms. Kept separate from
   .v-accent-text (plain colour, reused for stat values, where a gradient
   would be visual noise on a short figure). Italic removed — the bigger,
   bolder H1 this pairs with reads more confident set upright. */
.hero h1 .v-accent-em{
  background:linear-gradient(90deg,var(--text-bright),var(--accent-text) 70%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;
  filter:drop-shadow(0 0 26px rgba(var(--glow),.28));
}

/* Interior pages use the same landing-zone confidence as the homepage, but
   without turning their headings into another card. The quiet left rule gives
   a non-split hero a consistent editorial anchor without a bordered box. */
.hero-inner:not(.is-split){
  position:relative;
  padding-left:28px;
  border-left:1px solid var(--tint-4);
}
.hero-inner:not(.is-split)::before{
  content:"";position:absolute;left:-1px;top:0;width:1px;height:56px;
  background:var(--accent2);box-shadow:0 0 14px rgba(var(--glow),.38);
}
.hero-inner:not(.is-split) .v-eyebrow{margin-top:1px}
@media (max-width:560px){
  .hero-inner:not(.is-split){padding-left:18px}
}

/* Same quiet-left-rule treatment, applied directly to a .hero-col instead
   of .hero-inner — for a SPLIT hero's text column on a page that used to
   wrap it in .hero-hud-frame (the bordered card + "Live" corner tag).
   .hero-hud-frame is index.html's one signature moment now; repeating an
   identical bordered card on every other page's hero was the actual "same
   component, different words" problem — the four pages' right-hand panels
   (dashboard mock, plan gauges, system map, setup checklist) were already
   distinct from each other, but the framed text column on the left never
   was. Features/Pricing/Contact use this instead: same tokens, same
   restraint, no repeated box. */
.hero-col.is-unframed{
  position:relative;
  padding-left:28px;
  border-left:1px solid var(--tint-4);
}
.hero-col.is-unframed::before{
  content:"";position:absolute;left:-1px;top:0;width:1px;height:56px;
  background:var(--accent2);box-shadow:0 0 14px rgba(var(--glow),.38);
}
.hero-col.is-unframed .v-eyebrow{margin-top:1px}
@media (max-width:560px){
  .hero-col.is-unframed{padding-left:18px}
}

@media (max-width:880px){
  .hero-inner.is-split{
    grid-template-columns:minmax(0,1fr);
  }
  .hero-col-panel{order:2}
  .hero-bg-visual{max-width:380px;height:270px;opacity:.55}
}

/* ============================================================
   GRIDS
   ============================================================ */
.grid{display:grid;gap:clamp(14px,2vw,20px)}
.grid-2{grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr))}
/* A dead-even 50/50 split reads as a template regardless of what's in the
   two columns — the origin-story section pairs a longer prose column with
   a short checklist card, so giving the copy the wider half matches what's
   actually in each column instead of forcing them to match each other. */
@media (min-width:780px){
  .grid-2.is-editorial{grid-template-columns:1.15fr .85fr}
}
.grid-3{grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
.grid-4{grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr))}
/* A grid whose item count doesn't divide evenly into however many
   columns auto-fit lands on at a given width strands the remainder on
   its own row, off-centre (confirmed: a 5-card .grid-4 renders 4+1 at
   1280px and 2+2+1 at 768px). auto-fit can't see the item count to
   correct for this itself, so grids that can't guarantee an even
   remainder opt into flex+wrap+center instead, which handles any
   leftover row cleanly regardless of count. */
.grid.is-odd-wrap{display:flex;flex-wrap:wrap;justify-content:center}
.grid.is-odd-wrap > *{flex:1 1 230px;max-width:340px}

/* ---- Problem grid — 3 equal cards read as the single most common
   AI-SaaS feature-row layout, especially as the first thing under the
   hero. One wide lead card (icon beside the text, not above it) plus two
   narrower supporting cards below breaks that silhouette without
   inventing a new card component — same .feature-card markup throughout,
   only the wrapping grid and the lead card's internal layout change. ---- */
.v-problem-grid{grid-template-columns:repeat(2,1fr)}
/* The lead card's three children (icon span, h3, p) are plain siblings in
   the markup, not pre-wrapped into an icon column + text column — grid-
   template-areas needs a named area per child to place them, which needs
   more than a bare selector, so each child gets its own explicit
   grid-row/grid-column instead of relying on auto-placement (which would
   otherwise fill the 2-column track left-to-right, top-to-bottom: icon,
   then h3 beside it, then p wrapping to a new row under the icon alone —
   exactly the broken "heading floats away from its icon" layout that
   auto-placement produces here). */
.v-problem-grid > *:first-child{
  grid-column:1/-1;
  display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;
  column-gap:20px;text-align:left;
}
.v-problem-grid > *:first-child .v-icon-tile{
  grid-column:1;grid-row:1/3;align-self:start;
  width:52px;height:52px;border-radius:14px;
}
.v-problem-grid > *:first-child .v-icon-tile svg{width:24px;height:24px}
.v-problem-grid > *:first-child h3{grid-column:2;grid-row:1;font-size:23px}
.v-problem-grid > *:first-child p{grid-column:2;grid-row:2}
@media (max-width:720px){
  .v-problem-grid{grid-template-columns:1fr}
  .v-problem-grid > *:first-child{grid-template-columns:1fr;gap:14px}
  .v-problem-grid > *:first-child .v-icon-tile{grid-column:1;grid-row:1}
  .v-problem-grid > *:first-child h3{grid-column:1;grid-row:2}
  .v-problem-grid > *:first-child p{grid-column:1;grid-row:3}
}

/* ---- Pillar grid — the 5-card equivalent of the same problem. A 12-track
   bento: row one is two wide cards (7/5 split, not an even 6/6 — a dead-
   even split would just be the same generic grid at a different card
   count), row two is three even cards. Reflows to one column below the
   width a 12-track split can read as anything but cramped. ---- */
.v-pillar-grid{grid-template-columns:repeat(12,1fr)}
/* position:relative only — no overflow:hidden here, .card's own hover glow
   (.v-icon-tile's box-shadow on :hover) can extend a few px past its own
   box within the card's padding, and clipping it flush would blunt that
   effect for no reason; the numeral below only needs a positioning
   context, not a clip. */
.v-pillar-grid > *{grid-column:span 12;position:relative}
@media (min-width:720px){
  .v-pillar-grid > *:nth-child(1){grid-column:span 7}
  .v-pillar-grid > *:nth-child(2){grid-column:span 5}
  .v-pillar-grid > *:nth-child(3){grid-column:span 4}
  .v-pillar-grid > *:nth-child(4){grid-column:span 4}
  .v-pillar-grid > *:nth-child(5){grid-column:span 4}
}
/* A large, faint mono numeral in the corner — a numbered-manifest cue
   instead of a sixth generic icon-in-a-box, and it reinforces "five
   things" as a counted list rather than an arbitrary card count.
   aria-hidden in the markup; --glow at low alpha so it reads as an
   engraving on the glass, not a competing headline. */
.v-pillar-num{
  position:absolute;top:6px;right:14px;
  font-family:var(--v-mono);font-weight:600;font-size:44px;line-height:1;
  color:rgba(var(--glow),.14);
  pointer-events:none;
}

/* Feature card
   Sentence case here, not the sitewide uppercase h1-h4 default — h1/h2 are
   the loud, HUD-branded moment on a page; every card headline uppercase too
   made the whole page shout at the same volume. Contrast, not consistency,
   is what reads as considered typography once there's a display headline
   this size already on the page. Kept scoped to the card components below
   rather than un-uppercasing h3/h4 sitewide, since the docs pages use h3/h4
   for real document structure and rely on the sitewide rule. */
.feature-card h3,.step-card h3,.v-feature-row h3,.split-card h3{
  text-transform:none;letter-spacing:-.01em;
}
.feature-card h3{font-size:20px;margin:16px 0 10px}
.feature-card p{font-size:14.5px;color:var(--text-muted)}
.feature-card .card-link{
  display:inline-block;margin-top:14px;
  font-size:13px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  font-family:var(--head-font);
  color:var(--accent-text);
  transition:color .18s var(--v-ease);
}
/* Previously relied entirely on the generic a:hover{text-decoration:
   underline} — a "see more" affordance is worth a colour shift of its
   own, not just an underline indistinguishable from body-copy links. */
.feature-card .card-link:hover{color:var(--text-bright)}

/* ---- Feature rows — individual cards, one line of context each ----
   Previously a dense shared-border table of bare icon+label pairs — flat,
   and (per the corner-hover fix elsewhere in this file) a table of welded-
   together cells reads even more "square" than individual rounded cards
   do. Kept the one instinct worth keeping from that version: no per-card
   sentence-plus-link, which was the "wall of cards" this was originally
   built to avoid — one short phrase of real context is enough to scan at a
   glance without repeating features.html's own fuller copy. */
.v-feature-rows{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr));
  gap:clamp(12px,1.6vw,16px);
}
.v-feature-row{
  display:flex;flex-direction:column;align-items:flex-start;gap:12px;
  padding:20px 20px 22px;
  background:var(--v-glass-2);
  border:1px solid var(--v-line);
  border-radius:var(--v-r-lg);
  color:var(--text-main);
  transition:background .18s var(--v-ease),border-color .18s var(--v-ease),
    transform .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-feature-row:hover{
  background:rgba(var(--glow),.055);border-color:rgba(var(--glow),.22);
  text-decoration:none;color:var(--text-bright);
  transform:translateY(-3px);
  box-shadow:0 14px 28px rgba(var(--shadow-rgb),.32);
}
@media (hover:none){.v-feature-row:hover{transform:none}}
.v-feature-row:active{transform:translateY(-1px) scale(.98)}
.v-feature-row .v-icon-tile{width:38px;height:38px;border-radius:11px;flex:0 0 auto}
.v-feature-row .v-icon-tile svg{width:18px;height:18px}
.v-feature-row h3{font-size:15.5px;font-weight:700;margin:0;color:inherit;letter-spacing:-.01em}
.v-feature-row p{font-size:13px;color:var(--text-muted);margin:0;line-height:1.5}
.v-feature-row:hover .v-icon-tile{
  background:rgba(var(--glow),.18);border-color:rgba(var(--glow),.4);
  box-shadow:0 0 16px rgba(var(--glow),.28);
}

/* Numbered step */
.step-card{position:relative}
.step-num{
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:50%;
  background:rgba(var(--glow),.12);
  border:1px solid rgba(var(--glow),.30);
  color:var(--accent-text);
  font-family:var(--v-mono);font-size:14px;font-weight:600;
}
.step-card h3{font-size:19px;margin:15px 0 9px}
.step-card p{font-size:14.5px;color:var(--text-muted)}

/* ============================================================
   PRODUCT SHOWCASE — animated screenshot carousel
   ============================================================
   Replaces the old empty placeholder frames (assets/img/screens/README.txt
   has the earlier spec). No live/staging instance was available to capture
   real screens from this pass, so each slide is an illustrative mockup
   built from the same component language the actual admin panel uses
   (.v-mock-*, below) rather than a generic stock screenshot — aria-hidden,
   since the module names and what they do are already carried in real text
   (the caption, the section copy). Swapping a real capture in later means
   replacing one .v-showcase-screen's contents with an <img>; the frame,
   glow and carousel logic around it don't change.
   ============================================================ */
.v-showcase-wrap{
  position:relative;max-width:900px;margin:44px auto 0;
  padding:56px 8px 8px;
}
/* Outer aura — the soft glow field the whole frame floats in. */
.v-showcase-aura{
  position:absolute;inset:-14% -8% -4%;z-index:-2;pointer-events:none;
  background:radial-gradient(55% 60% at 50% 40%,rgba(var(--glow),.24),transparent 72%);
  filter:blur(48px);
}
@media (prefers-reduced-motion:no-preference){
  .v-showcase-aura{animation:vAmbient 8s ease-in-out infinite alternate}
}
/* Orbit ring — a slow conic sweep tracing the frame's edge. Same technique
   as .v-feature-sweep (a rotating conic-gradient), reshaped with a mask
   into a ring rather than a filled disc, so it reads as energy tracing the
   frame rather than a spinning badge behind it. */
.v-showcase-orbit{
  position:absolute;inset:38px 8px 0;z-index:-1;pointer-events:none;
  border-radius:24px;padding:1.5px;
  background:conic-gradient(from 0deg,transparent 0deg,rgba(var(--glow),.65) 26deg,transparent 90deg,transparent 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude;
}
@media (prefers-reduced-motion:no-preference){
  .v-showcase-orbit{animation:vFeatureSweep 6.5s linear infinite}
}
/* Light beam underneath — the frame reads as floating above a light
   source, same idea as the reference's own horizon glow. */
.v-showcase-beam{
  position:absolute;left:50%;bottom:-2%;z-index:-1;pointer-events:none;
  width:76%;height:140px;transform:translateX(-50%);
  background:radial-gradient(50% 100% at 50% 0%,rgba(var(--glow),.32),transparent 76%);
  filter:blur(22px);
}

.v-showcase-frame{
  position:relative;overflow:hidden;
  border-radius:20px;
  border:1px solid rgba(var(--glow),.26);
  background:linear-gradient(180deg,rgba(20,24,31,.88),rgba(9,10,14,.95));
  box-shadow:0 44px 100px rgba(0,0,0,.58),inset 0 1px 0 rgba(255,255,255,.07);
  backdrop-filter:blur(20px) saturate(140%);-webkit-backdrop-filter:blur(20px) saturate(140%);
}
/* Liquid-glass sheen — the site's flagship glass surface, so it's the one
   place the "light passing through glass" cue actually moves rather than
   sitting static. Fires as ONE brief sweep exactly when the slide changes
   (site.js toggles .is-sweeping in the same show() call that swaps the
   active screen and fires vShowcaseScan below) rather than looping on its
   own untethered clock — an earlier version ran on a free-running 9s loop
   against a 5s auto-advance, so the two "light" cues drifted in and out of
   phase and would occasionally collide mid-slide-fade, reading as a glitch
   rather than a deliberate effect. overflow:hidden on the frame above clips
   it to the rounded corners for free. Covers the whole frame including
   .v-showcase-foot's dots/arrows underneath it, but pointer-events:none
   means it never intercepts their clicks regardless of stacking order. */
.v-showcase-frame::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(115deg,transparent 40%,rgba(255,255,255,.07) 48%,rgba(255,255,255,.14) 50%,rgba(255,255,255,.07) 52%,transparent 60%);
  background-size:260% 260%;background-position:-40% -40%;
  opacity:0;
}
@media (prefers-reduced-motion:no-preference){
  .v-showcase-frame.is-sweeping::after{animation:vGlassSheen 1.1s var(--v-ease) 1 both}
}
@keyframes vGlassSheen{
  0%{background-position:-40% -40%;opacity:0}
  15%{opacity:1}
  100%{background-position:40% 40%;opacity:0}
}
.v-showcase-chrome{
  display:flex;align-items:center;gap:10px;
  padding:14px 20px;border-bottom:1px solid var(--v-line);
  background:rgba(255,255,255,.025);
}
.v-showcase-live-label{
  font-family:var(--v-mono);font-size:10.5px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-dim);
}
.v-showcase-screen-name{
  margin-left:auto;font-family:var(--v-mono);font-size:11px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--accent-text);
}
/* A real, ticking clock — the one honest "this is live" signal a mockup can
   carry without pretending to be a real screenshot. Same technique as the
   hero's own .v-status-panel-time (site.js's "showcase-clock" entry). */
.v-showcase-time{
  padding-left:12px;font-family:var(--v-mono);font-size:11px;
  letter-spacing:.06em;color:var(--text-dim);
}
.v-showcase-counter{
  font-family:var(--v-mono);font-size:11px;letter-spacing:.06em;color:var(--text-dim);
  padding-left:12px;border-left:1px solid var(--v-line);
}
.v-showcase-counter strong{color:var(--text-bright);font-weight:600}

/* A cropped "the meaningful UI, not the whole laptop screen" ratio rather
   than a full 16:10 — real product marketing shots are usually framed this
   tight too, and it's a better match for how much a mock screen actually
   has to show. */
.v-showcase-screens{position:relative;aspect-ratio:16/8.5}
.v-showcase-screen{
  position:absolute;inset:0;
  opacity:0;visibility:hidden;
  transition:opacity .5s var(--v-ease);
}
.v-showcase-screen.is-active{opacity:1;visibility:visible}
@media (prefers-reduced-motion:reduce){.v-showcase-screen{transition:none}}

/* On-cycle scan-line — fires once, briefly, whenever a screen becomes
   active (including the first, on load), the "materialising" beat. */
/* Resting state is invisible — the animation below is what reveals it, for
   its one brief pass. Without this, prefers-reduced-motion (which the media
   query further down never lets the animation apply under) left it sitting
   at its plain CSS position: fully opaque, permanently, over the real
   screen content it's meant to sweep across once and disappear from. */
.v-showcase-screen::after{
  content:"";position:absolute;inset:0;z-index:6;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(var(--glow),.55) 48%,transparent);
  opacity:0;
}
@media (prefers-reduced-motion:no-preference){
  .v-showcase-screen.is-active::after{animation:vShowcaseScan .9s var(--v-ease) 1 both}
}
@keyframes vShowcaseScan{
  0%{transform:translateY(-110%);opacity:0}
  18%{opacity:1}
  100%{transform:translateY(460%);opacity:0}
}

.v-showcase-foot{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:13px 18px;border-top:1px solid var(--v-line);
  background:rgba(255,255,255,.02);
}
.v-showcase-dots{display:flex;gap:8px}
.v-showcase-dot{
  width:7px;height:7px;border-radius:50%;padding:0;border:none;cursor:pointer;
  background:rgba(255,255,255,.16);
  transition:background .2s var(--v-ease),box-shadow .2s var(--v-ease),transform .2s var(--v-ease);
}
.v-showcase-dot:hover{transform:scale(1.2)}
.v-showcase-dot:active{transform:scale(.9)}
.v-showcase-dot.is-active{background:var(--accent2);box-shadow:0 0 9px rgba(var(--glow),.68)}
.v-showcase-arrows{display:flex;gap:8px}
.v-showcase-arrow{
  display:inline-flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:50%;flex:0 0 auto;
  border:1px solid var(--v-line-strong);background:rgba(255,255,255,.04);
  color:var(--text-bright);cursor:pointer;
  transition:border-color .2s var(--v-ease),background .2s var(--v-ease),transform .2s var(--v-ease);
}
.v-showcase-arrow:hover{border-color:rgba(var(--glow),.42);background:rgba(var(--glow),.10)}
.v-showcase-arrow:active{transform:scale(.9)}
.v-showcase-arrow svg{width:15px;height:15px}
@media (max-width:560px){
  .v-showcase-wrap{padding-top:44px}
  .v-showcase-screen-name{display:none}
  .v-showcase-counter{padding-left:0;border-left:none}
  .v-showcase-pips,.v-showcase-counter[data-live="showcase-uptime"]{display:none}
}

/* ---- Mockup screens ----
   One shared app shell (sidebar + main), three different main contents —
   reads as the same real product across all three, not three unrelated
   graphics. Every value shown is sample data, kept close to what the
   surrounding site copy already uses (Range Rover Sport et al.). */
.v-mock-app{
  position:absolute;inset:0;display:grid;grid-template-columns:60px minmax(0,1fr);
  /* Owns the clip now — .hero-bg-visual (the parent) dropped its own
     overflow:hidden so the floating second-card layer behind it isn't
     cut off. This still needs to be clipped to the rounded shape. */
  border-radius:inherit;overflow:hidden;
}
.v-mock-sidebar{
  background:var(--tint-1);border-right:1px solid var(--v-line);
  display:flex;flex-direction:column;align-items:center;gap:13px;padding:16px 0;
}
.v-mock-sidebar span{
  width:28px;height:28px;border-radius:8px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.05);color:var(--text-dim);
}
.v-mock-sidebar span.is-active{
  background:rgba(var(--glow),.18);color:var(--accent-text);
  box-shadow:0 0 14px rgba(var(--glow),.32);
}
.v-mock-sidebar svg{width:15px;height:15px}
.v-mock-main{padding:18px 22px;overflow:hidden;min-width:0;display:flex;flex-direction:column;gap:14px}
.v-mock-label{
  display:block;margin-bottom:7px;font-family:var(--v-mono);
  font-size:9px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--text-dim);
}
.v-mock-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
.v-mock-stat{padding:10px 11px;border-radius:9px;background:var(--tint-1);border:1px solid var(--v-line)}
.v-mock-stat strong{display:block;font-family:var(--head-font);font-size:19px;color:var(--text-bright);letter-spacing:.02em}
.v-mock-rows{display:grid;gap:7px}
.v-mock-row{
  display:flex;align-items:center;gap:9px;
  padding:9px 11px;border-radius:8px;
  background:rgba(255,255,255,.02);border:1px solid var(--v-line);
  font-size:11px;
  transition:opacity .26s var(--v-ease);
}
.v-mock-row .v-mock-dot{width:6px;height:6px;border-radius:50%;background:var(--accent2);box-shadow:0 0 6px rgba(var(--glow),.6);flex:0 0 auto}
.v-mock-row b{color:var(--text-bright);font-weight:600}
.v-mock-row span{color:var(--text-dim);margin-left:auto;font-family:var(--v-mono);font-size:9.5px;white-space:nowrap}
.v-mock-chip-row{display:flex;gap:7px;flex-wrap:wrap}
.v-mock-chip{padding:6px 12px;border-radius:999px;border:1px solid var(--v-line);font-size:10.5px;color:var(--text-muted);white-space:nowrap}
.v-mock-chip.is-selected{border-color:rgba(var(--glow),.42);background:rgba(var(--glow),.11);color:var(--accent-text)}
.v-mock-field{padding:10px 12px;border-radius:8px;background:var(--tint-1);border:1px solid var(--v-line);font-size:10.5px;color:var(--text-dim)}
.v-mock-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.v-mock-btn{
  align-self:flex-start;margin-top:auto;padding:9px 18px;border-radius:999px;
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));
  color:var(--text-on-accent);font-family:var(--head-font);font-size:11.5px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;
  box-shadow:0 6px 16px rgba(var(--glow),.28);
}
.v-mock-gallery{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.v-mock-thumb{aspect-ratio:1;border-radius:8px;background:linear-gradient(135deg,rgba(var(--glow),.16),var(--tint-1));border:1px solid var(--v-line)}
@media (max-width:640px){
  /* The 16:8.5 aspect-ratio that fits five rows of content at 900px+ frame
     widths shrinks to well under 200px tall once the frame itself is only
     ~330px wide on a phone — nowhere near enough room, and .v-showcase-frame's
     own overflow:hidden was silently cropping the bottom of every mock
     screen rather than erroring. A fixed min-height (content-driven, not
     ratio-driven) fixes it; .v-showcase-screen's children are absolutely
     positioned, so min-height alone is enough to establish the parent's
     actual height here. */
  .v-showcase-screens{aspect-ratio:auto;min-height:430px}
  .v-mock-app{grid-template-columns:44px minmax(0,1fr)}
  .v-mock-sidebar{gap:9px;padding:12px 0}
  .v-mock-sidebar span{width:22px;height:22px;border-radius:6px}
  .v-mock-sidebar svg{width:12px;height:12px}
  .v-mock-main{padding:13px 15px;gap:10px}
  .v-mock-stats{gap:6px}
  .v-mock-stat{padding:7px 8px}
  .v-mock-stat strong{font-size:15px}
  .v-mock-row{padding:7px 9px;font-size:9.5px}
  .v-mock-gallery{grid-template-columns:repeat(4,1fr);gap:5px}
}

/* ============================================================
   SPLIT — two-column comparison
   ============================================================ */
.split{display:grid;gap:clamp(14px,2vw,20px);grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
.split-card h3{font-size:22px;margin:16px 0 6px}
.split-card .split-sub{font-size:13px;color:var(--accent-text);font-family:var(--v-mono);letter-spacing:.06em}
.split-card ul{margin:18px 0 0;padding:0;list-style:none;display:grid;gap:11px}
.split-card li{
  position:relative;padding-left:22px;font-size:14.5px;color:var(--text-muted);
}
.split-card li::before{
  content:"";position:absolute;left:0;top:.62em;
  width:7px;height:7px;border-radius:50%;
  background:var(--accent2);box-shadow:0 0 8px rgba(var(--glow),.5);
}

/* ============================================================
   PRICING
   ============================================================ */
.price-wrap{max-width:520px;margin-inline:auto}
.price-card{text-align:center}
.price-amount{
  display:flex;align-items:baseline;justify-content:center;gap:4px;
  margin:18px 0 6px;
  font-family:var(--head-font);font-weight:700;
  font-size:clamp(48px,9vw,68px);line-height:1;
  color:var(--text-bright);
  text-shadow:0 0 30px rgba(var(--glow),.22);
  font-variant-numeric:tabular-nums;
}
.price-amount .price-per{
  font-family:var(--body-font);font-size:16px;font-weight:500;
  text-transform:none;letter-spacing:0;color:var(--text-muted);
}
.price-sub{font-size:14px;color:var(--text-muted)}
.price-card .v-check-list{margin-top:18px;text-align:left}
.price-card .btn-row{margin-top:18px;justify-content:center}
.price-foot{margin-top:18px;font-size:13px;color:var(--text-dim)}

/* Price teaser strip on the home page */
.price-teaser{text-align:center}
.price-teaser .price-amount{margin-top:14px}

/* Pricing needs comparison clarity more than decorative treatment. Equal
   columns and a restrained marker make the recommended plan easy to scan. */
.grid-3 > .price-card{display:flex;flex-direction:column;min-height:100%}
.price-card > .v-eyebrow{align-self:center}
.price-card{border-radius:12px}
.price-card.v-featured{border-color:rgba(var(--glow),.38);box-shadow:0 16px 36px rgba(var(--shadow-rgb),.36),0 0 0 1px rgba(var(--glow),.09)}
.price-card .v-check-list{text-align:left}
.price-card .btn-row{margin-top:auto;padding-top:18px}

/* ============================================================
   FAQ — native <details>, keyboard-accessible for free
   ============================================================ */
.faq{display:grid;gap:12px;max-width:820px;margin-inline:auto}
.faq details{
  border:1px solid var(--v-line);
  border-radius:var(--v-r-md);
  background:linear-gradient(180deg,rgba(23,23,29,.82),rgba(16,16,21,.88));
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:var(--v-shadow);
  overflow:hidden;
  transition:border-color .22s var(--v-ease),box-shadow .22s var(--v-ease);
}
.faq details[open]{
  border-color:rgba(var(--glow),.32);
  box-shadow:0 20px 46px rgba(var(--shadow-rgb),.5),0 0 34px rgba(var(--glow),.18);
}
.faq summary{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:17px 20px;cursor:pointer;list-style:none;
  font-family:var(--head-font);font-size:18px;font-weight:700;
  text-transform:uppercase;letter-spacing:.01em;color:var(--text-bright);
  transition:background .18s var(--v-ease);
}
.faq summary::-webkit-details-marker{display:none}
/* A real chevron that rotates, not a "+"/"−" glyph swap — the swap left
   .faq summary::after's own transition:transform declaration dead code,
   since nothing ever set a transform for it to animate. Drawn from two
   borders on an empty box (the standard CSS chevron) so rotation is an
   actual transform the transition below can tween smoothly. */
.faq summary::after{
  content:"";flex:0 0 auto;
  width:9px;height:9px;
  border-right:2px solid var(--accent-text);
  border-bottom:2px solid var(--accent-text);
  transform:rotate(45deg);
  transition:transform .28s var(--v-ease);
}
.faq details[open] summary::after{transform:rotate(225deg)}
@media (prefers-reduced-motion:reduce){.faq summary::after{transition:none}}
.faq summary:hover{background:rgba(255,255,255,.035)}
.faq summary:active{background:rgba(255,255,255,.05)}
.faq .faq-body{padding:0 20px 19px;font-size:15px;color:var(--text-muted)}
.faq .faq-body p + p{margin-top:12px}
.faq .faq-body a{color:var(--accent-text);transition:color .18s var(--v-ease)}
.faq .faq-body a:hover{color:var(--text-bright)}

/* ============================================================
   CALLOUT / CTA BAND
   ============================================================ */
.cta-band{text-align:center}
.cta-band h2{font-size:clamp(26px,4vw,38px);margin-top:16px}
.cta-band .lede{margin:16px auto 0;max-width:52ch}
.cta-band .btn-row{margin-top:28px;justify-content:center}

/* ============================================================
   INTEGRATIONS STRIP
   ============================================================ */
.chip-set{display:flex;flex-wrap:wrap;gap:9px}
.chip{
  padding:8px 15px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line);
  background:var(--v-glass-2);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  font-size:13.5px;color:var(--text-muted);
}

/* ============================================================
   FEATURES PAGE — sticky in-page sub-nav
   ============================================================ */
.subnav{
  position:sticky;top:var(--header-h);z-index:80;
  background:rgba(11,14,18,.92);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  border-bottom:1px solid var(--v-line);
}
/* Two sticky bars stack on this page, not one.
   `html { scroll-padding-top }` allows for the header alone, so every in-page
   jump here — #bookings, #packages, #compare — landed with the section top
   tucked under the subnav. Barely noticeable on a heading; on the comparison
   table it hides the Solo/Crew/Fleet header row, which is the row that makes
   the table readable at all.
   scroll-margin on the TARGET rather than :has() on the root: it needs no
   selector gymnastics, it only applies on the page that has these sections,
   and it is supported everywhere the rest of this stylesheet already is.
   ~45px is the subnav's own height — 11px padding either side of a ~23px
   pill — on top of what the root already allows. */
.feature-block{scroll-margin-top:45px}
/* Row wrapping the scroll strip and its two arrow buttons — .shell lives
   here now (not on .subnav-scroll itself) so the strip can sit at
   flex:1 between two fixed-width buttons instead of owning the full
   shell width on its own. */
.subnav-row{display:flex;align-items:center;gap:8px}
.subnav-scroll{
  display:flex;gap:6px;overflow-x:auto;flex:1;min-width:0;
  padding-block:9px;
  scrollbar-width:none;
  /* proximity, not mandatory — mandatory fights a fast flick/drag by
     always forcing the nearest snap point, which reads as the strip
     "fighting back" against the gesture that just moved it. */
  scroll-snap-type:x proximity;
  cursor:grab;
  /* A faded edge is a well-understood "more to scroll" cue on its own —
     no arrows/JS needed. Without it a 14-item strip that overflows the
     viewport gave no hint at all that it scrolls horizontally. Arrow
     buttons (site.js, .subnav-arrow below) and wheel/drag support are
     the actual fix for desktop mice with no horizontal scroll gesture;
     this fade is the passive cue for everyone else. */
  mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 22px),transparent 100%);
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 22px,#000 calc(100% - 22px),transparent 100%);
}
.subnav-scroll::-webkit-scrollbar{display:none}
/* .is-dragging (site.js, while a mouse drag is in progress) turns off
   smooth-scroll and snapping so scrollLeft tracks the pointer exactly,
   with no animation lag or snap point fighting the drag mid-gesture. */
.subnav-scroll.is-dragging{cursor:grabbing;scroll-snap-type:none;scroll-behavior:auto}
.subnav a{scroll-snap-align:start}

/* Desktop-only scroll buttons — a mouse has no built-in horizontal
   gesture the way a trackpad's two-finger swipe does, so without these
   a mouse user had no way to move this list at all beyond clicking
   through every link in turn. Hidden on touch (hover:none): a touch
   swipe already does this natively, and the buttons would just be two
   more targets in the way. */
.subnav-arrow{
  display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;border-radius:50%;flex:0 0 auto;
  border:1px solid var(--v-line-strong);background:rgba(255,255,255,.04);
  color:var(--text-bright);cursor:pointer;
  transition:border-color .2s var(--v-ease),background .2s var(--v-ease),opacity .2s var(--v-ease);
}
.subnav-arrow svg{width:14px;height:14px}
.subnav-arrow:hover{border-color:rgba(var(--glow),.42);background:rgba(var(--glow),.10)}
.subnav-arrow:active{transform:scale(.92)}
.subnav-arrow:disabled{opacity:.25;cursor:default;pointer-events:none}
@media (hover:none){.subnav-arrow{display:none}}
/* A real scroll-position marker, not just a fade cue — the fade alone
   (still on .subnav-scroll above) told you there was more, but not how far
   there was to go or where you currently were in it. site.js measures
   .subnav-scroll's scrollLeft/scrollWidth and sizes+positions this thumb
   to match, the same relationship a native scrollbar thumb has to its
   track — just styled to match the rest of the panel instead of the
   browser's own bar (which .subnav-scroll hides). */
.subnav-track{
  position:relative;height:3px;
  margin-bottom:9px;
  border-radius:var(--v-r-pill);
  background:var(--tint-2);
}
.subnav-track-thumb{
  position:absolute;top:0;left:0;height:100%;
  border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent),var(--accent2));
  box-shadow:0 0 8px rgba(var(--glow),.45);
  transition:left .12s linear,width .12s linear;
}
.subnav a{
  flex:0 0 auto;
  padding:7px 13px;border-radius:var(--v-r-sm);
  border:1px solid transparent;
  color:var(--text-muted);font-size:13.5px;font-weight:500;white-space:nowrap;
  transition:color .18s var(--v-ease),background .18s var(--v-ease),border-color .18s var(--v-ease);
}
.subnav a:hover{color:var(--text-bright);background:rgba(255,255,255,.05);text-decoration:none}
.subnav a.is-active{
  color:var(--accent-text);
  background:rgba(var(--glow),.09);
  border-color:rgba(var(--glow),.24);
}

/* Feature block */
.feature-block{
  padding-block:clamp(38px,5vw,64px);
  border-top:1px solid var(--v-line);
}
.feature-block:first-of-type{border-top:none}
.feature-block-head{display:flex;align-items:flex-start;gap:16px;margin-bottom:24px}
.feature-block-head > div{padding-left:16px;border-left:1px solid var(--v-line)}
.feature-block-head h2{font-size:clamp(24px,3.4vw,34px)}
.feature-block-head .lede{margin-top:10px;font-size:15.5px}
.feature-block .v-icon-tile{margin-top:3px}

/* ============================================================
   CONTACT
   ============================================================ */
.contact-grid{display:grid;gap:clamp(16px,2.5vw,24px);grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
.email-line{
  display:block;margin:14px 0 4px;
  font-family:var(--v-mono);font-size:clamp(16px,2.6vw,21px);
  color:var(--text-bright);word-break:break-all;
  text-decoration:underline;text-decoration-color:rgba(var(--glow),.65);text-underline-offset:7px;
}
.contact-list{margin:0;padding:0;list-style:none;display:grid;gap:10px}
.contact-list li{
  position:relative;padding-left:20px;font-size:14.5px;color:var(--text-muted);
}
.contact-list li::before{
  content:"";position:absolute;left:0;top:.62em;
  width:6px;height:6px;border-radius:50%;background:rgba(var(--glow),.6);
}

/* ============================================================
   DOCS
   ============================================================ */
.docs-layout{
  display:grid;gap:clamp(24px,4vw,44px);
  grid-template-columns:250px minmax(0,1fr);
  align-items:start;
  padding-block:clamp(30px,4vw,52px);
}
.docs-sidebar{
  position:sticky;top:calc(var(--header-h) + 22px);
  max-height:calc(100vh - var(--header-h) - 44px);
  overflow-y:auto;
  padding:18px 10px 18px 14px;
  border-radius:var(--v-r-lg);
  background:rgba(17,21,27,.88);
  border:1px solid var(--v-line);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
.docs-sidebar h3{
  font-family:var(--v-mono);font-size:11px;font-weight:600;
  letter-spacing:.14em;color:var(--text-dim);
  margin:24px 0 10px;text-transform:uppercase;
}
.docs-sidebar h3:first-child{margin-top:0}
.docs-sidebar ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.docs-sidebar a{
  display:block;padding:7px 12px;border-radius:var(--v-r-sm);
  color:var(--text-muted);font-size:14px;line-height:1.4;
  border-left:2px solid transparent;
  transition:color .18s var(--v-ease),background .18s var(--v-ease);
}
.docs-sidebar a:hover{color:var(--text-bright);background:rgba(255,255,255,.04);text-decoration:none}
.docs-sidebar a[aria-current="page"]{
  color:var(--accent-text);
  background:rgba(var(--glow),.08);
  border-left-color:var(--accent2);
}

.docs-drawer-toggle{display:none}

.docs-main{min-width:0}
.doc-body{max-width:74ch;padding-left:24px;border-left:1px solid var(--v-line)}
.doc-body h1{font-size:clamp(30px,5vw,44px)}
.doc-body h2{
  font-size:clamp(21px,3vw,27px);
  margin:44px 0 14px;padding-top:22px;
  border-top:1px solid var(--v-line);
}
/* The source markdown puts a `---` before most `##`, which lands as an <hr>
   immediately above a heading that already draws its own top rule — two
   lines a few pixels apart. The heading's rule is the one worth keeping. */
.doc-body hr + h2{margin-top:0;padding-top:0;border-top:none}
.doc-body h1 + p{margin-top:18px}
.doc-body h3{font-size:18px;margin:28px 0 10px}
.doc-body p{margin:14px 0;color:var(--text-main)}
.doc-body ul,.doc-body ol{margin:14px 0;padding-left:22px;display:grid;gap:8px}
.doc-body li{color:var(--text-main)}
.doc-body li::marker{color:var(--accent-text)}
.doc-body strong{color:var(--text-bright);font-weight:600}
.doc-body code{
  font-family:var(--v-mono);font-size:.88em;
  padding:2px 6px;border-radius:5px;
  background:var(--tint-2);
  border:1px solid var(--v-line);
  color:var(--accent-text);
}
.doc-body a{
  text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:rgba(var(--glow),.45);
  transition:color .18s var(--v-ease),text-decoration-color .18s var(--v-ease);
}
.doc-body a:hover{color:var(--text-bright);text-decoration-color:var(--accent-text)}

/* Tables scroll inside their own container — the page never does.
   .data-table carries the styling so marketing pages can use a table
   without pretending to be .doc-body; docs tables get it for free. */
.table-wrap{
  overflow-x:auto;margin:18px 0;
  border-radius:var(--v-r-md);
  border:1px solid var(--v-line);
  background:rgba(255,255,255,.02);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  box-shadow:inset 0 0 0 1px rgba(var(--glow),.05);
}
/* .data-table's min-width:420px means most of these overflow on a phone —
   with no visible scrollbar the last column just read as clipped/truncated
   text, no hint that scrolling reveals the rest. A mask-fade (like
   .subnav-scroll's) turned out invisible here — .table-wrap's own
   background is already close enough to the page background that fading
   one into the other reads as no change at all. An explicit corner tag
   (same look as .hero-hud-frame-tag) is unambiguous regardless of
   background contrast.
   The tag used to straddle the box's bottom edge (half in, half out, like
   .hero-hud-frame-tag) — but .table-wrap sets overflow-x:auto, and per the
   CSS overflow spec that silently forces overflow-y to auto too (setting
   only one axis to non-visible computes the other to auto, never leaves it
   visible), so anything positioned outside the box's own bounds — including
   this tag — was being clipped in half by the box's own scroll container.
   Kept fully inside instead (with reserved padding so it never sits over a
   real row) — the only layout that can't be clipped by an ancestor's own
   overflow. */
@media (max-width:640px){
  .table-wrap{position:relative;padding-bottom:34px}
  .table-wrap::after{
    content:"\21C4 swipe for more";
    position:absolute;bottom:8px;right:10px;z-index:2;
    padding:3px 9px;border-radius:3px;
    background:var(--v-bg-1);
    border:1px solid rgba(var(--glow),.4);
    color:var(--accent-text);
    font-family:var(--v-mono);font-size:9.5px;font-weight:700;
    letter-spacing:.1em;text-transform:uppercase;
    white-space:nowrap;
    pointer-events:none;
  }
}
.data-table,.doc-body table{border-collapse:collapse;width:100%;min-width:420px;font-size:14.5px}
.data-table th,.data-table td,
.doc-body th,.doc-body td{
  text-align:left;padding:11px 15px;
  border-bottom:1px solid var(--v-line);
  vertical-align:top;
  color:var(--text-main);
}
.data-table thead th,
.doc-body thead th{
  background:rgba(255,255,255,.045);
  font-family:var(--v-mono);font-size:11.5px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);
  white-space:nowrap;
  border-bottom:1px solid rgba(var(--glow),.14);
}
.data-table tbody tr:last-child td,
.doc-body tbody tr:last-child td{border-bottom:none}
.data-table td strong,
.doc-body td strong{white-space:nowrap;color:var(--text-bright);font-weight:600}

/* Tier matrix — the plan-comparison table on features.html. A genuine
   "which column am I" read at a glance: the recommended plan's whole
   column gets a faint accent wash (matching pricing.html's own "Most
   chosen" card treatment), not just its header cell. */
.v-tier-matrix thead th:nth-child(3){color:var(--accent-text)}
.v-tier-matrix td:nth-child(3){background:rgba(var(--glow),.05)}
.v-tier-matrix tr:first-child td:nth-child(3){border-top-left-radius:0}
.v-tier-matrix thead th:nth-child(3)::after{
  content:"MOST CHOSEN";display:block;margin-top:3px;
  font-size:8.5px;letter-spacing:.14em;color:var(--accent2);
}

/* Blockquote callouts */
.doc-body blockquote{
  margin:20px 0;padding:15px 18px;
  border:1px solid var(--v-line);
  border-left:3px solid rgba(var(--glow),.5);
  border-radius:0 var(--v-r-md) var(--v-r-md) 0;
  background:rgba(var(--glow),.05);
  color:var(--text-muted);font-size:14.5px;
}
.doc-body blockquote p{margin:0;color:inherit}
.doc-body blockquote p + p{margin-top:10px}
.doc-body blockquote strong{color:var(--accent-text)}

.doc-body hr{
  border:none;height:1px;margin:34px 0;
  background:var(--v-line);
}
/* .v-rule-accent is a single class, so the element selector above outranks
   it and would flatten the accent rule to a plain hairline. */
.doc-body hr.v-rule-accent{
  width:46px;height:3px;margin:14px 0 0;border-radius:var(--v-r-pill);
  background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--glow),.55);
}

/* Prev / next */
.doc-nav{
  display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr));
  margin-top:48px;padding-top:26px;border-top:1px solid var(--v-line);
}
.doc-nav a{
  padding:15px 18px;border-radius:var(--v-r-md);
  border:1px solid var(--v-line);
  background:rgba(255,255,255,.028);
  transition:border-color .18s var(--v-ease);
}
.doc-nav a:hover{border-color:rgba(var(--glow),.26);text-decoration:none}
.doc-nav .dn-label{
  display:block;font-family:var(--v-mono);font-size:10.5px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);margin-bottom:5px;
}
.doc-nav .dn-title{
  display:block;font-family:var(--head-font);font-size:17px;font-weight:700;
  text-transform:uppercase;color:var(--text-bright);
}
.doc-nav .is-prev{text-align:left}
.doc-nav .is-next{text-align:right}
/* On one column the pair stacks, and a right-aligned "Next" under a
   left-aligned "Previous" reads as a mistake rather than a pair. */
@media (max-width:560px){
  .doc-nav .is-next{text-align:left}
}

/* Docs index cards */
.doc-index-card h3{font-size:18px;margin-bottom:8px}
.doc-index-card p{font-size:14px;color:var(--text-muted)}
.doc-index-card{border-left:2px solid transparent}
.doc-index-card:hover{border-left-color:var(--accent2);transform:translateY(-2px)}
@media (hover:none){.doc-index-card:hover{transform:none}}
@media (max-width:560px){.doc-body{padding-left:16px}}

@media (max-width:880px){
  .docs-layout{grid-template-columns:minmax(0,1fr)}
  .docs-drawer-toggle{
    display:inline-flex;align-items:center;gap:10px;
    width:100%;padding:13px 16px;
    border-radius:var(--v-r-md);
    border:1px solid var(--v-line-strong);
    background:rgba(255,255,255,.04);
    font-family:var(--head-font);font-size:15px;font-weight:700;
    text-transform:uppercase;letter-spacing:.04em;
    cursor:pointer;
  }
  .docs-drawer-toggle::after{content:"\25BE";margin-left:auto;color:var(--accent-text)}
  .docs-drawer-toggle[aria-expanded="true"]::after{content:"\25B4"}
  .docs-sidebar{
    position:static;max-height:none;overflow:visible;
    padding:16px 14px;
  }
  .docs-sidebar[hidden]{display:none}
}

/* ============================================================
   SECTION BACKGROUNDS — alternating washes
   ============================================================
   Pure decoration on a ::before layer: position:absolute + z-index:-1
   inside a position:relative section, so it can never affect layout or
   cause the horizontal overflow the footer grid bug taught to test for.
   Applied by hand, alternating down a page, not by nth-of-type — some
   sections (the pricing grid, the FAQ) read better plain.
   ============================================================ */
/* overflow:hidden so each section's own decorative ::before (negative-inset
   glows, the dot-grid mask) is clipped to its own box rather than relying on
   body{overflow-x:hidden} to hide the bleed — measured with tools/measure.mjs
   after adding these layers: without this, scrollWidth grew by roughly the
   glow's own negative inset at every viewport, 320 to 1920. */
.section,.feature-block{position:relative;overflow:hidden}
.section.bg-a::before,.feature-block.bg-a::before,
.section.bg-b::before,.feature-block.bg-b::before,
.section.bg-dots::before,.feature-block.bg-dots::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
}
.section.bg-a::before,.feature-block.bg-a::before{
  background:radial-gradient(52% 60% at 12% 0%,rgba(var(--glow),.07),transparent 68%);
  clip-path:polygon(0 0,100% 0,100% 100%,0 97%);
}
.section.bg-b::before,.feature-block.bg-b::before{
  background:radial-gradient(50% 65% at 88% 100%,rgba(var(--glow),.06),transparent 70%);
  clip-path:polygon(0 3%,100% 0,100% 100%,0 100%);
}
.section.bg-dots::before,.feature-block.bg-dots::before{
  background-image:radial-gradient(var(--tint-2) 1px,transparent 1px);
  background-size:26px 26px;
  -webkit-mask-image:radial-gradient(60% 55% at 50% 35%,#000 35%,transparent 82%);
  mask-image:radial-gradient(60% 55% at 50% 35%,#000 35%,transparent 82%);
}

/* ============================================================
   CARD MOTION — lift + icon glow on hover
   ============================================================ */
.card{transition:box-shadow .22s var(--v-ease),border-color .22s var(--v-ease),transform .22s var(--v-ease)}
.card:hover{transform:translateY(-4px)}
.card.is-static:hover{transform:none}
@media (hover:none){.card:hover{transform:none}}
/* Only clickable cards (a.card — .v-feature-row, .doc-index-card, etc.)
   get a pressed state; plain <article> cards aren't interactive. */
a.card:active{transform:translateY(-1px) scale(.98)}

.v-icon-tile{transition:box-shadow .25s var(--v-ease),background .25s var(--v-ease),transform .25s var(--v-ease)}
.card:hover .v-icon-tile{
  background:rgba(var(--glow),.17);
  border-color:rgba(var(--glow),.38);
  box-shadow:0 0 20px rgba(var(--glow),.30);
  transform:scale(1.08);
}
@media (hover:none){.card:hover .v-icon-tile{background:rgba(var(--glow),.10);border-color:rgba(var(--glow),.22);box-shadow:none;transform:none}}
@media (prefers-reduced-motion:reduce){
  .card:hover,.shot:hover{transform:none}
  .card:hover .v-icon-tile{transform:none}
}

/* ============================================================
   HERO — an extra glow layer and a shimmering accent word
   The base ambient background (body::after) is shared by every page;
   this adds one more, hero-scoped, so the page you land on reads with
   a bit more depth than an interior page needs.
   ============================================================ */
.hero{position:relative;overflow:hidden}
/* Mesh-gradient field, not a single glow — three blobs in the brand's own
   blue ramp (neon blue, deep racing blue, the mid accent-text tone),
   so it reads as one coherent living surface rather than a flat spotlight.
   Drifts slowly (translate+scale, not background-position, so the blur
   stays cheap) — an organic, continuous "alive" background rather than a
   static wash, which is what actually reads as futuristic here, not any
   single colour choice. */
.hero::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  inset:-16% -12% auto -12%;height:148%;
  background:
    radial-gradient(34% 42% at 76% 14%,rgba(31,108,255,.22),transparent 70%),
    radial-gradient(30% 36% at 16% 28%,rgba(0,82,255,.17),transparent 72%),
    radial-gradient(26% 32% at 52% 64%,rgba(61,169,255,.13),transparent 75%);
  filter:blur(30px);
}
@media (prefers-reduced-motion:no-preference){
  .hero::before{animation:vHeroMeshDrift 24s ease-in-out infinite}
}
@keyframes vHeroMeshDrift{
  0%,100%{transform:translate(0,0) scale(1)}
  33%{transform:translate(-2.5%,1.5%) scale(1.05)}
  66%{transform:translate(2%,-2%) scale(.97)}
}
/* Per-page mesh position — same three-blob recipe, shifted so the one
   deliberately-reused hero shell (.hero-hud-frame, below) doesn't also
   land on an identical light field on every page. Home keeps the base
   values above (unmarked body — most-visited page, no override needed). */
body.page-features .hero::before{
  background:
    radial-gradient(34% 42% at 22% 18%,rgba(31,108,255,.22),transparent 70%),
    radial-gradient(30% 36% at 82% 30%,rgba(0,82,255,.17),transparent 72%),
    radial-gradient(26% 32% at 50% 70%,rgba(61,169,255,.13),transparent 75%);
}
body.page-pricing .hero::before{
  background:
    radial-gradient(36% 44% at 50% 6%,rgba(31,108,255,.22),transparent 70%),
    radial-gradient(28% 34% at 12% 60%,rgba(0,82,255,.17),transparent 72%),
    radial-gradient(26% 32% at 88% 66%,rgba(61,169,255,.13),transparent 75%);
}
body.page-contact .hero::before{
  background:
    radial-gradient(32% 40% at 84% 60%,rgba(31,108,255,.22),transparent 70%),
    radial-gradient(30% 36% at 18% 12%,rgba(0,82,255,.17),transparent 72%),
    radial-gradient(24% 30% at 58% 90%,rgba(61,169,255,.13),transparent 75%);
}
.hero h1 .v-accent-text{
  color:var(--accent-text);
}

/* Hero background visual — a large, faded reproduction of the admin
   dashboard sitting behind the hero copy, built from the same .v-mock-*
   primitives the showcase carousel already uses (no new image asset).
   Masked to fade out toward the left so the heading stays fully legible;
   hidden below the width where there's no room for it not to fight the
   text. This is what makes the hero read as "here is the software"
   rather than plain text on a gradient. */
/* .hero-bg-visual now lives inside its own grid column (.hero-col-panel,
   the same is-split pattern pricing.html/features.html already use for
   their hero right column) rather than being absolutely positioned as a
   % of the full viewport. That percentage approach is what caused the
   earlier bugs: the gap to the text grew without bound on a wide
   monitor, and reconciling its position against the text column's own
   independent centering was never reliable across viewport widths. A
   real grid column can't overlap its sibling by construction — no gap
   math, no collision risk, and it now matches how every other hero on
   the site lays out a text-column + visual-column pair. */
/* 2027 pass — ultra-thin hairline (was var(--v-line-strong), a much
   harder edge) plus a genuine floating-card read: a soft top glass
   highlight, a deeper contact shadow, and a second, dimmer card
   (.hero-col-panel::after, below) peeking out from behind at an offset.
   overflow:hidden moved onto .v-mock-app (the inner content) instead of
   living here, so that second card isn't clipped by this element's own
   box — it needs to visibly extend past it. */
.hero-bg-visual{
  position:relative;z-index:-1;pointer-events:none;
  width:100%;max-width:440px;height:320px;margin-inline:auto;
  transform:perspective(1400px) rotateY(-8deg) rotate(-1.5deg);
  /* Rounder than --v-r-lg (8px) on purpose — at this element's size an 8px
     radius reads as barely-rounded, almost square. Every hero visual panel
     (this one, .v-status-panel, .hero-hud-frame) gets the same larger
     radius so the hero reads as a rounded instrument, not a square one. */
  border-radius:26px;border:1px solid rgba(255,255,255,.09);
  background:var(--v-glass);
  /* box-shadow's own shape always matches the element's border-radius —
     the 12px/14px offset + negative spread below reads as a second,
     dimmer card edge peeking out from behind, with none of the manual
     size/position math a separate stacked pseudo-element would need to
     stay aligned to this box as it resizes across breakpoints. */
  box-shadow:0 34px 70px -18px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.07),
    12px 14px 0 -3px rgba(255,255,255,.05),var(--v-glow-lg);
  opacity:.82;
  mask-image:linear-gradient(100deg,transparent 0%,black 16%,black 100%);
  -webkit-mask-image:linear-gradient(100deg,transparent 0%,black 16%,black 100%);
}

/* ============================================================
   HERO — REDESIGN: two-column layout, live-status panel, chips
   Reuses existing tokens/primitives (--glow, --v-line, .card's glass
   recipe, .v-eyebrow's pill shape) rather than a parallel system.
   ============================================================ */

/* Ambient glow behind the right column, layered on top of the page-wide
   body::after glows and the left-biased .hero::before glow above. */
.hero-col-panel{position:relative;--tilt-x:0deg;--tilt-y:0deg}
.hero-col-panel::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  inset:-18% -14%;
  background:radial-gradient(55% 60% at 62% 40%,rgba(var(--glow),.16),transparent 72%);
  filter:blur(22px);
}
/* Injected by assets/js/hero-shader.js (homepage only) as the first child
   of .hero-col-panel, so it sits behind .hero-bg-visual in paint order
   without needing its own z-index. Never present at all without WebGL2 or
   under reduced motion — the ::before glow above is a complete visual on
   its own either way, this only ever adds to it.
   Radial mask fades the canvas to nothing well before it reaches the
   column's own edges — without it the shader paints a hard-edged
   rectangle the full size of .hero-col-panel (which is much larger than
   the rounded .hero-bg-visual mockup it sits behind), reading as a stray
   glowing panel on top of/around the mockup rather than ambient light
   behind it. */
.hero-shader-canvas{
  position:absolute;inset:0;z-index:-1;
  width:100%;height:100%;pointer-events:none;
  opacity:.55;
  mask-image:radial-gradient(58% 55% at 50% 45%,#000 0%,#000 30%,transparent 76%);
  -webkit-mask-image:radial-gradient(58% 55% at 50% 45%,#000 0%,#000 30%,transparent 76%);
}
/* Cursor-parallax tilt (site.js sets --tilt-x/--tilt-y on mousemove within
   the hero) — desktop/fine-pointer only, and only when motion is welcome.
   Composes with .hero-bg-visual's own perspective transform rather than
   fighting it, since this transform lives one level up on the wrapper. */
@media (prefers-reduced-motion:no-preference) and (hover:hover) and (pointer:fine){
  .hero-col-panel{
    transform:perspective(1200px) rotateX(var(--tilt-y)) rotateY(var(--tilt-x));
    transition:transform .3s var(--v-ease);
  }
}

.v-eyebrow-live{gap:9px}

/* Pulse dot — reused by the eyebrow, panel header light, and status rows */
.v-pulse-dot{
  position:relative;
  display:inline-block;flex:0 0 auto;
  width:7px;height:7px;border-radius:50%;
  background:var(--accent2);
  box-shadow:0 0 8px rgba(var(--glow),.65);
}
.v-pulse-dot::after{
  content:"";position:absolute;inset:-4px;
  border-radius:50%;
  border:1px solid rgba(var(--glow),.55);
  opacity:0;
}
@media (prefers-reduced-motion:no-preference){
  .v-pulse-dot::after{animation:vPulseRing 2.2s var(--v-ease) infinite}
}
@keyframes vPulseRing{
  0%{opacity:.65;transform:scale(.6)}
  70%{opacity:0;transform:scale(1.9)}
  100%{opacity:0;transform:scale(1.9)}
}

/* Feature-bar chips — flatter, neutral sibling of .v-eyebrow (not
   accent-glowing, to keep "one glowing element per viewport") */
.hero-chip-row{
  list-style:none;margin:26px 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:10px;
}
.hero-chip{
  padding:8px 14px;border-radius:var(--v-r-pill);
  background:var(--v-glass-2);
  border:1px solid var(--v-line);
  color:var(--text-muted);
  font-family:var(--v-mono);
  font-size:11.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}

/* Secondary CTA — accent-tinted hover instead of global white-tinted
   .btn:hover, for a clear primary/secondary pair. Previously had no
   base rule of its own — visually fine (it inherits .btn completely at
   rest) but fragile, since any future change to .btn's selector could
   silently take .btn-ghost's look with it. This base rule gives it a
   faint resting border so it's self-contained and reads as the
   secondary CTA before you ever hover it, not just after. */
.btn-ghost{border-color:rgba(var(--glow),.18)}
.btn-ghost:hover{
  border-color:rgba(var(--glow),.35);
  background:rgba(var(--glow),.08);
  color:var(--text-bright);
}

/* Right-column status panel — .card's glass recipe with an
   accent-tinted border so it reads "live" rather than static. 2027 pass:
   thinner hairline, a glass-edge inset highlight, and the same stacked-
   card box-shadow trick as .hero-bg-visual (box-shadow always matches
   this element's own border-radius, so the "second card" edge stays
   aligned automatically at every breakpoint). */
.v-status-panel{
  background:rgba(16,20,26,.94);
  border:1px solid rgba(255,255,255,.08);
  border-radius:26px;
  box-shadow:0 30px 60px -16px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.06),
    12px 14px 0 -3px rgba(255,255,255,.04),var(--v-glow-md);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  overflow:hidden;
}
.v-status-panel-bar{
  display:flex;align-items:center;gap:10px;
  padding:14px 20px;
  border-bottom:1px solid var(--v-line);
  background:rgba(255,255,255,.02);
}
.v-status-panel-time{margin-left:auto;color:var(--text-dim);font-family:var(--v-mono);font-size:11px;letter-spacing:.08em}
.v-status-panel-title{
  font-family:var(--v-mono);
  font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-dim);
}
.v-status-panel-body{
  display:grid;gap:10px;
  padding:18px 20px 22px;
}
.v-console-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.v-console-summary div{min-width:0;padding:11px 12px;border-left:1px solid var(--v-line)}
.v-console-summary div:first-child{border-left:0;padding-left:0}
.v-console-summary span{display:block;font-size:10px;color:var(--text-dim);font-family:var(--v-mono);letter-spacing:.04em;white-space:nowrap}
.v-console-summary .v-console-label{margin-bottom:4px;text-transform:uppercase;letter-spacing:.11em}
.v-console-summary strong{display:block;color:var(--text-bright);font-family:var(--head-font);font-size:24px;line-height:1.1;letter-spacing:.04em}
.v-console-divider{height:1px;background:var(--v-line);margin:4px 0}
.v-status-row{
  display:grid;grid-template-columns:42px minmax(0,1fr) auto;align-items:center;gap:12px;
  padding:12px 13px;border-radius:var(--v-r-sm);
  background:rgba(255,255,255,.025);
  border:1px solid var(--v-line);
  transition:opacity .26s var(--v-ease);
}
/* Ticking figures/ticker text (site.js) fade rather than snap — a plain
   textContent swap under a live-feeling panel reads as a glitch otherwise. */
[data-live="scheduled"],[data-live="revenue"],[data-live="ticker"]{
  display:inline-block;transition:opacity .22s var(--v-ease);
}
.v-status-row.is-current{border-color:rgba(var(--glow),.25);background:rgba(var(--glow),.055)}
.v-status-row-time{font-family:var(--v-mono);font-size:11px;color:var(--text-dim)}
.v-status-row-label{
  font-size:13.5px;font-weight:500;color:var(--text-main);
}
.v-status-row-label strong,.v-status-row-label small{display:block}.v-status-row-label strong{color:var(--text-bright);font-size:13px;font-weight:600}.v-status-row-label small{margin-top:2px;color:var(--text-dim);font-size:11px}
.v-status-row-state{
  display:inline-flex;align-items:center;gap:8px;flex:0 0 auto;
  font-family:var(--v-mono);
  font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--accent-text);
  white-space:nowrap;
}
.v-status-row-state.is-muted{color:var(--text-dim)}
.v-status-panel-foot{display:flex;align-items:center;gap:9px;padding:12px 20px;border-top:1px solid var(--v-line);color:var(--text-dim);font-family:var(--v-mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase}
@media (max-width:480px){.v-console-summary{gap:4px}.v-console-summary div{padding:9px 8px}.v-console-summary strong{font-size:20px}.v-console-summary span{font-size:8.5px}.v-status-row{grid-template-columns:36px minmax(0,1fr)}.v-status-row-state{grid-column:2;font-size:9.5px}}

/* Features hero: a system map that makes the shared-data proposition visible
   without pretending to be a screenshot of the product. */
.feature-hero-inner{align-items:center}
.v-feature-map{position:relative;padding:24px 20px;min-height:286px;display:grid;place-items:center;overflow:hidden}
.v-feature-map::before,.v-feature-map::after{content:"";position:absolute;pointer-events:none}
.v-feature-map::before{width:176px;height:176px;border:1px solid rgba(var(--glow),.16);transform:rotate(45deg)}
.v-feature-map::after{width:244px;height:244px;border:1px solid var(--tint-2);transform:rotate(45deg)}
/* "Shared data" made visible as motion, not a usage claim — a slow radar
   sweep circulating behind the module nodes, reading as one system with
   data moving through it rather than ten labels sitting in a static grid.
   A separate layer (not ::before/::after, both already spoken for above)
   so it can sit strictly between the two static rings and the node labels. */
.v-feature-sweep{
  position:absolute;inset:8px;z-index:2;pointer-events:none;border-radius:50%;
  background:conic-gradient(from 0deg,transparent 0deg,rgba(var(--glow),.22) 18deg,transparent 46deg,transparent 360deg);
}
@media (prefers-reduced-motion:no-preference){
  .v-feature-sweep{animation:vFeatureSweep 7s linear infinite}
}
@keyframes vFeatureSweep{to{transform:rotate(360deg)}}
.v-feature-nodes{position:absolute;inset:20px;z-index:3;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:22px;place-items:center}
.v-feature-nodes span{position:relative;display:inline-flex;align-items:center;justify-content:center;min-width:72px;padding:5px 7px;background:rgba(16,20,26,.96);border:1px solid var(--v-line);color:var(--text-muted);font-family:var(--v-mono);font-size:9px;letter-spacing:.04em;text-transform:uppercase;text-align:center;line-height:1.25}
.v-feature-nodes span::before{content:"";position:absolute;width:4px;height:4px;top:-3px;left:-3px;background:var(--accent2);box-shadow:0 0 7px rgba(var(--glow),.55)}
@media (prefers-reduced-motion:no-preference){.v-feature-nodes span:nth-child(2n)::before{animation:vLedBlink 3.4s steps(2,end) infinite}.v-feature-nodes span:nth-child(3n)::before{animation:vLedBlink 5s steps(2,end) infinite}}
@media (max-width:480px){.v-feature-map{min-height:258px;padding:18px 10px}.v-feature-map::after{width:210px;height:210px}.v-feature-nodes{inset:14px;gap:10px}.v-feature-nodes span{min-width:59px;font-size:8px;padding:4px}}

/* Pricing hero: a quiet tier visual makes the price structure legible before
   the full comparison begins below. */
.pricing-hero-inner{align-items:center}
.v-plan-map{display:grid;gap:10px;padding:22px 20px 14px}
/* 104px, not 86 — "CONCOURS" (the longest plan name) overflowed the
   narrower column into the gauge track beside it, its last letter
   sitting right on top of the track's own glow. Fixed (not max-content)
   so all three rows' tracks still start at the same x regardless of
   which row's name is longest. */
.v-plan-row{display:grid;grid-template-columns:104px minmax(0,1fr) 46px;align-items:center;gap:12px;padding:12px 11px;border:1px solid var(--v-line);background:rgba(255,255,255,.022)}
.v-plan-row.is-selected{border-color:rgba(var(--glow),.30);background:rgba(var(--glow),.055)}
.v-plan-name{color:var(--text-bright);font-family:var(--head-font);font-size:18px;letter-spacing:.05em;text-transform:uppercase;line-height:1}
.v-plan-name small{display:block;margin-top:5px;color:var(--accent-text);font-family:var(--v-mono);font-size:8px;font-weight:600;letter-spacing:.08em;white-space:nowrap}
.v-plan-track{display:block;height:5px;background:rgba(255,255,255,.08);overflow:hidden}
.v-plan-track i{display:block;height:100%;background:var(--accent2);box-shadow:0 0 9px rgba(var(--glow),.52)}
.v-plan-row strong{color:var(--text-bright);font-family:var(--v-mono);font-size:13px;text-align:right}
.v-plan-summary{display:grid;grid-template-columns:repeat(3,1fr);margin:0 20px 20px;padding-top:14px;border-top:1px solid var(--v-line)}
.v-plan-summary span{position:relative;padding-left:10px;color:var(--text-dim);font-family:var(--v-mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase}
.v-plan-summary span::before{content:"";position:absolute;left:0;top:.45em;width:4px;height:4px;background:var(--accent2);box-shadow:0 0 7px rgba(var(--glow),.5)}
/* All three rows read as live gauges now (17 Aug 2026), not one lit row
   among two static ones — the selected row still pulses hardest, the other
   two more quietly and out of phase, so the panel never looks like it's
   just breathing in unison. */
@media (prefers-reduced-motion:no-preference){
  .v-plan-row .v-plan-track i{animation:vPlanMeterQuiet 4.6s ease-in-out infinite}
  .v-plan-row:nth-child(1) .v-plan-track i{animation-delay:-1.1s}
  .v-plan-row:nth-child(3) .v-plan-track i{animation-delay:-2.9s}
  .v-plan-row.is-selected .v-plan-track i{animation:vPlanMeter 3.5s ease-in-out infinite}
}
@keyframes vPlanMeter{0%,100%{opacity:.68}50%{opacity:1;box-shadow:0 0 15px rgba(var(--glow),.82)}}
@keyframes vPlanMeterQuiet{0%,100%{opacity:.5}50%{opacity:.82;box-shadow:0 0 10px rgba(var(--glow),.48)}}
@media (max-width:480px){.v-plan-map{padding-inline:14px}.v-plan-row{grid-template-columns:88px minmax(0,1fr) 42px;gap:8px}.v-plan-name{font-size:16px}.v-plan-summary{margin-inline:14px}.v-plan-summary span{font-size:8px}}

/* ============================================================
   SCROLL REVEAL — opt-in, motion-safe, and JS-safe
   ============================================================
   Scoped to html.js, a class set by an inline script in each page's <head>.
   Without it the hidden state never applies — so if JavaScript is off or
   site.js fails to load, the content is simply visible rather than
   permanently invisible. Never hide content behind a script that might
   not run.
   ============================================================ */
@media (prefers-reduced-motion:no-preference){
  .js .reveal{
    opacity:0;transform:translateY(16px);
    transition:opacity .6s var(--v-ease),transform .6s var(--v-ease);
  }
  .js .reveal.is-visible{opacity:1;transform:none}

  /* .reveal-stagger: same idea, but each direct child (a card in a grid)
     fades up on its own, delayed a beat after the one before it — set as
     an inline transition-delay by site.js right before .is-visible lands,
     so the delay only ever applies going IN, never reversed or reused. */
  .js .reveal-stagger > *{
    opacity:0;transform:translateY(16px);
    transition:opacity .55s var(--v-ease),transform .55s var(--v-ease);
  }
  .js .reveal-stagger.is-visible > *{opacity:1;transform:none}
}

/* ============================================================
   DILUTION CALCULATOR — dilution-calculator.html only.
   Same shape as the product's own admin dilution calculator
   (icon-card presets, a hero readout, a two-colour bar, a table)
   re-skinned in this site's own tokens rather than the app's
   .dil-* classes, since this page carries no product/stock data.
   ============================================================ */
.calc-shell{
  display:grid;grid-template-columns:1.1fr .9fr;gap:28px;align-items:start;
}
@media (max-width:860px){.calc-shell{grid-template-columns:1fr}}

.calc-icon-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.calc-icon-card{
  display:flex;flex-direction:column;align-items:center;gap:7px;padding:14px 6px;
  border-radius:var(--v-r-md);border:1px solid var(--v-line);background:var(--v-glass-2);
  cursor:pointer;color:inherit;font:inherit;text-align:center;transition:border-color .18s var(--v-ease),background .18s var(--v-ease),transform .18s var(--v-ease);
}
.calc-icon-card:hover{border-color:rgba(var(--glow),.35)}
.calc-icon-card:active{transform:scale(.97)}
.calc-icon-card.is-active{border-color:var(--accent-text);background:rgba(var(--glow),.08)}
.calc-icon-card-ico{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--tint-2);flex:0 0 auto}
.calc-icon-card-ico svg{width:16px;height:16px;color:var(--text-muted)}
.calc-icon-card.is-active .calc-icon-card-ico{background:linear-gradient(135deg,var(--accent),var(--cta-grad-end))}
.calc-icon-card.is-active .calc-icon-card-ico svg{color:var(--text-on-accent)}
.calc-icon-card-name{font-size:12.5px;font-weight:700;color:var(--text-bright)}
.calc-icon-card-sub{font-size:10.5px;font-weight:700;color:var(--text-dim);background:var(--tint-2);padding:2px 8px;border-radius:999px}
.calc-icon-card.is-active .calc-icon-card-sub{color:var(--accent-text);background:rgba(var(--glow),.16)}

/* Collapsed by default — "More ratios"/"More sizes" used to sit open,
   permanently, as ten-odd extra buttons stacked under the three icon
   cards above them, which is most of why the left calc-card ran so much
   taller than the result card beside it. Native <details> again: no JS
   needed, keyboard/screen-reader support for free. */
.calc-more-details{margin-top:10px}
.calc-more-details summary{
  display:inline-flex;align-items:center;gap:6px;cursor:pointer;list-style:none;
  font-size:12.5px;color:var(--text-dim);
  transition:color .18s var(--v-ease);
}
.calc-more-details summary::-webkit-details-marker{display:none}
.calc-more-details summary::after{
  content:"";flex:0 0 auto;width:7px;height:7px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg);transition:transform .18s var(--v-ease);
}
.calc-more-details[open] summary::after{transform:rotate(225deg)}
.calc-more-details summary:hover{color:var(--text-bright)}
.calc-more-details .calc-more{margin-top:10px}
.calc-more{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
.calc-more button{
  padding:7px 12px;border-radius:var(--v-r-sm);border:1px solid var(--v-line);background:var(--v-glass-2);
  color:var(--text-main);font:inherit;font-size:12.5px;cursor:pointer;transition:border-color .18s var(--v-ease),background .18s var(--v-ease),transform .18s var(--v-ease);
}
.calc-more button:hover{border-color:rgba(var(--glow),.35)}
.calc-more button:active{transform:scale(.95)}
.calc-more button.is-active{border-color:transparent;background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));color:var(--text-on-accent);font-weight:600}

.calc-inline{display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:nowrap}
.calc-inline input,.calc-inline select{
  padding:11px 12px;border-radius:var(--v-r-sm);border:1px solid var(--v-line);background:var(--v-bg-0);
  color:var(--text-bright);font:inherit;font-size:15px;max-width:110px;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.calc-inline input:hover,.calc-inline select:hover{border-color:var(--v-line-strong)}
.calc-inline input:focus-visible,.calc-inline select:focus-visible{
  outline:none;border-color:var(--accent-text);box-shadow:0 0 0 3px rgba(var(--glow),.28);
}
.calc-inline select{max-width:80px}
.calc-inline span{font-size:13px;color:var(--text-muted);white-space:nowrap}

.calc-card{padding:22px}
.calc-card + .calc-card{margin-top:18px}
.calc-card h3{font-size:15px;letter-spacing:.04em}

.calc-hero{display:flex;align-items:center;gap:14px;margin-top:2px}
.calc-hero-ico{width:34px;height:34px;flex:0 0 auto;color:var(--accent-text)}
.calc-hero-num{font-family:var(--head-font);font-size:38px;font-weight:700;line-height:1;color:var(--text-bright);font-variant-numeric:tabular-nums}
.calc-hero-sub{font-size:13px;color:var(--text-muted);margin-top:4px}

.calc-bar{position:relative;height:16px;border-radius:999px;overflow:hidden;background:var(--v-bg-0);display:flex;margin-top:18px;border:1px solid var(--v-line)}
.calc-bar-chem{height:100%;background:linear-gradient(90deg,var(--accent2),var(--accent));min-width:1.5%}
.calc-bar-water{height:100%;background:var(--tint-3);flex:1}
.calc-bar-labels{display:flex;justify-content:space-between;margin-top:8px;font-size:12px;color:var(--text-muted)}
.calc-bar-labels span{display:inline-flex;align-items:center;gap:5px}
.calc-bar-labels svg{width:12px;height:12px}
.calc-bar-labels .is-chem svg{color:var(--accent-text)}

.calc-table{width:100%;border-collapse:collapse;margin-top:18px;font-size:14.5px}
.calc-table td{padding:9px 0;border-bottom:1px solid var(--v-line);color:var(--text-main)}
.calc-table td:last-child{text-align:right;font-weight:700;color:var(--text-bright);font-family:var(--v-mono)}
.calc-table tr:last-child td{border-bottom:none}

.calc-ranges td strong{color:var(--text-bright)}
.calc-mistake-list{display:grid;gap:14px}
.calc-mistake{display:flex;gap:12px;align-items:flex-start}
.calc-mistake-num{flex:0 0 auto;width:26px;height:26px;border-radius:50%;background:rgba(var(--glow),.14);color:var(--accent-text);font-family:var(--v-mono);font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:2px}

/* ============================================================
   HUD / TACTICAL TELEMETRY PRIMITIVES
   Additive layer on top of the existing card/button/panel system for
   website_upgrade.md's gaming-HUD pass — hero, showcase frame, feature
   rows and pricing cards only (not docs, which stays plain prose).
   ============================================================ */

/* Ambient coordinate-grid background — a real element, not a pseudo-
   element, because .hero already owns both ::before (glow) and ::after
   (scanline sweep); a third layer needs its own node. Sits furthest back
   (z-index -2) behind those two. Turned up to read clearly, not as a
   texture — this is the "instrumentation panel" backdrop the whole pass
   is meant to evoke. */
/* Floating light specks, not ruled grid lines — the grid this class used to
   draw read as a flat spreadsheet backdrop, the opposite of the mesh
   background's soft organic feel a few rules up. Scattered soft points of
   light (a "shine catching a clean, detailed surface" read, not unlike the
   hero's own "wet forecourt" line) drift and breathe slowly instead. Same
   class name kept so no HTML changes were needed across the pages that
   already carry this div. */
.hero-hud-grid{
  position:absolute;inset:0;z-index:-2;pointer-events:none;
  background-image:
    radial-gradient(circle,rgba(31,108,255,.9) 0,rgba(31,108,255,0) 70%),
    radial-gradient(circle,rgba(61,169,255,.8) 0,rgba(61,169,255,0) 70%),
    radial-gradient(circle,rgba(31,108,255,.7) 0,rgba(31,108,255,0) 70%),
    radial-gradient(circle,rgba(0,82,255,.75) 0,rgba(0,82,255,0) 70%),
    radial-gradient(circle,rgba(61,169,255,.65) 0,rgba(61,169,255,0) 70%),
    radial-gradient(circle,rgba(31,108,255,.6) 0,rgba(31,108,255,0) 70%);
  background-repeat:no-repeat;
  background-size:5px 5px,4px 4px,3px 3px,6px 6px,4px 4px,3px 3px;
  background-position:14% 22%,78% 14%,32% 68%,86% 58%,55% 38%,20% 82%;
  filter:blur(.3px);
  mask-image:radial-gradient(85% 75% at 50% 25%,#000 55%,transparent 96%);
  -webkit-mask-image:radial-gradient(85% 75% at 50% 25%,#000 55%,transparent 96%);
}
@media (prefers-reduced-motion:no-preference){
  .hero-hud-grid{animation:vSpeckFloat 12s ease-in-out infinite}
}
@keyframes vSpeckFloat{
  0%,100%{transform:translate(0,0);opacity:.55}
  50%{transform:translate(0,-14px);opacity:1}
}

/* Tactical mono sub-label — real text content (authored as "[SYS.01]",
   "// LIVE FEED" etc directly in the HTML), never ::before-generated, so
   it stays in the accessibility tree and is easy to edit per instance.
   Chip-styled (border + fill), not plain text, so it reads as an
   instrument readout rather than a caption. */
.v-tactical-label{
  display:inline-block;
  padding:4px 10px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.22);
  background:rgba(var(--glow),.1);
  font-family:var(--body-font);font-size:.75rem;font-weight:600;
  letter-spacing:normal;text-transform:none;color:var(--text-dim);
}
.v-tactical-label.is-accent{color:var(--accent-text);border-color:rgba(var(--glow),.4);background:rgba(var(--glow),.12)}

/* Bracket-corner HUD frame — four L-shaped corner accents drawn as one
   multi-layer background on ::after. Hover/focus-only, not resting
   visible — an always-on version was tried and read as messy/inconsistent
   across dozens of cards with varying rounded corners (the straight
   bracket lines don't follow the card's own border-radius curve, so
   resting visible on every card just added visual noise). Inset a few
   px so the lines sit inside the curve rather than crossing it, and kept
   small and thin so it reads as an accent, not a cage. Not paired with
   .v-featured (which already uses both ::before and ::after for its own
   hairline/corner-glow) — used standalone or on plain .card/row elements
   only. */
/* Rounded glow ring, not corner brackets — every card on this site is pill/
   rounded-cornered (.v-r-pill/.v-r-sm/.v-r-md/.v-r-lg), and a right-angle
   bracket cuts across that curve and reads as "square" even though the card
   underneath isn't. border-radius:inherit makes the ring follow whatever
   radius the card actually has, so it can never look squared-off. */
.v-hud-card{position:relative}
.v-hud-card::after{
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  border-radius:inherit;
  opacity:0;transition:opacity .25s var(--v-ease);
  box-shadow:0 0 0 1px rgba(var(--glow),.4),0 0 22px rgba(var(--glow),.22);
}
.v-hud-card:hover::after,.v-hud-card:focus-within::after{opacity:1}
@media (hover:none){.v-hud-card::after{opacity:.6}}

/* Full HUD frame around the hero content — a real visible border, big
   corner brackets and a corner status tag, so the "elite instrument
   panel" read lands the moment the page loads, not only on hover. */
/* .hero-inner's base rule (further up, "HERO" section) caps it at 820px
   and — because it's also a .shell, which centers via margin-inline:auto
   — that narrow box ends up centered in the *viewport*, not aligned with
   every other section's .shell (which centers a much wider 1180px box).
   Barely visible without a border; glaringly obvious with the framed HUD
   box added below, which is what made it read as "moved right" on a wide
   screen. Scoped to only the pages that got the frame (not e.g.
   dilution-calculator.html's plain hero) via this modifier class, rather
   than changing the shared base rule. */
.hero-inner.has-hud-frame{max-width:var(--shell)}
/* Rounded, not bracketed — this used to carry the same right-angle corner
   brackets as .v-hud-card's old hover accent, and at a 10px radius the
   brackets nearly matched the corner curve. Once the radius grew to match
   the rest of the hero (26px, same as .hero-bg-visual/.v-status-panel),
   sharp brackets crossing that curve would look like exactly the "square"
   problem this redesign is fixing, not fixing it. A soft double-ring glow
   (outer + inset box-shadow, both following border-radius natively) gives
   the same "instrument bezel" read without fighting the shape. */
.hero-hud-frame{
  position:relative;
  padding:clamp(18px,2.6vw,30px) clamp(20px,2.8vw,34px);
  border:1px solid rgba(var(--glow),.32);
  border-radius:26px;
  background:linear-gradient(160deg,rgba(var(--glow),.06),transparent 55%);
  box-shadow:inset 0 0 60px rgba(var(--glow),.05),0 0 40px rgba(var(--glow),.06),
    0 0 0 1px rgba(var(--glow),.08);
}
.hero-hud-frame-tag{
  position:absolute;top:-11px;left:22px;z-index:2;
  padding:3px 12px;border-radius:var(--v-r-pill);
  background:rgba(var(--glow),.14);
  border:1px solid rgba(var(--glow),.35);
  color:var(--accent-text);
  font-family:var(--body-font);font-size:.75rem;font-weight:600;
  letter-spacing:normal;text-transform:none;
}
@media (prefers-reduced-motion:no-preference){
  .hero-hud-frame{animation:vHudFramePulse 5s ease-in-out infinite}
}
@keyframes vHudFramePulse{
  0%,100%{box-shadow:inset 0 0 60px rgba(var(--glow),.05),0 0 40px rgba(var(--glow),.06)}
  50%{box-shadow:inset 0 0 70px rgba(var(--glow),.09),0 0 56px rgba(var(--glow),.12)}
}

/* Segmented telemetry track — an LED-slot overlay for a .v-plan-track (or
   any thin fill bar), reading as a digital gauge rather than a plain
   progress bar. Overlay rather than a background swap so the divider
   lines sit on top of the accent fill too, not just the empty track. */
.v-plan-track{position:relative}
.v-telemetry-gauge::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:repeating-linear-gradient(90deg,rgba(7,9,12,.55) 0 2px,transparent 2px 9px);
}

/* Primary CTA — elite/understated pass (superseded the old "esports"
   triple-chevron >>> reveal, which read as busy and repeated the same
   effect on every button on the site). The label keeps its own <span>
   so it stacks above the sheen layer below.
   <a class="btn v-cta v-cta-gaming"><span>Label</span><i class="v-cta-arrow" aria-hidden="true"><svg>...</svg></i></a> */
.v-cta-gaming{
  position:relative;overflow:hidden;
  box-shadow:0 0 0 1px rgba(var(--glow),.4),0 10px 28px rgba(var(--glow),.24);
  transition:box-shadow .3s var(--v-ease);
}
.v-cta-gaming span{position:relative;z-index:1}
/* Soft glow sheen, not a literal backdrop-filter blur — the button's own
   fill is fully opaque, so backdrop-filter behind it would have nothing
   to blur. A blurred radial glow layered under the label reads the same
   way (soft, diffuse light) and actually renders. */
.v-cta-gaming::before{
  content:"";position:absolute;inset:-40% -20%;z-index:0;pointer-events:none;
  background:radial-gradient(60% 90% at 50% 120%,rgba(255,255,255,.5),transparent 70%);
  opacity:0;filter:blur(14px);
  transition:opacity .35s var(--v-ease);
}
.v-cta-gaming:hover,.v-cta-gaming:focus-visible{
  box-shadow:0 0 0 1.5px rgba(var(--glow),.68),0 16px 38px rgba(var(--glow),.36),0 0 26px rgba(var(--glow),.26);
}
.v-cta-gaming:hover::before,.v-cta-gaming:focus-visible::before{opacity:.16}
@media (prefers-reduced-motion:no-preference){
  .v-cta-gaming{animation:vCtaGlowPulse 2.6s ease-in-out infinite}
}
@keyframes vCtaGlowPulse{
  0%,100%{box-shadow:0 0 0 1px rgba(var(--glow),.4),0 10px 28px rgba(var(--glow),.24)}
  50%{box-shadow:0 0 0 1px rgba(var(--glow),.65),0 10px 28px rgba(var(--glow),.24),0 0 22px rgba(var(--glow),.28)}
}
/* Single arrow, hidden at rest and revealed on hover/focus with a slide
   + soft overshoot ("spring") rather than the old instant >>> reveal —
   width still carries the reveal (so the label doesn't reflow at rest),
   but the icon itself slides in from a slight offset instead of just
   fading into its allotted space. */
.v-cta-arrow{
  position:relative;z-index:1;
  display:inline-flex;align-items:center;justify-content:center;
  width:0;margin-left:0;overflow:hidden;opacity:0;
  transform:translateX(-6px);
  transition:width .4s cubic-bezier(.34,1.56,.64,1),margin-left .4s cubic-bezier(.34,1.56,.64,1),
    transform .4s cubic-bezier(.34,1.56,.64,1),opacity .25s var(--v-ease);
}
.v-cta-arrow svg{width:16px;height:16px;flex:0 0 auto;display:block}
.v-cta-gaming:hover .v-cta-arrow,.v-cta-gaming:focus-visible .v-cta-arrow{
  width:16px;margin-left:8px;opacity:1;transform:translateX(0);
}
@media (prefers-reduced-motion:reduce){
  .v-cta-arrow{transition:opacity .2s var(--v-ease),width .2s var(--v-ease),margin-left .2s var(--v-ease)}
}

/* Status badge — small pill for "System ready" / "Most chosen" tags on a
   featured card. Neon Blue, matching the recommended-plan highlight, used
   once per grid so it still reads as a single call-out. */
.v-hud-status-badge{
  display:inline-flex;align-items:center;gap:6px;
  padding:5px 12px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.35);
  background:rgba(var(--glow),.12);
  color:var(--accent-text);
  font-family:var(--body-font);font-size:.75rem;font-weight:600;
  letter-spacing:normal;text-transform:none;
}
.v-hud-status-badge .v-pulse-dot{background:var(--accent2);box-shadow:0 0 8px rgba(var(--glow),.6)}
.v-hud-status-badge .v-pulse-dot::after{border-color:rgba(var(--glow),.55)}

/* Console window-control pips — decorative chrome dots for the showcase
   frame's top bar, reusing the frame's existing hairline/spacing. */
.v-showcase-pips{display:flex;gap:6px;margin-right:4px}
.v-showcase-pips span{width:7px;height:7px;border-radius:50%;background:var(--tint-4)}
.v-showcase-pips span:nth-child(1){background:rgba(var(--hud-crimson-rgb),.55)}
.v-showcase-pips span:nth-child(2){background:rgba(var(--hud-amber-rgb),.55)}
.v-showcase-pips span:nth-child(3){background:rgba(31,108,255,.55)}

/* Rotating status-line under the showcase frame, flashed briefly whenever
   a slide becomes active — same is-active hook site.js already toggles. */
.v-showcase-statusline{
  padding:9px 20px;border-top:1px solid var(--v-line);
  font-family:var(--body-font);font-size:.75rem;letter-spacing:normal;
  color:var(--text-dim);
  opacity:0;transition:opacity .3s var(--v-ease);
}
.v-showcase-statusline.is-flash{opacity:1;color:var(--accent-text)}

/* Persistent corner telemetry — a fixed HUD readout in the bottom-right
   of every page (injected once by site.js, not per-page markup), the one
   element that makes the "gaming HUD" language read as a property of the
   whole site rather than something that only shows up in a couple of
   sections. Hidden under prefers-reduced-motion (the clock inside it
   ticks) and on narrow screens, where it would just cover content. */
/* body > .v-persistent-hud (not just .v-persistent-hud): the global
   `body > :not(script):not(.ambient-signal){position:relative;z-index:1}`
   rule (site.css, ambient-background section) outranks a single class
   selector and was silently forcing this back to position:relative — the
   same category of cascade fight documented elsewhere in this project.
   Needs the higher-specificity selector, not just !important, to win
   reliably. */
body > .v-persistent-hud{
  position:fixed !important;right:18px;bottom:18px;z-index:150 !important;
  display:flex;align-items:center;gap:8px;
  padding:8px 14px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.28);
  background:rgba(17,24,39,.88);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:0 12px 30px rgba(var(--shadow-rgb),.55),0 0 24px rgba(var(--glow),.1);
  font-family:var(--body-font);font-size:.75rem;font-weight:600;letter-spacing:normal;
  color:var(--text-dim);
  pointer-events:none;
}
.v-persistent-hud strong{color:var(--accent-text);font-weight:600}
.v-persistent-hud .v-pulse-dot{margin-right:1px}
/* Same specificity as the base body > .v-persistent-hud rule above (a
   plain .v-persistent-hud selector here would lose to it and never
   actually hide anything, regardless of the media query matching). */
@media (max-width:760px),(prefers-reduced-motion:reduce){body > .v-persistent-hud{display:none}}

/* Back to top — bottom-left (the HUD readout above already owns bottom-
   right), injected by site.js once per page. Same `body >` specificity
   trap as .v-persistent-hud above: the global
   `body > :not(script):not(.ambient-signal){position:relative}` rule
   would otherwise win and silently undo position:fixed here too. Hidden
   via opacity/pointer-events (not display) so the scroll-triggered
   transition actually has something to animate. */
body > .v-to-top{
  position:fixed !important;left:18px;bottom:18px;z-index:150 !important;
  display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;padding:0;
  border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.32);
  background:rgba(10,12,16,.86);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:0 12px 30px rgba(var(--shadow-rgb),.55),0 0 24px rgba(var(--glow),.12);
  color:var(--accent-text);
  cursor:pointer;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity .25s var(--v-ease),transform .25s var(--v-ease),
    background .18s var(--v-ease),border-color .18s var(--v-ease);
}
body > .v-to-top svg{width:18px;height:18px}
body > .v-to-top.is-visible{opacity:1;transform:translateY(0);pointer-events:auto}
body > .v-to-top:hover{background:rgba(var(--glow),.14);border-color:rgba(var(--glow),.5)}
@media (prefers-reduced-motion:reduce){body > .v-to-top{transition:opacity .15s}}

/* ============================================================
   FLAGSHIP FEATURE BLOCK — the Reporting/Job Tracker/Payroll section
   on features.html. Same .feature-block shape as every other module,
   but with a lit top edge and a stronger ambient glow so it reads as
   the section worth stopping on, not a redesign of the pattern itself.
   ============================================================ */
.v-flagship-block{position:relative}
.v-flagship-block::after{
  content:"";position:absolute;top:0;left:0;right:0;height:2px;z-index:1;
  background:linear-gradient(90deg,transparent,var(--accent2) 20%,var(--accent-text) 50%,var(--accent2) 80%,transparent);
  box-shadow:0 0 16px rgba(var(--glow),.5);
}

/* Live-reading metric — a big glowing number treatment for the profit-
   per-hour figure, reusing the same mono/accent/glow language as
   .v-telemetry-gauge and .price-amount rather than inventing a new one. */
.v-live-metric{
  position:relative;margin-top:18px;padding:18px 20px;
  border-radius:var(--v-r);
  border:1px solid rgba(var(--glow),.22);
  background:linear-gradient(180deg,rgba(var(--glow),.09),rgba(var(--glow),.02));
  overflow:hidden;
}
.v-live-metric::before{
  content:"";position:absolute;inset:0;z-index:0;
  background:repeating-linear-gradient(90deg,rgba(7,9,12,.4) 0 2px,transparent 2px 9px);
  opacity:.5;pointer-events:none;
}
.v-live-metric-label{
  position:relative;display:block;
  font-family:var(--v-mono);font-size:10.5px;font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;color:var(--text-dim);
}
.v-live-metric-value{
  position:relative;display:flex;align-items:center;gap:9px;margin-top:6px;
  font-family:var(--head-font);font-weight:700;
  font-size:clamp(32px,5vw,42px);line-height:1;
  color:var(--accent-text);
  text-shadow:0 0 26px rgba(var(--glow),.4);
}
.v-live-metric-pulse{
  position:relative;flex:0 0 auto;
  width:9px;height:9px;border-radius:50%;
  background:var(--accent2);
  box-shadow:0 0 10px rgba(var(--glow),.7);
}
@media (prefers-reduced-motion:no-preference){
  .v-live-metric-pulse::after{
    content:"";position:absolute;inset:-5px;
    border-radius:50%;border:1px solid var(--accent2);
    animation:vPulseRing 2.1s ease-out infinite;
  }
}
.v-live-metric-sub{
  position:relative;display:block;margin-top:6px;
  font-family:var(--v-mono);font-size:11px;color:var(--text-dim);letter-spacing:.04em;
}

/* ============================================================
   CARD HOVER LIFT — applies wherever .card is used (feature grids,
   doc index, add-ons, "what hosted means" etc.) so every plain card
   on the site gets the same subtle affordance. Cards that already
   define their own hover motion (.v-glow-card, .v-feature-row) are
   untouched — this only adds `transform`, a property neither of
   those set, so it layers in rather than fighting them. .is-static
   opts a card back out: those are structural/informational, not
   something to invite a click on. */
.card:hover{transform:translateY(-4px)}
.card.is-static:hover{transform:none}
@media (hover:none){.card:hover{transform:none}}
@media (prefers-reduced-motion:reduce){.card:hover{transform:none}}

/* ============================================================
   TRUST STRIP — integration wordmarks near a primary CTA. Plain-text
   marks in the site's own mono/pill language, not fetched logo
   assets: every name here is a real supported integration (see
   pricing.html add-ons and "what you still bring"), so this reads as
   a factual list rather than borrowed-logo credibility.
   ============================================================ */
.v-trust-strip{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px 22px;
  margin-top:22px;
}
.v-trust-strip-label{
  font-family:var(--v-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-dim);white-space:nowrap;
}
.v-trust-strip-marks{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.v-trust-mark{
  padding:6px 13px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line);background:rgba(255,255,255,.03);
  color:var(--text-muted);
  font-family:var(--head-font);font-size:12.5px;font-weight:700;letter-spacing:.02em;
  transition:border-color .18s var(--v-ease),color .18s var(--v-ease),background .18s var(--v-ease);
}
.v-trust-mark:hover{color:var(--text-bright);border-color:rgba(var(--glow),.35);background:rgba(var(--glow),.06)}

/* ============================================================
   PRICING — billing toggle, live add-on calculator, sticky CTA
   ============================================================ */
.v-billing-toggle-wrap{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:14px;
  margin:0 0 clamp(26px,4vw,38px);text-align:center;
}
.v-billing-toggle{
  position:relative;display:inline-flex;padding:4px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line);background:rgba(255,255,255,.025);
}
.v-billing-toggle input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.v-billing-toggle label{
  padding:10px 20px;border-radius:var(--v-r-pill);
  color:var(--text-muted);cursor:pointer;
  font-family:var(--head-font);font-size:13.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  transition:color .18s var(--v-ease),background .18s var(--v-ease);
}
.v-billing-toggle label:has(input:focus-visible){outline:2px solid rgba(var(--glow),.75);outline-offset:2px}
.v-billing-toggle label:has(input:checked){
  color:var(--text-on-accent);
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));
  box-shadow:0 4px 14px rgba(var(--glow),.3);
}
.v-billing-save{
  padding:6px 13px;border-radius:var(--v-r-pill);
  border:1px solid rgba(var(--glow),.28);background:rgba(var(--glow),.08);
  color:var(--accent-text);
  font-family:var(--v-mono);font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  white-space:nowrap;opacity:.55;transition:opacity .2s var(--v-ease);
}
.v-billing-toggle-wrap.is-annual .v-billing-save{opacity:1}

/* Plan-select segmented control for the add-on calculator — same shape
   language as .v-billing-toggle, sized for three options instead of two. */
.v-calc-plan-select{
  display:inline-flex;flex-wrap:wrap;gap:4px;
  padding:4px;margin-bottom:24px;border-radius:var(--v-r-pill);
  border:1px solid var(--v-line);background:rgba(255,255,255,.025);
}
.v-calc-plan-select input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.v-calc-plan-select label{
  padding:9px 18px;border-radius:var(--v-r-pill);
  color:var(--text-muted);cursor:pointer;
  font-family:var(--head-font);font-size:13px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  transition:color .18s var(--v-ease),background .18s var(--v-ease);
}
.v-calc-plan-select label:has(input:focus-visible){outline:2px solid rgba(var(--glow),.75);outline-offset:2px}
.v-calc-plan-select label:has(input:checked){
  color:var(--text-on-accent);
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));
}

/* Checkable add-on cards — a real checkbox drives every state via :has(),
   so the total bar below only ever has to read the DOM's actual checked
   state, never a duplicate JS flag that could drift from it. */
.v-addon-card{
  position:relative;display:flex;flex-direction:column;gap:10px;
  cursor:pointer;text-align:left;
}
.v-addon-card input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.v-addon-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.v-addon-check-ui{
  position:relative;flex:0 0 auto;width:20px;height:20px;margin-top:2px;
  border-radius:6px;border:1px solid var(--v-line-strong);background:rgba(255,255,255,.03);
  transition:border-color .18s var(--v-ease),background .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-addon-check-ui::after{
  content:"";position:absolute;left:5px;top:3px;width:8px;height:4px;
  border-left:2px solid var(--v-bg-0);border-bottom:2px solid var(--v-bg-0);
  transform:rotate(-50deg) scale(0);transition:transform .16s var(--v-ease);
}
.v-addon-card:hover .v-addon-check-ui{border-color:rgba(var(--glow),.4)}
.v-addon-card:has(input:checked){border-color:rgba(var(--glow),.4);background:rgba(var(--glow),.06)}
.v-addon-card:has(input:checked) .v-addon-check-ui{
  background:linear-gradient(135deg,var(--accent),var(--cta-grad-end));border-color:transparent;
  box-shadow:0 0 14px rgba(var(--glow),.4);
}
.v-addon-card:has(input:checked) .v-addon-check-ui::after{transform:rotate(-50deg) scale(1)}
.v-addon-card:has(input:focus-visible){outline:2px solid rgba(var(--glow),.75);outline-offset:2px}
.v-addon-price{
  flex:0 0 auto;padding-top:1px;
  font-family:var(--v-mono);font-size:12.5px;font-weight:700;color:var(--accent-text);white-space:nowrap;
}
.v-addon-card h3{font-size:16px;margin:0}
.v-addon-card p{font-size:13px;color:var(--text-muted);margin:0}
.v-addon-included-tag{
  align-self:flex-start;padding:3px 9px;border-radius:var(--v-r-pill);
  background:var(--tint-2);color:var(--text-dim);
  font-family:var(--v-mono);font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
}
/* Included-on-this-plan or pay-per-use items: still readable, not a
   phantom checkbox promising a total contribution that never lands. */
.v-addon-card.is-included,.v-addon-card.is-variable{cursor:default}
.v-addon-card.is-included .v-addon-check-ui,.v-addon-card.is-variable .v-addon-check-ui{display:none}

.v-addon-total-bar{
  margin-top:22px;padding:18px 22px;border-radius:var(--v-r-lg);
  border:1px solid rgba(var(--glow),.26);
  background:linear-gradient(180deg,rgba(18,24,36,.66),rgba(10,13,20,.8));
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:14px 22px;
}
.v-addon-total-label{
  display:block;font-family:var(--v-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-dim);
}
.v-addon-total-value{
  font-family:var(--head-font);font-size:clamp(24px,3vw,30px);font-weight:700;color:var(--text-bright);
  font-variant-numeric:tabular-nums;
}
.v-addon-total-value span{font-size:15px;font-weight:500;color:var(--text-muted);text-transform:none}
.v-addon-total-note{flex:1 1 260px;font-size:13px;color:var(--text-muted);margin:0}

/* Floating conversion bar — appears once the visitor has scrolled past
   the price cards (site.js observes the grid and toggles .is-visible).
   `visibility` is chained after `opacity`/`transform` so the link inside
   drops out of tab order while hidden instead of sitting there as an
   invisible, reachable stop. */
body > .v-sticky-cta{
  position:fixed !important;left:0;right:0;bottom:0;z-index:140 !important;
  display:flex;justify-content:center;
  padding:14px var(--gutter);
  border-top:1px solid rgba(var(--glow),.3);
  background:rgba(9,11,15,.94);
  backdrop-filter:blur(16px) saturate(150%);-webkit-backdrop-filter:blur(16px) saturate(150%);
  box-shadow:0 -14px 40px rgba(var(--shadow-rgb),.45);
  transform:translateY(100%);opacity:0;visibility:hidden;
  transition:transform .3s var(--v-ease),opacity .3s var(--v-ease),visibility 0s linear .3s;
}
body > .v-sticky-cta.is-visible{
  transform:translateY(0);opacity:1;visibility:visible;
  transition:transform .3s var(--v-ease),opacity .3s var(--v-ease),visibility 0s linear;
}
.v-sticky-cta-inner{
  width:100%;max-width:var(--shell);
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 20px;
}
.v-sticky-cta-label{font-size:14px;color:var(--text-main)}
.v-sticky-cta-label strong{color:var(--text-bright);font-family:var(--head-font);letter-spacing:.02em}
.v-sticky-cta-price{color:var(--accent-text);font-family:var(--v-mono);font-weight:700}
@media (prefers-reduced-motion:reduce){body > .v-sticky-cta{transition:opacity .15s,visibility 0s}}
@media (max-width:560px){.v-sticky-cta-label{font-size:13px}}
/* The two other fixed corner controls sit at bottom:18px — bump them
   clear of the conversion bar (site.js mirrors .is-visible onto <body>
   as .has-sticky-cta) rather than letting the bar cover them. */
body.has-sticky-cta > .v-to-top,
body.has-sticky-cta > .v-persistent-hud{bottom:86px}

/* ============================================================
   2-COLUMN ACCORDION — opt-in variant of .faq for question sets long
   enough to benefit (pricing FAQ, docs Troubleshooting). CSS multicol,
   not grid: each entry's open/closed height is independent, so opening
   one answer never reflows a sibling in the other column the way a
   grid row would. Doc pages have their own 74ch reading column
   (.doc-body); the override below lets the accordion use its full
   width instead of being squeezed to text-measure.
   ============================================================ */
@media (min-width:760px){
  .faq.is-2col{max-width:1100px;display:block;column-count:2;column-gap:16px}
  .faq.is-2col > details{
    display:block;break-inside:avoid;-webkit-column-break-inside:avoid;margin-bottom:12px;
  }
  .doc-body .faq.is-2col{max-width:none}
}

/* ============================================================
   CONTACT COMPOSER — dark-surface fields with an explicit focus ring,
   feeding the mailto link on contact.html (see site.js [data-composer]).
   Degrades cleanly: with JS off, the button's own pre-filled mailto
   template still works exactly as it did before these fields existed.
   ============================================================ */
.v-field-group{display:grid;gap:14px;margin:20px 0 4px}
.v-field{display:grid;gap:6px}
.v-field label{
  font-family:var(--v-mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-dim);
}
.v-field input,.v-field select,.v-field textarea{
  padding:12px 14px;border-radius:var(--v-r-sm);border:1px solid var(--v-line);
  background:var(--v-bg-2);color:var(--text-bright);font:inherit;font-size:14.5px;
  transition:border-color .18s var(--v-ease),box-shadow .18s var(--v-ease);
}
.v-field input:hover,.v-field select:hover,.v-field textarea:hover{border-color:var(--v-line-strong)}
.v-field input:focus-visible,.v-field select:focus-visible,.v-field textarea:focus-visible{
  outline:none;border-color:var(--accent-text);box-shadow:0 0 0 3px rgba(var(--glow),.28);
}
.v-field textarea{resize:vertical;min-height:74px;line-height:1.5}

/* ============================================================
   PRINT — docs are the only thing worth printing
   ============================================================ */
@media print{
  body::before,body::after{display:none}
  .site-header,.site-footer,.docs-sidebar,.doc-nav,.subnav,.docs-drawer-toggle{display:none}
  body{background:#fff;color:#000}
  .doc-body,.doc-body p,.doc-body li,.doc-body strong{color:#000}
  .docs-layout{grid-template-columns:1fr;padding:0}
}
