/* =============================================================================
   Bus-stop situational awareness — presentation deck
   Binding: slides/design/system.md · slides/design/dom-contract.md

   Light mode. GT Cinetype (mono, licensed webfont) sets every display moment;
   Untitled Sans carries labels and body, no letter-spacing anywhere. Designer
   overrides of system.md's dark palette, no-mono and tracking rules — system.md
   still needs reconciling to match.

   This file owns BOTH states of every [data-reveal] element. js/ owns the
   switch (.is-revealed) and the <html data-motion="reduced"> flag only.
   ========================================================================== */


/* --- Faces ---------------------------------------------------------------- */

/* Variable, wght axis 400-700. The two static cuts that shipped first could only
   render 400 and 700, so every `font-weight: 500` silently fell back to 400 and
   the display 600 snapped to Bold — the weight ladder in system.md was fiction.
   This file makes 500 and 600 real instances. SIL Open Font License; safe to
   redistribute, which the commercial alternatives on this machine are not. */
@font-face {
  font-family: "Instrument Sans";
  src: url("../fonts/InstrumentSans-Variable.ttf") format("truetype-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: block;
}

/* Untitled Sans — the body/text face (licensed webfont, licence held). Klim's
   plain grotesque, meant to disappear into the work. Regular only (400), so the
   label weights below resolve to 400 and font-synthesis is off — no faux
   medium; hierarchy comes from colour and size, not weight. */
@font-face {
  font-family: "Untitled Sans";
  src: url("../fonts/untitled-sans-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

/* Gloock, not Instrument Serif. Instrument Serif is OFL and would have been safe,
   but it is the most saturated display serif of 2026 — a judge who has sat through
   forty decks has very likely seen it three times already today. Gloock is the same
   high-contrast editorial register with a heavier vertical stress, and is nowhere
   near as worn. Also SIL OFL, fsType 0, so it commits cleanly. */
@font-face {
  font-family: "Gloock";
  src: url("../fonts/Gloock-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

/* GT Cinetype — the display face for every large moment in the deck. A licensed
   Grilli Type webfont (web licence held); it is monospaced by design, and its
   use here is a deliberate designer override of the earlier no-mono guidance in
   system.md. Two weights held: Light 300, Regular 400. */
@font-face {
  font-family: "GT Cinetype";
  src: url("../fonts/GT-Cinetype-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "GT Cinetype";
  src: url("../fonts/GT-Cinetype-Light.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: block;
}


/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Colour — light mode. Four tones, text tones holding AAA (>=7:1) on --bg.
     (Designer override of system.md's dark palette.) The canvas-sequence
     sections re-scope these back to the dark set — see .slide--seq. */
  --bg: #FFFFFF;
  --ink: #101315;   /* 18.65:1 — primary text                                */
  --muted: #4B5155; /*  8.05:1 — secondary text (AAA)                        */
  --accent: #414A50;/*  9.05:1 — structure only; darker than --muted so the
                                 slide-5 local path stays the prominent one   */

  /* Type unit: 2.25 at 1920w, scales with the projector. system.md §Scale.
     Ladder ratios 1 : 1.167 : 1.5 : 2.333 : 4 preserved exactly. */
  --u: calc(100vw / 1920 * 2.25);

  --t-micro: calc(12 * var(--u));      /*  27px @1920 — hardware labels      */
  --t-label: calc(14 * var(--u));      /*  31px @1920 — eyebrows             */
  --t-body: calc(18 * var(--u));       /*  40px @1920 — the rare sentence    */
  --t-subhead: calc(28 * var(--u));    /*  63px @1920 — ledger subjects      */
  --t-display: calc(48 * var(--u));    /* 108px @1920 — one number / phrase  */

  /* Fallback chains end in a generic family, never a named system face. */
  --font-sans: "Untitled Sans", sans-serif;
  --font-serif: "Gloock", serif;
  --font-display: "GT Cinetype", monospace;

  /* Layout. system.md §Layout — minimum 8vw / 10vh, nothing near an edge. */
  --margin-x: 8vw;
  --margin-y: 10vh;
  --gutter: calc(24 * var(--u));
  --hairline: max(1px, calc(100vw / 1920));

  color-scheme: light;
}


/* --- Base ----------------------------------------------------------------- */

html {
  background-color: var(--bg);
  overscroll-behavior-y: none;
}

/* A slide parked out of the deck for now: no .slide class means the nav skips
   it; display:none means it never renders. Restore by re-adding the class. */
.is-hidden {
  display: none;
}

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--t-body);
  line-height: 1.6;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
}

p {
  margin: 0;
}

:focus-visible {
  outline: calc(2 * var(--hairline)) solid var(--accent);
  outline-offset: calc(4 * var(--u));
}


/* --- Slide roots ----------------------------------------------------------
   One viewport each, 100svh (not vh — mobile chrome, per the scrollytelling
   skill). 12 columns and three rows, so a single element can sit on a third
   rather than dead centre.

   The two canvas sections are excluded via :not(.slide--seq): dom-contract.md
   forbids CSS setting height, position or display on .slide--seq / .seq-sticky.
   -------------------------------------------------------------------------- */

.slide:not(.slide--seq) {
  position: relative;
  height: 100svh;
  box-sizing: border-box;
  padding: var(--margin-y) var(--margin-x);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(3, 1fr);
  column-gap: var(--gutter);
}

/* min-height only. The harness sets height to 4-5 viewports as an inline style,
   which always wins, so this can never fight it. It exists so the section is
   still one viewport tall before js/ has loaded. */
.slide--seq {
  min-height: 100svh;
  /* Dark stage. The explode/orbit frames are baked on #0A0B0C and the harness
     fills #0A0B0C behind them, so these two sections stay a dark theatre inside
     the otherwise-light deck. Re-scope the four tones back to the dark set so
     the overlay callouts read light-on-dark. No frame re-render needed; swap
     these for a light re-shoot if we ever want the sequences light too. */
  background-color: #0A0B0C;
  --ink: #F2F4F5;
  --muted: #9AA3A8;
  --accent: #CFD9E0;
}

.seq-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-sizing: border-box;
  padding: var(--margin-y) var(--margin-x);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(3, 1fr);
  column-gap: var(--gutter);
}


/* --- Type roles -----------------------------------------------------------
   Hierarchy comes from size, weight, tracking and colour. Nothing else.
   -------------------------------------------------------------------------- */

/* Eyebrow above a figure. Sentence case, as authored. */
.eyebrow {
  font-size: var(--t-label);
  font-weight: 500;
  line-height: 1.4;
  color: var(--muted);
}

/* Port names and sensor names. Grotesque, never a second face to signal
   "technical". system.md §Hardware labels. */
.hw-label {
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1.4;
  color: var(--muted);
}

/* Source credit beneath any figure on a visible surface.
   Format: QUALIFIER · SOURCE · YEAR. The qualifier is mandatory when the
   figure is a model rather than a count. This is citation, not decoration —
   it makes putting an unsourced number on a slide structurally awkward. */
.credit {
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1.4;
  color: var(--muted);
}

/* One number. GT Cinetype (mono) — no negative tracking, it overlaps a mono. */
.figure {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 300;
  line-height: 1.3;
  color: var(--ink);
}

.figure-unit {
  color: var(--muted);
}

/* One short sentence, at display size. Weight stays at 400: the display-600
   weight in system.md is for figures; a sentence set in Bold at 108px shouts,
   and slides 8 and 9 are the two slides that must not. */
.statement {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 300;
  line-height: 1.3;
  color: var(--ink);
}

/* There is deliberately NO accent text rule.
   --accent (#CFD9E0, 13.75:1 on --bg) is DARKER than --ink (#F2F4F5, 17.85:1) and
   they differ by only 1.30:1, so painting a line accent to *promote* it makes it
   recede — slide 8 inverted its own hierarchy this way and on a projector the
   distinction vanished. --accent is for STRUCTURE only: the slide-5 local-sensing
   path, edge highlights, focus rings. Text is --ink primary, --muted secondary. */


/* --- The signature --------------------------------------------------------
   Slides 1 and 10 share this block verbatim: same face, same size, same
   tracking, same grid placement, same words. It is the only repeated
   composition in the deck, and the serif appears nowhere else.
   -------------------------------------------------------------------------- */

.signature {
  grid-column: 1 / span 9;
  grid-row: 3;
  align-self: end;
}

/* The ASCII portrait of Hasan's grandfather — right, with the name on the left. */
.person-portrait {
  grid-column: 7 / span 6;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: end;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 72svh;
  display: block;
}

/* Keep the name in the left half on slide 1 only — the slide-10 bookend
   stays the full-width signature it shares with slide 1's default. */
#slide-1 .signature {
  grid-column: 1 / span 5;
}

.signature-line {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--t-display);
  line-height: 1.3;
  color: var(--ink);
}


/* --- Slide 0 · cold open --------------------------------------------------
   The braille spinner holds for 3s, then crossfades into the ASCII portrait.
   Both share the centre grid cell so the fade happens in place. Timing runs on
   load — the deck opens on this slide. Reduced motion skips straight to the art. */
.cold-open,
.cold-open-spinner {
  grid-column: 1 / span 12;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: center;
  display: block;
}

.cold-open-spinner {
  width: min(7vw, 132px);
  height: auto;
  animation: cold-open-out 600ms cubic-bezier(0, 0, 0.2, 1) 1000ms both;
}

.cold-open {
  width: auto;
  height: auto;
  max-width: min(70vw, 1040px);
  max-height: 76svh;
  opacity: 0;
  animation: cold-open-in 1000ms ease-out 1000ms both;
}

@keyframes cold-open-out { to { opacity: 0; } }
@keyframes cold-open-in { to { opacity: 1; } }

/* Reduced motion: no spinner, no fade — the portrait is simply there. */
:root[data-motion="reduced"] .cold-open-spinner { display: none; }
:root[data-motion="reduced"] .cold-open { opacity: 1; animation: none; }

@media (prefers-reduced-motion: reduce) {
  .cold-open-spinner { display: none; }
  .cold-open { opacity: 1; animation: none; }
}


/* --- Slide 2 · scale ------------------------------------------------------ */

.stat {
  display: flex;
  flex-direction: column;
  row-gap: calc(10 * var(--u));
}

.stat--scale {
  grid-column: 1 / span 6;
  grid-row: 3;
  align-self: end;
}

/* The ASCII portrait on the right half. White ground, so it sits seamlessly. */
.scale-portrait {
  grid-column: 7 / span 6;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: end;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 82svh;
  display: block;
}


/* --- Slide 3 · the ledger -------------------------------------------------
   A description list, not a bullet list: subject, hairline leader, qualifier.
   No markers, no discs, no leading dashes. The leader is the dd's ::before so
   the markup stays a valid <dl>.
   -------------------------------------------------------------------------- */

/* Ledger + its source credit travel together in one grid cell, so the credit
   cannot auto-place itself into row 1. It sits under the last row, on one line. */
.ledger-block {
  grid-column: 1 / span 11;
  grid-row: 2 / span 2;
  align-self: end;
  display: flex;
  flex-direction: column;
  row-gap: calc(22 * var(--u));
}

.ledger {
  margin: 0;
  display: flex;
  flex-direction: column;
}

/* Tabular: a fixed tool column on the left, the statement filling the rest, and
   a discrete grey hairline ABOVE each row — no leader between the two. */
.ledger-row {
  display: flex;
  align-items: baseline;
  column-gap: calc(24 * var(--u));
  padding: calc(22 * var(--u)) 0;
  border-top: var(--hairline) solid rgba(16, 19, 21, 0.14);
}

.ledger-row dt {
  flex: 0 0 30%;
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.4;
  color: var(--muted);
}

.ledger-row dd {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.4;
  color: var(--ink);
}


/* --- Slide 4 · explode callouts ------------------------------------------- */

.callouts {
  grid-column: 1 / span 4;
  grid-row: 3;
  align-self: end;
  display: flex;
  flex-direction: column;
  row-gap: calc(14 * var(--u));
}


/* --- Slide 5 · the system diagram -----------------------------------------
   Five nodes, four edges. Every stroke is --muted except the local sensing
   path, which is the slide's one accent element and the slide's whole point:
   it reaches the device without touching the network.
   -------------------------------------------------------------------------- */

.diagram-frame {
  grid-column: 1 / span 12;
  grid-row: 1 / span 3;
  align-self: center;
}

.deck-diagram {
  display: block;
  width: 100%;
  height: auto;
  max-height: 100%;
}

/* Node boxes: a hairline rectangle around each label. */
.diagram-box {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.25;
}

.diagram-label {
  fill: var(--muted);
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 500;
}

/* Sub-caption under a node (e.g. Modal's "YOLO detects · Claude reads"). */
.diagram-caption {
  fill: var(--muted);
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 400;
}

.diagram-edge {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.25;
}

/* The local sensing path is slide 5's single accent element. It carries the
   whole argument of the slide — safety output reaches the wrist without the
   network — so it is the one thing allowed to be brighter and heavier. */
.diagram-box--local,
.diagram-edge--local {
  stroke: var(--accent);
  stroke-width: 2;
}

.diagram-arrow {
  fill: var(--muted);
}

.diagram-arrow--local {
  fill: var(--accent);
}


/* --- Slide 6 · the two latency figures ------------------------------------ */

.stat--latency-first {
  grid-column: 1 / span 4;
  grid-row: 3;
  align-self: start;
}

.stat--latency-second {
  grid-column: 6 / span 5;
  grid-row: 3;
  align-self: start;
}


/* --- Slides 8 and 9 · statements ------------------------------------------ */

.statement-block {
  grid-column: 1 / span 10;
  grid-row: 3;
  align-self: end;
  display: flex;
  flex-direction: column;
  row-gap: calc(12 * var(--u));
}

/* Braille for "th you" — a quiet mark, top-right of the thank-you slide. */
.thanks-braille {
  grid-column: 9 / span 4;
  grid-row: 1;
  justify-self: end;
  align-self: start;
  font-size: var(--t-display);
  line-height: 1;
  color: var(--muted);
}


/* --- Slide 11 · the build (spec tables) -----------------------------------
   Two label/value tables, hardware and stack. Horizontal separators only —
   no vertical rules, no boxes. A discrete grey hairline under each row. */
.spec-group {
  grid-row: 1 / span 3;
  align-self: center;
  display: flex;
  flex-direction: column;
  row-gap: calc(24 * var(--u));
}

.spec-group--hardware { grid-column: 1 / span 5; }
.spec-group--stack { grid-column: 8 / span 5; }

.spec-head {
  font-size: var(--t-subhead);
  font-weight: 400;
  line-height: 1.3;
  color: var(--ink);
}

.spec-table {
  width: 100%;
  border-collapse: collapse;
}

.spec-table td {
  padding: calc(12 * var(--u)) 0;
  font-size: var(--t-label);
  line-height: 1.3;
  vertical-align: baseline;
  border-bottom: var(--hairline) solid rgba(16, 19, 21, 0.14);
}

.spec-table tr:last-child td {
  border-bottom: 0;
}

.spec-table .k {
  width: 34%;
  padding-right: calc(20 * var(--u));
  color: var(--muted);
}

.spec-table .v {
  color: var(--ink);
}


/* --- Slide 10 · the device (board + labels) --------------------------------
   The board is a dark render on transparent; the four module labels sit in the
   quadrants beside it. Left labels right-aligned, right labels left-aligned. */
.board-layout {
  grid-column: 1 / span 12;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: center;
  display: flex;
  align-items: stretch;
  gap: calc(56 * var(--u));
}

.board-img {
  align-self: center;
  width: auto;
  height: auto;
  max-height: 72svh;
  max-width: 40vw;
  display: block;
}

.board-labels {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(78 * var(--u)) 0;
}

.board-labels--left,
.board-labels--right {
  align-items: flex-start;
  text-align: left;
}

.board-name {
  font-size: var(--t-body);
  line-height: 1.2;
  color: var(--ink);
}

.board-sku {
  font-size: var(--t-label);
  line-height: 1.4;
  color: var(--muted);
}


/* --- Reveals --------------------------------------------------------------
   dom-contract.md §Reveals, verbatim. js/ adds .is-revealed at 20% visible.
   -------------------------------------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(calc(30 * var(--u) / 2.25));
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: opacity 500ms cubic-bezier(0, 0, 0.2, 1),
              transform 500ms cubic-bezier(0, 0, 0.2, 1);
}

/* Inside SVG a translateY resolves in user units, which would slide the
   arrowheads out of register with their edges. Those fade only. */
.deck-diagram [data-reveal] {
  transform: none;
}


/* --- Reduced motion -------------------------------------------------------
   Both layers handle it. js/ sets the attribute, skips Lenis and paints final
   frames; CSS makes every reveal instant. The media query is the safety net if
   the attribute never lands. Neither alone is sufficient. Content is never
   withheld: reduced motion must not mean missing content.
   -------------------------------------------------------------------------- */

:root[data-motion="reduced"] [data-reveal],
:root[data-motion="reduced"] [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
