/* V1 — design tokens and components.
 *
 * The tokens are the V1 homepage's (hand-off of 2026-09-10): Ink, Carbon
 * and Graphite are the dark production-interface foundation, Paper and
 * White the light surfaces, Steel is metadata and progress, Signal is
 * the ready state and the primary action, Approve green is the approved
 * state only, Attention red is attention and destructive states only.
 * Those four are semantic and are never interchanged. Type is Anton for
 * display, IBM Plex Mono for metadata, Instrument Sans for body. Corners
 * are square throughout.
 */

:root {
  --ink: #14171A;              /* the base */
  --carbon: #1C2024;           /* a raised surface on Ink */
  --graphite: #2B3036;         /* a rule or border on Ink */
  --paper: #E8E8E3;            /* the light surface; body text on Ink */
  --white: #FFFFFF;            /* a raised surface on Paper */
  --paper-2: #C9C9C2;          /* a rule on Paper: the system's `border-on-light`
                                  (1.35:1 on Paper - decorative, never a control's edge) */
  --steel: #8D949A;            /* metadata and progress, on Ink and Carbon */
  --signal: #DFFF00;           /* ready, and the primary action */
  --signal-pressed: #C7E600;   /* the primary action, pressed (`action-primary-bg-active`) */
  --signal-hover: #EBFF33;     /* the primary action, hovered (`action-primary-bg-hover`) */
  --approve: #22C55E;          /* approved, only */
  --attention: #FF4A3D;        /* attention and destructive, only */

  /* The accents as text on Paper. Signal on Paper is 1.1:1, Attention
     2.7:1 and Approve 1.9:1 (WCAG AA wants 4.5:1 for text; even the
     3:1 large-text floor fails Signal), so on a light surface a label,
     a chip's text or a highlight word takes a deeper shade of the same
     hue. On Ink the tokens themselves pass (15.8:1, 5.4:1, 7.9:1) and
     are used as they are. Steel on Paper is 2.5:1, so muted text on
     Paper is a darker grey (4.74:1); on Ink it is `--muted-on-ink`.
     The Signal shade is #5F6D00 since 2026-09-23, the owner's choice:
     4.65:1 on Paper and 5.72:1 on White - past the 4.5:1 text floor
     with little to spare. It was the system's `signal-on-light` #4B4A14
     (7.5:1), and before that #566300 (5.4:1), a shade of this repo's
     own; the system says its token is for DISPLAY type only, and this
     portal also uses it for small markers on Paper (an eyebrow, the
     current step, a bullet) because the system names no other Signal
     for a light surface. The other two -ink shades are this portal's
     and have no counterpart in the system. */
  --signal-ink: #5F6D00;       /* 4.65:1 on Paper, 5.72:1 on White */
  --attention-ink: #B82A1A;    /* 5.1:1 on Paper */
  --approve-ink: #13753A;      /* 4.7:1 on Paper */
  /* Secondary text on Paper: the system's `v1-muted-on-paper`, 4.74:1
     on Paper and 5.83:1 on White. Until 2026-09-22 this was #5B6268
     (5.04:1), a shade darker than the system on the argument that it
     differed toward contrast; both pass AA, the axe audit holds the
     line, and one grey in two places is the drift this repo keeps
     paying for. .on-ink sets `--muted-on-ink`. */
  --muted: #5F666C;
  /* The design system's `v1-muted-on-ink`: 7.9:1 on Ink. Its
     `action-secondary-border` - the edge of a secondary control on a
     dark ground, "the light outline of MENU" in its own words. */
  --muted-on-ink: #A6ADB3;

  /* Defaults for anything outside a surface: the Paper set. */
  --body: var(--ink); --card: var(--white); --card-border: transparent;
  --lit: var(--signal-ink); --hot: var(--attention-ink); --ok: var(--approve-ink);
  --focus: var(--ink);
  --input-bg: var(--white); --input-fg: var(--ink); --input-border: var(--muted);
  --check-edge: var(--ink); --chevron: var(--chevron-ink);
  /* The select's chevron, drawn as the icon set draws one (24 grid,
     1.75 stroke, square caps) in the colour of the field's text. A data:
     URI cannot read a custom property, so there is one per text colour
     and each surface names its own; the CSP allows `img-src data:`. */
  --chevron-ink: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2314171A' stroke-width='1.75' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  --chevron-paper: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E8E8E3' stroke-width='1.75' stroke-linecap='square'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");

  --font-display: 'Anton', 'Druk Condensed', 'Trade Gothic Condensed Heavy', Impact, 'Arial Narrow Bold', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --font-sans: 'Instrument Sans', Inter, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;

  --wrap: 1180px;
  /* The system's `layout-max` and `section-pad-y`. The marketing page
     takes both (its `--wrap` is `--layout-max`); the portal keeps 1180,
     because the system lists product UI screens and the layout spacing
     as still open, and a form or a table is not easier to use wider. */
  --layout-max: 1440px;
  --section-pad-y: 96px;
  /* `layout-pad-sm` 20 to `layout-pad` 48. */
  --gutter: clamp(20px, 4vw, 48px);
  /* The system's `ease-standard`. The old curve's .7 control point is
     an ease-out with a soft landing; the system has "no elastic or
     bounce curves" and moves fast, measured and stepped. */
  --ease: cubic-bezier(.2, 0, 0, 1);
  /* A hairline: `border-default` on Ink, `border-on-light` on Paper. */
  --hairline: var(--paper-2);
}

/* Mobile first (docs/mobile-guidelines.md): base styles are for a
   ~360px phone, and every media query below is `min-width`, adding
   columns and rows as the screen grows. Breakpoints: 640 (sm), 768
   (md), 1024 (lg). */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  overflow-wrap: anywhere;      /* a filename, a note, a URL: never wider than the screen */
}
img, svg, video { max-width: 100%; display: block; }
[hidden] { display: none !important; }
a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4, p { margin: 0; }
/* Touch is the primary input: no tap delay, a visible focus ring for
   the keyboard, a pressed state that is not hover. */
