/* Vortex: shared foundation. Tokens, fonts, base elements, and the components every
   page shares: nav, buttons, panels, statuses, key display, alerts, footer.
   Loaded by every page, before the page's own stylesheet. The 404 rules (.nf) sit at the end.
   Rewritten in step 3 of the redesign; the design rules are in DESIGN.md. */
/* Live-only states: the live site renders these, no page in the static copy does.
   Keep them, and check them on _states.html (dev only) after any shared restyle:
   .page-title, .page-subtitle (pages outside this copy); .status-* other than active/fulfilled; .alert-success-vortex, .alert-danger-vortex (flash messages) */

/* ===== FONTS (self-hosted, SIL OFL; licences in static/fonts/) ===== */
@font-face {
  font-family: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/chakra-petch-500-latin.woff2') format('woff2');
  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: 'Chakra Petch';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/chakra-petch-500-latin-ext.woff2') format('woff2');
  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: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/chakra-petch-600-latin.woff2') format('woff2');
  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: 'Chakra Petch';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/chakra-petch-600-latin-ext.woff2') format('woff2');
  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: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/chakra-petch-700-latin.woff2') format('woff2');
  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: 'Chakra Petch';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/chakra-petch-700-latin-ext.woff2') format('woff2');
  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: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url('../fonts/archivo-latin.woff2') format('woff2');
  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: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url('../fonts/archivo-latin-ext.woff2') format('woff2');
  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: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2');
  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: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  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;
}

/* ===== TOKENS ===== */
:root {
  /* Surfaces: near-black graphite. */
  --bg: #0B0D12;
  --bg-raised: #11141B;
  --surface: #151923;
  --surface-2: #1B202B;
  --line: rgba(255, 255, 255, 0.07);
  --line-strong: rgba(255, 255, 255, 0.13);

  /* Text. --text-2 stays at or above 4.5:1 on --surface. */
  --text: #E6E8EC;
  --text-2: #8E96A4;

  /* Cyan: actions and the product, only. */
  --cyan: #64C8FF;
  --cyan-hover: #8AD6FF;
  --cyan-ink: #06121B;
  --cyan-soft: rgba(100, 200, 255, 0.10);

  /* Gold: value only (loot, gp, prices, premium, VIP). One gold. */
  --gold: #F2C46B;
  --gold-ink: #1B1405;
  --gold-soft: rgba(242, 196, 107, 0.12);
  --gold-line: rgba(242, 196, 107, 0.35);

  /* State. */
  --ok: #34D399;
  --ok-soft: rgba(52, 211, 153, 0.12);
  --danger: #F87171;
  --danger-soft: rgba(248, 113, 113, 0.12);

  /* Type. */
  --font-display: 'Chakra Petch', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Archivo', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
  /* Display tuning, so a font swap is a token change: width, tracking, and a size
     multiplier for big display type (condensed faces run larger than wide ones). */
  --display-stretch: 100%;
  --display-tracking: -0.01em;
  --display-scale: 0.82;

  /* Shape. */
  --radius-label: 3px;
  --radius-control: 6px;
  --radius-panel: 10px;

  /* Space: 4px base. */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px; --s-10: 128px;

  /* Motion. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-press: 120ms;
  --dur-hover: 150ms;
  --dur-menu: 200ms;
  --dur-modal: 240ms;
  --dur-morph: 320ms;

  --nav-h: 64px;

  /* Legacy names, kept so page CSS, inline styles and scripts pick up the new palette.
     Remove each one once nothing references it (tracked in PORTING.md). */
  --vortex-primary: var(--cyan);
  --vortex-hover: var(--cyan-hover);
  --vortex-bg: var(--bg);
  --vortex-card: var(--surface);
  --vortex-card-border: var(--line);
  --vortex-text: var(--text);
  --vortex-muted: var(--text-2);
  --vortex-input: var(--bg-raised);
  --vortex-surface: var(--surface);
  --vortex-green: var(--ok);
  --vortex-red: var(--danger);
  --vortex-amber: var(--gold);

  /* Bootstrap reads these for its reboot styles (links, body). */
  --bs-body-bg: var(--bg);
  --bs-body-color: var(--text);
  --bs-body-font-family: var(--font-body);
  --bs-link-color: #64C8FF;
  --bs-link-color-rgb: 100, 200, 255;
  --bs-link-hover-color: #8AD6FF;
  --bs-link-hover-color-rgb: 138, 214, 255;
}

