/* ==========================================================================
   base.css — design tokens, reset, typography, layout primitives
   Load order: base.css → components.css → <page>.css
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   --------------------------------------------------------------------------
   BRAND COLOURS SAMPLED FROM exchange/static/exchange/img/icon.png.
   The two dominant pixel clusters in the logo are:
     brand blue   #3329AE  (34% of opaque pixels)  HSL 244.5 61.9% 42.2%
     brand yellow #F9DC06  (61% of opaque pixels)  HSL  52.8 95.3% 50.0%
   Both are used verbatim as --blue-600 and --yellow-500; the rest of each ramp
   is the same hue and saturation at stepped lightness. To retheme, edit only
   these two ramps — everything else consumes the semantic aliases below.

   All 33 foreground/background pairs in use were verified against WCAG; every
   one passes AA and most reach AAA. Notable values:
     body text on white ................ 17.92:1  AAA
     white on brand blue (header) ...... 10.15:1  AAA
     brand yellow on brand blue ......... 7.38:1  AAA
     blue-900 on brand yellow (CTA) .... 12.72:1  AAA
     muted text on white ................ 6.32:1  AA
   -------------------------------------------------------------------------- */

:root {
  /* --- brand ramps (edit these to retheme) --- */
  --blue-900: #151146;
  --blue-800: #1E1867;
  --blue-700: #282088;
  --blue-600: #3329AE;   /* exact logo blue */
  --blue-500: #4439D0;
  --blue-100: #E0DEF7;
  --blue-050: #EFEEFB;

  --yellow-700: #756703;
  --yellow-600: #A99604;
  --yellow-500: #F9DC06; /* exact logo yellow */
  --yellow-400: #FAE442;
  --yellow-100: #FEF8CD;

  /* --- neutrals (tinted very slightly toward the brand hue) --- */
  --ink-900: #14152B;
  --ink-700: #3A3B52;
  --ink-500: #5C5E77;
  --white:   #FFFFFF;
  /* The page ground is no longer paper white. A rate board is a wall of small
     figures that people read for a while, and #FFFFFF behind it is the single
     brightest thing a screen can emit — it fatigues the eye long before the
     numbers become hard to read. These two are the brand hue carried down to
     near-white, so the site still reads as white at a glance while giving the
     eye somewhere softer to rest:
     Four steps, lightest to bluest — a card is always lighter than the ground
     it sits on, which is what lets the rate board read as a panel now that
     neither of them is white:
       --paper    cards, table rows, anything that floats
       --surface  a band inside a card (the rate table's caption)
       --sky      the page ground itself
       --haze     the page-head band, the one step below the ground
     --white stays for the few places that need true white — the logo disc, the
     text colour on dark bands, the top of the TV header's gradient. */
  --paper:   #F6F8FD;
  --surface: #EDF1FB;
  --sky:     #E6ECF9;
  --haze:    #DBE3F5;
  --line:    #DDE1F0;
  /* A second, heavier hairline. --line is tuned to separate two panels that
     already differ in fill; it is too faint to be the whole outline of a
     control sitting on bare white, which is what the header's switcher and
     menu button became when the bar stopped being blue. 3.1:1 on white. */
  --line-strong: #B3B8D2;

  /* --- status --- */
  --success: #0F7B4F;
  --danger:  #B3261E;

  /* --- semantic aliases: use THESE in components, not the ramps --- */
  --color-primary:        var(--blue-600);
  --color-primary-strong: var(--blue-800);
  --color-primary-deep:   var(--blue-900);
  --color-primary-hover:  var(--blue-700);
  --color-primary-tint:   var(--blue-050);
  --color-accent:         var(--yellow-500);
  --color-accent-hover:   var(--yellow-400);
  --color-accent-text:    var(--yellow-700);

  /* Focus ring colour. The brand yellow is only 1.38:1 on white, so it cannot
     be the ring everywhere. Light surfaces get the brand blue; dark sections
     re-declare --color-focus to the yellow further down. Custom properties
     inherit, so any focusable descendant picks up the right one. */
  --color-focus: var(--blue-600);

  /* --- dark surfaces ---------------------------------------------------
     Every dark band on the site resolves through one of these four tokens,
     so the header/footer weight can be retuned here rather than hunted for
     across five stylesheets. Previously the footer and trust strip sat on
     --blue-900 (#151146), which reads closer to black than to the brand and
     made the page feel bottom-heavy. They now sit one to two steps up the
     ramp, which keeps the brand hue while lifting the weight.
     White text clears AAA on all of them (10.15:1 on the lightest). */
  /* The header is the one band that is NOT dark. It was the exact logo blue,
     which swallowed the logo's own blue ring and made the bar the heaviest
     thing on every page. It is now white, with the brand blue and yellow
     carried by the type instead of the surface — the logo reads at full
     strength and the deep-blue payment ticker directly beneath it becomes the
     line that separates chrome from content. */
  --color-surface-header: var(--white);
  --color-surface-header-scrolled: var(--white);
  /* Deliberately true white while the page below is tinted: the bar is chrome,
     it is 70px tall rather than a full screen, and the small step down onto
     --paper is what now separates it from the content without a heavier rule. */
  --color-surface-footer: var(--blue-700);   /* was --blue-900 */
  --color-surface-band:   var(--blue-800);   /* trust strip; was --blue-900 */
  --color-surface-hero:   linear-gradient(160deg,
                            var(--blue-600) 0%,
                            var(--blue-800) 100%);

  /* --- motion ----------------------------------------------------------
     Durations and easings are tokens so the whole site can be sped up or
     calmed down from one place. Nothing should hardcode a duration. */
  --dur-fast:   150ms;  /* colour/hover feedback */
  --dur-mid:    250ms;  /* header state, card lift */
  --dur-slow:   400ms;  /* larger UI state changes */
  --dur-reveal: 520ms;  /* on-scroll section reveals */
  --ease-out:   cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0.05, 0.36, 1);
  /* Travel distance for reveal/entrance transforms. */
  --rise: 16px;

  --color-text:        var(--ink-900);
  --color-text-muted:  var(--ink-500);
  --color-text-invert: var(--white);
  --color-bg:          var(--sky);      /* the page ground */
  --color-surface-card: var(--paper);   /* anything that floats on it */
  --color-surface-head: var(--haze);    /* the page-head band */
  --color-bg-subtle:   var(--surface);
  --color-border:      var(--line);
  --color-border-strong: var(--line-strong);

  /* --- spacing scale (4px rhythm) --- */
  --space-1: 0.25rem;   /*  4px */
  --space-2: 0.5rem;    /*  8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */
  --space-6: 2rem;      /* 32px */
  --space-7: 3rem;      /* 48px */
  --space-8: 4rem;      /* 64px */
  --space-9: 6rem;      /* 96px */

  /* --- type scale --- */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
               Arial, "Noto Sans", sans-serif;
  --text-xs:   0.75rem;   /* 12px */
  --text-sm:   0.875rem;  /* 14px */
  --text-base: 1rem;      /* 16px */
  --text-md:   1.125rem;  /* 18px */
  --text-lg:   1.25rem;   /* 20px */
  --text-xl:   1.5rem;    /* 24px */
  --text-2xl:  2rem;      /* 32px */
  --text-3xl:  2.5rem;    /* 40px */

  --leading-tight: 1.2;
  --leading-snug:  1.35;
  --leading-body:  1.65;

  /* --- radii, shadow, layout --- */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(10, 27, 61, 0.06),
               0 1px 3px rgba(10, 27, 61, 0.08);
  --shadow-md: 0 2px 4px rgba(10, 27, 61, 0.06),
               0 8px 20px rgba(10, 27, 61, 0.10);

  --container: 1200px;
  --container-pad: var(--space-4);

  /* --- z-index scale --- */
  --z-base: 0;
  --z-sticky: 100;
  --z-nav: 200;
  --z-skip: 1000;
}

