/* ==========================================================================
   Prospira Tweaks — design system "royal liquid glass"
   Page-agnostic layer: tokens, fonts, primitives, header/nav/footer,
   injected UI (auth.js cart + user menu),
   Tailwind-utility compat for legacy inline scripts.
   Palette and glass recipes mirror the Prospira Tweaking Utility app 1:1.
   ========================================================================== */

/* ---------- Fonts (self-hosted, variable) ---------- */
@font-face {
  font-family: 'Geist Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/geist-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/geist-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/geist-mono-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/geist-mono-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url('/fonts/instrument-serif-latin-400-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif';
  font-style: italic;
  font-display: swap;
  font-weight: 400;
  src: url('/fonts/instrument-serif-latin-400-italic.woff2') format('woff2');
}

/* ---------- Design tokens ---------- */
:root {
  /* accent - royal blue, the ONLY accent */
  --accent: #2a5bff;
  --accent-rgb: 42, 91, 255;
  --accent-2: #6366f1;
  --accent-deep: #1e3aae;

  /* surfaces */
  --bg-0: #02030a;
  --bg-1: #04060c;
  --bg-2: #0a0d18;

  /* text */
  --text-hi: #f0f5ff;
  --text-mid: rgba(214, 225, 245, 0.72);
  --text-low: rgba(214, 225, 245, 0.48);

  /* borders */
  --border-soft: rgba(150, 180, 255, 0.08);
  --border-mid: rgba(150, 180, 255, 0.14);
  --border-strong: rgba(150, 180, 255, 0.2);
  --border-accent: rgba(42, 91, 255, 0.42);
  --island-border: rgba(150, 180, 255, 0.07);

  /* liquid glass (verbatim app tokens) */
  --glass-bg:
    radial-gradient(60% 100% at 6% 0%, rgba(190, 210, 255, 0.1), transparent 55%),
    radial-gradient(130% 180% at 0% 0%, rgba(42, 91, 255, 0.09), transparent 56%),
    radial-gradient(80% 120% at 100% 100%, rgba(30, 58, 174, 0.05), transparent 68%),
    linear-gradient(180deg, rgba(11, 15, 30, 0.22), rgba(7, 10, 22, 0.32));
  --glass-blur: blur(26px) saturate(180%);
  --glass-border: rgba(255, 255, 255, 0.05);
  --glass-hairline: linear-gradient(90deg, transparent, rgba(190, 210, 255, 0.4), transparent);
  --glass-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    0 20px 44px -24px rgba(0, 0, 0, 0.6),
    0 0 50px -36px rgba(42, 91, 255, 0.16);

  /* royal card - gradient-only recipe, safe without backdrop blur */
  /* card tokens aliased to the glass system: every card now shares the exact
     announce / footer / nav tone (one source of truth for all page glass). */
  --card-bg: var(--glass-bg);
  --card-border: var(--glass-border);
  --card-border-hover: rgba(120, 160, 255, 0.36);
  --card-shadow: var(--glass-shadow);
  --card-shadow-hover:
    inset 0 1px 0 rgba(255, 255, 255, 0.15),
    0 28px 52px -28px rgba(0, 0, 0, 0.68),
    0 0 24px -28px rgba(42, 91, 255, 0.22);

  /* icon tile (42x42 royal glass plate, identical to the app) */
  --tile-bg:
    radial-gradient(135% 135% at 25% 12%, rgba(95, 135, 255, 0.18), transparent 64%),
    linear-gradient(180deg, rgba(18, 26, 54, 0.82), rgba(10, 16, 34, 0.9));
  --tile-border: rgba(150, 180, 255, 0.2);
  --icon-color: #d4e2ff;

  /* geometry */
  --r-card: 16px;
  --r-card-lg: 18px;
  --r-panel: 22px;
  --r-tile: 12px;
  --r-pill: 999px;

  /* type */
  --font-sans: 'Geist Variable', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'Geist Mono Variable', ui-monospace, 'Cascadia Mono', Menlo, monospace;
  --font-serif: 'Instrument Serif', ui-serif, Georgia, serif;

  /* motion */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-1: 0.18s;
  --dur-2: 0.34s;

  /* scroll-reveal grammar. Promoted from .page-home (css/landing.css) so every
     subpage inherits one entrance system instead of redefining it. Values are
     byte-identical to the home originals - this widened the scope, it did not
     retune the motion. */
  --reveal-dur: 0.72s;
  --reveal-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --reveal-rise: 24px;
  --stagger-step: 65ms;

  /* layout */
  --container: 1200px;
  --nav-h: 64px;
  --section-gap: clamp(72px, 10vw, 136px);

  /* z-scale */
  --z-nav: 50;
  --z-menu: 80;
  --z-overlay: 100;
}

/* ---------- Reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--bg-0);
  color: var(--text-hi);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden; /* fallback for engines without clip */
  overflow-x: clip;   /* clip contains horizontal bleed WITHOUT making <body> a scroll container - keeps position:sticky nav working on mobile */
}

/* ambient royal glow behind everything - cheap fixed gradients, no blur */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(68% 52% at 16% 8%, rgba(52, 104, 255, 0.13), transparent 60%),
    radial-gradient(58% 48% at 88% 16%, rgba(42, 91, 255, 0.11), transparent 58%),
    radial-gradient(52% 44% at 78% 62%, rgba(60, 110, 255, 0.09), transparent 60%),
    radial-gradient(60% 50% at 10% 74%, rgba(46, 96, 255, 0.08), transparent 60%),
    radial-gradient(46% 38% at 48% 42%, rgba(74, 122, 255, 0.05), transparent 66%),
    linear-gradient(180deg, #070c1a 0%, var(--bg-0) 40%);
}
/* fixed film grain over the aurora (port of the noise layer from 21st "Dark Gradient
   Background") — kills gradient banding and gives the ambient a premium, textured feel.
   Static, cheap, non-distracting. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; }

a {
  color: inherit;
  text-decoration: none;
  transition: color var(--dur-1) var(--ease-out);
}

button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

::selection { background: rgba(42, 91, 255, 0.4); color: #fff; }

:focus-visible {
  outline: 2px solid rgba(94, 140, 255, 0.85);
  outline-offset: 2px;
  border-radius: 4px;
}

/* scrollbar */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg-0); }
::-webkit-scrollbar-thumb {
  background: rgba(150, 180, 255, 0.16);
  border-radius: 8px;
  border: 2px solid var(--bg-0);
}
::-webkit-scrollbar-thumb:hover { background: rgba(150, 180, 255, 0.28); }

/* ---------- Tailwind-utility compat (REQUIRED by legacy inline JS) ---------- */
.hidden { display: none !important; }
.opacity-0 { opacity: 0; }
.opacity-100 { opacity: 1; }
.pointer-events-none { pointer-events: none; }
.pointer-events-auto { pointer-events: auto; }
.underline { text-decoration: underline; }
.text-primary { color: #7fa2ff; }
.mr-2 { margin-right: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mt-6 { margin-top: 1.5rem; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.gap-3 { gap: 0.75rem; }
.w-full { width: 100%; }
.space-y-3 > * + * { margin-top: 0.75rem; }
.leading-relaxed { line-height: 1.65; }
.font-bold { font-weight: 700; }
.tracking-tight { letter-spacing: -0.02em; }
.text-2xl { font-size: 1.5rem; line-height: 1.25; }
.rounded-2xl { border-radius: 1rem; }
.border { border-width: 1px; border-style: solid; }
.border-white\/10 { border-color: rgba(255, 255, 255, 0.1); }
.bg-surface\/90 { background-color: rgba(10, 13, 24, 0.92); }
.backdrop-blur-xl { backdrop-filter: blur(24px) saturate(160%); -webkit-backdrop-filter: blur(24px) saturate(160%); }
.shadow-glow { box-shadow: var(--glass-shadow); }
.text-white\/80 { color: rgba(240, 245, 255, 0.8); }
.transition-colors { transition: color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out); }
.duration-200 { transition-duration: 200ms; }
.hover\:text-white\/75:hover { color: rgba(255, 255, 255, 0.75); }
@media (min-width: 640px) {
  .sm\:flex-row { flex-direction: row; }
  .sm\:w-auto { width: auto; }
}
@media (min-width: 768px) {
  .md\:text-3xl { font-size: 1.875rem; line-height: 1.2; }
}

/* ---------- Primitives ---------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 32px);
}

/* blurred liquid glass - BUDGET: max 3 simultaneously composited layers */
.glass {
  position: relative;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow);
}
.glass::before {
  content: '';
  position: absolute;
  inset: 0 12% auto 12%;
  height: 1px;
  background: var(--glass-hairline);
  pointer-events: none;
}

/* royal card - the app's Advanced-tab card body, no blur.
   Carries a cursor-following spotlight (--mx/--my/--spot set from js/landing.js). */
.card {
  --mx: 50%;
  --my: 50%;
  --spot: 0;
  position: relative;
  border-radius: var(--r-card-lg);
  border: 1px solid var(--card-border);
  background:
    radial-gradient(340px circle at var(--mx) var(--my), rgba(90, 130, 255, calc(var(--spot) * 0.9)), transparent 60%),
    var(--card-bg);
  box-shadow: var(--card-shadow);
  transition:
    transform var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out);
}
.card-hover:hover, a.card:hover, .card:hover { --spot: 0.16; }
.card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 10%;
  right: 10%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(190, 210, 255, 0.42), transparent);
  pointer-events: none;
}
a.card, .card-hover { cursor: pointer; }
.card-hover:hover, a.card:hover {
  transform: translateY(-2px);
  border-color: var(--card-border-hover);
  box-shadow: var(--card-shadow-hover);
}

