/* ==========================================================================
   AIR.2026 — design system
   Dark, instrumented, technical: the same discipline as the Airoot site, a
   deliberately different voice. Navy ground instead of black, teal signal instead
   of crimson, violet as the second accent, and Sora / IBM Plex instead of Space
   Grotesk. Nothing here is shared with that site's stylesheet.
   ========================================================================== */

/* --- fonts ---------------------------------------------------------------- */
/* Self-hosted. A programme page that a student opens on campus wifi behind a
   proxy cannot depend on a font CDN resolving. font-display:swap so text is
   readable immediately and reflows once, rather than being invisible first. */

@font-face {
  font-family: "Sora";
  src: url("/airxlab/assets/fonts/Sora-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Sora";
  src: url("/airxlab/assets/fonts/Sora-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex";
  src: url("/airxlab/assets/fonts/IBMPlexSans-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex";
  src: url("/airxlab/assets/fonts/IBMPlexSans-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "PlexMono";
  src: url("/airxlab/assets/fonts/IBMPlexMono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "PlexMono";
  src: url("/airxlab/assets/fonts/IBMPlexMono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

/* --- tokens --------------------------------------------------------------- */

:root {
  /* Ground. Navy rather than neutral black: it reads as instrumentation rather
     than as a void, and it lets the teal sit on it without vibrating. */
  --void:    #070C18;
  --base:    #0A1122;
  --panel:   #0E172C;
  --panel-2: #131E38;
  --panel-3: #18264A;

  --line:    rgba(168, 192, 240, .12);
  --line-hi: rgba(168, 192, 240, .26);

  /* Signal: teal-cyan. Used for the one important thing in any view. */
  --signal:    #19D3B7;
  --signal-hi: #4CE6CE;
  /* Teal is bright enough that white-on-teal fails AA, so a solid button puts
     near-black INK on it instead. Measured 9.6:1. */
  --on-signal: #04140F;

  /* Second accent: violet. Structure and gradients, never small text. */
  --accent:    #8B7BFF;
  --accent-tx: #B3A8FF;     /* the readable tint, for text on dark */
  /* Third: amber, for "note this" — a deadline, a seat count. */
  --warm:      #FFB454;

  --text:   #EAF0FB;
  --text-2: #B6C3DC;
  --text-3: #8593B2;

  --font-display: "Sora", system-ui, sans-serif;
  --font-body: "Plex", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "PlexMono", ui-monospace, "Cascadia Mono", monospace;

  /* Type scale, fluid. Sora is wide, so the display sizes are a touch smaller
     than the equivalent Space Grotesk steps would be. */
  --step--1: clamp(.78rem, .76rem + .12vw, .86rem);
  --step-0:  clamp(.95rem, .92rem + .16vw, 1.05rem);
  --step-1:  clamp(1.16rem, 1.1rem + .3vw, 1.35rem);
  --step-2:  clamp(1.45rem, 1.32rem + .6vw, 1.85rem);
  --step-3:  clamp(1.85rem, 1.6rem + 1.2vw, 2.7rem);
  --step-4:  clamp(2.3rem, 1.8rem + 2.6vw, 4rem);
  --step-5:  clamp(2.9rem, 2rem + 4.6vw, 5.6rem);

  --shell: 1240px;
  --gutter: clamp(1.1rem, 4vw, 2.6rem);
  --radius: 2px;         /* nearly square: instrument panels, not app cards */
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* --- reset ---------------------------------------------------------------- */

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

html {
  /* Anchors on this site jump to sections behind a fixed header. */
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
}
/* overflow-x: clip, not hidden: hidden on html turns the whole page into a
   scroll container and breaks position:sticky inside it, which this site uses
   for the pinned timeline. */
html, body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--void);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

img, svg, canvas, video { display: block; max-width: 100%; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -.02em;
  text-wrap: balance;
}
p { text-wrap: pretty; }

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

.shell { width: min(var(--shell), 100% - var(--gutter) * 2); margin-inline: auto; }
.skip {
  position: absolute; top: -100px; left: 1rem; z-index: 200;
  padding: .6rem 1rem; background: var(--signal); color: var(--on-signal);
  font-weight: 500;
}
.skip:focus { top: 1rem; }

/* --- shared type helpers -------------------------------------------------- */

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--signal);
}
.lede { font-size: var(--step-1); color: var(--text-2); max-width: 62ch; }
.mono { font-family: var(--font-mono); }

/* One word of a heading set in outline. Carried through the site as the single
   display flourish, so the accent lives in the type rather than in another
   coloured box. -webkit-text-stroke is the only way to do it without duplicating
   the node; the @supports block keeps the word legible if it is unavailable,
   because unstroked transparent text is invisible. */
.outline {
  color: transparent;
  -webkit-text-stroke: 1.4px var(--signal);
  paint-order: stroke fill;
}
@supports not ((-webkit-text-stroke: 1px red) or (text-stroke: 1px red)) {
  .outline { color: var(--signal); }
}

/* --- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .8rem 1.3rem;
  min-height: 44px;
  background: var(--signal);
  color: var(--on-signal);
  border: 0;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--step-0);
  letter-spacing: -.01em;
  text-decoration: none;
  cursor: pointer;
  transition: background .18s var(--ease), transform .18s var(--ease);
}
.btn:hover { background: var(--signal-hi); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-sm { padding: .55rem .95rem; min-height: 38px; font-size: var(--step--1); }

/* The secondary button sits over the hero canvas, so it carries its own opaque
   panel rather than being transparent. Measured contrast against a near-transparent
   fill is meaningless — and so is the text's real legibility when the drone drifts
   behind it. */
.btn-ghost {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line-hi);
}
.btn-ghost:hover { background: var(--panel-3); border-color: var(--signal); }

/* --- loader --------------------------------------------------------------- */

.loader {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  background: var(--void);
  transition: opacity .5s var(--ease), visibility .5s;
}
body:not([data-loading]) .loader { opacity: 0; visibility: hidden; }
.loader-mark { width: 54px; color: var(--text-3); }
.loader-arc { transform-origin: center; animation: spin 1.1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* --- reading rail --------------------------------------------------------- */

.rail {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 90;
  height: 2px;
  background: transparent;
  pointer-events: none;
}
.rail i {
  display: block;
  height: 100%;
  width: calc(var(--scroll, 0) * 100%);
  background: linear-gradient(90deg, var(--signal), var(--accent));
}

/* --- pointer glow --------------------------------------------------------- */
/* A soft light that follows the pointer. It does NOT replace the cursor: the
   system arrow keeps doing its job, which is telling you what is clickable. */

.glow { display: none; }
@media (pointer: fine) {
  .glow {
    display: block;
    position: fixed;
    top: 0; left: 0;
    z-index: 1;
    width: 420px; height: 420px;
    margin: -210px 0 0 -210px;
    border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(circle,
      color-mix(in srgb, var(--signal) 15%, transparent) 0%,
      color-mix(in srgb, var(--accent) 8%, transparent) 42%,
      transparent 70%);
    opacity: 0;
    transition: opacity .5s var(--ease);
    will-change: transform;
  }
  body[data-pointer] .glow { opacity: 1; }
}

/* --- header --------------------------------------------------------------- */

.head {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .8rem var(--gutter);
  background: color-mix(in srgb, var(--void) 82%, transparent);
  border-bottom: 1px solid transparent;
  transition: background .3s var(--ease), border-color .3s var(--ease);
}
/* Solid only once the page has moved, so the hero is not boxed in at rest.
   No backdrop-filter: it destabilises headless screenshot capture and costs a
   lot on mobile GPUs. */
.head[data-stuck] {
  background: color-mix(in srgb, var(--void) 96%, transparent);
  border-bottom-color: var(--line);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  color: var(--text);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-1);
  letter-spacing: -.03em;
}
.brand-mark { width: 30px; height: 30px; color: var(--text-3); flex: none; }
.brand:hover .brand-mark { color: var(--signal); }
.brand-txt b { color: var(--signal); font-weight: 700; }

.nav { margin-left: auto; display: flex; align-items: center; gap: .3rem; }
.nav a {
  padding: .5rem .7rem;
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--step--1);
  letter-spacing: .01em;
  border-bottom: 1px solid transparent;
}
.nav a:hover { color: var(--text); }
.nav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--signal); }
/* The panel's Register button is an <a> inside .nav, and `.nav a` is the more
   specific selector, so it was overriding .btn's colour and painting --text-2 on
   teal: measured 1.07:1. The button keeps its own ink. */
.nav a.btn { color: var(--on-signal); }
.nav a.btn:hover { color: var(--on-signal); }
.nav-cta { display: none; }
.head-cta { flex: none; }

.nav-toggle {
  display: none;
  width: 42px; height: 42px;
  background: transparent;
  border: 1px solid var(--line-hi);
  border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 18px; height: 1.5px;
  margin: 3.5px auto;
  background: var(--text);
  transition: transform .25s var(--ease), opacity .2s;
}
.head[data-open] .nav-toggle span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.head[data-open] .nav-toggle span:nth-child(2) { opacity: 0; }
.head[data-open] .nav-toggle span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 920px) {
  .head-cta { display: none; }
  .nav-toggle { display: block; margin-left: auto; }
  .nav {
    position: fixed;
    inset: 62px 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: .5rem var(--gutter) 1.2rem;
    background: var(--base);
    border-bottom: 1px solid var(--line);
    /* visibility, not display: the panel must be reachable by keyboard only when
       open, and a display switch cannot be transitioned. */
    visibility: hidden;
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity .22s var(--ease), transform .22s var(--ease), visibility .22s;
  }
  .head[data-open] .nav { visibility: visible; opacity: 1; transform: none; }
  .nav a { padding: .85rem .2rem; border-bottom: 1px solid var(--line); }
  .nav a[aria-current="page"] { border-bottom-color: var(--signal); }
  .nav-cta { display: inline-flex; justify-content: center; margin-top: 1rem; border-bottom: 0; }
}