/* ===== BASE ===== */
*, *::before, *::after { box-sizing: border-box; }

html {
  color-scheme: dark;
  scrollbar-color: #2A303C var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  margin: 0;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: rgba(100, 200, 255, 0.15);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .028 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-stretch: var(--display-stretch);
  letter-spacing: var(--display-tracking);
  line-height: 1.05;
  text-wrap: balance;
}

a { text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
a, button, input, select, textarea, label, summary { touch-action: manipulation; }

::selection { background: #1E4861; color: #fff; }
input, textarea { caret-color: var(--cyan); }

:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
}

/* Anchor targets clear the fixed nav. */
[id] { scroll-margin-top: calc(var(--nav-h) + 24px); }

/* The display face (Chakra Petch) only runs at 20px and up. Small label headings stay in the body face. */
.pd-section h2, .pc-panel-title, .mac-install-title, .footer-col-title { font-family: var(--font-body); line-height: 1.3; }

/* Big prices are display type; their period and currency notes stay in the body face. */
.v2p-amount, .pc-amount { font-family: var(--font-display); letter-spacing: var(--display-tracking); }
.v2p-period, .pc-period { font-family: var(--font-body); }

/* Numbers that sit in columns or change in place. */
.v2-num, .pc-amount, .v2p-amount, .acct-credits-balance, .key-value, #gp-counter-value {
  font-variant-numeric: tabular-nums lining-nums;
}

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

/* ===== NAVBAR (mirrors home.ejs) ===== */
.vortex-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  background: rgba(11, 13, 18, 0.86);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  padding: 0 24px;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: 16px;
  transition: background-color var(--dur-menu) ease, border-color var(--dur-menu) ease;
}
/* Homepage: transparent over the hero until the page scrolls (nav.js adds .is-solid). */
.vortex-nav.nav-ready.nav-over-hero:not(.is-solid):not(.is-open) {
  background: transparent; border-bottom-color: transparent;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.vortex-nav-brand {
  margin-right: auto;
  display: flex; align-items: center; gap: 10px;
  text-decoration: none;
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.35rem; font-weight: 700; letter-spacing: 0.06em;
  line-height: 1;
}
.vortex-nav-links {
  display: flex; align-items: center; gap: 24px;
  list-style: none; margin: 0; padding: 0;
}
.vortex-nav-links a {
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.9375rem; font-weight: 500;
  transition: color var(--dur-hover) ease, background-color var(--dur-hover) ease, border-color var(--dur-hover) ease;
}
/* Same Download button as the homepage menu; on other pages it opens the homepage's download window. */
.vortex-nav-links a.nav-download-btn,
.nav-download-btn {
  display: inline-flex; align-items: center;
  background: var(--cyan);
  color: var(--cyan-ink) !important;
  padding: 8px 16px;
  border-radius: var(--radius-control);
  font-weight: 600;
  line-height: 1.25;
  transition: background-color var(--dur-hover) ease, transform var(--dur-press) var(--ease-out);
}
.nav-discord-link {
  color: var(--text-2) !important;
  display: inline-flex; align-items: center; gap: 6px;
}

/* ===== NAV AUTH STATE ===== */
.nav-login-btn {
  background: transparent;
  color: var(--text) !important;
  border: 1px solid var(--line-strong);
  padding: 7px 14px;
  border-radius: var(--radius-control);
  font-weight: 600;
  line-height: 1.25;
  display: inline-flex; align-items: center; gap: 6px;
}
.nav-user-chip > span:last-child { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-user-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 12px 4px 4px;
  border-radius: var(--radius-control);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text) !important;
  font-weight: 600;
}
@media (hover: hover) and (pointer: fine) {
  .nav-user-chip:hover { border-color: var(--line-strong); background: var(--surface-2); }
}
.nav-avatar {
  width: 26px; height: 26px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong);
}
.nav-avatar-fallback {
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 0.8rem;
}
.nav-logout-form { margin: 0; display: flex; }
.nav-logout-btn {
  background: none;
  border: none;
  color: var(--text-2);
  font-size: 1rem;
  cursor: pointer;
  padding: 6px;
  border-radius: var(--radius-control);
  transition: color var(--dur-hover) ease, background-color var(--dur-hover) ease;
}
@media (hover: hover) and (pointer: fine) {
  .nav-logout-btn:hover { color: var(--danger); background: var(--danger-soft); }
}