a, button, input, select, textarea, summary, label { touch-action: manipulation; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* Surfaces ------------------------------------------------------------- */
/* A surface sets the tokens its descendants read - muted text, body
   text, a card's fill, the focus ring, which accent shade is text-safe -
   as custom properties, so the *nearest* surface wins. Descendant rules
   of the form `.on-ink .card` do not nest: a Paper section inside an Ink
   body matched both and the later rule painted the request form's card
   Ink with Ink labels on it.

   The top bar is Ink on every page, so it is an Ink surface on every
   page - the admin pages too, whose body is Paper. It used to read the
   body's tokens, which on Paper made the nav's keyboard focus ring Ink
   on the Ink bar: invisible (measured 2026-09-24 on /admin/projects,
   outline #14171A on #14171A). And a page that chooses its own surface -
   the admin new-project form is on Ink - keeps the bar every other page
   in its area has. */
.on-ink, .topbar {
  background: var(--ink); color: var(--paper);
  /* What the browser draws for itself follows the surface: the date
     field's calendar icon (Ink on Carbon without this - a control you
     cannot see), a platform select list where `base-select` is missing,
     the file button, scrollbars. */
  color-scheme: dark;
  /* Secondary text on Ink is the system's `text-muted-on-dark`, 7.9:1.
     It was Steel, which the system keeps for `text-metadata` - mono keys
     and captions - and which is 4.3:1 on Graphite. Steel is still used
     by name where a thing IS metadata or progress. */
  --muted: var(--muted-on-ink); --body: var(--paper); --focus: var(--signal);
  --ghost-edge: var(--muted-on-ink); --ghost-edge-hover: var(--paper); --ghost-fill: var(--carbon);
  --card: var(--carbon); --card-border: var(--graphite); --hairline: var(--graphite);
  /* A field's edge is the only thing that says it is a field, so it
     meets 3:1: the system's `field-border` is Steel, 5.3:1 on Carbon.
     It was Graphite, 1.2:1 - which the system names as the mistake
     ("never rely on one as the only boundary of a control"). */
  --input-bg: var(--carbon); --input-fg: var(--paper); --input-border: var(--steel);
  --check-edge: var(--signal); --chevron: var(--chevron-paper);
  --lit: var(--signal); --hot: var(--attention); --ok: var(--approve);
  --chip-lit-bg: transparent; --chip-hot-bg: transparent; --chip-ok-bg: transparent;
  --chip-lit-fg: var(--signal); --chip-hot-fg: var(--attention); --chip-ok-fg: var(--approve);
}
.on-paper {
  background: var(--paper); color: var(--ink); color-scheme: light;
  /* The focus ring on Paper is Ink: "on chartreuse or Paper grounds ...
     the ring turns Ink" (Signal on Paper is 1.1:1; its olive shade is
     for display type). */
  --muted: #5F666C; --body: var(--ink); --focus: var(--ink);
  --ghost-edge: var(--ink); --ghost-edge-hover: var(--ink); --ghost-fill: var(--white);
  --card: var(--white); --card-border: transparent; --hairline: var(--paper-2);
  /* The light mirror: muted grey, 6.2:1 on White. It was Paper-2,
     1.5:1. The system defines no light field; this is the dark rule
     (a mid-grey that meets 3:1) applied on the other surface. */
  --input-bg: var(--white); --input-fg: var(--ink); --input-border: var(--muted);
  /* A ticked box is a Signal fill on either surface (Ink on every Signal
     fill), and Signal on White is 1.14:1 - so on Paper its edge is Ink,
     or the box would be a tick with no square round it. */
  --check-edge: var(--ink); --chevron: var(--chevron-ink);
  --lit: var(--signal-ink); --hot: var(--attention-ink); --ok: var(--approve-ink);
  /* a chip on Paper is filled with the token and lettered in Ink */
  --chip-lit-bg: var(--signal); --chip-hot-bg: var(--attention); --chip-ok-bg: var(--approve);
  --chip-lit-fg: var(--ink); --chip-hot-fg: var(--ink); --chip-ok-fg: var(--ink);
}
.muted { color: var(--muted); }
.body { color: var(--body); }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }
/* A marketing section: `section-pad-y` 96 on a wide screen, `space-8`
   64 ("tablet section padding") at the narrow end. It was 56 to 120; the
   system names nothing under 64 for a section, so a phone takes 64. */
.band { padding: clamp(64px, 9vw, var(--section-pad-y)) 0; }
.band-tight { padding: clamp(32px, 5vw, 64px) 0; }

/* Type ------------------------------------------------------------------ */
.display {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  /* The system's `leading-display` 1 and `tracking-display` -0.01em:
     the oversized silhouette is set tight, not opened up. It was .92
     leading and a positive .005em. */
  line-height: 1;
  letter-spacing: -.01em;
}
.display-xl { font-size: clamp(56px, 10vw, 136px); }
.display-lg { font-size: clamp(40px, 6.4vw, 88px); }
.display-md { font-size: clamp(30px, 4vw, 52px); }
.display-sm { font-size: clamp(24px, 2.6vw, 34px); }
/* The system's `display-4`: "screen titles in product UI, card
   headlines", and fixed where the three above it are fluid. Every page
   behind sign-in, the share pages and the error page use it for their
   title and the project page's cards for their headline, so a card no
   longer sets its heading larger than the page it sits on (they were
   `display-lg`, up to 88px, and `display-md`, up to 52px). The sign-in
   and password pages keep `display-lg`: they are the brand's front
   door, not a screen of the product. */
.display-4 { font-size: 36px; }
.display .lit { color: var(--lit); }   /* Signal on Ink; its text-safe shade on Paper (1.1:1 otherwise) */
.lit { color: var(--lit); }
.display .hot { color: var(--hot); }

.eyebrow {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);          /* an eyebrow is metadata: Steel on Ink, never Attention */
  display: inline-block;
}
.eyebrow a { text-decoration: none; }
.eyebrow::after { content: ""; display: block; width: 40px; height: 2px; background: currentColor; margin-top: 8px; }
.eyebrow.lit { color: var(--lit); }
.eyebrow.plain::after { display: none; }