/* island - faint glass tray behind card groups (no blur, perf) */
.island {
  border-radius: 24px;
  padding: clamp(14px, 2vw, 22px);
  border: 1px solid var(--island-border);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.022), rgba(255, 255, 255, 0.006)),
    radial-gradient(130% 120% at 0% -10%, rgba(42, 91, 255, 0.05), transparent 60%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.045);
}

/* icon tile - 42x42 royal glass plate, lucide glyph 22/1.9 */
.icon-tile {
  width: 42px;
  height: 42px;
  border-radius: var(--r-tile);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  overflow: hidden;
  border: 1px solid var(--tile-border);
  background: var(--tile-bg);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.13);
  color: var(--icon-color);
}
.icon-tile svg { width: 22px; height: 22px; }

/* eyebrow - mono uppercase kicker */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(150, 185, 255, 0.85);
}

/* badge chip */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.55), rgba(10, 14, 30, 0.6));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-mid);
}

/* ---------- Buttons (glass recipes from the app) ---------- */
.btn {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 46px;
  padding: 11px 24px;
  border-radius: var(--r-pill);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: -0.005em;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  transition:
    transform var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out),
    background-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.btn svg { width: 18px; height: 18px; flex-shrink: 0; position: relative; }
.btn:active { transform: translateY(1px); }
/* liquid-glass: persistent top gloss (paints UNDER the label) */
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), transparent 44%);
  opacity: 0.8;
}
/* buttons carry no ::after decoration — hover is a calm border-brighten only (see the
   per-variant :hover rules below), matching the hero .xicon-cta. No sheen, no arrow. */
@keyframes btn-sheen {
  0% { left: -75%; opacity: 0; }
  22% { opacity: 0.9; }
  100% { left: 135%; opacity: 0; }
}
/* same liquid-glass sweep on the other prominent CTAs */
[data-discord-login], .cart-pop-checkout { position: relative; overflow: hidden; }
[data-discord-login]::after, .cart-pop-checkout::after {
  content: '';
  position: absolute;
  top: 0;
  left: -75%;
  width: 50%;
  height: 100%;
  pointer-events: none;
  transform: skewX(-16deg);
  background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.26), transparent);
  opacity: 0;
}
[data-discord-login]:hover::after, .cart-pop-checkout:hover::after { animation: btn-sheen 0.85s var(--ease-out); }
[data-discord-login] > *, .cart-pop-checkout > * { position: relative; }

.btn-primary {
  color: #eaf0ff;
  border: 1px solid rgba(120, 155, 255, 0.42);
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(90, 130, 255, 0.26), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.42), rgba(30, 58, 174, 0.38));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 6px 16px -14px rgba(42, 91, 255, 0.3);
}
.btn-primary:hover {
  border-color: rgba(140, 170, 255, 0.6);
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(100, 140, 255, 0.34), transparent 60%),
    linear-gradient(180deg, rgba(52, 100, 255, 0.5), rgba(36, 68, 196, 0.46));
}

.btn-ghost {
  color: var(--text-hi);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.4), rgba(10, 14, 30, 0.46));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.btn-ghost:hover {
  border-color: rgba(130, 165, 255, 0.45);
}

/* opt-in expanding arrow (add class .btn-xarrow) for text-only navigational CTAs. Buttons
   that already carry an icon (Discord) or a static arrow (.shiny-cta, .flow-go) must NOT get
   it — no double glyphs. The arrow is a mask tinted with the button's own text color; the
   negative margin cancels the flex gap while collapsed, so there's no trailing space at rest. */
.btn-xarrow::after {
  content: '';
  --btn-arrow: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='2.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M5%2012h14'/%3E%3Cpath%20d='m12%205%207%207-7%207'/%3E%3C/svg%3E") no-repeat center / contain;
  flex-shrink: 0;
  width: 0;
  height: 15px;
  margin-left: -9px;               /* cancel the flex gap while collapsed */
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
  transform: translateX(4px);
  background-color: currentColor;
  -webkit-mask: var(--btn-arrow);
  mask: var(--btn-arrow);
  transition:
    width 0.25s var(--ease-out),
    margin 0.25s var(--ease-out),
    opacity 0.25s var(--ease-out),
    transform 0.25s var(--ease-out);
}
.btn-xarrow:hover::after,
.btn-xarrow:focus-visible::after {
  width: 15px;
  margin-left: 0;                  /* reveal the 9px flex gap */
  opacity: 1;
  transform: translateX(0);
}
@media (prefers-reduced-motion: reduce) {
  .btn-xarrow::after { transition: none; }
}

/* ---------- Header / floating glass nav ---------- */
header[data-nav-floating] {
  position: sticky;
  top: 14px;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  gap: 18px;
  width: min(var(--container), calc(100% - 28px));
  margin: 14px auto 0;
  min-height: var(--nav-h);
  padding: 10px 12px 10px 20px;
  border-radius: var(--r-card-lg);
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow);
  overflow: visible; /* auth.js anchors dropdowns here - never clip, never transform */
  transition: background-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
header[data-nav-floating]::before {
  content: '';
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: var(--glass-hairline);
  pointer-events: none;
}
header[data-nav-floating][data-scrolled] {
  background:
    radial-gradient(60% 100% at 6% 0%, rgba(190, 210, 255, 0.08), transparent 55%),
    linear-gradient(180deg, rgba(7, 10, 22, 0.82), rgba(4, 6, 14, 0.88));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 18px 40px -20px rgba(0, 0, 0, 0.7),
    0 0 44px -32px rgba(42, 91, 255, 0.2);
}

/* brand */
header[data-nav-floating] > a[aria-label="Home"] {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: -0.01em;
  color: var(--text-hi);
  white-space: nowrap;
}
header[data-nav-floating] > a[aria-label="Home"] img {
  width: 26px;
  height: 26px;
  object-fit: contain;
}

/* primary nav */
header[data-nav-floating] nav {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-inline: auto;
}
a[data-nav-link] {
  padding: 9px 15px;
  border-radius: var(--r-pill);
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text-mid);
  border: 1px solid transparent;
  transition: color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
a[data-nav-link]:hover {
  color: var(--text-hi);
  background: rgba(150, 180, 255, 0.07);
}
a[data-nav-link][aria-current="page"] {
  color: #dfe8ff;
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(42, 91, 255, 0.3), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.16), rgba(30, 58, 174, 0.14));
  border-color: rgba(120, 155, 255, 0.28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* language switcher */
#i18n-switcher { position: relative; }
#i18n-switcher .i18n-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 46px;
  min-height: 44px;      /* 38px was under the size a finger reliably hits */
  padding: 8px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.4), rgba(10, 14, 30, 0.46));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  color: var(--text-mid);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
#i18n-switcher .i18n-trigger:hover {
  color: var(--text-hi);
  border-color: rgba(120, 160, 255, 0.36);
}
#i18n-switcher .i18n-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 150px;
  padding: 6px;
  border-radius: 14px;
  border: 1px solid var(--card-border);
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(42, 91, 255, 0.14), transparent 55%),
    linear-gradient(180deg, rgba(13, 18, 38, 0.97), rgba(8, 12, 26, 0.98));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 22px 44px -20px rgba(0, 0, 0, 0.75);
  z-index: var(--z-menu);
}
#i18n-switcher .i18n-opt {
  display: flex;
  width: 100%;
  align-items: center;
  padding: 9px 12px;
  border-radius: 9px;
  font-size: 0.88rem;
  color: var(--text-mid);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
#i18n-switcher .i18n-opt:hover { color: var(--text-hi); background: rgba(150, 180, 255, 0.08); }
#i18n-switcher .i18n-opt.is-active {
  color: #dfe8ff;
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(42, 91, 255, 0.3), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.18), rgba(30, 58, 174, 0.16));
}

/* cart link in header */
[data-nav-cart] {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 38px;
  padding: 8px 14px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.4), rgba(10, 14, 30, 0.46));
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-mid);
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
[data-nav-cart]:hover { color: var(--text-hi); border-color: rgba(120, 160, 255, 0.36); }
.cart-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, rgba(62, 108, 255, 0.95), rgba(36, 72, 214, 0.95));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 0 12px -4px rgba(42, 91, 255, 0.8);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  color: #f2f6ff;
}

