/* ---- base.css — the shared floor every page stands on ---------------------
   Loaded by every page, after tokens.css and before any page-specific sheet.
   Holds only what is genuinely universal: the reset, document defaults, the
   type scale, the accessibility floor, the shared layout container and the
   site-wide backdrop.

   Anything that belongs to one page belongs in that page's own stylesheet.
   The hero's blobs and glitch stay in hero.css; they appear on the homepage
   only. Resist the pull to let this file become a dumping ground — a base
   stylesheet that knows about specific pages is how a design system rots. */

/* ---- Reset ---------------------------------------------------------------
   border-box means a stated width is the width you actually get, padding and
   border included. Without it, adding padding silently makes elements wider
   than intended and layouts drift. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The browser's default margins differ between elements and between browsers.
   Clearing them means spacing comes from the scale in tokens.css and nothing
   arrives by accident. */
body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd {
  margin: 0;
}

/* ---- Document ------------------------------------------------------------ */

html {
  /* The page colour lives on the root, not on <body>. That is deliberate: the
     root's background is what paints the browser canvas, which leaves the
     layer behind <body> free for the backdrop below. Put this on <body> and
     the backdrop is covered up and never seen. */
  background: var(--bg);

  /* Anchor links and in-page jumps glide rather than teleport — but only for
     visitors who have not asked for reduced motion (handled at the foot). */
  scroll-behavior: smooth;
  /* Stops iOS inflating text in landscape, which otherwise breaks the type
     scale on exactly the devices least able to spare the room. */
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100vh;
  color: var(--ink-muted);

  /* A column so the footer can be pushed to the bottom on short pages. Without
     it, a page with little content leaves the footer stranded halfway down with
     dead space beneath — which reads as a page that failed to load rather than
     one that is simply short. */
  display: flex;
  flex-direction: column;

  /* Lexend for all reading copy. The pixel faces are for the wordmark and the
     occasional big title only — the audience here is busy, mostly
     non-technical, and a pixel face over a paragraph is genuinely hard work. */
  font-family: var(--font-body);
  font-weight: var(--weight-light);
  font-size: var(--text-base);
  line-height: var(--leading-body);

  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  /* Nothing may cause sideways scrolling. A single over-wide element on a
     phone otherwise makes the entire page shuffle left and right. */
  overflow-x: hidden;
}

/* ---- The site-wide backdrop ----------------------------------------------
   THERE ARE TWO OF THESE, and only ever one on screen at a time.

   The one that normally runs is the canvas below, drawn by backdrop.js: a
   halftone of a flowing noise field, dots varying in size and shade, moving up
   at a third of the scroll speed. It is the hero's texture promoted to run
   behind every page.

   The one immediately after it is the CSS fallback: a plain repeating gradient
   of identical dots, drifting upward on its own. It is what shows when
   JavaScript is off, blocked, or still loading — so a page is never a flat
   sheet of near-black. backdrop.js hides it the moment it takes over, by
   putting `has-canvas-backdrop` on <html>, which means the fallback is visible
   by default and switched off only once something better is actually running.
   That order matters: written the other way round, a JavaScript failure would
   leave no backdrop at all. */

/* The canvas. Fixed, so it covers exactly one screen however long the page is,
   and behind everything: z-index -1 puts it under <body>'s content, which is
   the layer html's own background colour leaves free. */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Its own compositor layer, so scrolling the page never repaints it and
     redrawing it never repaints the page. */
  will-change: transform;
}

/* ---- The no-JavaScript fallback ------------------------------------------
   One fixed layer of faint dots that every page slides over, drifting slowly
   upward under its own steam. Deliberately plainer than the canvas: identical
   dots at one opacity, because that is all a repeating gradient can do.

   It moves by transform, not by shifting the background position. Transforms
   are handed to the graphics card and cost nothing per frame; repositioning a
   background makes the browser repaint the entire screen sixty times a second.
   The layer is one tile taller than the viewport so that as it travels up, the
   bottom edge never appears. */
.has-canvas-backdrop body::before {
  display: none;
}

body::before {
  content: '';
  position: fixed;
  /* Positioned, therefore out of the flex flow above — it is a backdrop, not a
     column item. The negative bottom is the extra tile of overhang. */
  inset: 0 0 calc(var(--dot-pitch) * -1) 0;
  z-index: -1;
  pointer-events: none;

  background-image: radial-gradient(
    circle at center,
    rgba(var(--dot-tint), var(--dot-alpha)) var(--dot-size),
    transparent calc(var(--dot-size) + 0.6px)
  );
  background-size: var(--dot-pitch) var(--dot-pitch);

  animation: backdrop-drift var(--dot-drift) linear infinite;
  will-change: transform;
}

/* Exactly one tile of travel, then it repeats. Because the pattern is
   identical every tile, the restart is invisible and the drift reads as
   endless. */
@keyframes backdrop-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, calc(var(--dot-pitch) * -1), 0); }
}

/* ---- Media ---------------------------------------------------------------
   Images never exceed their container, and keep their proportions when the
   container squeezes them. `display: block` removes the few pixels of gap
   browsers leave under an inline image to make room for a text baseline. */
img,
picture,
svg,
video,
canvas {
  display: block;
  max-width: 100%;
  height: auto;
}

/* ---- Type ----------------------------------------------------------------
   Headings are Lexend by default, on the scale from tokens.css. The pixel
   display face is opt-in via .display — it is the studio's signature and is
   rationed, not sprinkled. */