.mono { font-family: var(--font-mono); }
.label {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.small { font-size: 14px; }
.lede { font-size: clamp(18px, 1.6vw, 22px); line-height: 1.45; max-width: 46ch; }
.copy { max-width: 62ch; }
.copy p + p { margin-top: 1em; }
/* Marks ----------------------------------------------------------------- */
.mark { width: 28px; height: 28px; flex: none; }
/* The wordmark is the name in the display face. Two characters cannot
   wrap; the size is fixed rather than fluid so it reads as a mark, not
   as a heading. */
.wordmark {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: .02em;
  line-height: .9;
  font-size: 28px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex: none;
  white-space: nowrap;
  min-height: 44px;
  min-width: 44px;              /* two letters are 24px wide; the target is not */
}
.wordmark .stack { display: flex; flex-direction: row; gap: .3em; }
/* The supplied logo (V1-Logo-Master.svg, 2026-09-23), drawn in the
   link's own colour so it reads on Ink and on Paper alike. */
.wordmark .logo { display: block; width: auto; }

/* Buttons ---------------------------------------------------------------- */
.btn {
  appearance: none;
  border: 1px solid transparent;
  border-radius: 0;
  padding: 12px 24px;           /* `control-pad-x` */
  min-height: 44px;             /* a tap target, on every size */
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  cursor: pointer;
  line-height: 1;
  transition: transform .12s var(--ease), background .12s, border-color .12s;
  white-space: nowrap;
}
/* Press is a 1px nudge and, on the primary, its pressed shade - the
   system's `action-primary-bg-active`. Nothing fades: the .9 opacity a
   press used to add is gone. */
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--signal); color: var(--ink); border-color: var(--signal); }
.btn-hot { background: var(--attention); color: var(--ink); border-color: var(--attention); }
/* The secondary control. On Ink its edge is `action-secondary-border`
   (#A6ADB3, 7.9:1) and lightens to Paper on hover with a Carbon fill;
   on Paper it is `border-control-on-light`, Ink. It was currentColor -
   Paper on Ink, which is the system's HOVER edge, so a hover had
   nowhere to go - and hovered to a neutral grey at 12%. */
.btn-ghost { background: transparent; border-color: var(--ghost-edge, var(--ink)); }
.btn-primary:active { background: var(--signal-pressed); }
.btn-ink { background: var(--ink); color: var(--paper); border-color: var(--ink); }
/* There is no green button. The Approve action is the card's chartreuse
   primary - the system's ProjectCard: "Keep one chartreuse primary per
   card (Approve). Revise is the secondary." - and green is what pressing
   it REVEALS: the badge below. */
@media (hover: hover) {
  .btn-primary:hover { background: var(--signal-hover); }
  .btn-ghost:hover { background: var(--ghost-fill, var(--white)); border-color: var(--ghost-edge-hover, var(--ink)); }
}
.btn-sm { padding: 8px 16px; }
.btn-lg { padding: 16px 24px; min-height: 56px; }   /* `control-h-lg`; 14px, the system's lg label */
/* Phones: a button in a form or a call-to-action row is full width;
   from `sm` it is as wide as its label. */
form .btn, .ctas .btn { width: 100%; }
@media (min-width: 640px) { form .btn, .ctas .btn { width: auto; } }
/* A link set in running text or a table cell keeps its line height and
   gets its 44px from padding the layout does not see. `.flash a` since
   2026-09-22: the admin's "watch the log" and "log" links sit in a flash
   that only renders while a run is in flight or after one failed, so no
   sweep had seen them - 109x18. `a.display` since 2026-09-29: the
   exports page's project names are headings set as links, and the page
   lists only delivered projects, which no sweep fixture had until the
   cut-down states - 208x24 at 320 wide. */
p a, li a, td a, .small a, .help a, .quiet a, .meta a, .timeline a, .eyebrow a, .kpi a, .footer a, .flash a, a.display {
  display: inline-block; padding: 12px 8px; margin: -12px -8px; min-height: 44px; min-width: 44px;
  box-sizing: border-box; vertical-align: baseline;
}
/* The system names no disabled state; this is the portal's. */
.btn[disabled], .btn.disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }
.btn-wrap { white-space: normal; text-align: center; line-height: 1.3; }
.btn svg { width: 1em; height: 1em; flex: none; }   /* an svg with no size is 300px wide by default */

/* Cards, chips, rules ---------------------------------------------------- */
.card { padding: 24px; border: 1px solid var(--card-border); background: var(--card); }
.card.ink { background: var(--ink); color: var(--paper); }
.card-title { margin-bottom: 12px; }
.rule { height: 1px; background: var(--hairline); border: 0; margin: 0; }

.chip {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 4px 8px;
  border: 1px solid currentColor;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  line-height: 1;
  white-space: nowrap;
}
/* A chip you can CLICK is a control, so it takes the same 44px `.btn`
   takes. Only anchors: every other chip in the product is a status
   <span>, which is not a tap target and must not grow a table row.
   `admin/projects.html`'s stage filters are the only chip links there
   are, and they sat at 24px - the page is also the only rendered route
   the a11y sweep had never visited, which is exactly why nothing
   caught them.

   The other house pattern (`p a, li a, td a, ...` above: 44px from
   padding plus a negative margin, "padding the layout does not see")
   is deliberately NOT used here. That is right for a link in running
   text and wrong for a chip, whose border and background make the box
   visible - a chip should actually BE 44px. */
a.chip { min-height: 44px; min-width: 44px; justify-content: center; }
.chip .dot { width: 7px; height: 7px; background: currentColor; }
/* The system's ApprovalBadge: "the one place Approval Green appears",
   shown only as the result of a person approving - a shot, a cut, an
   approved master - or a delivery confirmed, and never for the system
   being ready. Outline on a dark ground; `solid` (green fill, Ink text)
   for an approved master or a confirmed delivery. On Paper the text
   takes the green's text-safe shade. */
.approval {
  display: inline-flex; align-items: center; gap: 12px; padding: 8px 16px;
  border: 1px solid var(--approve); color: var(--ok); background: transparent;
  font-family: var(--font-mono); font-weight: 600; font-size: 14px;
  letter-spacing: .08em; text-transform: uppercase; line-height: 1;
}
.approval svg { width: 14px; height: 14px; flex: none; }
.approval.solid { background: var(--approve); color: var(--ink); }
.chip-lit { color: var(--chip-lit-fg); background: var(--chip-lit-bg); }
.chip-hot { color: var(--chip-hot-fg); background: var(--chip-hot-bg); }
.chip-ok { color: var(--chip-ok-fg); background: var(--chip-ok-bg); }
.chip-muted { color: var(--muted); }
/* `chip-busy` marks a chip whose work is in flight, and has no rule.
   Its dot used to pulse; the system bans that outright ("no elastic
   bounce, liquid fill, pulse, glow"), and the chip's own word -
   Working, Processing, running - already carries the state, which is
   the rule that binds here anyway: status is never colour or motion
   alone. The system's device for "this is moving" is the ten-cell
   progress device, stepping one cell at a time, which is not built. */