@media (min-width: 768px) {
  :root {
    --container-pad: var(--space-6);
    --text-3xl: 3rem;   /* 48px */
  }
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */

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

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

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

ul[class],
ol[class] { list-style: none; }

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

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

button { cursor: pointer; }

table {
  border-collapse: collapse;
  width: 100%;
}

address { font-style: normal; }

@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;
  }
  /* Collapsing durations is not enough on its own: anything whose resting
     state is opacity:0 waiting on a transition would stay invisible. Every
     motion pattern below is force-resolved to its final state here, and
     motion.js additionally skips the observer entirely. */
  .reveal,
  .reveal-stagger > *,
  .hero__content > *,
  .hero__rates {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}

/* --------------------------------------------------------------------------
   3. Typography
   -------------------------------------------------------------------------- */

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-text);
  background-color: var(--color-bg);
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  /* Safety net against a sideways scroll on phones. `clip`, not `hidden`:
     hidden would turn <body> into a scroll container and break the sticky
     header. Nothing on the site is expected to reach this — it is here so a
     single long word or wide row can never drag the whole page sideways. */
  overflow-x: clip;
}

main { flex: 1 0 auto; }

h1, h2, h3, h4 {
  color: var(--color-primary-strong);
  line-height: var(--leading-tight);
  font-weight: 700;
  text-wrap: balance;
}