/* ===== PAGE SHELL ===== */
.page-shell {
  position: relative;
  z-index: 1;
  max-width: 960px;
  margin: 0 auto;
  padding: 128px 24px 80px;
}
.page-title {
  font-family: var(--font-display);
  font-size: calc(2.75rem * var(--display-scale));
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: var(--display-tracking);
  margin: 0 0 8px;
  color: var(--text);
}
.page-subtitle {
  color: var(--text-2);
  font-size: 1rem;
  margin: 0 0 40px;
}

/* ===== CARDS ===== */
.panel-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  padding: 28px;
  position: relative;
  overflow: hidden;
  margin-bottom: 24px;
}
.panel-card-accent { border-color: rgba(100, 200, 255, 0.28); }

/* ===== STATUS LABELS ===== */
/* Small square labels: a coloured square and the word, no pill. */
.status-chip {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 0.75rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  line-height: 1.4;
}
.status-chip::before {
  content: '';
  width: 7px; height: 7px; border-radius: 1px;
  background: currentColor;
}
.status-active, .status-fulfilled { color: var(--ok); }
.status-expired, .status-refunded { color: var(--gold); }
.status-revoked, .status-disputed, .status-failed { color: var(--danger); }
.status-pending, .status-processing { color: var(--cyan); }

/* ===== LICENCE KEY DISPLAY ===== */
.key-display {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--bg-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-panel);
  padding: 16px 20px;
}
.key-value {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text);
  user-select: all;
  flex: 1 1 220px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.key-actions { display: flex; gap: 8px; }
.key-btn {
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--text);
  border-radius: var(--radius-control);
  padding: 8px 14px;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: background-color var(--dur-hover) ease, border-color var(--dur-hover) ease, transform var(--dur-press) var(--ease-out);
}

/* ===== BUTTONS ===== */
/* Primary: solid cyan. One per view. */
.btn-primary-glow {
  background: var(--cyan);
  color: var(--cyan-ink);
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  padding: 12px 22px;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.25;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: background-color var(--dur-hover) ease, transform var(--dur-press) var(--ease-out);
}
/* Secondary: hairline outline. Discord actions use it too; Discord purple is not a button colour. */
.btn-outline-vortex,
.btn-discord {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
  padding: 12px 22px;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.25;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: background-color var(--dur-hover) ease, border-color var(--dur-hover) ease, transform var(--dur-press) var(--ease-out);
}

/* ===== TEXT LINKS: one hover language (owner's pick: "turns cyan, underline appears") =====
   Standalone links (nav, hero, "see all", back links, footer columns) have no resting underline.
   Links inside sentences keep a faint one, so they never rely on colour alone.
   On hover every link turns cyan and a cyan underline appears; arrows nudge forward. */
