/* ==========================================================================
   LEECOI — Globals
   ==========================================================================

   Scope discipline: this file uses ELEMENT selectors only (plus a small number
   of unavoidable accessibility helpers). It never styles a class that a
   component owns.

   That rule is what keeps specificity flat across the whole stylesheet. Every
   component rule is a single class (0,1,0) and therefore always beats these
   element rules (0,0,1) without needing `!important` anywhere.

   Load order is significant and is documented in `components.css`.
   ========================================================================== */

/* ==========================================================================
   1. FONTS
   --------------------------------------------------------------------------
   Self-hosted variable fonts, vendored by `tools/fetch-fonts.mjs`.

   `font-display: swap` renders immediately in the fallback and swaps when the
   file lands, so text is never invisible. The fallback stacks in
   variables.css are metric-similar enough that the swap does not reflow the
   page noticeably.

   `unicode-range` means a visitor reading English never downloads the 83 KB
   latin-ext file at all.
   ========================================================================== */

/* Inter — latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/inter-latin-var.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;
}

/* Inter — latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/inter-latin-ext-var.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;
}

/* Roboto Mono — latin. Data labels are numerals and ASCII, so latin-ext is
   deliberately not vendored for this family. */
@font-face {
  font-family: 'Roboto Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../fonts/roboto-mono-latin-var.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;
}

/* ==========================================================================
   2. RESET
   --------------------------------------------------------------------------
   Targeted, not a library. Each rule earns its place.
   ========================================================================== */

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

/* Margin is applied deliberately by components, never inherited by accident. */
* {
  margin: 0;
}

html {
  /* Smooth scrolling is opt-in for everyone and revoked under
     prefers-reduced-motion in section 7. */
  scroll-behavior: smooth;

  /* Stops iOS inflating text in landscape, which breaks the fluid scale. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;

  /* Anchor targets clear the sticky header instead of hiding beneath it.
     Reads the same token the header itself uses. */
  scroll-padding-top: calc(var(--header-h) + var(--space-lg));
}

body {
  min-height: 100vh;
  min-height: 100dvh;

  font-family: var(--font-sans);
  font-size: var(--step-body);
  line-height: var(--step-body-lh);
  font-weight: var(--weight-normal);

  /* The document defaults to the dark field. Light bands opt in per section
     via `.surface-light`, so the page background behind a short page is the
     dark field rather than white. */
  background-color: var(--p-base);
  color: var(--p-white);

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

  /* Never let the browser fake a weight the variable font already has. */
  font-synthesis-weight: none;

  /* Belt and braces against horizontal overflow. The real fix is always the
     offending element, and `tools/check-layout.mjs` fails the build rather
     than letting this rule quietly hide a bug. */
  overflow-x: clip;
}

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

img,
video {
  height: auto;
}

/* ---- Headings ---------------------------------------------------------- */
/* Sizes live on component and utility classes, not here. This block only
   normalises inheritance so an unstyled heading is never browser-default
   serif at browser-default size. */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: var(--weight-semi);
  line-height: 1.2;

  /* Keeps a two-line headline from leaving one orphaned word. */
  text-wrap: balance;
}

/* Avoids ragged last lines in paragraphs without balancing the whole block. */
p,
li {
  text-wrap: pretty;
}

/* ---- Lists ------------------------------------------------------------- */
/* Lists are used structurally throughout (nav, card grids, module outputs).
   Markers and padding are reinstated by the component that wants them. */
ul,
ol {
  padding: 0;
  list-style: none;
}

/* ---- Links ------------------------------------------------------------- */
a {
  color: inherit;
  text-decoration: none;

  /* Underlines, where a component asks for them, sit clear of descenders. */
  text-underline-offset: 0.2em;
  text-decoration-thickness: from-font;
}

/* ---- Forms ------------------------------------------------------------- */
button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  letter-spacing: inherit;
}

button {
  cursor: pointer;
  /* Safari refuses to centre button text without this. */
  text-align: inherit;
}

textarea {
  resize: vertical;
  /* Under ~2.5 lines a textarea is indistinguishable from an input. */
  min-height: 6rem;
}

/* Search and number inputs get platform chrome that ignores the type scale. */
input[type='search'] {
  -webkit-appearance: none;
  appearance: none;
}

:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* ---- Tables ------------------------------------------------------------ */
/* Used for real tabular data: phase roadmaps, module breakdowns. */
table {
  width: 100%;
  border-collapse: collapse;
  /* Numerals in a table must align in columns to be scannable. */
  font-variant-numeric: tabular-nums;
}

th {
  text-align: left;
  font-weight: var(--weight-semi);
}

/* ---- Hidden ------------------------------------------------------------
   The `hidden` attribute is only `display: none` from the UA stylesheet, at
   element specificity. ANY class that sets `display` beats it, so
   `<div class="notice" hidden>` renders as an empty bordered box, which is
   exactly what the registration form's status region did.

   One of the very few places `!important` is correct: `hidden` is a statement
   that the element is not currently relevant, and no layout rule should be able
   to overrule that by accident. */
[hidden] {
  display: none !important;
}

/* ---- Misc -------------------------------------------------------------- */
hr {
  height: 1px;
  border: 0;
  background-color: var(--line, rgb(255 255 255 / 0.08));
}

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

/* Long unbroken strings in prose (a package name, a URL) must not push the
   layout sideways. */