.square { width: 6px; height: 6px; background: var(--lit); display: inline-block; flex: none; }
.bullets { list-style: none; padding: 0; margin: 0; display: grid; gap: 12px; }
.bullets li { display: flex; gap: 12px; align-items: baseline; }
.bullets li::before { content: ""; width: 6px; height: 6px; background: var(--lit); flex: none; position: relative; top: -2px; }

/* Grid helpers ------------------------------------------------------------ */
/* Card grids: as many columns as fit a minimum, one on a phone, no
   breakpoint to maintain. */
.grid { display: grid; gap: 24px; }   /* `grid-gutter` */
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 215px), 1fr)); }
.split { display: grid; gap: 28px; grid-template-columns: 1fr; align-items: center; }
@media (min-width: 768px) {
  .split { gap: 40px; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
}
.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.between { justify-content: space-between; }
.right { margin-left: auto; }
.mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 28px; } .mt-4 { margin-top: 44px; }
.mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 28px; } .mb-4 { margin-bottom: 44px; }

/* Forms ------------------------------------------------------------------- */
/* `align-content: start`: a field is a grid item too, and in a `.grid-2`
   row it is stretched to its neighbour's height. Without this its own
   auto rows share the extra - measured 2026-09-24 on the admin
   new-project form at 1200 wide, while it was two columns: "Sequence
   size"'s help ran to two lines and the "Sequence fps" box beside it
   grew to 51px and sat 7px lower than its 44px neighbour.
   The label, the control and the help now keep their own heights and
   the spare room goes under them. */
.field { display: grid; gap: 8px; align-content: start; }
.field label { font-family: var(--font-mono); font-weight: 600; font-size: 14px; letter-spacing: .08em; text-transform: uppercase; }
.field .help { font-size: 14px; color: var(--muted); }
/* The list is by TYPE, so a type nobody had used before falls through
   to the browser's own colours - which on a dark card is a contrast
   failure, and is how `url` and `tel` arrived with the Notifications
   card. Add the type here when a form first uses one. */
.input, .field input[type="text"], .field input[type="email"], .field input[type="password"],
.field input[type="date"], .field input[type="number"], .field input[type="url"],
.field input[type="tel"], .field select, .field textarea {
  width: 100%;
  border: 1px solid var(--input-border);
  background: var(--input-bg);
  color: var(--input-fg);
  padding: 8px 12px;            /* 24 + 16 + 2 = 42, so min-height sets `control-h` 44 */
  min-height: 44px;
  border-radius: 0;
  font-size: 16px;              /* under 16px iOS zooms the page on focus */
}
/* An input reads its surface's tokens too: a `.on-ink .field input` rule
   reached past a Paper section inside an Ink body and painted the request
   form's fields dark on a white card. */
/* Focus on a field is the global `:focus-visible` ring above - the
   system's own rule, "a 2px solid `border-focus` ring offset 2px", Signal
   on Ink and Ink on Paper - and nothing else. The field's own box does
   not change at all: same border, same colour, same size.

   Until 2026-09-24 a field's focus was its own rule, a ring drawn INSIDE
   the box (`outline-offset: -2px`) plus `border-color: var(--focus)`,
   which was written to stop an outside ring reading as the field
   growing. It traded one move for another. Nothing in layout moved -
   every rect on both new-project forms measured identical focused and
   not, at 320 to 1440 wide - but the field's own edge was repainted: a
   1px grey border became a 2px Ink ring whose inner edge stepped in 1px
   on all four sides, and clicking Project name then City did that to
   one box and undid it on the other. That is the "boxes jump a little"
   reported on /admin/projects/new. The rule also fired on `:focus`, not
   `:focus-visible`, and matched every input in a `.field` - so a radio
   clicked with a mouse got a 2px square outline round its circle. Now
   the ring sits 2px clear of the box it marks, follows its shape (round
   on a radio), and shows for the keyboard. */
/* A shot chip on the share page: one or two digits in a small button,
   which comes out 40px wide and fails the 44px tap minimum the sweep
   holds every control to. The number is the whole label, so it is the
   box that has to grow rather than the text. */
.shot-chip { min-width: 44px; }

/* The brand swatch. NOT in the rule above, because `width: 100%` on a
   colour well makes a full-width slab of one colour where a small
   square is wanted - but it still has to be a tap target, and the
   accessibility sweep caught it at 50x27 when it was left alone. */
.field input[type="color"] {
  width: 56px; min-height: 44px; padding: 4px;
  border: 1px solid var(--input-border); background: var(--input-bg);
  border-radius: 0; flex: none;
}
/* A colour as its swatch and its hex, on one row at every width: the
   swatch's 56px, then the text field in the rest. The new-project form
   had them in a `.row`, where the text field's `width: 100%` (the rule
   above) wrapped it onto a line of its own under the swatch; the card
   editor already laid them out this way, in a copy of its own that
   this replaces. */
.color-pair { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 12px; align-items: center; }
.field textarea { min-height: 120px; resize: vertical; }
.field textarea.code { font-family: var(--font-mono); font-size: 16px; min-height: 260px; white-space: pre; }
/* The dropdown. Closed, it is a field like the others, with the icon
   set's chevron where the platform's arrow was: `appearance: none` takes
   the native fill, arrow and (on macOS) rounded bezel away on every
   engine. Open, it is progressive. Where the engine has customizable
   select (`appearance: base-select`; Chromium 135 and later) the list is
   drawn from the surface's field tokens - a Carbon panel with Paper text
   on Ink, White and Ink on Paper - and the chosen option carries its
   check in `--lit`. Everywhere else the open list stays the platform's
   own, and that is accepted rather than replaced: a hand-built listbox
   would have to re-earn the keyboard, type-ahead, screen-reader and
   phone behaviour a native select already has, and the mobile
   guidelines ask for the native control ("Use <select> natively on
   mobile rather than custom dropdowns"). */
