/* =============================================================
   walkdeeznust — landing and legal site
   Cherry Ink · Field Edition · v2.0
   -------------------------------------------------------------
   Every token below is copied from the Flutter app, not invented
   here. When the app changes, change it here too:

     lib/globals/constants/app_colors.dart      → §1.1
     lib/globals/constants/app_typography.dart  → §1.2
     lib/globals/constants/app_spacing.dart     → §1.3, §1.4

   Flutter expresses tracking in dp at a fixed size; CSS expresses
   it in em so it survives responsive type. Each value below is
   the app's dp figure divided by the app's size for that step —
   the arithmetic is shown inline so it can be re-checked.

   The three load-bearing rules:
     · One type weight. Size, colour, tracking and case do the work.
     · Cherry marks a live human. On a website there is no live
       human, so cherry appears exactly twice: the presence dot on
       the brand mark's shoulder — where the mark is the person —
       and the wordmark's full stop. Nothing else.
     · Rectangles are the app; circles are people.
   ============================================================= */

/* ---------- 0. FONT ---------- */
/* The app's own face, self-hosted. No third-party font CDN, so the
   CSP in _headers can stay locked to 'self'. */
@font-face {
  font-family: "Fixel Text";
  src: url("/site/fonts/FixelText-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- 1. TOKENS ---------- */
:root {
  /* -- 1.1 Palette · app_colors.dart --------------------------------- */
  --ink:             #1D1B1A;  /* primary   · text, rules, chrome       */
  --paper:           #FAF9F7;  /* background· the ground                */
  --paper-ink:       #FAF9F7;  /* onPrimary · text on ink fills         */
  --surface:         #FFFFFF;  /* surface   · sheets and cards          */
  --bone:            #F0EDE8;  /* surfaceVariant · chips, input fills   */
  --hairline:        #E6E2DC;  /* 1px rules                             */
  --hairline-strong: #CFC9C1;  /* rules on white; input borders         */
  --smoke:           #6E6862;  /* textSecondary · labels, metadata      */
  --ash:             #9A948D;  /* textTertiary · disabled/placeholder   */
  --brick:           #8C2F23;  /* error · the whole safety register     */
  --error-surface:   #F7EDEA;  /* fill behind safety copy               */
  --teal:            #15535C;  /* the `contrast` posture, nothing else  */
  --cherry:          #B3172D;  /* live humans only — see the note above */

  /* Not from app_colors.dart. Added for the waitlist counter only, where
     green is the one convention that reads as "live right now". Used by
     .live__dot and nothing else — do not spread it. */
  --live:            #1B8A4B;

  /* -- 1.2 Type · app_typography.dart -------------------------------- */
  --font: "Fixel Text", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Sizes. The app is a 375–430pt phone; a document site legitimately
     has more width, so display steps interpolate between two real
     tokens rather than inventing a size between them. */
  --t-display2:  clamp(2.5rem, 1.6rem + 3.6vw, 3.25rem); /* 40 → 52 */
  --t-display3:  clamp(2rem,   1.5rem + 2vw,   2.5rem);  /* 32 → 40 */
  --t-title:     1.75rem;    /* 28 */
  --t-headline:  1.375rem;   /* 22 */
  --t-body:      1.0625rem;  /* 17 */
  --t-body-sm:   0.9375rem;  /* 15 */
  --t-caption:   0.8125rem;  /* 13 */
  --t-label:     0.6875rem;  /* 11 */
  --t-micro:     0.625rem;   /* 10 */

  /* Tracking, as dp ÷ size from the app's TextStyles. */
  --ls-display2: -0.0385em;  /* −2.0 / 52 */
  --ls-display3: -0.035em;   /* −1.4 / 40 */
  --ls-title:    -0.0286em;  /* −0.8 / 28 */
  --ls-headline: -0.0182em;  /* −0.4 / 22 */
  --ls-body:      0;
  --ls-caption:   0.0077em;  /*  +0.1 / 13 */
  --ls-label:     0.1455em;  /*  +1.6 / 11 */
  --ls-micro:     0.22em;    /*  +2.2 / 10 */
  --ls-quiet:    -0.015em;   /* the no-italic fallback, §4.3           */

  /* Line heights, verbatim. */
  --lh-display:  1.00;
  --lh-title:    1.10;
  --lh-headline: 1.20;
  --lh-body:     1.45;
  --lh-caption:  1.35;
  --lh-flat:     1.00;

  /* -- 1.3 Space · AppSpacing ---------------------------------------- */
  --s0:  0px;   --s1:  2px;   --s2:  4px;   --s3:  8px;
  --s4:  12px;  --s5:  16px;  --s6:  20px;  --s7:  24px;
  --s8:  32px;  --s9:  44px;  --s10: 64px;

  /* -- 1.4 Radius · AppRadius ---------------------------------------- */
  --r0: 0;      /* everything                                          */
  --r1: 2px;    /* buttons, chips, badges — letterpress softening       */

  /* -- 1.4b Motion · app_motion.dart --------------------------------- */
  --curve-standard: cubic-bezier(0.20, 0, 0, 1);  /* AppCurves.standard   */
  --d-instant: 90ms;                              /* AppDuration.instant  */
  --d-quick:   160ms;
  --d-base:    240ms;

  /* -- 1.5 Layout ---------------------------------------------------- */
  --gutter: var(--s6);          /* the app's 375pt gutter              */
  --container: 1100px;
  --measure: 34em;              /* AppType.bodyMaxMeasureEm            */
  --tap: 44px;                  /* AppSpacing.minTapTarget             */
  --control: 52px;              /* AppSpacing.controlHeight            */
}

/* AppSpacing.gutterOf: s7 once the viewport passes 400. */
@media (min-width: 400px) {
  :root { --gutter: var(--s7); }
}

/* ---------- 2. RESET ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

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

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-weight: 400;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Tabular figures everywhere, as the app enables globally. */
  font-variant-numeric: tabular-nums;
}

/* One weight, one style. Fixel Text ships a 400 and nothing else, so the
   browser must never be allowed to fake the rest — a synthetic bold or a
   sheared oblique is exactly what the scale is built to avoid. */
strong, b, h1, h2, h3, h4, h5, h6, th, summary, optgroup { font-weight: 400; }
* { font-synthesis: none; -webkit-font-synthesis: none; }

img, svg { display: block; max-width: 100%; }
svg { flex: none; }
ul, ol { padding: 0; list-style: none; }

/* `hidden` must win. Several blocks here set `display: flex`, which otherwise
   silently defeats the attribute and renders things meant to be closed. */
[hidden] { display: none !important; }

/* Links are ink. Cherry is not a link colour — see §3.2 of the system. */
a { color: var(--ink); }
a:hover { color: var(--ink); text-decoration-color: currentColor; }

/* Focus rings are ink, never cherry. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
::selection { background: var(--ink); color: var(--paper-ink); }

.page { min-height: 100vh; min-height: 100svh; display: flex; flex-direction: column; }
.page main { flex: 1; }

/* ---------- 3. PRIMITIVES ---------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: 760px; }

.skip-link {
  position: absolute; top: var(--s3); left: var(--s3); z-index: 999;
  padding: var(--s3) var(--s4);
  background: var(--ink); color: var(--paper-ink);
  border-radius: var(--r1);
  font-size: var(--t-label); line-height: var(--lh-flat);
  letter-spacing: var(--ls-label);
  text-transform: uppercase; text-decoration: none;
  transform: translateY(-300%);
}
.skip-link:focus { transform: none; color: var(--paper-ink); }

/* The two type registers the whole system is built from. */
.label {
  font-size: var(--t-label); line-height: var(--lh-flat);
  letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--smoke);
}
.micro {
  font-size: var(--t-micro); line-height: 1.3;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase; color: var(--smoke);
}