/* Discord login button (logged-out) */
[data-discord-login] {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 42px;
  padding: 9px 19px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(120, 155, 255, 0.42);
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(90, 130, 255, 0.26), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.42), rgba(30, 58, 174, 0.38));
  /* inset top highlight only: the outer royal glow is gone so the nav stays flat */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
  font-size: 0.9rem;
  font-weight: 500;
  color: #eaf0ff;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
/* hover changes the border only: no lift, no glow */
[data-discord-login]:hover {
  border-color: rgba(140, 170, 255, 0.6);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
[data-discord-login] svg, [data-discord-login] i { width: 18px; height: 18px; }

/* logged-in state (auth.js swaps innerHTML) */
[data-discord-login].logged-in {
  padding: 6px 14px 6px 8px;
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.5), rgba(10, 14, 30, 0.56));
  border-color: var(--border-mid);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
[data-discord-login].logged-in:hover { border-color: rgba(120, 160, 255, 0.36); transform: none; }
.discord-user-content { display: inline-flex; align-items: center; gap: 9px; }
.discord-user-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border-strong);
}
.discord-user-avatar-fallback {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  color: var(--icon-color);
}
.discord-user-name {
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.88rem;
  color: var(--text-hi);
}

/* user dropdown menu (auth.js) */
.user-menu-wrap { position: relative; display: inline-flex; }
.user-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 200px;
  padding: 6px;
  border-radius: 14px;
  border: 1px solid var(--card-border);
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(42, 91, 255, 0.14), transparent 55%),
    linear-gradient(180deg, rgba(13, 18, 38, 0.97), rgba(8, 12, 26, 0.98));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 22px 44px -20px rgba(0, 0, 0, 0.75);
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
  z-index: var(--z-menu);
}
.user-menu-wrap.is-open .user-menu {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.user-menu a, .user-menu button {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 13px;
  border-radius: 9px;
  font-size: 0.9rem;
  color: var(--text-mid);
  cursor: pointer;
  text-align: left;
  transition: color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
.user-menu a:hover, .user-menu button:hover {
  color: var(--text-hi);
  background: rgba(150, 180, 255, 0.08);
}
.user-menu svg, .user-menu i { width: 17px; height: 17px; flex-shrink: 0; opacity: 0.8; }

/* mobile menu button */
#mobile-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-tile);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.4), rgba(10, 14, 30, 0.46));
  color: var(--text-hi);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out);
}
#mobile-menu-btn:hover { border-color: rgba(120, 160, 255, 0.36); }
#mobile-menu-btn svg { width: 20px; height: 20px; }

/* mobile menu overlay (portaled to <body> by auth.js) */
#mobile-menu {
  position: fixed;
  inset: 0;
  z-index: var(--z-menu);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 32px 24px;
  background:
    radial-gradient(90% 60% at 50% -10%, rgba(42, 91, 255, 0.16), transparent 60%),
    linear-gradient(180deg, rgba(5, 8, 18, 0.96), rgba(2, 3, 10, 0.98));
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  transition: opacity 0.25s var(--ease-out);
}
#mobile-menu a {
  padding: 13px 26px;
  border-radius: var(--r-pill);
  font-size: 1.25rem;
  font-weight: 500;
  color: var(--text-mid);
  transition: color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
#mobile-menu a:hover { color: var(--text-hi); background: rgba(150, 180, 255, 0.07); }
#mobile-menu [data-discord-login] { margin-top: 18px; }
#mobile-menu-close {
  position: absolute;
  top: 20px;
  right: 20px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-tile);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.5), rgba(10, 14, 30, 0.56));
  color: var(--text-hi);
  cursor: pointer;
}
#mobile-menu-close svg { width: 20px; height: 20px; }

/* nav responsive collapse */
@media (max-width: 1023px) {
  header[data-nav-floating] nav { display: none; }
  header[data-nav-floating] { gap: 10px; padding-right: 10px; }
  header[data-nav-floating] > a[aria-label="Home"] { margin-right: auto; }
  #mobile-menu-btn { display: inline-flex; }
  header[data-nav-floating] [data-discord-login] { display: none; }
}
@media (max-width: 479px) {
  #i18n-switcher .i18n-trigger { min-width: 42px; padding: 8px 10px; }
}

/* ---------- Cart popover (auth.js injected) ---------- */
.cart-pop-wrap { position: relative; display: inline-flex; }
.cart-popover {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  width: 320px;
  border-radius: var(--r-card);
  border: 1px solid var(--card-border);
  background:
    radial-gradient(120% 150% at 100% 0%, rgba(42, 91, 255, 0.16), transparent 55%),
    linear-gradient(180deg, rgba(13, 18, 38, 0.97), rgba(8, 12, 26, 0.98));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 26px 52px -22px rgba(0, 0, 0, 0.78),
    0 0 34px -26px rgba(42, 91, 255, 0.3);
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
  z-index: var(--z-menu);
}
.cart-pop-wrap.is-open .cart-popover {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.cart-pop-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px 10px;
}
.cart-pop-title {
  font-size: 0.78rem;
  font-family: var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-low);
}
.cart-pop-close, .cart-pop-remove {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  border: 1px solid transparent;
  color: var(--text-low);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
.cart-pop-close:hover, .cart-pop-remove:hover {
  color: var(--text-hi);
  border-color: var(--border-mid);
  background: rgba(150, 180, 255, 0.07);
}
.cart-pop-close svg, .cart-pop-remove svg,
.cart-pop-close i, .cart-pop-remove i { width: 16px; height: 16px; }
.cart-pop-body { padding: 4px 16px 12px; }
.cart-pop-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border-radius: 13px;
  border: 1px solid var(--border-soft);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.4), rgba(10, 14, 30, 0.46));
}
.cart-pop-thumb {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border-mid);
  background: var(--bg-2);
  flex-shrink: 0;
}
.cart-pop-thumb img { width: 100%; height: 100%; object-fit: cover; }
.cart-pop-info { flex: 1; min-width: 0; }
.cart-pop-name {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-hi);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cart-pop-price {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: rgba(150, 185, 255, 0.9);
  margin-top: 2px;
}
.cart-pop-link {
  display: inline-block;
  margin-top: 4px;
  font-size: 0.78rem;
  color: var(--text-low);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cart-pop-link:hover { color: var(--text-hi); }
.cart-pop-empty {
  padding: 18px 4px;
  text-align: center;
  font-size: 0.88rem;
  color: var(--text-low);
}
.cart-pop-actions { padding: 0 16px 16px; }
.cart-pop-checkout {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(120, 155, 255, 0.42);
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(90, 130, 255, 0.26), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.42), rgba(30, 58, 174, 0.38));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 8px 22px -12px rgba(42, 91, 255, 0.55);
  font-size: 0.9rem;
  font-weight: 500;
  color: #eaf0ff;
  transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.cart-pop-checkout:hover {
  border-color: rgba(140, 170, 255, 0.6);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 12px 28px -12px rgba(42, 91, 255, 0.65);
}

/* ---------- Footer ---------- */
#site-footer {
  margin-top: var(--section-gap);
  /* same liquid-glass tone as the nav / cards / announce so it belongs to the set
     (translucent: the aurora + stars still show through). */
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  padding: clamp(36px, 6vw, 64px) clamp(18px, 4vw, 32px) 32px;
}
#site-footer nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 26px;
  max-width: var(--container);
  margin: 0 auto 26px;
}
#site-footer nav a {
  padding: 6px 2px;
  font-size: 0.9rem;
  color: var(--text-mid);
}
#site-footer nav a:hover { color: var(--text-hi); }
#site-footer p {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.08em;
  color: var(--text-low);
}

/* ---------- Section rhythm ----------
   Promoted verbatim from `.page-home .section*` (css/landing.css) so any subpage
   gets the vertical rhythm and heading scale without carrying the home layer.
   Home keeps its own leaner --section-gap override, so its spacing is unchanged. */