:is(.vortex-nav-links a:not([class]), .nav-discord-link, .hero-link, .hero-discord, .activity-foot a, .v2p-plugins-all, .pd-back, .footer-col a, .modal-guide-link, .pc-name a, .pc-cta-link, .pd-more-all, .pd-more-item) { --link-rest: transparent; }
:is(.v2p-list a, .v2p-fine a, .pc-note a, .pc-fine a, .pd-facts a, .v2-fine a, .v2-link, .v2-check a, .footer-legal a, .tos-wrap a) { --link-rest: rgba(255, 255, 255, 0.28); color: var(--text); }
:is(.vortex-nav-links a:not([class]), .nav-discord-link, .hero-link, .hero-discord, .activity-foot a, .v2p-plugins-all, .pd-back, .footer-col a, .modal-guide-link, .pc-name a, .pc-cta-link, .pd-more-all, .pd-more-item, .v2p-list a, .v2p-fine a, .pc-note a, .pc-fine a, .pd-facts a, .v2-fine a, .v2-link, .v2-check a, .footer-legal a, .tos-wrap a) {
  text-decoration: none;
  background-image: linear-gradient(var(--cyan), var(--cyan)), linear-gradient(var(--link-rest), var(--link-rest));
  background-repeat: no-repeat;
  background-origin: content-box;
  background-position: 0 100%, 0 100%;
  background-size: 0% 1px, 100% 1px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  transition: color var(--dur-hover) ease;
}
:is(.vortex-nav-links a:not([class]), .nav-discord-link, .hero-link, .hero-discord, .activity-foot a, .v2p-plugins-all, .pd-back, .footer-col a, .modal-guide-link, .pc-name a, .pc-cta-link, .pd-more-all, .pd-more-item, .v2p-list a, .v2p-fine a, .pc-note a, .pc-fine a, .pd-facts a, .v2-fine a, .v2-link, .v2-check a, .footer-legal a, .tos-wrap a) .bi-arrow-right { display: inline-block; transition: transform var(--dur-hover) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  :is(.vortex-nav-links a:not([class]):hover, .nav-discord-link:hover, .hero-link:hover, .hero-discord:hover, .activity-foot a:hover, .v2p-plugins-all:hover, .pd-back:hover, .footer-col a:hover, .modal-guide-link:hover, .pc-name a:hover, .pc-cta-link:hover, .pd-more-all:hover, .pd-more-item:hover, .v2p-list a:hover, .v2p-fine a:hover, .pc-note a:hover, .pc-fine a:hover, .pd-facts a:hover, .v2-fine a:hover, .v2-link:hover, .v2-check a:hover, .footer-legal a:hover, .tos-wrap a:hover) { color: var(--cyan); background-size: 100% 1px, 100% 1px; }
  :is(.vortex-nav-links a:not([class]):hover, .nav-discord-link:hover, .hero-link:hover, .hero-discord:hover, .activity-foot a:hover, .v2p-plugins-all:hover, .pd-back:hover, .footer-col a:hover, .modal-guide-link:hover, .pc-name a:hover, .pc-cta-link:hover, .pd-more-all:hover, .pd-more-item:hover, .v2p-list a:hover, .v2p-fine a:hover, .pc-note a:hover, .pc-fine a:hover, .pd-facts a:hover, .v2-fine a:hover, .v2-link:hover, .v2-check a:hover, .footer-legal a:hover, .tos-wrap a:hover) .bi-arrow-right { transform: translateX(3px); }
  .nav-discord-link:hover { color: var(--cyan) !important; }
}

/* ===== BUTTONS: one hover language (owner's pick: "outline ring appears") =====
   Primary (cyan) and secondary (hairline) buttons behave the same on every page: a thin ring
   appears around the button with a small gap, and every press gives a small scale.
   Keyboard focus keeps its own, thicker ring. */