.t-display  { font-size: var(--t-display2); line-height: 0.94; letter-spacing: var(--ls-display2); }
.t-display3 { font-size: var(--t-display3); line-height: var(--lh-display); letter-spacing: var(--ls-display3); }
.t-title    { font-size: var(--t-title);    line-height: var(--lh-title);    letter-spacing: var(--ls-title); }
.t-headline { font-size: var(--t-headline); line-height: var(--lh-headline); letter-spacing: var(--ls-headline); }

/* Fixel Text has no italic (design system §4.3). The specified fallback is
   display3 in smoke at 90% with tracking loosened — a quieter aside. Never
   a synthetic oblique. */
.t-quiet {
  font-size: calc(var(--t-display3) * 0.9);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-quiet);
  color: var(--smoke);
}

.body    { font-size: var(--t-body);    line-height: var(--lh-body); max-width: var(--measure); text-wrap: pretty; }
.body-sm { font-size: var(--t-body-sm); line-height: var(--lh-body); max-width: var(--measure); text-wrap: pretty; }
.caption { font-size: var(--t-caption); line-height: var(--lh-caption); letter-spacing: var(--ls-caption); color: var(--smoke); text-wrap: pretty; }

/* The cherry full stop — the app's one job, finished. Attaches to the
   wordmark only: never a heading, never body copy, never the letters. */