.section { margin-top: var(--section-gap); }
.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  max-width: 720px;
  margin-inline: auto;
  margin-bottom: clamp(30px, 4.5vw, 52px);
}
.section-head h2 {
  font-size: clamp(1.7rem, 3.6vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.12;
  color: var(--text-hi);
}
.section-sub {
  font-size: 1.02rem;
  line-height: 1.65;
  color: var(--text-mid);
  max-width: 58ch;
}
/* Heading accent: serif-italic royal gradient on the <em> payoff phrase. */
.section-head h2 em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.012em;
  background: linear-gradient(115deg, #d4e2ff 8%, #7fa2ff 54%, var(--accent) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* ---------- Scroll reveal ----------
   Promoted from `.page-home .reveal*` (css/landing.css). The observer in
   js/landing.js was already page-agnostic; only the CSS was locked to home.
   .reveal        observer hook (JS adds .is-visible)
   .reveal-group  stagger host: its .reveal-item children arrive in sequence
   .reveal-item   staggered child, driven by an ANIMATION on purpose. a transition
                  shorthand here would silently wipe the element's own hover
                  transition, and hover behaviour must stay exactly as it is.
   .reveal-still  container is a trigger only, it does not travel itself
   .reveal-fade   fade with no travel, for things that already move */
.reveal {
  opacity: 0;
  transform: translateY(var(--reveal-rise));
  transition: opacity var(--reveal-dur) var(--reveal-ease),
              transform var(--reveal-dur) var(--reveal-ease);
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}
/* fade only. must sit after .is-visible: equal specificity, later wins */
.reveal-fade { --reveal-rise: 0px; }
.reveal.reveal-still {
  opacity: 1;
  transform: none;
  transition: none;
}
.reveal-group .reveal-item { opacity: 0; }
.reveal-group.is-visible .reveal-item {
  animation: flow-card-in var(--reveal-dur) var(--reveal-ease) both;
  animation-delay: calc(var(--stagger-lead, 0ms) + var(--stagger-step) * var(--i, 0));
}
@keyframes flow-card-in {
  from { opacity: 0; transform: translateY(var(--reveal-rise, 14px)); }
  to { opacity: 1; transform: none; }
}


/* ---------- Footer enrichment (moved from css/landing.css) ----------
   The brand column, link grid, glowing top hairline and the flicker canvas were
   locked to .page-home, which left every subpage with the bare nav+copyright
   footer. De-scoped here so the whole site shares one footer. Values verbatim. */

#site-footer { position: relative; overflow: hidden; padding-bottom: clamp(20px, 2.5vw, 30px); }
/* glowing glass hairline centered on the top edge (premium capstone accent) */
.footer-edge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(440px, 62%);
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(130, 170, 255, 0.75), transparent);
  box-shadow: 0 0 14px 1px rgba(42, 91, 255, 0.5);
  pointer-events: none;
  z-index: 2;
}
#site-footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 60%;
  height: 160px;
  pointer-events: none;
  background: radial-gradient(50% 100% at 50% 0%, rgba(42, 91, 255, 0.12), transparent 70%);
}

.footer-top {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.15fr 2fr;
  gap: clamp(30px, 5vw, 68px);
  max-width: var(--container);
  margin: 0 auto;
  align-items: start;
}
.footer-brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  max-width: 340px;
}
.footer-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--text-hi);
}
#site-footer .footer-tag {
  text-align: left;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--text-mid);
}
.footer-discord {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 2px;
  padding: 10px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.5), rgba(10, 14, 30, 0.56));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  font-size: 0.88rem;
  font-weight: 500;
  color: #dde7ff;
  transition: border-color var(--dur-1) var(--ease-out);
}
.footer-discord svg { color: #8fb0ff; }
/* same hover as the .btn-ghost Discord button in the final CTA: border colour only.
   the old rule lifted the button 1px and lit a glow, which made the two Discord CTAs
   behave differently on the same page. */
.footer-discord:hover {
  border-color: rgba(130, 165, 255, 0.45);
}

.footer-links {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(18px, 3vw, 40px);
}
.footer-col h3 {
  margin-bottom: 15px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(150, 185, 255, 0.7);
}
.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: 11px; }
.footer-col a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  color: var(--text-mid);
  transition: color var(--dur-1) var(--ease-out);
}
.footer-col a:hover { color: #dde7ff; }
.footer-col a svg { flex-shrink: 0; color: rgba(150, 185, 255, 0.6); }

.footer-bottom {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 20px;
  max-width: var(--container);
  margin: clamp(30px, 4vw, 48px) auto 0;
  padding-top: 20px;
  border-top: 1px solid var(--border-soft);
}
#site-footer .footer-bottom p {
  margin: 0;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  color: var(--text-low);
}

@media (max-width: 900px) {
  .footer-top { grid-template-columns: 1fr; gap: 34px; }
  .footer-links { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 24px; }
}
/* flickering-dot texture spanning the WHOLE footer (js/footer-fx.js), with the
   PROSPIRA wordmark revealed on the right. Sits behind the footer content; the top
   fades out so the dots melt into the page above (Atlas-style subtle texture). */
.footer-fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 100%);
}
.footer-fx canvas { display: block; width: 100%; height: 100%; }
@media (max-width: 767px) {
  /* mobile: the wordmark sits in a strip BELOW the copyright bar, so give it room */
  #site-footer { padding-bottom: clamp(78px, 22vw, 104px); }
}

/* ---------- Page background layer (moved from css/landing.css) ----------
   The fixed starfield (.page-stars, js/sparkles.js) and the per-section canvas
   backdrops (.has-bgfx/.bgfx, js/section-bgfx.js). Also page-agnostic now. */

/* Static canvas backdrops painted once by js/section-bgfx.js (no animation -
   the only moving background stays the hero shader). The canvas bleeds a
   little past its section and fades out via mask so it melts into the page. */
/* fixed, whole-page animated starfield (js/sparkles.js, canvas[data-sparkles]),
   sitting over the aurora and under the grain + content. */