/* --- sections ------------------------------------------------------------- */

main { position: relative; z-index: 2; }

.band { padding: clamp(3.6rem, 9vh, 7rem) 0; }
.band + .band { border-top: 1px solid var(--line); }
.band-head { max-width: 74ch; margin-bottom: clamp(1.8rem, 4vh, 3rem); }
.band-head h2 { font-size: var(--step-3); margin: .5rem 0 .8rem; }

/* Reveal on scroll. Kept small — 14px and a fade — because a big entrance on
   every block reads as a slideshow. */
[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
[data-reveal].in { opacity: 1; transform: none; }

/* --- footer --------------------------------------------------------------- */

.foot { border-top: 1px solid var(--line); padding: clamp(2.6rem, 6vh, 4rem) 0 1.6rem; background: var(--base); }
.foot-grid {
  display: grid;
  gap: 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 760px)  { .foot-grid { grid-template-columns: 1.4fr 1fr 1fr; } }
@media (min-width: 1040px) { .foot-grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; } }

.foot-mark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-2);
  letter-spacing: -.03em;
  margin-bottom: .6rem;
}
.foot-mark b { color: var(--signal); }
.foot-about p { color: var(--text-3); font-size: var(--step--1); max-width: 44ch; }
.foot h2 {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: .9rem;
}
.foot ul { list-style: none; padding: 0; display: grid; gap: .55rem; }
.foot li { font-size: var(--step--1); color: var(--text-2); }
/* Each of these is the only thing in its row, so WCAG 2.5.8's inline-link exception
   does not apply to them and they have to carry a 24px target of their own. At the
   footer's font size the text box is 18px, so the padding is doing real work here
   rather than being decorative. */
.foot a {
  display: inline-block;
  min-height: 24px;
  padding-block: 3px;
  color: var(--text-2);
  text-decoration: none;
}
.foot a:hover { color: var(--signal); text-decoration: underline; text-underline-offset: 3px; }
.foot-svc li { color: var(--text-3); }

.foot-bar {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem 1.4rem;
  justify-content: space-between;
  margin-top: 2.6rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line);
  font-size: var(--step--1);
  color: var(--text-3);
}
.foot-note { max-width: 60ch; }

/* --- reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .glow { display: none; }
  .loader-arc { animation: none; }
  .btn:hover { transform: none; }
}