.stop { color: var(--cherry); }

/* ---------- 4. MASTHEAD (landing) ---------- */
.masthead__banner {
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-block: var(--s6) var(--s5);
  text-align: center;
}
.banner {
  display: inline-flex; align-items: center; gap: clamp(8px, 1.6vw, 20px);
  text-decoration: none; color: var(--ink);
  font-size: var(--t-display2);
  line-height: 0.94;
  letter-spacing: var(--ls-display2);
}
.banner__mark { width: clamp(32px, 5vw, 52px); height: clamp(32px, 5vw, 52px); }

.masthead__strip { border-bottom: 1px solid var(--ink); }
.masthead__place { padding-block: var(--s4); }

/* Place and news on one centred line, with a hairline tick between them.
   Wraps to two centred rows on a narrow phone rather than shrinking, and
   drops the tick when it does — a separator between stacked rows is just a
   mark floating in the middle of the strip. */
.masthead__line {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 var(--s5);
}
.masthead__sep {
  width: 1px;
  height: 11px;
  background: var(--hairline-strong);
}
/* Two rows, stacked explicitly. Wrapping cannot be left to flex-wrap here:
   both items are shrinkable, so at phone width they squeeze into one line and
   the place runs under the news dot instead of breaking. */
@media (max-width: 620px) {
  .masthead__line { flex-direction: column; gap: var(--s1); }
  .masthead__place { padding-bottom: 0; }
  .masthead__news { padding-top: 0; }
  .masthead__sep { display: none; }
}

/* The one line on the page that is ink rather than smoke at micro size, and
   the only place the live dot appears outside the panel. Both are earned:
   this is the announcement, and it is a link — the strip is now a door as
   well as a location. */
.masthead__news {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  padding-block: var(--s4);
  color: var(--ink);
  text-decoration: none;
}
.masthead__news:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- 5. STATEMENT (landing) ---------- */

/* ---- 5b. HERO ----
   The statement and the ask, standing on the campus. The paper is no longer
   blank behind them: NUST H-12 is drawn underneath as a dot matrix, at an
   opacity where it reads as ground rather than as a picture.

   It is one screen on purpose. Everything the page has to say is either here
   or in the single band below it, so nothing that matters needs scrolling to. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(28px, 5vw, 56px) clamp(32px, 5vw, 64px);
  /* Holds the band to roughly one screen so the map has a shape to sit in.
     It grows past this whenever the content needs the room. */
  min-height: clamp(540px, 74vh, 720px);
  display: grid;
  align-content: center;
}

/* Two layers over the same frame: the drawing, which sits under the paper
   wash, and the place names, which sit over it. Both are laid out by the same
   rules below so a label never drifts off the place it names. */