.page-stars { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
.page-stars canvas { width: 100%; height: 100%; display: block; }
.has-bgfx { position: relative; isolation: isolate; }
.bgfx {
  position: absolute;
  inset: clamp(-96px, -7vw, -48px) 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
}
.bgfx canvas {
  width: 100%;
  height: 100%;
  display: block;
}

@media (max-width: 768px) {
  .bgfx { inset: -40px 0; }
}

/* ---------- Navigation behaviour (moved from css/landing.css) ----------
   The morph and the sticky-nav overflow fix were locked to .page-home, so
   /utility/ rendered a permanent glass pill instead of the home page bar.
   Values verbatim; only the scope changed. */
/* ---------- Nav morph: flat transparent bar at the very top,
   collapsing into the floating glass pill once you scroll. Uses the existing
   data-scrolled toggle (js/landing.js navScrollState).

   SCOPE WARNING: the selector list below is exactly "the pages that load
   js/landing.js". Adding a page here WITHOUT that script leaves the header
   permanently transparent, because nothing ever sets [data-scrolled] and the
   glass pill on ::after never fades in. ---------- */
:is(.page-home, .page-utility, .page-services) header[data-nav-floating] {
  width: min(var(--container), calc(100% - 28px));
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition:
    width var(--dur-2) var(--ease-out),
    padding var(--dur-2) var(--ease-out);
}
/* The glass pill (blur + tint + border + shadow) lives on ::after so it fades in as
   one smooth panel via opacity. NOTE: the mobile "nav slides away on scroll" bug was
   NOT this backdrop-filter - the real cause was overflow on <body> (an ancestor of
   the sticky <header>), fixed in the "Sticky-nav mobile fix" block below. With that
   fixed, the frosted pill is safe on every device, so the live glass is restored. */
:is(.page-home, .page-utility, .page-services) header[data-nav-floating]::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 1px solid var(--glass-border);
  background:
    radial-gradient(60% 100% at 6% 0%, rgba(190, 210, 255, 0.08), transparent 55%),
    linear-gradient(180deg, rgba(7, 10, 22, 0.82), rgba(4, 6, 14, 0.88));
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 18px 40px -20px rgba(0, 0, 0, 0.7),
    0 0 44px -32px rgba(42, 91, 255, 0.2);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
  pointer-events: none;
}
/* the pill's top hairline only reads once the pill exists (scrolled) */
:is(.page-home, .page-utility, .page-services) header[data-nav-floating]::before {
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
:is(.page-home, .page-utility, .page-services) header[data-nav-floating][data-scrolled] {
  width: min(940px, calc(100% - 28px));
  padding: 9px 12px 9px 18px;
  background: transparent;   /* keep the element clear; glass is the ::after panel */
  box-shadow: none;
}
:is(.page-home, .page-utility, .page-services) header[data-nav-floating][data-scrolled]::after { opacity: 1; }
:is(.page-home, .page-utility, .page-services) header[data-nav-floating][data-scrolled]::before { opacity: 1; }

/* ---------- Sticky-nav mobile fix ----------
   Mobile Chrome / Samsung Internet break position:sticky when ANY ancestor of the
   sticky element has overflow other than visible - INCLUDING overflow:clip. Desktop
   tolerates it; mobile does not (the nav slides up and vanishes during touch scroll,
   which is exactly what was happening even though getBoundingClientRect read top:14).
   The base stylesheet puts overflow-x on <body>, an ancestor of <header>. So on these
   pages we clear <body> overflow entirely and instead contain the horizontal
   bleed (champion glow, testimonials marquee, footer wordmark) on <main> and
   <footer> - which are siblings of <header>, never its ancestors. Sticky then runs
   on the compositor natively on every device. */
:is(.page-home, .page-utility, .page-services) { overflow: visible; }                        /* body: no overflow on the sticky ancestor chain */
:is(.page-home, .page-utility, .page-services) > main,
:is(.page-home, .page-utility, .page-services) > footer { overflow-x: clip; }                /* contain the horizontal bleed here instead */


/* ---------- FAQ skin (moved from css/landing.css) ----------
   The accordion chrome: rounded entry, the +/x toggle that turns 45deg when
   open, the data-cat chip on the right and the answer indent that lines up
   under the question. De-scoped from .page-home so subpages share one FAQ.
   Behaviour (smooth height) comes from smoothFaq in js/landing.js, which
   already keys off details.home-faq-entry / .home-faq-answer. */
.faq-inner { max-width: 860px; }
.home-faq-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* height animation contract: ALL vertical padding lives on summary +
   .home-faq-answer; <details> itself only carries chrome */
.home-faq-entry {
  position: relative;
  border-radius: var(--r-card-lg);
  border: 1px solid var(--card-border);
  background: var(--card-bg);
  box-shadow: var(--card-shadow);
  overflow: hidden;
  transition:
    border-color var(--dur-2) var(--ease-out),
    transform var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
/* royal glow that follows the cursor (js/landing.js magicCards feeds --mx/--my);
   falls back to a soft top-left wash before the pointer ever enters the card */
.home-faq-entry::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(300px circle at var(--mx, 14%) var(--my, -10%), rgba(42, 91, 255, 0.18), transparent 68%);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
  pointer-events: none;
}
/* no lift on hover: the 2px rise moved the +/x toggle with the card, which read as the
   icon jumping. hover feedback is now colour and glow only, nothing shifts position. */
.home-faq-entry:hover {
  border-color: var(--card-border-hover);
}
.home-faq-entry:hover::before,
.home-faq-entry[open]::before { opacity: 1; }
.home-faq-entry[open] {
  border-color: rgba(130, 165, 255, 0.5);
  box-shadow: var(--card-shadow), 0 0 42px -22px rgba(42, 91, 255, 0.55);
}
.home-faq-entry summary {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: #e7eeff;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.home-faq-entry summary::-webkit-details-marker { display: none; }
/* the question must be ONE flex item - otherwise inline <span translate="no"> children
   become separate flex items and the summary's gap pushes the words apart */
.home-faq-entry summary .faq-q { flex: 1 1 auto; min-width: 0; }
/* circular +/x toggle on the left (the + rotates 45deg into an x when open) */
.home-faq-entry summary::before {
  content: '';
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a8c0ff' stroke-width='1.7' stroke-linecap='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E") center / 17px no-repeat,
    linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.015));
  transition:
    transform var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
.home-faq-entry[open] summary::before {
  transform: rotate(45deg);
  border-color: rgba(130, 165, 255, 0.7);
  box-shadow: 0 0 18px -4px rgba(42, 91, 255, 0.6), inset 0 0 0 1px rgba(42, 91, 255, 0.2);
}
/* the toggle no longer swells on hover either. its only transform is the 45deg turn
   from + to x when the entry opens, which is state, not a hover effect. */
.home-faq-entry summary:focus-visible {
  outline: 2px solid rgba(130, 165, 255, 0.55);
  outline-offset: -3px;
  border-radius: inherit;
}
/* An entry with no category must not render the chip at all. attr() on a missing
   attribute resolves to an empty string, not to nothing, so the chip still drew
   its border and padding and left an empty box floating at the right edge of the
   row. Pages whose FAQ is one single topic (the /services/ one is all about the
   day of the session) have nothing to put there and should show nothing. */
.home-faq-entry summary:not([data-cat])::after { display: none; }

/* category chip on the right (label comes from data-cat on the summary) */
.home-faq-entry summary::after {
  content: attr(data-cat);
  flex: 0 0 auto;
  margin-left: auto;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid rgba(120, 150, 235, 0.28);
  background: rgba(42, 91, 255, 0.06);
  color: #9fb4ee;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: border-color var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.home-faq-entry[open] summary::after {
  border-color: rgba(130, 165, 255, 0.55);
  color: #cdddff;
}
.home-faq-answer {
  padding: 0 24px 18px calc(20px + 38px + 16px); /* align under the question, clear of the toggle */
  font-size: 0.93rem;
  line-height: 1.62;
  color: var(--text-mid);
}
.home-faq-answer p + p,
.home-faq-answer p + ul,
.home-faq-answer ul + p { margin-top: 10px; }
.home-faq-answer ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
/* `ul > li`, not `> ul > li`. Every answer wraps its content in .faq-inner, so
   the child combinator never matched a single list on the site: the rows got
   neither the indent nor the anchor, while the bullet below was drawn anyway. */
.home-faq-answer ul > li {
  position: relative;
  padding-left: 20px;
}
/* A list that draws its own markers opts out. Two markers per row is one too
   many, and the built-in bullet would sit under the custom one. */
.home-faq-answer ul[data-no-bullets] > li { padding-left: 0; }
/* The bullet is absolutely positioned, so it may only be drawn on rows the rule
   above actually anchored. It used to match every li at any depth while the
   positioning matched none of them, so every bullet fell back to the
   .home-faq-entry box and the whole list stacked into a single dot in its top
   left corner. Bullet and anchor now share one selector and cannot drift apart. */
.home-faq-answer ul:not([data-no-bullets]) > li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(42, 91, 255, 0.75);
  box-shadow: 0 0 6px rgba(42, 91, 255, 0.5);
}
.home-faq-answer ul ul { margin-top: 7px; }
.home-faq-answer a {
  color: #a8c0ff;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.home-faq-answer a:hover { color: #d4e2ff; }
@media (max-width: 560px) {
  .home-faq-entry summary {
    gap: 12px;
    padding: 10px 14px;
    font-size: 0.95rem;
  }
  .home-faq-entry summary::before { width: 34px; height: 34px; background-size: 16px; }
  .home-faq-entry summary::after { display: none; }        /* chip is a desktop enhancement */
  .home-faq-answer { padding: 0 15px 15px calc(14px + 34px + 12px); }
}



/* ---------- Screen-reader-only (moved from css/landing.css) ---------- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- BorderBeam (moved from css/landing.css) ----------
   A light dot travelling a rounded-rect outline. js/landing.js borderBeam()
   measures each [data-border-beam] and writes --beam-path; it was already
   page-agnostic, only this CSS was locked to .page-home. */
/* BorderBeam: a light dot travelling the frame outline. The layer is masked to a thin
   ring (border-box minus content-box) so the glow only shows on the border. */
.border-beam {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 2.2px;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}
.border-beam-dot {
  position: absolute;
  top: 0;
  left: 0;
  /* round, symmetric glow (NO offset-rotate): a rotating streak spiked its angular
     speed on the short corner arcs (slow-then-jerk); a radial blob has no orientation,
     so it glides through the rounded corners at a perfectly constant speed. width sets
     how LONG the lit stretch reads on the border. */
  width: 620px;
  height: 620px;
  offset-path: var(--beam-path);
  offset-distance: 0%;
  background: radial-gradient(circle at center, rgba(198, 216, 255, 1) 0%, rgba(140, 172, 255, 0.6) 22%, rgba(120, 155, 255, 0.22) 40%, transparent 60%);
  animation: border-beam-move 9s linear infinite;
}
@media (max-width: 640px) {
  /* smaller frame -> shorter perimeter: scale the glow down so it stays a travelling
     beam (not a fully-lit border), but keep it clearly visible via a thicker ring. */
  .border-beam { padding: 2.6px; }
  .border-beam-dot { width: 320px; height: 320px; }
}
@keyframes border-beam-move { to { offset-distance: 100%; } }
/* the featured plan card is far smaller than the hero frame - scale the travelling
   light down so it reads as a moving beam, not a permanently lit edge. */
:is(.plan-card, .u-plan, .s-plan) .border-beam-dot {
  width: 460px;
  height: 460px;
  animation-duration: 7s;
  /* the popular card already has a bright royal border, so a royal beam disappears into
     it - a white-hot core is what actually reads as a travelling light here. */
  background: radial-gradient(circle at center, #ffffff 0%, rgba(224, 237, 255, 0.92) 13%, rgba(165, 196, 255, 0.45) 32%, transparent 62%);
}
:is(.plan-card, .u-plan, .s-plan) .border-beam {
  /* straddle the card's border line: with a 3px ring, pulling the layer 1.5px out puts
     half the beam outside the edge and half inside, i.e. centred ON the line. */
  inset: -1.5px;
  border-radius: calc(var(--r-card-lg) + 1.5px);
  padding: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .border-beam-dot { animation: none !important; opacity: 0.5; }
}

/* ---------- Billing-period switch (moved from css/landing.css) ----------
   Royal segmented control. js/landing.js pricingSwitch() measures the active
   .plan-tab and drives [data-switch-thumb]; that script was already page
   agnostic, only this skin was locked to .page-home. */
.plan-switch-wrap { display: flex; justify-content: center; margin-bottom: clamp(28px, 4vw, 44px); }
.plan-switch {
  position: relative;
  display: inline-flex;
  gap: 4px;
  padding: 5px;
  max-width: 100%;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.55), rgba(10, 14, 30, 0.62));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.plan-tab {
  position: relative;
  z-index: 1;                        /* above the sliding thumb */
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--text-low);
  white-space: nowrap;
  cursor: pointer;
  transition:
    color var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    background var(--dur-2) var(--ease-out);
}
.plan-tab:not(.is-active):hover { color: var(--text-mid); }
/* the active pill itself is just text now - the sliding thumb carries the fill, so it
   can travel between options instead of snapping (js/landing.js positions it). */