.field select, select.input {
  appearance: none; -webkit-appearance: none;
  padding-right: 40px;           /* 12 + the 16px chevron + 12 */
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px 16px;
  cursor: pointer;
}
@supports (appearance: base-select) {
  .field select, select.input, .field select::picker(select), select.input::picker(select) {
    appearance: base-select;
  }
  .field select::picker-icon, select.input::picker-icon { display: none; }   /* the chevron above is the one */
  .field select::picker(select), select.input::picker(select) {
    background: var(--input-bg); color: var(--input-fg);
    border: 1px solid var(--input-border); border-radius: 0; box-shadow: none;
    padding: 4px 0; margin: 8px 0;
  }
  .field select option, select.input option {
    display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 8px 12px;
    font-family: var(--font-sans); font-size: 16px; line-height: 1.5;
    background: var(--input-bg); color: var(--input-fg);
  }
  .field select option::checkmark, select.input option::checkmark { color: var(--lit); }
  .field select option:hover, select.input option:hover { background: var(--hairline); }
  .field select option:focus-visible, select.input option:focus-visible {
    background: var(--hairline); outline: 2px solid var(--focus); outline-offset: -2px;
  }
}

/* A box to tick and a choice of one, drawn rather than the platform's:
   a 20px square and a 20px ring on the field's own edge and fill. Ticked
   is a Signal fill with an Ink tick (Ink on every Signal fill; its edge
   is `--check-edge`, Ink on Paper where Signal alone would vanish). A
   chosen radio is a `--lit` ring round a `--lit` dot - the dot is the
   fill clipped to the content box, so no gradient and no pseudo-element.
   The label is the tap target (`.checks label`, 44px), so the box can be
   the size of a mark. The card editor's Uppercase and Safe area boxes
   were drawn this way in a copy of their own; this is the one copy.
   With forced colours on, the platform draws them again, since a
   Signal fill is exactly what a high-contrast theme replaces. */
input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 20px; height: 20px; margin: 0;
  border: 1px solid var(--input-border); border-radius: 0;
  background: var(--input-bg) no-repeat center / 16px 16px;
  vertical-align: middle; cursor: pointer;
}
input[type="radio"] { border-radius: 50%; padding: 4px; background-clip: content-box; }
input[type="checkbox"]:checked {
  border-color: var(--check-edge); background-color: var(--signal);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2314171A' stroke-width='2' stroke-linecap='square'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E");
}
input[type="radio"]:checked { border-color: var(--lit); background-color: var(--lit); }
input[type="checkbox"]:disabled, input[type="radio"]:disabled { border-color: var(--hairline); cursor: not-allowed; }
input[type="checkbox"]:checked:disabled, input[type="radio"]:checked:disabled { border-color: var(--steel); background-color: var(--steel); }
@media (forced-colors: active) {
  input[type="checkbox"], input[type="radio"] { appearance: auto; -webkit-appearance: auto; }
}
.checks { display: flex; gap: 8px 16px; flex-wrap: wrap; }
/* The words sit in ONE span beside the box. As a bare text node and a
   `.muted` span they were two flex items shrinking against each other,
   and under `overflow-wrap: anywhere` a phone set "Blurred backdrop" as
   BLURRE / D / BACKDR / OP in a column of its own while the radio itself
   was squeezed to 13px wide. */
.checks label { display: inline-flex; gap: 12px; align-items: center; min-height: 44px; font-family: var(--font-mono); font-size: 14px; text-transform: uppercase; letter-spacing: .06em; cursor: pointer; }
.checks label:has(:disabled) { color: var(--muted); cursor: not-allowed; }
/* A box beside a SENTENCE - the music form's rights attestation. The
   `.checks` shape (the label is the 44px target, the words one span
   beside the box) with the words left in the body face, since a
   sentence in uppercase mono reads as shouting, and the box top-aligned
   with the first line: 20px against a 21px line at 14px. It was a
   `.row`, which wraps, and a sentence's span is wider than any phone at
   max-content, so the box sat on a line of its own above its words at
   every width. `flex: 1 1 0` with `min-width: 0` lets the span take the
   rest of the row and wrap inside it instead. */
.check-line { display: flex; gap: 12px; align-items: flex-start; min-height: 44px; cursor: pointer; }
.check-line > span { flex: 1 1 0; min-width: 0; }
.error { color: var(--hot); font-family: var(--font-mono); font-size: 14px; }

/* Flash messages ------------------------------------------------------------ */
.flash {
  font-family: var(--font-mono); font-size: 14px;
  padding: 12px 16px; border-left: 2px solid var(--lit);
  background: var(--card); margin: 16px 0;
  display: flex; gap: 12px; align-items: baseline;
}
/* A flash is a flat card with a 2px rule (`border-width-strong`) that
   carries its kind; it was a 12-14% tint of that colour, and the system
   allows no translucent fill. `ok` - "Project created.", "Password
   changed.", a note accepted - is the system confirming it did what was
   asked, which is Signal's job: it was Approval Green, and green means a
   human approved a cut or a delivery was confirmed, nothing else. */
.flash.warn { border-color: var(--hot); }
.flash.secret { border-color: var(--hot); font-weight: 600; }
.flash.secret code { font-size: 15px; letter-spacing: .06em; user-select: all; }

/* Tables ------------------------------------------------------------------- */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-family: var(--font-mono); font-weight: 600; font-size: 12px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  padding: 8px 12px; border-bottom: 1px solid var(--paper-2);
}
.on-ink .table th { border-color: var(--graphite); }
.table td { padding: 12px; border-bottom: 1px solid var(--hairline); vertical-align: top; }
.table td.num, .table th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.table tr:last-child td { border-bottom: 0; }
.table .thumbs { display: flex; gap: 4px; }
.table .thumbs img { height: 44px; width: auto; background: var(--hairline); }
/* Any table can be wider than a phone; every one scrolls inside its
   own container, and a flex/grid child never grows past its parent to
   make room for one (min-width: auto is the default, and it did). */