.hero__map {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.hero__map { z-index: -1; }

/* Square, and exactly the size of the drawing, so a label given as a
   percentage lands where it belongs. */
.hero__mapframe {
  position: relative;
  width: min(94vw, 88vh, 900px);
  aspect-ratio: 1;
}

/* Once there is room, the map moves off centre and stands on the left of the
   band. Centred, the panel sat on the middle of the campus and hid the part
   worth looking at; over here the whole drawing reads and the panel only
   crosses its right edge. The column of type does not move. */
@media (min-width: 900px) {
  .hero__map { place-items: center start; padding-left: 0; }
  /* Bigger than the band on purpose: the north and west corners run off the
     top and left edges, so the campus reads as ground the page is standing on
     rather than as a picture sitting inside it. */
  .hero__mapframe {
    width: min(88vw, 126vh, 1240px);
    transform: translate(-9%, -11%);
  }
  /* With the map off to the left, the wash pulls in to just the column of
     type — out there the drawing is meant to be read, not quietened. */
  .hero__inner::before {
    background: radial-gradient(46% 74% at 50% 52%,
                rgba(250, 249, 247, 0.95) 0%,
                rgba(250, 249, 247, 0.84) 60%,
                rgba(250, 249, 247, 0) 100%);
  }
}



/* Sized off the viewport so the campus always fills the band, and faded top
   and bottom so it never fights the masthead rule or the band's own edge. */
.hero__map img {
  /* Square, so a single width sizes it: never wider than the viewport, never
     taller than the band it sits in — a percentage height would resolve
     against a grid area that is itself sized by this image. */
  width: 100%;
  height: auto;
  /* §2 caps every image at its box; the frame is what sizes this one, and on
     a phone the frame is allowed to run wider than the band. */
  max-width: none;
  opacity: 0.62;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 74%, transparent 97%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 74%, transparent 97%);
}

/* One slow settle as the page opens — the map arriving, not chrome animating.
   Nothing else in the hero moves, so the headline never shifts under the eye
   and stays a clean LCP. */
.js .hero__map { animation: map-settle 900ms var(--curve-standard) both; }
@keyframes map-settle {
  from { opacity: 0; transform: scale(1.025); }
  to   { opacity: 1; transform: none; }
}

/* On a phone the campus is texture rather than a shape: it runs past both
   edges so the band still feels like ground, instead of a small stamp lost
   behind the panel. */
@media (max-width: 700px) {
  /* Scaled well past the viewport on purpose: at phone width the whole
     campus shrinks each dot below a pixel and the texture disappears, so the
     band shows the middle of the map at a size where the grid still reads. */
  .hero__mapframe { width: 190vw; }
  .hero__map img { opacity: 0.6; }
  .hero { min-height: 0; }
  /* The panel is opaque and takes most of the width here, so the wash only
     has to cover the headline. Below it the dots are free to show. */
  .hero__inner::before {
    background: radial-gradient(86% 26% at 50% 14%,
                rgba(250, 249, 247, 0.94) 0%,
                rgba(250, 249, 247, 0) 100%);
  }
}

.hero__inner { position: relative; }

/* A wash of paper under the column of type. The dots are ground, and ground
   does not get to compete with the sentence standing on it — this keeps the
   statement and the three queue facts legible without lightening the map at
   the edges, where it is doing its job. */
.hero__inner::before {
  content: "";
  position: absolute;
  inset: -6% -4%;
  z-index: -1;
  background: radial-gradient(62% 64% at 50% 52%,
              rgba(250, 249, 247, 0.90) 0%,
              rgba(250, 249, 247, 0.72) 58%,
              rgba(250, 249, 247, 0) 100%);
  pointer-events: none;
}

/* One centred column. §4.2 reserves centred display type for splash and the
   blocked screen; a landing page is the same situation — a single statement
   with nothing beside it to align against. */
.lede { max-width: 720px; margin-inline: auto; text-align: center; }
.lede__head { max-width: 18ch; margin-inline: auto; text-wrap: balance; }
.lede__mission {
  margin-top: var(--s6);
  font-size: var(--t-headline);
  line-height: var(--lh-headline);
  letter-spacing: var(--ls-headline);
  color: var(--smoke);
  text-wrap: pretty;
}
.lede__scope {
  margin-top: var(--s5);
  font-size: var(--t-label);
  line-height: var(--lh-flat);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ash);
}

/* The four written steps that used to live here are gone: §5c tells the same
   story in the app's own screens, in one band instead of four stacked rows. */