h1 { font-size: var(--text-3xl); letter-spacing: -0.02em; }
h2 { font-size: var(--text-2xl); letter-spacing: -0.01em; }
h3 { font-size: var(--text-lg); }
h4 { font-size: var(--text-base); }

p { text-wrap: pretty; }

a { color: var(--color-primary); }
a:hover { color: var(--color-primary-hover); }

strong { font-weight: 700; }

/* --------------------------------------------------------------------------
   4. Focus & accessibility helpers
   -------------------------------------------------------------------------- */

:where(a, button, select, input, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Dark bands: swap the ring to the brand yellow, which reads clearly on blue
   (7.38:1) where the blue ring would vanish into the background. The header is
   no longer one of them — it is white, so it keeps the default blue ring. */
.site-footer,
.hero,
.trust,
.cta {
  --color-focus: var(--yellow-500);
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: var(--z-skip);
  padding: var(--space-3) var(--space-4);
  background: var(--color-accent);
  color: var(--color-primary-deep);
  font-weight: 700;
  border-radius: var(--radius-md);
  transform: translateY(-200%);
  transition: transform 150ms ease-out;
}

.skip-link:focus {
  transform: translateY(0);
  color: var(--color-primary-deep);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   5. Layout primitives
   -------------------------------------------------------------------------- */

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

.page-head {
  background: var(--color-surface-head);
  border-bottom: 1px solid var(--color-border);
  padding-block: var(--space-6) var(--space-6);
}

.page-head__title { margin-bottom: var(--space-3); }

.page-head__lead {
  color: var(--color-text-muted);
  font-size: var(--text-md);
  max-width: 60ch;
}

.section-head {
  max-width: 60ch;
  margin-bottom: var(--space-6);
}

.section-head__title { margin-bottom: var(--space-3); }

.section-head__lead {
  color: var(--color-text-muted);
  font-size: var(--text-md);
}

@media (min-width: 768px) {
  .page-head { padding-block: var(--space-8); }
  .section-head { margin-bottom: var(--space-7); }
}

/* --------------------------------------------------------------------------
   6. Motion primitives
   --------------------------------------------------------------------------
   Two patterns, both driven by js/motion.js:

     .reveal          one element fades and rises into place
     .reveal-stagger  a container whose direct children do the same, offset

   Both are gated behind `.js` on <html>, so with JavaScript disabled the
   content is simply visible — nothing is hidden behind a script that may
   never run. The `.is-ready` gate exists so that content already inside the
   viewport on first paint appears instantly rather than animating: motion.js
   marks those visible BEFORE adding .is-ready, and the transition only exists
   once .is-ready is present. That keeps the rate figures static on arrival.
   -------------------------------------------------------------------------- */

.js .reveal {
  opacity: 0;
  transform: translate3d(0, var(--rise), 0);
  will-change: opacity, transform;
}

.js .reveal.is-ready {
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* Dropped once the element has arrived, so the compositor is not asked to
   hold a layer for every section on the page for the rest of the session. */
.js .reveal.is-settled { will-change: auto; }

.js .reveal-stagger > * {
  opacity: 0;
  transform: translate3d(0, var(--rise), 0);
}

.js .reveal-stagger.is-ready > * {
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}

.js .reveal-stagger.is-visible > * {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* 70ms apart: enough to read as a sequence, short enough that the last tile
   in a six-up grid still lands within ~900ms of the first. */
.js .reveal-stagger.is-visible > *:nth-child(1) { transition-delay: 0ms; }
.js .reveal-stagger.is-visible > *:nth-child(2) { transition-delay: 70ms; }
.js .reveal-stagger.is-visible > *:nth-child(3) { transition-delay: 140ms; }
.js .reveal-stagger.is-visible > *:nth-child(4) { transition-delay: 210ms; }
.js .reveal-stagger.is-visible > *:nth-child(5) { transition-delay: 280ms; }
.js .reveal-stagger.is-visible > *:nth-child(6) { transition-delay: 350ms; }
.js .reveal-stagger.is-visible > *:nth-child(n + 7) { transition-delay: 420ms; }

/* --------------------------------------------------------------------------
   7. Page entrance
   --------------------------------------------------------------------------
   Deliberately applied to <main> and the footer rather than <body>: the header
   must not fade, both because the nav should feel immediately anchored and
   because an opacity animation on an ancestor of the sticky header would give
   it a new containing block. Short and opacity-only — this is here to take the
   hard edge off navigation, not to be noticed.
   -------------------------------------------------------------------------- */

@keyframes page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.js main,
.js .site-footer {
  animation: page-in 220ms var(--ease-out) both;
}
