/* ==========================================================================
   base.css — reset, design tokens, themes, typography, primitives

   This is the foundation every other stylesheet builds on. It owns:
     1. Reset and box sizing
     2. Design tokens (spacing, radii, type, shadows, motion)
     3. The theme system
     4. Page background
     5. Typography
     6. Focus and accessibility
     7. The loading screen
     8. Toast notifications
     9. Scroll reveal
    10. Utilities

   Everything visual is driven by custom properties declared here. There is
   exactly one place a colour is defined, which is why the theme switcher
   can repaint the whole site by changing one attribute on <html>.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reset
   -------------------------------------------------------------------------- */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Offset for the sticky header when jumping to an anchor. */
  scroll-padding-top: 6rem;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
  /* Nothing may ever scroll the page sideways. */
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img,
picture,
svg,
video,
canvas {
  display: block;
  max-width: 100%;
}

/* The browser's own [hidden] rule is a UA style, so any author rule that
   sets display (every .btn does) silently beats it and the element stays on
   screen. This makes the attribute mean what it says. */
[hidden] {
  display: none !important;
}

img,
video {
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  cursor: pointer;
}

ul[role='list'],
ol[role='list'] {
  list-style: none;
  padding: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

/* Long words and URLs must not push the layout wider than the screen. */
p,
h1,
h2,
h3,
h4,
li,
dd,
dt {
  overflow-wrap: break-word;
}

/* --------------------------------------------------------------------------
   2. Design tokens
   -------------------------------------------------------------------------- */

:root {
  /* Spacing — a 4px-based scale. Every gap in the site comes from here. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 2.5rem;
  --space-8: 3rem;
  --space-9: 4rem;
  --space-10: 5rem;
  --space-11: 6rem;
  --space-12: 8rem;

  /* Vertical rhythm between major page sections, fluid across viewports. */
  --section-gap: clamp(3.5rem, 9vw, 7rem);

  /* Layout */
  --wrap: 1180px;
  --wrap-narrow: 760px;
  --gutter: clamp(1rem, 4vw, 2.5rem);
  --header-h: 4.5rem;

  /* Radii */
  --r-sm: 0.5rem;
  --r-md: 0.875rem;
  --r-lg: 1.25rem;
  --r-xl: 1.75rem;
  --r-full: 999px;

  /* Type scale — fluid, so headings never break the layout on small screens */
  --fs-xs: 0.75rem;
  --fs-sm: 0.8125rem;
  --fs-base: 1rem;
  --fs-md: clamp(1.0625rem, 0.4vw + 0.95rem, 1.125rem);
  --fs-lg: clamp(1.25rem, 0.8vw + 1.05rem, 1.5rem);
  --fs-xl: clamp(1.5rem, 1.4vw + 1.15rem, 2rem);
  --fs-2xl: clamp(1.875rem, 2.4vw + 1.25rem, 2.75rem);
  --fs-3xl: clamp(2.25rem, 4.2vw + 1.2rem, 4rem);

  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-normal: 1.65;

  --ff-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  --ff-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono',
    Menlo, Consolas, 'Liberation Mono', monospace;

  /* Neutral surfaces. These stay constant across themes; only the accent and
     the background tint change, which keeps contrast predictable. */
  --text: #e9edf7;
  --text-dim: #9aa4bd;
  --text-mute: #6c7690;

  --surface-1: rgba(255, 255, 255, 0.035);
  --surface-2: rgba(255, 255, 255, 0.055);
  --surface-3: rgba(255, 255, 255, 0.08);
  --border: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.16);

  /* Semantic status colours — deliberately independent of the theme, so a
     red theme can never make a healthy service look broken. */
  --ok: #3ddc97;
  --ok-dim: rgba(61, 220, 151, 0.14);
  --warn: #f7b955;
  --warn-dim: rgba(247, 185, 85, 0.14);
  --bad: #ff6b6b;
  --bad-dim: rgba(255, 107, 107, 0.14);
  --idle: #8b95ad;
  --idle-dim: rgba(139, 149, 173, 0.14);

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 8px 24px -8px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 24px 60px -20px rgba(0, 0, 0, 0.7);

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --t-fast: 140ms;
  --t-base: 240ms;
  --t-slow: 420ms;

  --z-particles: 0;
  --z-content: 1;
  --z-header: 100;
  --z-menu: 150;
  --z-toast: 200;
  --z-loader: 300;
}