/* ---------- 6. BUTTONS ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s4) var(--s6);
  background: var(--ink); color: var(--paper-ink);
  border: 1px solid var(--ink);
  border-radius: var(--r1);
  font-size: var(--t-body-sm); line-height: var(--lh-flat);
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background 120ms linear, color 120ms linear;
}
.btn:hover { background: var(--paper); color: var(--ink); }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper-ink); }

/* ---------- 7. HEADER / FOOTER (documents) ---------- */
.site-header { border-bottom: 2px solid var(--ink); }
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s6); min-height: 62px; padding-block: var(--s4);
}
.brand {
  display: inline-flex; align-items: center; gap: var(--s4);
  text-decoration: none; color: var(--ink);
  font-size: var(--t-headline); letter-spacing: var(--ls-headline);
}
.brand__mark { width: 28px; height: 28px; }
.site-nav { display: flex; align-items: center; gap: var(--s6); }

.site-footer { border-top: 1px solid var(--ink); padding-block: var(--s6); margin-top: var(--s8); }
.site-footer__row {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--s4) var(--s6);
}
.site-footer__links { display: flex; flex-wrap: wrap; gap: var(--s5) var(--s6); }
.site-footer__links a,
.site-footer__row a { text-decoration: none; }
.site-footer__links a:hover,
.site-footer__row a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- 8. DOCUMENTS ---------- */
.doc { padding-block: var(--s8) var(--s10); }
.doc__header { margin-bottom: var(--s7); padding-bottom: var(--s5); border-bottom: 1px solid var(--ink); }
.doc__title { margin-block: var(--s4) var(--s4); }
.doc__meta {
  font-size: var(--t-micro); letter-spacing: var(--ls-micro);
  text-transform: uppercase; color: var(--smoke); line-height: 1.6;
}

.doc__body { max-width: 68ch; }
.doc__body h2 {
  font-size: var(--t-headline); letter-spacing: var(--ls-headline); line-height: var(--lh-headline);
  margin-block: var(--s9) var(--s5);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--ink);
  scroll-margin-top: var(--s7);
}
.doc__body h3 {
  font-size: var(--t-body); line-height: var(--lh-body);
  margin-block: var(--s7) var(--s4);
}
.doc__body p { margin-bottom: var(--s5); font-size: var(--t-body-sm); line-height: var(--lh-body); }
.doc__body ul { margin-bottom: var(--s7); }
.doc__body ul li {
  position: relative; padding-left: var(--s7); margin-bottom: var(--s4);
  font-size: var(--t-body-sm); line-height: var(--lh-body);
}
/* A printed dash, not a bullet — the system has no filled decorations. */
.doc__body ul li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 10px; height: 1px; background: var(--ink);
}
.doc__body a { text-decoration-color: var(--hairline-strong); text-underline-offset: 3px; }
.doc__body a:hover { text-decoration-color: var(--ink); }

/* There is no bold in this system. Inline emphasis is a hairline rule under
   the phrase — a printed device, no weight, no colour outside the palette. */
.doc__body strong {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--hairline-strong);
  text-underline-offset: 3px;
}

.toc {
  border-top: 1px solid var(--ink); border-bottom: 1px solid var(--ink);
  padding-block: var(--s5); margin-bottom: var(--s7);
}
/* The header already closes with a rule; don't strike a second one 20px later. */
.doc__header + .toc { border-top: 0; margin-top: calc(-1 * var(--s4)); }
.toc h2 {
  font-size: var(--t-micro) !important; letter-spacing: var(--ls-micro);
  text-transform: uppercase; color: var(--smoke);
  border: 0 !important; padding: 0 !important; margin: 0 0 var(--s4) !important;
  line-height: var(--lh-flat) !important;
}
.toc ol { counter-reset: toc; columns: 2; column-gap: var(--s9); }
@media (max-width: 620px) { .toc ol { columns: 1; } }
.toc li { counter-increment: toc; margin-bottom: var(--s3); break-inside: avoid; }
.toc a { font-size: var(--t-body-sm); text-decoration: none; }
.toc a::before { content: counter(toc) " · "; color: var(--smoke); }
.toc a:hover { text-decoration: underline; text-underline-offset: 3px; }

.callout {
  border-left: 2px solid var(--ink);
  background: var(--bone);
  padding: var(--s5);
  margin-block: var(--s7);
}
.callout p:last-child { margin-bottom: 0; }
/* The safety register: brick on errorSurface, plain sentences, no wit. */
.callout--safety { border-left-color: var(--brick); background: var(--error-surface); }
.callout--safety strong { text-decoration-color: var(--brick); }