.table-wrap {
  overflow-x: auto; max-width: 100%; position: relative;
  /* Cells size to their content here, against the `overflow-wrap:
     anywhere` on `body`. That rule keeps a filename from pushing a
     PAGE sideways, but inside a scroll box it does the opposite of
     what is wanted: `anywhere` is used when the browser computes an
     intrinsic min-content width, so a cell can shrink to one
     character, the table never exceeds the box, the box never
     scrolls, and a phone gets a ladder of letters instead. Measured
     at 93 cells across the admin pages - `td.code` 50px wide over 6
     lines for "1080x1350" - none of which any test could see until
     the sweep learned to look. Scrolling IS this container's job. */
  overflow-wrap: normal;
  /* The affordance: a 2px rule on whichever edge has more table behind
     it. Two covers scroll WITH the content (`local`) over two rules that
     stay with the box (`scroll`), so a rule shows only where its cover
     has moved away - no script, as before. It was a soft shadow on each
     edge (Lea Verou's scrolling shadows); the system forbids gradients
     and shadows unconditionally, and a flat rule is its own word for an
     edge. Every layer is ONE flat colour: `linear-gradient(c, c)` is
     only how CSS writes a solid-colour image layer, and nothing blends.
     The rule is `--muted` - Steel on Carbon 5.3:1, and 6.1:1 on White -
     because it is the only thing saying there is more table. */
  background:
    linear-gradient(var(--card), var(--card)) left / 8px 100% no-repeat local,
    linear-gradient(var(--card), var(--card)) right / 8px 100% no-repeat local,
    linear-gradient(var(--muted), var(--muted)) left / 2px 100% no-repeat scroll,
    linear-gradient(var(--muted), var(--muted)) right / 2px 100% no-repeat scroll;
}
/* position: relative so an absolutely positioned descendant (a `.sr`
   header label) is contained by the scroll box rather than landing off
   the right edge of the page. */
.table { max-width: 100%; }
.stack > *, .grid > *, .split > *, .field > * { min-width: 0; }
.field input[type="file"] { width: 100%; max-width: 100%; }   /* its intrinsic width is wider than a phone */
.table code, .code { font-family: var(--font-mono); font-size: 14px; }
.table tr.err td { color: var(--hot); }

/* Stage tracker ---------------------------------------------- */
/* The design system's StepTracker (its component code, not only its
   README): a ring per stage at `border-width-strong`, V1 Signal for a
   complete and the current stage - the complete one with the check -
   Steel for what is still to come, and a Steel hairline joining them.
   Rings, never fills, and never green: "Don't colour a completed step
   green - green stays for human approval", which is the ApprovalBadge
   on the cut card. Set as a column because it lives in a side card;
   the system's is a row, and lists screen layouts as open.

   Until 2026-09-22 every finished step was a filled green tick, then
   (6543fa2) a Steel, Signal or green fill by what the step was; both
   were wrong by the component. */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; }
.checklist li {
  display: grid; grid-template-columns: 24px 1fr auto; column-gap: 12px; align-items: center;
  position: relative; padding: 8px 0;
  font-family: var(--font-mono); font-size: 14px; letter-spacing: .06em; text-transform: uppercase;
}
.checklist li + li::before {
  content: ""; position: absolute; left: 11px; top: -8px; width: 2px; height: 16px;
  background: var(--steel);
}
.checklist .tick {
  width: 24px; height: 24px; border-radius: 50%; border: 2px solid var(--steel);
  display: grid; place-items: center; color: var(--lit);
}
.checklist li.done .tick, .checklist li.now .tick, .checklist li.busy .tick { border-color: var(--lit); }
.checklist .tick svg { width: 12px; height: 12px; }
.checklist li.pending { color: var(--muted); }
.checklist .when { color: var(--muted); font-size: 12px; }
.checklist li.now .when, .checklist li.busy .when { color: var(--lit); }

/* Progress device ----------------------------------------------- */
/* The design system's ProgressDevice, "used wherever another product
   would reach for a spinner or a percentage bar": exactly ten square
   cells, hard fills, `progress-cell-size` 20 and `progress-cell-gap` 6
   (the system's own token, and off its own 4px grid - held by name in
   tests/test_design_system.py). Unfilled is a Steel outline; a counted
   cell is Steel (`processing`); `ready` is all ten V1 Signal. Never
   green, never round, never a width that slides. The columns shrink
   below 20px only when a card is narrower than the device's 254px, which
   a 320px phone's card is. Drawn on dark cards only: Steel on Paper is
   2.5:1, under the 3:1 a graphic needs. */
.device { display: grid; grid-template-columns: repeat(10, minmax(0, 20px)); gap: 6px; }
.device i { aspect-ratio: 1; border: 1px solid var(--steel); }
.device i.on { background: var(--steel); }
.device.ready i { background: var(--lit); border-color: var(--lit); }

/* Log / code blocks ------------------------------------------------------------ */
pre.log {
  font-family: var(--font-mono); font-size: 14px; line-height: 1.5;
  background: var(--ink); color: var(--paper); padding: 16px;
  overflow: auto; max-height: 70svh; margin: 0; white-space: pre-wrap; word-break: break-word;
}

/* Header / footer ---------------------------------------------------------------- */
/* On a phone the nav is a drawer behind a Menu button (with JS; with CSS
   alone it is a wrapped row under the wordmark, which works, just takes
   more of the screen). From `md` it is a row in the bar. The brand link
   and, on the marketing page, the one call to action stay in the bar
   whatever the width. */
.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--ink); color: var(--paper);
  border-bottom: 1px solid var(--graphite);
  padding-top: env(safe-area-inset-top);
}
.topbar .wrap { display: flex; align-items: center; gap: 8px 12px; min-height: 56px; flex-wrap: wrap; }
.js .topbar .wrap { flex-wrap: nowrap; }   /* with the nav in a drawer, one row: brand, the call to action, Menu */
.topbar .cta { margin-left: auto; min-width: 0; white-space: normal; text-align: center; line-height: 1.15; padding: 8px 12px; }   /* two lines on a narrow phone rather than a wider bar */
.menu {
  display: none; margin-left: auto; min-height: 44px; min-width: 44px; padding: 0 12px;
  background: none; border: 1px solid var(--muted-on-ink); color: var(--paper); cursor: pointer;
  font-family: var(--font-mono); font-weight: 600; font-size: 14px; letter-spacing: .1em; text-transform: uppercase;
  align-items: center; gap: 8px;
}
.topbar .cta + .menu { margin-left: 0; }
.js .menu { display: inline-flex; }
.nav { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; width: 100%; padding-bottom: 8px; }
.nav a, .nav button.link {
  font-family: var(--font-mono); font-weight: 600; font-size: 14px; letter-spacing: .1em;
  text-transform: uppercase; text-decoration: none; padding: 0 12px; min-height: 44px;
  min-width: 44px;              /* "V1" is a two-letter link; the target is still 44 wide */
  display: inline-flex; align-items: center; color: var(--paper); white-space: nowrap;
  border: 0; border-bottom: 2px solid transparent; background: none; cursor: pointer;
}
.nav a:active, .nav button.link:active { transform: translateY(1px); }
@media (hover: hover) { .nav a:hover, .nav button.link:hover { color: var(--paper); } }
.nav a.on { color: var(--signal); border-bottom-color: var(--signal); }
.nav a.btn-primary { color: var(--ink); }   /* the nav's link colour must not win over the button's */
.nav .who { font-family: var(--font-mono); font-size: 14px; color: var(--steel); padding: 0 8px; }
.nav form { display: contents; }
/* the drawer */
.js .nav {
  position: fixed; inset: 0 0 0 auto; width: min(320px, 85vw); z-index: 30;
  flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 8px;
  padding: calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom));
  background: var(--ink); border-left: 1px solid var(--graphite);
  overflow-y: auto; overscroll-behavior: contain;
  transform: translateX(100%); visibility: hidden;
  transition: transform .18s var(--ease), visibility 0s .18s;   /* `duration-base` */
}
.js .nav[data-open] { transform: none; visibility: visible; transition: transform .18s var(--ease); }
.js .nav a, .js .nav button.link { width: 100%; justify-content: flex-start; border-bottom: 0; border-left: 2px solid transparent; }
.js .nav a.on { border-left-color: var(--signal); }
.js .nav .who { padding: 12px; }
/* Behind the open drawer: flat Ink. It was Ink at 60%; the system has
   no scrim, allows no translucent field, and its one sanctioned alpha is
   for a control over video. The drawer reads as a full-screen menu. */