/* --------------------------------------------------------------------------
   3. Themes

   Each theme sets the accent family AND the background tint. Changing the
   theme visibly repaints the page background, not just a link colour — that
   is the whole point of the switcher.
   -------------------------------------------------------------------------- */

:root,
[data-theme='blue'] {
  --accent: #4a8cff;
  --accent-soft: #7fb0ff;
  --accent-deep: #2a63d6;
  --accent-contrast: #04101f;
  --accent-glow: rgba(74, 140, 255, 0.4);
  --accent-wash: rgba(74, 140, 255, 0.11);
  --accent-line: rgba(74, 140, 255, 0.32);

  --bg: #070a12;
  --bg-elev: #0c111d;
  --tint-a: rgba(74, 140, 255, 0.16);
  --tint-b: rgba(96, 76, 255, 0.12);
  --tint-c: rgba(40, 170, 220, 0.08);
}

[data-theme='red'] {
  --accent: #ff5a5a;
  --accent-soft: #ff8f85;
  --accent-deep: #d13232;
  --accent-contrast: #1f0505;
  --accent-glow: rgba(255, 90, 90, 0.4);
  --accent-wash: rgba(255, 90, 90, 0.11);
  --accent-line: rgba(255, 90, 90, 0.32);

  --bg: #0d0709;
  --bg-elev: #160c10;
  --tint-a: rgba(255, 90, 90, 0.16);
  --tint-b: rgba(255, 120, 60, 0.1);
  --tint-c: rgba(190, 40, 90, 0.1);
}

[data-theme='purple'] {
  --accent: #a66bff;
  --accent-soft: #c79bff;
  --accent-deep: #7b3fd4;
  --accent-contrast: #100520;
  --accent-glow: rgba(166, 107, 255, 0.4);
  --accent-wash: rgba(166, 107, 255, 0.11);
  --accent-line: rgba(166, 107, 255, 0.32);

  --bg: #090714;
  --bg-elev: #120c1e;
  --tint-a: rgba(166, 107, 255, 0.17);
  --tint-b: rgba(96, 76, 255, 0.12);
  --tint-c: rgba(220, 90, 200, 0.08);
}

/* --------------------------------------------------------------------------
   4. Page background

   A fixed, layered gradient mesh driven by the theme tints. It sits behind
   everything and costs nothing to animate because it never does.
   -------------------------------------------------------------------------- */

body {
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--ff-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  transition: background-color var(--t-slow) var(--ease);
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(60rem 40rem at 15% -10%, var(--tint-a), transparent 60%),
    radial-gradient(50rem 36rem at 88% 4%, var(--tint-b), transparent 62%),
    radial-gradient(48rem 40rem at 50% 105%, var(--tint-c), transparent 65%);
  transition: background var(--t-slow) var(--ease);
}

/* A very subtle grain/grid so large empty areas are not flat dead space. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.4;
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(70rem 50rem at 50% 0%, #000 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(70rem 50rem at 50% 0%, #000 0%, transparent 75%);
}

/* Optional decorative particle canvas, injected by JS. Purely cosmetic: if
   the script never runs, the gradient background above still looks complete. */
#particles {
  position: fixed;
  inset: 0;
  z-index: var(--z-particles);
  pointer-events: none;
  opacity: 0.55;
}

/* --------------------------------------------------------------------------
   5. Typography
   -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  line-height: var(--lh-tight);
  font-weight: 650;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-3xl);
  letter-spacing: -0.035em;
}

h2 {
  font-size: var(--fs-2xl);
  letter-spacing: -0.03em;
}

h3 {
  font-size: var(--fs-lg);
}

h4 {
  font-size: var(--fs-md);
}

p {
  text-wrap: pretty;
}

strong {
  font-weight: 620;
  color: #fff;
}

code,
kbd,
samp {
  font-family: var(--ff-mono);
  font-size: 0.9em;
}

/* Long-form prose: constrained measure so lines stay readable. */
.prose {
  max-width: 68ch;
  color: var(--text-dim);
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
}

.prose > * + * {
  margin-top: var(--space-5);
}

.prose strong {
  color: var(--text);
}

/* Small uppercase label used above section headings. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-soft);
}

.eyebrow::before {
  content: '';
  width: 1.5rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent));
}

.text-dim {
  color: var(--text-dim);
}

.text-mute {
  color: var(--text-mute);
}

/* --------------------------------------------------------------------------
   6. Focus and accessibility
   -------------------------------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* Remove the default outline only where we provide our own visible one. */