.plan-tab.is-active { color: #eaf1ff; }
.plan-switch-thumb {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
  border-radius: var(--r-pill);
  border: 1px solid rgba(120, 155, 255, 0.42);
  background:
    radial-gradient(130% 150% at 0% 0%, rgba(90, 130, 255, 0.26), transparent 60%),
    linear-gradient(180deg, rgba(42, 91, 255, 0.42), rgba(30, 58, 174, 0.38));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 8px 22px -14px rgba(42, 91, 255, 0.6);
  /* slight overshoot = the spring feel of the reference, without a physics lib */
  transition:
    transform 0.42s cubic-bezier(0.34, 1.35, 0.5, 1),
    width 0.42s cubic-bezier(0.34, 1.35, 0.5, 1),
    height 0.42s cubic-bezier(0.34, 1.35, 0.5, 1),
    opacity 0.2s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .plan-switch-thumb { transition: opacity 0.2s linear; }
}
.plan-save {
  padding: 2px 7px;
  border-radius: var(--r-pill);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #bcd0ff;
  background: rgba(42, 91, 255, 0.22);
  border: 1px solid rgba(120, 160, 255, 0.3);
}
.plan-tab.is-active .plan-save {
  color: #eef3ff;
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.26);
}

/* Touch targets, decided by input device rather than by width. A 39px tab is
   comfortable with a mouse and too small for a finger, and the two do not line
   up with screen size: a 1440px touchscreen laptop needs the bigger target and
   a 760px desktop window does not. pointer: coarse asks the right question.
   The width query stays as a backstop for the tablet range. */
@media (pointer: coarse) {
  .plan-tab { padding-block: 14px; }
}
@media (max-width: 900px) {
  .plan-tab { padding-block: 14px; }
}

/* Narrow screens: the four periods plus their "Save x%" badges cannot fit one row
   (they overflowed, and flex-wrap left a ragged pill). Lay them out as a tidy 2x2
   grid that fills the available width instead. */
@media (max-width: 640px) {
  .plan-switch-wrap { padding-inline: 2px; }
  .plan-switch {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 5px;
    width: 100%;
    border-radius: 20px;          /* pill -> rounded block now that it is two rows */
  }
  /* Three options in a two-column grid left the third one alone with an empty
     cell beside it. Spanning it across both columns fills the row and, unlike
     forcing three columns, leaves the "Save 37%" badge room to breathe even on
     a 320px phone. */
  .plan-switch > .plan-tab:last-child { grid-column: 1 / -1; }
  .plan-tab {
    justify-content: center;
    gap: 6px;
    /* 14px, not 10: takes the tab to a 44px touch target. At 10 it measured 37,
       and at 13 the Lifetime tab came out 42, because the other two are padded
       out by the "Save 37%" badge and it is not. Size for the tab without one. */
    padding: 14px 6px;
    font-size: 0.72rem;
  }
  .plan-save { padding: 2px 6px; font-size: 0.56rem; }
}
/* very small phones: tighten once more so nothing clips */
@media (max-width: 360px) {
  .plan-tab { gap: 5px; padding: 14px 4px; font-size: 0.67rem; }
  .plan-save { padding: 2px 5px; font-size: 0.53rem; }
}


/* ---------- Announcement bar (moved from css/landing.css) ----------
   Site-wide chrome now, not home content: any page that loads this stylesheet
   and drops the markup above <header> gets the bar. Sits at z-index 40, i.e.
   above a hero pulled under the nav, below the sticky header (50) and the
   mobile menu (80). */