.nav-backdrop { position: fixed; inset: 0; z-index: 25; background: var(--ink); }
/* The site's own confirm (`window.v1ask`, _base.html, 2026-10-21): a
   Carbon card in the middle of the screen over opaque Ink, as the
   editor's send choice is - no see-through fill. Its two buttons wrap
   rather than overflow, the pressed button's words on the primary. The
   box inside fills it, so only a tap outside the box is on the dialog
   itself, and that is not an answer. */
.v1-ask { width: min(100% - 32px, 480px); max-height: calc(100% - 32px); margin: auto; padding: 0; border: 1px solid var(--graphite); background: var(--carbon); color: var(--paper); }
.v1-ask-box { padding: 24px; }
.v1-ask::backdrop { background: var(--ink); }
.v1-ask-q { margin: 0 0 24px; font-size: 16px; line-height: 1.5; }
.v1-ask-row { display: flex; flex-wrap: wrap; gap: 12px; justify-content: flex-end; }
.v1-ask-row .btn { flex: 1 1 auto; white-space: normal; text-align: center; }
body.nav-open { overflow: hidden; }
@media (min-width: 768px) {
  .topbar .wrap { flex-wrap: nowrap; gap: 28px; min-height: 64px; }
  .topbar .cta { margin-left: 0; order: 2; }
  .menu, .js .menu { display: none; }
  /* a row that wraps to a second line on a tablet rather than break a
     word; the account name joins it from lg */
  .nav, .js .nav {
    position: static; width: auto; margin-left: auto; padding: 0; z-index: auto;
    flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0 8px;
    background: none; border: 0; overflow: visible; transform: none; visibility: visible; transition: none;
  }
  .nav .who { display: none; }
  .js .nav a, .js .nav button.link { width: auto; border-left: 0; border-bottom: 2px solid transparent; }
  .js .nav a.on { border-left: 0; border-bottom-color: var(--signal); }
  .js .nav .who { padding: 0 8px; }
  .nav-backdrop { display: none !important; }
  body.nav-open { overflow: auto; }
}
@media (min-width: 1024px) {
  .nav .who, .js .nav .who { display: inline-flex; }
}
.footer { padding: 40px 0 calc(40px + env(safe-area-inset-bottom)); font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
.footer .wrap { display: flex; gap: 24px; flex-wrap: wrap; align-items: center; justify-content: space-between; }
/* The marketing footer (`marketing_footer` in _macros.html), the same on
   the home page and /data. */
.mkt-footer .wrap { align-items: flex-start; }
.mkt-footer .col { display: grid; gap: 8px; }
.mkt-footer .col > * { justify-self: start; }   /* a link is as wide as its text, not the column */
.mkt-footer .closing { font-family: var(--font-display); text-transform: uppercase; font-size: clamp(22px, 2.6vw, 34px); line-height: 1; }

/* Page frames for the two portals ------------------------------------------------- */
.page { padding: clamp(20px, 3vw, 40px) 0 80px; }
.page-head { display: flex; gap: 20px; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; margin-bottom: 24px; }
.page-head .display { margin-top: 16px; }   /* the hero's eyebrow-to-heading step (.mt-2), so every heading block sits the same way */
/* A form that is the whole page - both new-project forms, the customer's
   and staff's, one width. They were 640 and 720 in two inline styles. */
.form-page { max-width: 640px; }
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.kpi { padding: 16px; }
.kpi .n { font-family: var(--font-display); font-size: 40px; line-height: 1; }
.kpi .t { font-family: var(--font-mono); font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-top: 8px; }
/* "Hover lightens ... nothing fades and nothing scales": a hover
   lightens the edge (`border-strong`), a press nudges 1px. It lifted
   2px on hover and faded to 85% on press. */
.project-card { text-decoration: none; display: grid; gap: 12px; padding: 20px; position: relative; border: 1px solid var(--card-border); transition: border-color .12s var(--ease); }
@media (hover: hover) { .project-card:hover { border-color: var(--muted); } }
.project-card:active { transform: translateY(1px); }
.project-card .name { font-family: var(--font-display); text-transform: uppercase; font-size: 26px; line-height: 1; letter-spacing: -.01em; }
.project-card .meta { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.project-card.needs-you { outline: 2px solid var(--lit); outline-offset: -2px; }
.timeline { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; font-size: 14px; }
.timeline li { display: grid; gap: 2px 12px; }
@media (min-width: 640px) { .timeline li { grid-template-columns: 130px 1fr; } }
.timeline .at { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.timeline .k { font-family: var(--font-mono); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
.timeline .d { color: var(--muted); }
.video { background: #000; aspect-ratio: 16 / 9; width: 100%; }
/* A dropzone is a control, and its dashed edge is all of it: Steel,
   not Graphite (1.35:1 on Ink). */
.dropzone { border: 1px dashed var(--steel); padding: 24px; text-align: center; display: grid; gap: 12px; justify-items: center; }
.on-paper .dropzone { border-color: var(--muted); }
.dropzone > * { min-width: 0; max-width: 100%; }
.dropzone input[type="file"] { font-family: var(--font-mono); font-size: 14px; width: 100%; }
.dropzone label { font-family: var(--font-mono); font-weight: 600; font-size: 14px; letter-spacing: .08em; text-transform: uppercase; }
details summary { cursor: pointer; font-family: var(--font-mono); font-size: 14px; letter-spacing: .08em; text-transform: uppercase; font-weight: 600; min-height: 44px; display: flex; align-items: center; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* The project page, as the approved mobile design (2026-09-23) --------- */
/* A back link, the name with its status on one row, the org, date and
   venue in Steel under it, the five-stage tracker, then a row of seven
   tabs, each its own view of the page (`?tab=`). Steel for the meta
   line: it is metadata, and on the live portal it was Signal, which
   says "ready". */
.proj-head { display: grid; gap: 12px; margin-bottom: 16px; }
.proj-head .back {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; justify-self: start;
  font-family: var(--font-mono); font-size: 14px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--lit); text-decoration: none;
}
.proj-head .back svg { width: 14px; height: 14px; }
.proj-head .title-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.proj-head .title-row h1 { line-height: 1; }
.proj-head .meta-line {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--steel);
}
/* The tracker: five circles on one continuous 2px line. A finished
   stage is a Signal ring with a tick, the current one a 2px Signal
   ring, a later one a Steel ring; the line is Signal behind the current
   stage and Steel ahead of it. Each segment is the ::before of the
   circle it leads into, so there is no gap to fall between them. */
.tracker { display: grid; gap: 8px; }
.tracker ol { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; max-width: 180px; }
.tracker li { display: flex; align-items: center; flex: 1 1 0; }
.tracker li:first-child { flex: 0 0 auto; }
.tracker li + li::before { content: ""; flex: 1 1 auto; height: 2px; background: var(--steel); }
.tracker li.done + li::before { background: var(--lit); }
.tracker .dotc {
  width: 20px; height: 20px; flex: none; border-radius: 50%; border: 1.5px solid var(--steel);
  display: grid; place-items: center; color: var(--lit);
}
.tracker li.done .dotc { border-color: var(--lit); }
.tracker li.now .dotc { border: 2px solid var(--lit); }
.tracker .dotc svg { width: 12px; height: 12px; }
.tracker .said {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--paper);
}
/* The tabs: one row that scrolls sideways inside itself, never the
   page. A fixed 40px Ink panel with a chevron covers the right edge,
   and the row's own right padding is wider than it, so the last tab
   scrolls clear. The script on the page hides the panel once the row
   is at its end or fits; without the script the panel simply stays. */
.tabs-wrap { position: relative; margin: 0 calc(-1 * var(--gutter)) 24px; border-bottom: 1px solid var(--graphite); }
.tabs {
  display: flex; overflow-x: auto; padding: 0 48px 0 calc(var(--gutter) - 12px);
  scrollbar-width: none; overflow-wrap: normal;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs > * {
  flex: none; display: inline-flex; align-items: center; min-height: 48px; padding: 0 12px;
  font-family: var(--font-mono); font-size: 14px; font-weight: 500; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap; text-decoration: none;
  color: var(--muted); border-bottom: 2px solid transparent;
}
.tabs a[aria-current="page"] { color: var(--paper); border-bottom-color: var(--signal); }
.tabs a:hover { color: var(--paper); }
/* A tab that has nothing to act on yet: faded, and not a link. */
.tabs .off { color: #4F565C; }
.tabs-more {
  position: absolute; top: 0; right: 0; bottom: 0; width: 40px; pointer-events: none;
  display: grid; place-items: center; background: var(--ink);
  border-left: 1px solid var(--graphite); color: var(--muted);
}
.tabs-more svg { width: 16px; height: 16px; }
/* The Signal-outlined status line at the top of a tab (stage 2). */
.notice {
  border: 1px solid var(--lit); padding: 12px 16px; color: var(--paper);
  font-family: var(--font-mono); font-size: 14px; letter-spacing: .06em; text-transform: uppercase;
}
.tab-body { display: grid; gap: 24px; }
.tab-body > * { min-width: 0; }
/* A section's own marker: a 16px Signal dash before a mono label. */
.sect { display: flex; align-items: center; gap: 12px; }
.sect::before { content: ""; width: 16px; height: 2px; background: var(--lit); flex: none; }
.sect > * { font-family: var(--font-mono); font-size: 12px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; }
/* The big dashed drop box: the file input under a label that is the
   whole box, so the box is the tap target and the input is still the
   browser's own. */
.dropbox {
  display: grid; justify-items: center; gap: 12px; padding: 32px 16px; text-align: center;
  border: 1px dashed var(--steel); background: var(--carbon);
}
.dropbox .big { font-family: var(--font-display); text-transform: uppercase; font-size: 28px; line-height: 1; }
.dropbox .sub { font-family: var(--font-mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--steel); }
/* A list of rows on hairlines: an upload, a clip, a link. */
.rows { display: grid; }
.rows > * {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  min-height: 56px; padding: 8px 0; border-top: 1px solid var(--graphite);
}
.rows > * > :first-child { min-width: 0; flex: 1 1 160px; }
.rows .name { display: block; font-weight: 500; }
.rows .meta { display: block; font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--steel); }
/* A link row that says what needs you: a square, two lines, an arrow. */
a.need { text-decoration: none; color: var(--paper); }
a.need .sq { width: 8px; height: 8px; flex: none; background: var(--lit); }
a.need .sq.hot { background: var(--attention); }
a.need svg { width: 18px; height: 18px; color: var(--muted); flex: none; }
/* Filter buttons over the footage list: links, so the view is a URL
   and works without a script. */
.filters { display: flex; gap: 8px; flex-wrap: wrap; }
.filters a {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 12px;
  border: 1px solid var(--muted); color: var(--paper); text-decoration: none;
  font-family: var(--font-mono); font-size: 14px; letter-spacing: .08em; text-transform: uppercase;
}
.filters a[aria-current="true"] { background: var(--signal); border-color: var(--signal); color: var(--ink); }
.big-num b { font-family: var(--font-display); font-size: 40px; line-height: 1; font-weight: 400; }