:focus:not(:focus-visible) {
  outline: none;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: calc(var(--z-loader) + 1);
  padding: var(--space-3) var(--space-5);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  border-radius: var(--r-sm);
  transform: translateY(-150%);
  transition: transform var(--t-fast) var(--ease);
}

.skip-link:focus {
  transform: translateY(0);
}

/* --------------------------------------------------------------------------
   7. Loading screen

   Reliability rules, because a stuck loader is worse than no loader:
     - The page content is NEVER hidden. The loader is an overlay on top of a
       fully rendered page, so a JavaScript failure cannot hide the site.
     - A pure-CSS animation removes the overlay after 3.5s even if no
       JavaScript runs at all. JS normally beats it comfortably.
     - <noscript> removes it immediately.
   -------------------------------------------------------------------------- */

.loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-loader);
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-5);
  padding: var(--gutter);
  background: var(--bg);
  /* The failsafe: hide after 3.5s with no JavaScript involved whatsoever. */
  animation: loader-failsafe 0s linear 3.5s forwards;
}

@keyframes loader-failsafe {
  to {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
}

/* The normal, JS-driven exit. Wins the race in every healthy page load. */
.loader.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--t-slow) var(--ease), visibility 0s linear var(--t-slow);
  animation: none;
}

.loader__mark {
  display: grid;
  place-items: center;
  width: 5rem;
  height: 5rem;
}

.loader__mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.loader__wordmark {
  font-size: var(--fs-xl);
  font-weight: 700;
  letter-spacing: -0.03em;
}

.loader__wordmark span {
  color: var(--accent);
}

.loader__bar {
  width: min(14rem, 60vw);
  height: 3px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  overflow: hidden;
}

.loader__bar::after {
  content: '';
  display: block;
  width: 40%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: loader-sweep 1.1s var(--ease) infinite;
}

@keyframes loader-sweep {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(350%);
  }
}

.loader__text {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-mute);
}

/* --------------------------------------------------------------------------
   8. Toast notifications
   -------------------------------------------------------------------------- */

.toasts {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(22rem, calc(100vw - 2 * var(--gutter)));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--bg-elev);
  box-shadow: var(--shadow-lg);
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  pointer-events: auto;
  animation: toast-in var(--t-base) var(--ease-out) both;
}

.toast.is-leaving {
  animation: toast-out var(--t-base) var(--ease) both;
}

.toast__dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.45rem;
  border-radius: var(--r-full);
  background: var(--accent);
}

.toast--success .toast__dot {
  background: var(--ok);
}

.toast--error .toast__dot {
  background: var(--bad);
}

.toast__body {
  min-width: 0;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem) scale(0.98);
  }
}

@keyframes toast-out {
  to {
    opacity: 0;
    transform: translateY(0.5rem) scale(0.98);
  }
}

/* --------------------------------------------------------------------------
   9. Scroll reveal

   Elements are only hidden once JavaScript has confirmed it is running, by
   adding .js-reveal to <html>. Without JS, .reveal does nothing at all and
   every bit of content is visible — content is never hidden by default.
   -------------------------------------------------------------------------- */

.js-reveal .reveal {
  opacity: 0;
  transform: translateY(1.25rem);
  transition: opacity var(--t-slow) var(--ease-out),
    transform var(--t-slow) var(--ease-out);
}

.js-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   10. Utilities
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--narrow {
  max-width: var(--wrap-narrow);
}

.stack > * + * {
  margin-top: var(--space-4);
}

.stack-lg > * + * {
  margin-top: var(--space-6);
}

.center {
  text-align: center;
}

.center .eyebrow {
  justify-content: center;
}

/* --------------------------------------------------------------------------
   Reduced motion

   Honour the preference globally: no transitions, no sweeps, no reveal
   animation. The loader still disappears — it just does so instantly.
   -------------------------------------------------------------------------- */

@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;
    scroll-behavior: auto !important;
  }

  /* Keep the no-JS failsafe working, just without the wait. */
  .loader {
    animation: loader-failsafe 0s linear 0.6s forwards;
  }

  .js-reveal .reveal {
    opacity: 1;
    transform: none;
  }

  #particles {
    display: none;
  }
}