/* Values still awaiting confirmation. Bone, never cherry — a draft is not
   a person. */
.fill {
  padding: 0.1em 0.3em; margin-inline: -0.1em;
  background: var(--bone);
  box-shadow: inset 0 -1px 0 var(--smoke);
}

.data-table { width: 100%; border-collapse: collapse; margin-block: var(--s5) var(--s7); }
.data-table th, .data-table td {
  text-align: left; padding: var(--s4);
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
  font-size: var(--t-caption); line-height: var(--lh-body);
}
.data-table thead th {
  font-size: var(--t-micro); letter-spacing: var(--ls-micro);
  text-transform: uppercase; color: var(--smoke);
  border-bottom-color: var(--ink);
  line-height: var(--lh-flat);
}
.data-table td strong { text-decoration: none; }
.table-scroll { overflow-x: auto; }

/* ---------- 8b. BETA PANEL ----------
   What stood here was the waitlist: a panel that collected a NUST address
   and told you your place in a queue. The app is open now, so the panel has
   one job instead — say the beta is live and hand you the door.

   The panel shell, the status bar and the meta row are the waitlist's, kept
   deliberately: the fold's composition was drawn around a 560px card sitting
   on the campus, and nothing about that changed. What went with the queue is
   everything that asked — the form, the department chips, the input
   treatments, the success state and the place-in-line. The waitlist Function
   and its D1 table are untouched in functions/, so the ask can come back.

   `.live` below is shared: it was the signup counter's dot, and it is now
   the beta's. Same meaning either way — something is happening right now.
   ---------------------------------------------------------------- */

/* A panel, not a paragraph. §1.1 calls --surface "sheets and cards", and a
   1dp ink edge is how the app draws anything you act inside. Opening the app
   should feel like stepping into something, not reading more prose. */
.beta {
  max-width: 560px;
  margin-inline: auto;
  margin-top: clamp(28px, 4vw, 48px);
  padding: var(--s7) var(--s7) var(--s8);
  border: 1px solid var(--ink);
  border-radius: var(--r1);
  background: var(--surface);
  text-align: center;
}
@media (max-width: 520px) {
  .beta { padding: var(--s6) var(--s5) var(--s7); }
}

/* The panel arrives rather than being already there. This is the waitlist
   success state's animation, verbatim — opacity plus 8dp on the standard
   curve — which §7 calls the only motion chrome is allowed. It ran when you
   joined the queue; it runs now when the page tells you the queue is over.

   Gated on .js for the same reason the four flow steps are: with the class
   absent the panel is simply there, never a blank card left by an animation
   that fill-mode held at opacity 0. It is also the only thing on the page
   that moves besides the map settling in behind it, and it is deliberately
   slower to start than that settle, so the two read as one arrival rather
   than two things twitching. */
.js .beta { animation: panel-arrive var(--d-base) var(--curve-standard) 120ms both; }
@keyframes panel-arrive {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .js .beta { animation: none; }
}

/* Status bar across the top of the panel: state on the left, the live dot on
   the right. Reads like a ticket window that is open. */
.beta__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding-bottom: var(--s5);
  margin-bottom: var(--s6);
  border-bottom: 1px solid var(--hairline);
  text-align: left;
}
.beta__tag { color: var(--ink); }

/* Display3, not title. The queue's head was a label on a form — the form
   below it was the event. Here the sentence IS the event, so it takes the
   next real step up the scale (§1.2, 32 → 40) and lands one step under the
   page's h1: loud enough to be the thing you read second, never competing
   with the statement it sits beneath. */
.beta__head {
  font-size: var(--t-display3);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display3);
  color: var(--ink);
  text-wrap: balance;
}

/* Body, not body-sm. The head above it grew, and a 15px lede under a 40px
   sentence reads as fine print — the two steps have to stay in proportion. */
.beta__lede {
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--smoke);
  max-width: 40ch;
  margin-inline: auto;
  margin-top: var(--s5);
  text-wrap: pretty;
}