h1, h2, h3, h4, h5, h6 {
  color: var(--ink);
  font-weight: var(--weight-regular);
  /* Headings are large, so they need proportionally tighter leading than body
     copy — at the same 1.65 the lines would drift apart and stop reading as
     one phrase. */
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

h1 { font-size: var(--text-3xl); line-height: var(--leading-tight); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }
h5, h6 { font-size: var(--text-base); }

/* Stops a paragraph ending in a single stranded word on its own last line. */
p {
  text-wrap: pretty;
}

a {
  color: inherit;
}

/* Form controls do not inherit type from the page by default, so without this
   the contact form would render in the browser's own font. */
input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

/* ---- Type utilities ------------------------------------------------------
   A deliberately short list. Each one earns its place by being needed on more
   than one page. */

/* The pixel display face. Always 400 weight: argent-pixel-cf ships only one
   weight, so asking for bold makes the browser fake it by smearing the glyphs
   sideways — which on a face drawn to a pixel grid reads as a rendering fault
   rather than as emphasis. Always tracked wide, because pixel letterforms jam
   together without added air. */
.display {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-tight);
  /* Letter-spacing adds its gap AFTER the final letter too, so the line ends
     with invisible padding. On a left-aligned heading that trailing space is
     harmless; pulling it back keeps the text box honest for anything measuring
     the element's width. What it must NOT do is shift the first letter — an
     indent here pushes the heading right of the eyebrow and paragraph beneath
     it, and the misalignment is visible at a glance. */
  margin-right: calc(var(--tracking-display) * -1);
}

/* Centred use is the exception, and it is the one case where the trailing gap
   does matter: it drags the whole line off-centre by half of itself. An indent
   of the same size puts it back. This is what hero.css does to the wordmark. */
.display--centred {
  text-align: center;
  margin-right: 0;
  text-indent: var(--tracking-display);
}

/* The small capitalised label above a section heading. It exists to say what
   kind of thing follows, so it is only ever used where there is something true
   to say — never as decoration. */
.eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The opening paragraph of a page or section: one step up from body copy. */
.lede {
  font-size: var(--text-lg);
  color: var(--ink);
  max-width: var(--measure);
}

/* Caps a run of text at a comfortable reading width. Roughly 65 characters a
   line, which is the width the eye tracks back from most reliably. */
.measure {
  max-width: var(--measure);
}

/* ---- Accessibility floor -------------------------------------------------
   Not optional and not decorative. */

/* A visible focus ring for keyboard users, and only for them — :focus-visible
   skips it on mouse clicks, which is why the old trick of removing outlines
   entirely was never necessary. Someone navigating by Tab must always be able
   to see where they are. The accent is used here because it is the one colour
   on the site nothing else competes with, so the ring is unmissable. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* Visually hidden but still read aloud by screen readers. Used for the skip
   link and for labels that would be redundant on screen but are needed by
   someone listening to the page. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The skip link: the first thing a keyboard user reaches, letting them jump
   past the nav straight to the content instead of tabbing through it on every
   single page. Off-screen until focused, then it appears. */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: 10000;

  /* Clears the 44px target minimum like every other interactive element.
     It is only visible while focused, but someone using it is navigating by
     keyboard or switch control and needs it to be a solid target. */
  display: flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-4);
  background: var(--ink);
  color: var(--bg);
  text-decoration: none;
  transform: translateY(-200%);
}

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

/* ---- Layout -------------------------------------------------------------- */

/* The shared measure. Every full-width section puts its content in one of
   these, so the left edge of text lines up down the whole site — one of the
   quiet things that separates a designed page from an assembled one. */
.wrap {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-gutter);
}

/* The standard vertical rhythm between major bands of content. One value,
   used everywhere, is what stops sections drifting to slightly different
   heights down the page. */
.section {
  padding-block: var(--section-y);
}

/* Where the skip link lands. Needs to be focusable to move the keyboard
   caret there, but must not become a Tab stop of its own — hence -1. */
#main:focus {
  outline: none;
}

main {
  /* Takes the leftover height so the footer is pushed to the bottom of the
     viewport on pages too short to fill it. */
  flex: 1 0 auto;

  /* The nav is fixed, so it occupies no space in the layout and the first
     heading on the page would sit underneath it. This reserves the gap: the
     nav's own height, plus the offset it sits at, plus a breath below. */
  padding-top: calc(var(--nav-height) + var(--space-4) + var(--space-8));
}

/* The homepage hero is a full-screen composition that starts at the very top
   of the viewport and deliberately runs behind the nav — the wordmark and
   blobs are the background the nav floats over. It opts out of the reserved
   gap; every other page keeps it. */
.page-hero main {
  padding-top: 0;
}

/* ---- Reduced motion ------------------------------------------------------
   Some people get genuinely unwell from screen motion, and set this at the
   operating system level. Honouring it is not a nicety.

   Animations are near-eliminated rather than removed outright: a duration of
   0 can cancel an animation's completion event, and any JavaScript waiting on
   that event would then never run. 0.01ms finishes instantly and still fires.

   This is also what stills the backdrop. The dot field stops drifting and
   simply sits there as static texture — the page keeps its surface, and
   nothing on screen moves on its own. */
@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;
  }

  /* Freed of its animation, the layer no longer needs its own compositing
     layer held in graphics memory for the whole visit. */
  body::before {
    will-change: auto;
  }
}