html :is(.btn-primary-glow, .nav-download-btn, .v2p-btn-primary, .v2p-btn-gold, .v2p-btn-discord, .pc-btn-gold, .pc-btn-discord, .v2-btn-primary, .v2-btn-gold, .btn-outline-vortex, .btn-discord, .nav-login-btn, .v2p-btn-ghost, .pc-btn-ghost, .v2-btn-ghost, .key-btn, .loot-pause, .dl-btn) {
  outline: 1px solid transparent;
  outline-offset: 3px;
  transition: outline-color var(--dur-hover) ease, background-color var(--dur-hover) ease,
    border-color var(--dur-hover) ease, color var(--dur-hover) ease, transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  html :is(.btn-primary-glow:hover:not(:disabled), .nav-download-btn:hover:not(:disabled), .v2p-btn-primary:hover:not(:disabled), .v2p-btn-gold:hover:not(:disabled), .v2p-btn-discord:hover:not(:disabled), .pc-btn-gold:hover:not(:disabled), .pc-btn-discord:hover:not(:disabled), .v2-btn-primary:hover:not(:disabled), .dl-btn.is-recommended:hover:not(:disabled)) { outline-color: var(--cyan); }
  /* The gold VIP button rings in gold. */
  html .v2-btn-gold:hover:not(:disabled) { outline-color: var(--gold); }
  html :is(.btn-outline-vortex:hover:not(:disabled), .btn-discord:hover:not(:disabled), .nav-login-btn:hover:not(:disabled), .v2p-btn-ghost:hover:not(:disabled), .pc-btn-ghost:hover:not(:disabled), .v2-btn-ghost:hover:not(:disabled), .key-btn:hover:not(:disabled), .loot-pause:hover:not(:disabled), .dl-btn:not(.is-recommended):hover:not(:disabled)) { outline-color: rgba(255, 255, 255, 0.55); }
}
html :is(.btn-primary-glow, .nav-download-btn, .v2p-btn-primary, .v2p-btn-gold, .v2p-btn-discord, .pc-btn-gold, .pc-btn-discord, .v2-btn-primary, .v2-btn-gold, .btn-outline-vortex, .btn-discord, .nav-login-btn, .v2p-btn-ghost, .pc-btn-ghost, .v2-btn-ghost, .key-btn, .loot-pause, .dl-btn):focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
html :is(.btn-primary-glow, .nav-download-btn, .v2p-btn-primary, .v2p-btn-gold, .v2p-btn-discord, .pc-btn-gold, .pc-btn-discord, .v2-btn-primary, .v2-btn-gold, .btn-outline-vortex, .btn-discord, .nav-login-btn, .v2p-btn-ghost, .pc-btn-ghost, .v2-btn-ghost, .key-btn, .loot-pause, .dl-btn):active:not(:disabled) { transform: scale(0.97); }

/* ===== PENDING SUBMITS (static/js/forms.js) ===== */
/* The pressed button shows it's working and can't be pressed twice. */
.is-pending { cursor: progress; opacity: 0.72; }
.is-pending::after {
  content: '';
  display: inline-block;
  flex-shrink: 0;
  width: 0.9em;
  height: 0.9em;
  margin-left: 2px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: -0.12em;
  animation: vx-spin 0.7s linear infinite;
}
@keyframes vx-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .is-pending::after { animation-duration: 2.4s; } }

/* ===== FLASH ALERTS ===== */
.alert-success-vortex {
  background: var(--ok-soft);
  border: 1px solid rgba(52, 211, 153, 0.3);
  color: var(--ok);
  border-radius: var(--radius-control);
  padding: 12px 16px;
  font-size: 0.9375rem;
  margin-bottom: 16px;
}
.alert-danger-vortex {
  background: var(--danger-soft);
  border: 1px solid rgba(248, 113, 113, 0.3);
  color: var(--danger);
  border-radius: var(--radius-control);
  padding: 12px 16px;
  font-size: 0.9375rem;
  margin-bottom: 16px;
}