.announce {
  /* the hero is pulled up by -116px to sit under the nav, and being positioned it painted
     over this bar and swallowed clicks on the Discord link. sits above the hero, below the
     sticky header (50) and the mobile menu (80). */
  position: relative;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 9px 16px;
  border-bottom: 1px solid var(--glass-border);
  /* same liquid-glass tone as the nav / cards / footer (translucent: aurora shows through) */
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  color: var(--text-mid);
  text-align: center;
}
.announce svg { color: rgba(150, 185, 255, 0.9); flex-shrink: 0; }
.announce a {
  position: relative;
  color: #a8c0ff;
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* text link is ~19px tall; pad the hit area out to a thumb-sized target. absolute, so
   the announcement bar keeps its exact height.
   -13px, not -12px: at 12 the target measured 19 + 24 = 43px, one pixel under the
   44px minimum. The pseudo-element is out of flow, so the bar height is unchanged. */
.announce a::after {
  content: "";
  position: absolute;
  inset: -13px 0;
}
.announce a:hover { color: #d4e2ff; }


/* ---------- App window frame (moved from css/landing.css) ----------
   The browser-style chrome the home carousel puts round every screenshot:
   glass frame, three window dots, a monospace app label. Shared under the
   neutral name .app-frame so a static figure does not have to pretend to be
   a carousel slide; .tbx-car-* stays valid so the home markup is untouched. */
.tbx-car-frame, .app-frame {
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid rgba(130, 160, 240, 0.24);
  background: linear-gradient(180deg, rgba(16, 22, 42, 0.7), rgba(9, 13, 28, 0.82));
  box-shadow: 0 40px 90px -46px rgba(0, 0, 0, 0.85);
}
.tbx-car-bar, .app-frame-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 13px;
  border-bottom: 1px solid rgba(150, 180, 255, 0.12);
  background: rgba(255, 255, 255, 0.02);
}
.tbx-car-bar span, .app-frame-bar span { width: 9px; height: 9px; border-radius: 50%; background: rgba(150, 170, 220, 0.28); }
.tbx-car-bar span:nth-child(1), .app-frame-bar span:nth-child(1) { background: rgba(255, 95, 86, 0.5); }
.tbx-car-bar span:nth-child(2), .app-frame-bar span:nth-child(2) { background: rgba(255, 189, 46, 0.5); }
.tbx-car-bar span:nth-child(3), .app-frame-bar span:nth-child(3) { background: rgba(39, 201, 63, 0.45); }
.tbx-car-bar em, .app-frame-bar em { margin-left: 8px; font-style: normal; font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.03em; color: #8ea0c8; }
/* matches the real app-window ratio of the screenshots, so nothing is cropped. the old
   1092/693 box cut ~10% off the right edge, which sliced the Restore buttons in Backups
   and the third card column in General. */
.tbx-car-frame img, .app-frame img { display: block; width: 100%; aspect-ratio: 1100 / 626; object-fit: cover; object-position: top left; }


/* ---------- Framed product shot (moved from css/landing.css) ----------
   The glass frame the home hero puts round its screenshot. Shared as .shot-frame
   so a static figure can use it without pretending to be the hero; .hero-shot-*
   stays valid so the home markup is untouched. The 3D bits belong to the tilt,
   not the frame, so they now hang off [data-hero-tilt] (js/landing.js heroTilt)
   instead of being paid for by every page that just wants the border. */
.hero-shot-frame,
.shot-frame {
  position: relative;
  max-width: 1040px;
  margin: 0 auto;
  padding: 10px;
  border-radius: 26px;
  border: 1px solid rgba(120, 150, 255, 0.16);
  background: linear-gradient(180deg, rgba(18, 24, 46, 0.9), rgba(9, 12, 26, 0.92));
}
[data-hero-tilt] {
  transform-style: preserve-3d;
  will-change: transform;
}
.hero-shot-inner,
.shot-inner {
  position: relative;
  border-radius: 17px;
  overflow: hidden;
  background: #0a0e1c;
}
.hero-shot,
.shot-inner img { display: block; width: 100%; height: auto; }

/* ---------- Spec pills (moved from css/landing.css) ----------
   Vanilla port of the originui hero pill: glass pill, leading icon divided from
   the label by a hairline, calm hover. Shared as .spec-pills so any page can use
   the row; .hero-stats stays valid so the home markup is untouched.

   The staggered hs-rise entrance is NOT here on purpose. It fires on page load,
   which only makes sense for a row sitting in the hero. Everywhere else the site
   enters on scroll through .reveal, so that animation stays scoped to home. */
.hero-stats,
.spec-pills {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 32px;
  padding: 0;
  list-style: none;
}
.hero-stats li,
.spec-pills li {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 14px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(150, 180, 255, 0.12);
  background: linear-gradient(180deg, rgba(30, 38, 66, 0.28), rgba(14, 18, 38, 0.32));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  font-family: var(--font-mono);
  font-size: 0.73rem;
  letter-spacing: 0.05em;
  color: rgba(198, 210, 238, 0.55);
  white-space: nowrap;
  transition:
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}
.hero-stats li:hover,
.spec-pills li:hover {
  border-color: rgba(150, 180, 255, 0.28);
  color: rgba(212, 222, 245, 0.8);
}
/* leading icon, divided from the label by a faint hairline (kept quiet so the row
   stays a supporting spec strip, not a second button group under the CTAs) */
.hero-stats li svg,
.spec-pills li svg {
  width: 21px;
  height: 21px;
  color: rgba(150, 180, 255, 0.62);
  flex-shrink: 0;
  padding-right: 9px;
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Honeypot, shared. Lives here and not in a page stylesheet because losing
   it renders the trap field as a real input: people type in it and the API
   drops them as bots, silently. A rule that costs signups when it goes missing
   does not belong in a layer a page can choose not to load. */
.wl-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* ── Toasts ───────────────────────────────────────────────────────────────
   Outcome messages for the waitlist. Anatomy is the familiar one (icon,
   title + description, dismiss), but built from this site's own glass rather
   than generic notification chrome, so it reads as part of the product.

   "Success" is royal, not green: the palette here has no green, and borrowing
   one would be the only foreign colour on the page. */
.pt-toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(384px, calc(100vw - 40px));
  pointer-events: none;               /* the stack never blocks the page */
}

.pt-toast {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 12px 15px 15px;
  border-radius: var(--r-card, 16px);
  border: 1px solid var(--border-mid, rgba(150, 180, 255, 0.18));
  background: rgba(10, 14, 30, 0.86);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
          backdrop-filter: blur(16px) saturate(140%);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.85);
  overflow: hidden;
  pointer-events: auto;
  animation: pt-toast-in 0.46s cubic-bezier(0.33, 1, 0.68, 1) both;
}

.pt-toast-icon {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(150, 180, 255, 0.26);
  background: rgba(42, 91, 255, 0.2);
  color: #cfe0ff;
}
.pt-toast.is-error .pt-toast-icon {
  border-color: rgba(255, 145, 145, 0.32);
  background: rgba(255, 90, 90, 0.15);
  color: #ffb4b4;
}

.pt-toast-body { flex: 1; min-width: 0; }
.pt-toast-title {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.35;
  color: var(--text-hi, #eaf0ff);
}
.pt-toast-desc {
  margin: 3px 0 0;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--text-mid, #b8c4e0);
}

.pt-toast-close {
  position: relative;
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-low, rgba(214, 225, 245, 0.6));
  cursor: pointer;
  opacity: 0.65;
  transition: opacity 0.18s ease, background-color 0.18s ease;
}
.pt-toast-close:hover { opacity: 1; background: rgba(150, 180, 255, 0.1); }
.pt-toast-close:focus-visible {
  opacity: 1;
  outline: 2px solid rgba(94, 140, 255, 0.85);
  outline-offset: 2px;
}

/* The one flourish: a royal hairline draining left to right, so the toast says
   it will leave on its own instead of just vanishing. */
.pt-toast-timer {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  transform-origin: left center;
  background: linear-gradient(90deg, rgba(42, 91, 255, 0), #2a5bff);
  animation: pt-toast-timer 6.5s linear forwards;
}
.pt-toast.is-error .pt-toast-timer {
  background: linear-gradient(90deg, rgba(255, 120, 120, 0), #ff7676);
}

/* Leaving is split across two elements on purpose. Collapsing height and
   padding on the card itself looked broken: the stack is pinned to the bottom
   of the screen, so the shrinking box crept downward while the disappearing
   padding pushed the text upward inside it, and the two slid apart.
   Now the card only moves and fades, keeping its shape, while the slot around
   it (which has no padding of its own) closes the space. */
.pt-toast-slot { overflow: hidden; }

.pt-toast-slot.is-leaving {
  height: 0 !important;
  margin-bottom: -10px;            /* swallows the flex gap as the slot closes */
  transition:
    height 0.28s cubic-bezier(0.4, 0, 0.2, 1) 0.08s,
    margin-bottom 0.28s cubic-bezier(0.4, 0, 0.2, 1) 0.08s;
}

.pt-toast.is-leaving {
  opacity: 0;
  transform: translate3d(30px, 0, 0) scale(0.97);
  pointer-events: none;
  transition:
    opacity 0.18s ease-out,
    transform 0.26s cubic-bezier(0.4, 0.02, 1, 0.7);
}

/* Arrives from the right and settles just past its resting place before coming
   back, so it reads as something that flew in and stopped rather than a box
   that faded up. */
@keyframes pt-toast-in {
  0%   { opacity: 0; transform: translate3d(34px, 8px, 0) scale(0.94); }
  55%  { opacity: 1; transform: translate3d(-4px, 0, 0) scale(1.008); }
  78%  { transform: translate3d(1px, 0, 0) scale(0.998); }
  100% { opacity: 1; transform: none; }
}
@keyframes pt-toast-timer {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* Phones: span the width, and stay clear of the notch and the home indicator.
   env() falls back to 0 where it is unsupported, so max() keeps the old inset. */
@media (max-width: 560px) {
  .pt-toasts {
    right: max(12px, env(safe-area-inset-right));
    left: max(12px, env(safe-area-inset-left));
    bottom: max(12px, env(safe-area-inset-bottom));
    width: auto;
    gap: 8px;
  }
  .pt-toast { padding: 13px 10px 14px 13px; border-radius: 14px; }
  .pt-toast-title { font-size: 0.9rem; }
  .pt-toast-desc { font-size: 0.82rem; }
}

/* Short screens, mostly phones held sideways: there is barely any height, so the
   toast tightens up rather than eating a third of the view. */
@media (max-height: 460px) {
  .pt-toast { padding: 10px 10px 11px 12px; gap: 10px; }
  .pt-toast-icon { width: 24px; height: 24px; }
  .pt-toast-desc { margin-top: 2px; font-size: 0.8rem; }
  .pt-toasts { gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .pt-toast { animation: none; }
  /* The space still closes, so the stack stays tidy. Only the travel goes. */
  .pt-toast.is-leaving { transform: none; transition: opacity 0.12s linear; }
  .pt-toast-slot.is-leaving {
    transition: height 0.12s linear, margin-bottom 0.12s linear;
  }
  .pt-toast-timer { animation: none; transform: scaleX(0); }
}

/* The X stays visually small, but on a touch screen it needs a finger-sized
   target. Grown with a pseudo-element so nothing about the look changes. */
@media (pointer: coarse) {
  .pt-toast-close::after { content: ''; position: absolute; inset: -9px; }
}

/* ── Page hero: bars, pill, split headline ─────────────────────────────────
   Promoted out of utility.css when /services/ needed the same hero. Two pages
   now render it, so it belongs in the layer neither of them owns: left where it
   was, the second page would have had to copy it, and the two copies would drift
   the first time one of them was touched. Page-specific spacing stays local. */
/* These two travel WITH the hero, not with the page that first used it.
   Left behind in utility.css they were undefined on every other page, which
   made the padding below an invalid calc(); the browser then dropped the whole
   shorthand while the negative margin-top survived, and the hero slid up under
   the floating nav. A rule and the tokens it reads are one unit to move. */
:root {
  --hero-lift: 64px;
  --hero-tail: 19px;
}

.u-hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  /* No min-height. It used to be a viewport-relative clamp, and because the hero
     also centres its content, any height the clamp added beyond the content was
     split in half and half of it landed UNDER the social row. That made the gap
     below the icons depend on window height: 31px at 1440x900, 69px at 760x900.
     Content-driven height means the tail is always exactly
     padding-bottom - --hero-lift, at every size. The hero still comes out
     550-690px tall, which is a hero, and the product shot keeps its slice above
     the fold instead of being pushed down by empty space. */
  margin-top: calc(var(--nav-h) * -1 - 14px);   /* sit under the floating nav */
  /* 72px at the bottom, not 60. The hero centres its content inside a fixed
     min-height, so anything that makes the content taller (the waitlist status
     message appearing after a submit) grows it DOWNWARDS, towards a showcase
     that is simultaneously pulled upwards. This is the floor of clearance that
     keeps the social row off the screenshot. */
  /* Only the last term is air you can see. The hero is pulled up under the
     floating nav, so the first two terms (64 + 14) are spent covering the nav
     and its top offset before a single pixel shows. At the old "+40" that left
     26px of visible breathing room and the badge sat jammed against the nav. */
  padding: calc(var(--nav-h) + 14px + 64px) 0 calc(var(--hero-lift) + var(--hero-tail));
  overflow: hidden;
}

/* --- gradient bars --- */
.u-bars {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: flex;
  overflow: hidden;
  pointer-events: none;
  /* Fade BOTH ends. The reference section is full-viewport, so its bars end at
     the fold and the cut is never seen. This hero ends mid-page, so at full
     strength the bars were sliced flat by overflow:hidden and read as a hard
     rule across the screen. Top fade keeps them off the headline; bottom fade
     dissolves them into the page instead of stopping dead. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 42%, #000 74%, transparent 99%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 42%, #000 74%, transparent 99%);
}
.u-bars span {
  flex: 1 0 calc(100% / 15);
  max-width: calc(100% / 15);
  height: 100%;
  transform-origin: bottom;
  transform: scaleY(calc(var(--h) / 100));
  /* 0.72, not 0.62: each bar's gradient peaks at its own base, which is exactly
     where the bottom mask now removes it. Raising the source recovers the
     presence the fade costs, without bringing back the hard edge. */
  background: linear-gradient(to top, rgba(42, 91, 255, 0.72), transparent 78%);
  animation: u-bar-pulse 2s var(--ease-out) infinite alternate;
  animation-delay: var(--d);
}
@keyframes u-bar-pulse {
  from { transform: scaleY(calc(var(--h) / 100)); opacity: 0.72; }
  to   { transform: scaleY(calc(var(--h) / 100 * 1.06)); opacity: 1; }
}

.u-hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 860px;
  margin-inline: auto;
}

/* badge in the reference's social-proof slot: an overlapping stack plus a count.
   The stack is game art, not faces. There are real customer avatars in
   /img/customers/, but those five people vouched for the REMOTE SERVICE, and
   the brief forbids attributing service testimonials to the app. Game logos
   claim nothing about who anyone is. */
.u-hero-pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 6px 16px 6px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-mid);
  background: rgba(10, 14, 30, 0.6);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  font-size: 0.85rem;
  color: var(--text-mid);
}
.u-pill-text strong { color: var(--text-hi); font-weight: 600; }
.u-avatars {
  display: inline-flex;
  align-items: center;
}
/* Port of the UserAvatars maths, done in CSS so it needs no JS:
     overlap 60%  -> each bubble pulls back 0.4 x its own size
     focusScale 1.2, so the hovered bubble grows and the ones after it slide
     right by (size x (1 + focusScale) / 2) - (size x overlap) = 15px at 30px. */
.u-av {
  --av: 30px;
  position: relative;
  width: var(--av);
  height: var(--av);
  border-radius: 50%;
  overflow: hidden;
  border: 1.5px solid rgba(150, 180, 255, 0.28);
  background: #0a0e1c;
  box-shadow: 0 3px 10px -4px rgba(0, 0, 0, 0.8);
  transition: transform 0.28s cubic-bezier(0.34, 1.35, 0.5, 1),
              border-color var(--dur-1) var(--ease-out);
}
.u-av + .u-av { margin-left: calc(var(--av) * -0.4); }
.u-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.u-avatars:hover .u-av:hover {
  transform: scale(1.2);
  border-color: rgba(150, 180, 255, 0.55);
  z-index: 2;
}
.u-avatars:hover .u-av:hover ~ .u-av { transform: translateX(15px); }

@media (prefers-reduced-motion: reduce) {
  .u-av { transition: none; }
  .u-avatars:hover .u-av:hover { transform: none; }
  .u-avatars:hover .u-av:hover ~ .u-av { transform: none; }
}
@media (max-width: 480px) {
  .u-av { --av: 26px; }
  .u-hero-pill { font-size: 0.8rem; gap: 10px; }
}

/* Internal rhythm tightened by ~28px in total (26/22/30/30 -> 20/18/26/24).
   Nothing here is cramped at these values, and the height it gives back is what
   lets the screenshot show without shrinking the type. */
.u-hero h1 {
  margin: 20px 0 0;
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--text-hi);
}
.u-h1-plain,
.u-h1-serif {
  display: block;
  font-size: clamp(1.9rem, 5.6vw, 3.5rem);
}
.u-h1-plain { font-weight: 500; }
/* the site's existing <em> motif, promoted to a whole headline line */
.u-h1-serif {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.012em;
  background: linear-gradient(115deg, #d4e2ff 8%, #7fa2ff 54%, var(--accent) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.u-hero-lede {
  margin-top: 18px;
  font-size: clamp(1rem, 2.2vw, 1.18rem);
  line-height: 1.6;
  color: var(--text-mid);
}

/* ── App gallery: starfield section + coverflow carousel ───────────────────
   Moved out of landing.css when /services/ needed the same gallery for the
   Toolbox. De-scoped rather than given a second page name: a component two
   pages render is a shared component, and a scope list is a thing to forget to
   update on the third. Driven by js/landing.js tbxCarousel. */
/* ============================ APP GALLERY STARFIELD ============================
   A subtle starfield behind the app gallery (vanilla port of 21st.dev Sparkles).
   js/sparkles.js draws the twinkling dots on <canvas data-sparkles>. The gallery sits
   centred in it via symmetric padding, and the stars fade evenly at top + bottom. */
.tbx {
  position: relative;
  overflow: hidden;
  /* small margin + symmetric padding: the gallery sits centred in the starfield, and the
     margin + padding together keep the gap to the neighbours on the ~116px rhythm */
  margin-top: clamp(24px, 2.4vw, 40px);
  padding-top: clamp(60px, 7vh, 96px);
  padding-bottom: clamp(60px, 7vh, 96px);
}
.tbx + .section { margin-top: clamp(24px, 2.4vw, 40px); }
.tbx > .container { position: relative; z-index: 1; }

/* ============================ APP GALLERY (carousel) ============================
   Coverflow carousel touring real screenshots from both Prospira apps — the Toolbox
   and the Tweaking Utility. Prev/next arrows, tick pagination + counter, and a caption
   (badge + title + description) that swaps per slide. Driven by js/landing.js tbxCarousel. */
.tbx-car { position: relative; margin-top: 28px; }
.tbx-car-row { position: relative; }
.tbx-car-stage {
  position: relative;
  overflow: hidden;
  padding: 14px 0 8px;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
}
.tbx-car-track {
  display: flex;
  gap: 30px;
  transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}
.tbx-car-slide {
  flex: 0 0 66%;
  opacity: 0.32;
  transform: scale(0.9);
  transition: opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out);
}
.tbx-car-slide.is-active { opacity: 1; transform: scale(1); }