/* ---- the door · §7.8 primary button ----
   An anchor, not a button: it navigates. It takes the submit control's
   geometry and label so the panel's one action still reads as the app's
   primary control, and it goes full width because it is the only thing to do
   on the page — nothing is sitting beside it to balance against.

   Hover inverts, which is §6's rule for every other button on the site: ink
   fill becomes paper, the label becomes ink, and the 1dp ink border — not
   transparent, so it survives the swap — holds the shape through it. The
   waitlist submit it replaced darkened to #302D2B instead, an off-token
   near-black over near-black that was invisible in practice. */
.beta__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* s10, not the app's 52pt control height. In the app a button is one of
     many; here it is the only thing on the page to do, and height is how it
     says so without a colour, a gradient or a shadow that §7.8 does not
     have. min-height, so a wrapped label cannot crop itself. */
  min-height: var(--s10);
  margin-top: var(--s8);
  border-radius: var(--r1);
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper-ink);
  /* One step up from AppType.buttonLabel's 12, at the same +1.4dp of
     tracking the label carries — 1.4 / 13 rather than 1.4 / 12. The app's
     figure is set for a 375pt phone; this control is 512px wide. */
  font-size: var(--t-caption);      /* 13 */
  letter-spacing: 0.1077em;         /* +1.4 / 13 */
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--d-instant) var(--curve-standard),
              color var(--d-instant) var(--curve-standard);
}
.beta__cta:hover {
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
}
.beta__cta:active { transform: scale(0.985); }
.beta__cta:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.beta__note {
  font-size: var(--t-caption);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-caption);
  color: var(--smoke);
  margin-top: var(--s5);
  text-wrap: pretty;
}

/* Three facts about the beta, set as a printed row beneath the panel. No
   rule of its own — the panel edge above already provides the separation. */
.beta__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s4) var(--s7);
  margin-top: var(--s6);
  font-size: var(--t-micro);
  line-height: var(--lh-flat);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--smoke);
}
.beta__meta strong { color: var(--ink); }
/* Three items of two words each is wider than the queue's three numbers, and
   at phone width the outer two reach the container padding. Tighten the gap
   rather than shorten the words. */
@media (max-width: 520px) {
  .beta__meta { gap: var(--s3) var(--s5); }
}


/* ---- live indicator ----
   NOTE: --live is the one colour on this site that is NOT in the app palette
   (§1.1). It was added deliberately for this dot, where green carries the
   universal "live right now" meaning that ink or smoke cannot. It is used
   here and nowhere else. Cherry was not used because §3.2 reserves it for a
   single live human, and this is a service being up, not a person. */
.live {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s4);
  border-radius: var(--r1);
  background: color-mix(in srgb, var(--live) 10%, transparent);
  font-size: var(--t-caption);
  line-height: var(--lh-flat);
  letter-spacing: var(--ls-caption);
  color: var(--smoke);
  white-space: nowrap;
}
.live__text strong { color: var(--ink); font-variant-numeric: tabular-nums; }

.live__dot {
  position: relative;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--live);
  flex: none;
}
/* The ring, not the dot, does the pulsing — a dot that changes size nudges
   the text beside it. */
.live__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--live);
  animation: live-ping 2s var(--curve-standard) infinite;
}
@keyframes live-ping {
  0%   { transform: scale(1);   opacity: 0.62; }
  70%  { transform: scale(2.6); opacity: 0; }
  100% { transform: scale(2.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .live__dot::after { animation: none; }
}



/* ---------- 8c. FLOW (landing) ----------
   The four screens as one band, read left to right: question, map, invite,
   walk. It is a rail rather than a stack because the whole point of the
   section is that it costs one screen — a visitor who never scrolls past the
   fold still sees the product, and a visitor who scrolls once sees all of it.

   The images are the handset shots exactly as exported: device frame included,
   transparent outside it, so they sit straight on the paper with no border,
   plinth or shadow of ours. The only curve on the page is the phone's own. */
.flow {
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  background: var(--surface);
  padding-block: clamp(24px, 3.4vw, 40px) clamp(28px, 3.8vw, 44px);
  /* Shared by the grid gap and by the connector rules drawn into it, so the
     two can never drift apart. */
  --flow-gap: clamp(16px, 2.6vw, 40px);
}

.flow__label {
  color: var(--ink);
  margin-bottom: var(--s6);
  font-size: var(--t-headline);
  line-height: var(--lh-headline);
  letter-spacing: var(--ls-headline);
  text-transform: none;
}

.flow__rail {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--flow-gap);
  align-items: start;
}

.flow__step { position: relative; }

/* The connector: a hairline across the gutter with an ink tick at its head,
   struck at the vertical middle of the handset. A printed arrow, not chrome.
   It draws itself from the step it leaves, once that step has landed. */
@media (min-width: 860px) {
  .flow__step:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 28%;
    left: 100%;
    width: var(--flow-gap);
    height: 1px;
    background:
      linear-gradient(to right, var(--hairline-strong), var(--hairline-strong)) left center / 100% 1px no-repeat,
      linear-gradient(to right, var(--ink), var(--ink)) right center / 5px 3px no-repeat;
    transform: scaleX(0);
    transform-origin: left center;
  }
  .js .flow__step.is-in::after {
    transform: none;
    transition: transform 420ms var(--curve-standard) 320ms;
  }
}