/* ===== FOOTER ===== */
/* The last, darkest band: one closing call to action, three link columns, then the fine print. */
.vortex-footer {
  position: relative;
  z-index: 1;
  padding: 0 24px;
  /* Fades in from the page instead of starting as a hard band; the light line marks the edge. */
  background: linear-gradient(180deg, rgba(8, 9, 13, 0), #08090D 180px);
  color: var(--text-2);
  font-size: 0.875rem;
}
/* The footer light line: a cyan light along the top edge, where the page ends. */
.vortex-footer::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 50%;
  width: min(960px, 80%);
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, rgba(100, 200, 255, 0.7), transparent);
  box-shadow: 0 0 18px 1px rgba(100, 200, 255, 0.28);
  pointer-events: none;
}
.vortex-footer a:not([class*="btn"]) { color: var(--text); text-decoration: none; }
.footer-cta, .footer-cols, .footer-base { max-width: 1200px; margin: 0 auto; }
.footer-cta {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 32px;
  padding: 72px 0 56px;
  border-bottom: 1px solid var(--line);
}
.footer-brand {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--font-display); font-stretch: var(--display-stretch);
  font-size: 1.75rem; font-weight: 700; letter-spacing: 0.06em; line-height: 1;
}
.footer-brand svg { width: 36px; height: 36px; color: var(--cyan); }
.footer-line { margin: 14px 0 0; font-size: 1.125rem; color: var(--text-2); }
.footer-cta-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.footer-cols {
  display: grid; grid-template-columns: repeat(3, minmax(0, 220px)); gap: 32px;
  padding: 48px 0;
}
.footer-col-title {
  margin: 0 0 14px;
  font-family: var(--font-body); font-size: 0.75rem; font-weight: 600; font-stretch: 100%;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-2);
}
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.footer-col a { display: inline-block; padding: 4px 0; }
.footer-base {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 16px 32px; align-items: start;
  padding: 28px 0 40px;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
}
.footer-about { grid-column: 1 / -1; margin: 0; max-width: 72ch; }
.footer-legal p { margin: 0 0 4px; }
.footer-source { justify-self: end; }
@media (max-width: 720px) {
  .footer-cta { align-items: flex-start; padding: 56px 0 40px; }
  .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-col a { padding: 10px 0; }
  .footer-base { grid-template-columns: minmax(0, 1fr); }
  .footer-source { justify-self: start; }
}

/* ===== AMBIENT GLOW (retired) =====
   The fixed glow is gone from the design; the element is removed from the pages in step 4. */
.page-glow { display: none; }

/* ===== NAV: PHONE MENU ===== */
.nav-toggle {
  display: none;
  align-items: center; justify-content: center;
  width: 44px; height: 44px;
  margin-right: -10px;
  background: none; border: 0; border-radius: var(--radius-control);
  color: var(--text);
  cursor: pointer;
}
.nav-toggle-bars { display: grid; gap: 5px; }
.nav-toggle-bar {
  display: block; width: 20px; height: 2px; border-radius: 1px; background: currentColor;
  transition: transform var(--dur-menu) var(--ease-out);
}
.vortex-nav.is-open .nav-toggle-bar:first-child { transform: translateY(3.5px) rotate(45deg); }
.vortex-nav.is-open .nav-toggle-bar:last-child { transform: translateY(-3.5px) rotate(-45deg); }

@media (max-width: 880px) {
  .vortex-nav { padding: 0 16px; }
  .vortex-nav-links { gap: 16px; }

  /* Without the nav script: every link in a strip that scrolls sideways. */
  .vortex-nav:not(.nav-ready) .vortex-nav-links {
    min-width: 0; overflow-x: auto; scrollbar-width: none; white-space: nowrap;
  }

  /* With it: the bar keeps Download and the menu button; the rest opens as a panel. */
  .vortex-nav.nav-ready .nav-toggle { display: inline-flex; }
  .vortex-nav.nav-ready .vortex-nav-links > li:not(.nav-download-wrapper) { display: none; }

  .vortex-nav.nav-ready.is-open .vortex-nav-links {
    position: absolute; top: var(--nav-h); left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    max-height: calc(100svh - var(--nav-h)); overflow-y: auto; overscroll-behavior: contain;
    padding: 8px 16px 20px;
    background: var(--bg-raised);
    border-bottom: 1px solid var(--line-strong);
    box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.6);
  }
  .vortex-nav.nav-ready.is-open .vortex-nav-links > li { display: block; }
  .vortex-nav.nav-ready.is-open .vortex-nav-links > li > a:not(.nav-download-btn):not(.nav-user-chip) {
    display: flex; align-items: center; gap: 8px;
    min-height: 52px; padding: 0 4px;
    font-size: 1.0625rem; color: var(--text);
    border-bottom: 1px solid var(--line);
  }
  .vortex-nav.nav-ready.is-open .nav-discord-link { color: var(--text) !important; }
  .vortex-nav.nav-ready.is-open .nav-user-chip { margin: 16px 0 4px; }
  .vortex-nav.nav-ready.is-open .nav-logout-btn { display: flex; align-items: center; min-height: 44px; font-size: 1rem; }
  .vortex-nav.nav-ready.is-open .nav-logout-btn::after { content: 'Log out'; margin-left: 10px; font-size: 1rem; font-family: var(--font-body); }
  .vortex-nav.nav-ready.is-open .nav-download-wrapper { order: 10; margin-top: 16px; }
  .vortex-nav.nav-ready.is-open .nav-download-btn { width: 100%; justify-content: center; padding: 14px 16px; }
  @media (prefers-reduced-motion: no-preference) {
    .vortex-nav.nav-ready.is-open .vortex-nav-links { animation: nav-menu-in var(--dur-menu) var(--ease-out); }
  }
}
@keyframes nav-menu-in { from { opacity: 0; transform: translateY(-4px); } }