/* browser-style glass frame around each screenshot */
/* MOVED to css/prospira.css and de-scoped as .tbx-car-frame / .app-frame.
   Only the carousel-specific active state stays here. */
.tbx-car-slide.is-active .tbx-car-frame {
  border-color: rgba(120, 160, 255, 0.4);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 46px 100px -44px rgba(0, 0, 0, 0.9);
}

/* arrows */
.tbx-car-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  cursor: pointer;
  color: var(--text-hi);
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(18, 24, 46, 0.72), rgba(10, 14, 30, 0.8));
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.7);
  transition: border-color var(--dur-1) var(--ease-out), background-color var(--dur-1) var(--ease-out), opacity var(--dur-1) var(--ease-out);
}
.tbx-car-nav:hover { border-color: rgba(120, 160, 255, 0.5); }
.tbx-car-nav:active { transform: translateY(-50%) scale(0.94); }
.tbx-car-nav[disabled] { opacity: 0.3; cursor: default; }
.tbx-car-nav svg { width: 22px; height: 22px; }
.tbx-car-prev { left: -8px; }
.tbx-car-next { right: -8px; }

/* caption (swaps per slide) */
.tbx-car-cap { text-align: center; max-width: 620px; margin: 18px auto 0; min-height: 74px; }
.tbx-car-cap h3 { font-size: 1.4rem; font-weight: 600; letter-spacing: -0.01em; color: var(--text-hi); margin: 0 0 8px; }
.tbx-car-cap p { font-size: 0.98rem; line-height: 1.6; color: #a2b0d4; }

/* footer: ticks + counter */
.tbx-car-foot { display: flex; align-items: center; justify-content: center; gap: 18px; margin-top: 16px; }
.tbx-car-dots { display: flex; align-items: center; gap: 7px; }
/* The visible dot is a 4px bar by design and a 4px bar cannot be hit with a
   finger. The bar keeps its size and an invisible box takes the tap instead.
   Height only, plus half the 7px gap each side: a wider box would overlap its
   neighbour and hand the tap to the wrong slide. */
.tbx-car-dot::after {
  content: "";
  position: absolute;
  left: -3px;
  right: -3px;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
.tbx-car-dot {
  position: relative;
  width: 20px;
  height: 4px;
  border-radius: 3px;
  cursor: pointer;
  border: 0;
  padding: 0;
  background: rgba(150, 175, 235, 0.24);
  transition: background-color var(--dur-1) var(--ease-out), width var(--dur-1) var(--ease-out);
}
/* the dash is 4px tall, far under the ~44px touch minimum. an invisible pad grows the
   hit area without touching the look; it reaches exactly half the 7px gap on each side,
   so neighbouring targets meet but never overlap (an overlap would swallow taps meant
   for the dot next door). */
.tbx-car-dot::after {
  content: "";
  position: absolute;
  top: 50%;
  left: -3.5px;
  right: -3.5px;
  height: 44px;
  transform: translateY(-50%);
}
.tbx-car-dot:hover { background: rgba(150, 175, 235, 0.44); }
.tbx-car-dot.is-active { width: 30px; background: var(--accent); }
.tbx-car-count { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; color: #8ea0c8; }
.tbx-car-count b { color: #cfe0ff; font-weight: 600; }

@media (max-width: 760px) {
  .tbx-car-slide { flex-basis: 84%; }
  .tbx-car-track { gap: 16px; }
  .tbx-car-nav { width: 42px; height: 42px; }
  .tbx-car-prev { left: 2px; }
  .tbx-car-next { right: 2px; }
  .tbx-car-cap { min-height: 98px; }
}
@media (prefers-reduced-motion: reduce) {
  .tbx-car-track,
  .tbx-car-slide { transition: none; }
}