pre {
  overflow-x: auto;
}

abbr[title] {
  text-decoration: underline dotted;
  cursor: help;
}

/* Any element carrying numbers that need to line up vertically. */
[data-numeric] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
}

/* ==========================================================================
   3. FOCUS
   --------------------------------------------------------------------------
   Non-negotiable and never removed.

   The ring colour comes from `--ring`, which each surface scope redefines.
   That indirection is not decoration: gold at full chroma measures 1.96:1 on
   the light paper field, well under the 3:1 floor for non-text contrast, so a
   single hard-coded gold ring would be effectively invisible on every light
   content section. The light scope substitutes the darker gold at 5.58:1.

   `:focus-visible` rather than `:focus`, so a mouse click on a button does not
   leave a ring behind, while every keyboard path still shows one.
   ========================================================================== */

:focus-visible {
  outline: 2px solid var(--ring, var(--p-gold));
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* Some browsers still paint a default ring alongside ours. */
:focus:not(:focus-visible) {
  outline: none;
}

::selection {
  background-color: var(--p-gold);
  color: var(--p-base);
}

/* ==========================================================================
   4. SKIP LINK
   --------------------------------------------------------------------------
   First focusable element on every page. Visually hidden until focused, then
   fully visible: an off-screen-forever skip link is a common way to fail the
   very check it exists to pass.
   ========================================================================== */

.skip-link {
  position: fixed;
  top: var(--space-md);
  left: var(--space-md);
  z-index: var(--z-skip);

  padding: var(--space-sm) var(--space-lg);
  border-radius: var(--radius-md);

  background-color: var(--p-gold);
  color: var(--p-base);
  font-family: var(--font-mono);
  font-size: var(--step-small);
  font-weight: var(--weight-medium);

  /* Lifted out of view rather than `display: none`, which would remove it
     from the tab order entirely. */
  transform: translateY(calc(-100% - var(--space-xl)));
  transition: transform var(--dur-fast) var(--ease-ui);
}

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

/* ==========================================================================
   5. SCREEN-READER HELPERS
   ========================================================================== */

/* Removed from the visual page, kept in the accessibility tree. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  /* inset(50%) beats the older 1px-rect clip: it does not leak a stray pixel
     and does not defeat text-to-speech in Safari. */
  clip-path: inset(50%);
}

/* Same, but becomes visible on focus. For controls that should stay hidden
   until a keyboard user reaches them. */
.visually-hidden-focusable:not(:focus):not(:focus-within) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

/* ==========================================================================
   6. MAIN LANDMARK
   ========================================================================== */

main {
  display: block;
  /* The header is fixed, so main would otherwise start underneath it. Pages
     whose hero paints its own background behind the transparent header opt
     out with `.main--under-header`. */
  scroll-margin-top: var(--header-h);
}

main:focus {
  outline: none;
}

/* ==========================================================================
   7. REDUCED MOTION
   --------------------------------------------------------------------------
   The important detail: entrance animations RESOLVE TO THEIR END STATE rather
   than being shortened. A reduced-motion visitor sees finished content, never
   a faster version of the animation and never content stuck at opacity 0
   because its reveal never fired.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  /* Finite animations run to completion instantly. Combined with
     `animation-fill-mode: forwards` on the entrance keyframes, that lands the
     element on its FINAL frame rather than stranding it on its first.

     Deliberately NOT `animation-play-state: paused`: pausing freezes an
     animation where it currently is, which for an entrance starting at
     `opacity: 0` means permanently invisible content. Infinite decorative
     loops (the hero glow, the marquee) are ended by the iteration count
     instead. */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  /* Belt and braces. The rules above should already resolve both of these, but
     an entrance that fails to resolve costs the visitor the content entirely,
     so the end state is also asserted directly. This is the single most
     important declaration in the file. */
  [data-reveal],
  [data-enter] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Purely ambient movement is removed rather than merely shortened. */
  .hero-glow,
  .marquee__track,
  .shimmer {
    animation: none !important;
  }
}

/* ==========================================================================
   8. FORCED COLOURS
   --------------------------------------------------------------------------
   Windows high-contrast mode replaces our palette wholesale. Borders that we
   drew with background gradients or translucent fills vanish, so anything
   whose meaning depends on an edge gets a real one back.
   ========================================================================== */

@media (forced-colors: active) {
  :focus-visible {
    outline: 2px solid CanvasText;
  }

  .btn,
  .card,
  .field__input {
    border: 1px solid CanvasText;
  }
}

/* ==========================================================================
   9. PRINT
   --------------------------------------------------------------------------
   People do print syllabus pages. Chrome is dropped, the field goes white,
   and link destinations are written out since a printed link is otherwise a
   dead end.
   ========================================================================== */

@media print {
  .site-header,
  .site-footer,
  .skip-link,
  [data-print-hide] {
    display: none !important;
  }

  body,
  .surface-dark,
  .surface-light,
  .surface-void {
    background: #fff !important;
    color: #000 !important;
  }

  main {
    padding: 0 !important;
  }

  a[href^='http']::after {
    content: ' (' attr(href) ')';
    font-size: 0.85em;
    word-break: break-all;
  }

  /* Never split a card or a table row across a page break. */
  .card,
  tr,
  figure {
    break-inside: avoid;
  }
}