@media (max-width: 640px) {
  .page-shell { padding-top: 104px; }
  .key-value { font-size: 1.05rem; }
}

/* ===== SCROLL REVEAL (cascade) =====
   static/js/reveal.js arms only elements that start below the fold, then adds .is-in as they
   enter. Without the script (or with reduced motion) nothing is armed, so everything shows. */
.reveal-armed { transition: opacity 500ms var(--ease-out), translate 500ms var(--ease-out); }
.reveal-armed:not(.is-in) { opacity: 0; translate: 0 14px; }
@media (prefers-reduced-motion: reduce) {
  .reveal-armed:not(.is-in) { opacity: 1; translate: none; }
}

/* ===== PAGE TRANSITIONS =====
   Every page fades into the next (Chrome, Edge and Safari; elsewhere pages simply load).
   The nav holds still, and a plugin's picture and name grow out of whatever was clicked:
   static/js/transitions.js names those elements at navigation time. Off under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.vortex-nav { view-transition-name: site-nav; }
::view-transition-old(root) { animation-duration: 160ms; }
::view-transition-new(root) { animation-duration: 220ms; animation-timing-function: var(--ease-out); }
/* The nav swaps to the new page's nav at once, above everything that moves. */
::view-transition-group(site-nav) { z-index: 2; animation: none; }
::view-transition-old(site-nav) { display: none; }
::view-transition-new(site-nav) { animation: none; }
/* The plugin's picture and name: 320ms from where they were clicked to where they land. */
::view-transition-group(plugin-art),
::view-transition-group(plugin-name) { z-index: 1; animation-duration: var(--dur-morph); animation-timing-function: var(--ease-out); }
::view-transition-old(plugin-art), ::view-transition-new(plugin-art),
::view-transition-old(plugin-name), ::view-transition-new(plugin-name) { animation-duration: var(--dur-morph); }
/* Pictures of different shapes (a row thumbnail, a tile, the hero) fill the moving box instead
   of stretching. */
::view-transition-image-pair(plugin-art) { overflow: clip; border-radius: var(--radius-panel); }
::view-transition-old(plugin-art), ::view-transition-new(plugin-art) { block-size: 100%; object-fit: cover; }

/* ===== SKIP LINK ===== */
.skip-link {
  position: fixed; left: 12px; top: 12px; z-index: 300;
  padding: 10px 14px;
  background: var(--cyan); color: var(--cyan-ink);
  border-radius: var(--radius-control);
  font-weight: 600; text-decoration: none;
}
.skip-link:not(:focus) {
  width: 1px; height: 1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ===== 404 ===== */
.nf { text-align: center; padding-top: 180px; }
.nf-code { margin: 0; font-family: var(--font-display); font-size: calc(clamp(6rem, 16vw, 10rem) * var(--display-scale)); font-weight: 700; letter-spacing: var(--display-tracking); line-height: 1; color: var(--text); }
.nf-title { margin: 16px 0 32px; font-family: var(--font-body); font-size: 1.125rem; font-weight: 500; letter-spacing: 0; color: var(--text-2); }