.flow__shot img { width: 100%; height: auto; }

.flow__n {
  margin-top: var(--s5);
  font-size: var(--t-micro); line-height: var(--lh-flat);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase; color: var(--smoke);
}
.flow__t {
  margin-top: var(--s4);
  font-size: var(--t-body);
  line-height: var(--lh-headline);
  letter-spacing: var(--ls-headline);
  text-wrap: balance;
}
.flow__d {
  margin-top: var(--s3);
  font-size: var(--t-caption);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-caption);
  color: var(--smoke);
  text-wrap: pretty;
}

/* Under 860px four columns squeeze the handsets to thumbnails, so the rail
   stacks. It reads as one vertical scroll — a sideways scroller nested inside
   a page that scrolls down is a trap: half of it never gets found.

   The cost of stacking is height, so each step turns on its side: a small
   handset beside its caption rather than above it. That keeps the whole band
   to roughly one screen instead of four. */
@media (max-width: 859px) {
  .flow__rail { grid-template-columns: none; gap: 0; }

  .flow__step {
    display: grid;
    grid-template-columns: clamp(96px, 26vw, 140px) 1fr;
    column-gap: var(--s6);
    align-items: center;
    padding-block: var(--s6);
    border-top: 1px solid var(--hairline);
  }
  .flow__step:first-child { border-top: 0; padding-top: var(--s3); }
  .flow__n { margin-top: 0; }
}

/* ---------- 8d. REVEAL ----------
   §10 says the interface is printed and only people move. A step arriving is
   a sheet being laid down, not chrome animating: opacity plus a short rise on
   the standard curve — the same gesture the success panel already uses — once,
   never reversed.

   The band deals itself out left to right, one step every 90ms, so the row
   reads as a sequence rather than four things appearing at once. The hidden
   state is scoped to .js, so with scripting off, or if the observer never
   runs, the page is simply visible. */
.js .reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 600ms var(--curve-standard),
              transform 600ms var(--curve-standard);
}
.js .reveal.is-in { opacity: 1; transform: none; }

.js .flow__step:nth-child(2) { transition-delay: 90ms; }
.js .flow__step:nth-child(3) { transition-delay: 180ms; }
.js .flow__step:nth-child(4) { transition-delay: 270ms; }

/* Sideways, the deal is a swipe: the carousel is read left to right too, but
   each card is its own screen, so it arrives on its own without the stagger. */
@media (max-width: 859px) {
  .js .flow__step { transition-delay: 0s; transform: translateY(10px); }
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; }
}

/* ---------- 9. 404 ---------- */
.notfound {
  min-height: 58vh;
  display: grid; place-content: center; justify-items: center;
  text-align: center; gap: var(--s5);
  padding-block: var(--s10);
}

/* ---------- 10. MOTION ---------- */
/* The interface is printed. Only people move — and there is no live person
   on a website, so nothing here animates but a hover fill. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- 11. PRINT ---------- */
@media print {
  .site-header, .site-footer, .toc, .skip-link { display: none !important; }
  body { background: #fff; }
  .doc__body { max-width: none; }
  a::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #555; }
}
