/* ==========================================================================
   The March of AI
   A vanilla CSS stage presentation. No build step.

   Order of this file:
     1  Tokens
     2  Reset and base
     3  Deck shell and stage scaling
     4  Slide frame: titles, sources, slide numbers, build steps
     5  The march: progress track
     6  Layout types
     7  Diagram primitives shared by the SVGs
     8  Voting
     9  Overlays: overview, help, sources, blank
    10  Reading mode
    11  Print
   ========================================================================== */

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

:root {
  /* Brand palette. Nothing outside this block declares a hex value. */
  --navy-deep: #0b2a45;
  --navy-brand: #07344f;
  --cyan: #2bcfcf;
  --cyan-muted: #1e9494;
  --cream: #f1f0ee;
  --steel: #a8b5c2;
  --white: #ffffff;

  /* Two more, for slide 2 alone, where red and green are doing semantic work
     that no palette colour can do: ninety five pilots that came to nothing and
     five that took root. Both are held well below signal strength so they sit
     inside the navy rather than shouting over it. Nothing else may use them. */
  --ember: #c9584a;
  --shoot: #57b97c;

  /* Derived, mixed from the seven above so no eighth colour enters. */
  --rule: color-mix(in srgb, var(--steel) 22%, transparent);
  --rule-strong: color-mix(in srgb, var(--steel) 38%, transparent);
  --surface: var(--navy-brand);
  --surface-raised: color-mix(in srgb, var(--navy-brand) 88%, var(--cream));
  --surface-sunk: color-mix(in srgb, var(--navy-deep) 70%, #000);

  /* Type */
  --font-display: 'Montserrat', 'Segoe UI', system-ui, sans-serif;
  --font-body: Calibri, 'Carlito', 'Segoe UI', system-ui, sans-serif;

  --fs-hero: 96px;
  --fs-title: 60px;
  --fs-lede: 40px;
  --fs-bignum: 200px;
  --fs-bignum-label: 30px;
  --fs-pull: 46px;
  --fs-body: 34px;
  --fs-card: 28px;
  --fs-card-head: 30px;
  --fs-meta: 24px;
  --fs-source: 19px;
  --fs-number: 18px;

  --lh-display: 1.14;
  --lh-body: 1.38;
  --measure: 26em;

  /* Spacing, 8px base */
  --pad-x: 96px;
  --pad-top: 76px;
  --pad-bottom: 128px;
  --gap-title: 56px;
  --gutter: 32px;

  /* Motion. Reduced motion zeroes every one of these. */
  --dur-slide: 420ms;
  --dur-build: 220ms;
  --dur-march: 620ms;
  --dur-label: 240ms;
  --dur-bar: 420ms;
  --dur-draw: 900ms;
  --dur-fadeout: 1500ms;
  --ease-march: cubic-bezier(0.22, 0.61, 0.36, 1);

  --stage-scale: 1;

  color-scheme: dark;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-slide: 0ms;
    --dur-build: 0ms;
    --dur-march: 0ms;
    --dur-label: 0ms;
    --dur-bar: 0ms;
    --dur-draw: 0ms;
    --dur-fadeout: 0ms;
  }
}

/* 2  Reset and base ------------------------------------------------------- */

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

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--navy-deep);
  color: var(--cream);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--lh-display);
  margin: 0;
  letter-spacing: -0.01em;
}

p,
ul,
ol {
  margin: 0;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

strong,
b {
  font-weight: 700;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

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

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The brand signature move: one key word in cyan inside a cream heading.
   Used three times in the deck (slides 1, 15 and 16), the cap in the brief. */
.key {
  color: var(--cyan);
}

/* 3  Deck shell and stage scaling ---------------------------------------- */

.deck {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--navy-deep);
}

/* The stage is positioned and scaled from its top left corner, with the
   letterbox offsets computed in JavaScript. Centring it with grid or flex
   alignment looks right at 1920 x 1080 and silently drifts at every smaller
   size, because an item wider than its track does not centre the way an item
   inside one does. */
.stage {
  position: absolute;
  top: 0;
  left: 0;
  width: 1920px;
  height: 1080px;
  transform-origin: top left;
  transform: translate(var(--stage-x, 0px), var(--stage-y, 0px)) scale(var(--stage-scale));
  isolation: isolate;
  /* `clip` rather than `hidden`: hidden makes the stage a scroll container, and
     the browser will happily scroll it when focus moves, which silently shifts
     every slide off the top. `clip` cannot be scrolled at all. */
  overflow: hidden;
  overflow: clip;
}

.slide {
  position: absolute;
  inset: 0;
  z-index: 1;
  padding: var(--pad-top) var(--pad-x) var(--pad-bottom);
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
}

/* The outgoing slide is held visible for the length of the transition so the
   two genuinely cross-fade, rather than one vanishing and the next appearing. */
.slide.is-active {
  z-index: 2;
  opacity: 1;
  visibility: visible;
  animation: slide-in-forward var(--dur-slide) var(--ease-march) both;
}

.slide.is-leaving {
  z-index: 1;
  visibility: visible;
  animation: slide-out-forward var(--dur-slide) var(--ease-march) both;
}

.stage[data-direction='back'] .slide.is-active {
  animation-name: slide-in-back;
}

.stage[data-direction='back'] .slide.is-leaving {
  animation-name: slide-out-back;
}

@keyframes slide-in-forward {
  from {
    opacity: 0;
    transform: translate3d(40px, 0, 0) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes slide-out-forward {
  from {
    opacity: 1;
    transform: none;
  }
  to {
    opacity: 0;
    transform: translate3d(-40px, 0, 0) scale(0.985);
  }
}

@keyframes slide-in-back {
  from {
    opacity: 0;
    transform: translate3d(-40px, 0, 0) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes slide-out-back {
  from {
    opacity: 1;
    transform: none;
  }
  to {
    opacity: 0;
    transform: translate3d(40px, 0, 0) scale(0.985);
  }
}

body.is-blanked .slide,
body.is-blanked .march {
  visibility: hidden;
}

/* 4  Slide frame --------------------------------------------------------- */

.slide > h2 {
  font-size: var(--fs-title);
  color: var(--cream);
  margin-bottom: var(--gap-title);
  max-width: 30em;
}

.slide[data-dense] {
  --fs-title: 56px;
  --fs-body: 31px;
  --fs-card: 26px;
  --gap-title: 40px;
}

.slide-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.source {
  position: absolute;
  left: var(--pad-x);
  right: 280px;
  bottom: 100px;
  font-size: var(--fs-source);
  line-height: 1.32;
  color: var(--steel);
  margin: 0;
}

.slide-number {
  display: none;
  position: absolute;
  right: var(--pad-x);
  bottom: 100px;
  font-size: var(--fs-number);
  color: var(--steel);
  font-variant-numeric: tabular-nums;
}

.caveat {
  font-size: var(--fs-meta);
  line-height: 1.34;
  color: var(--steel);
  max-width: 44em;
}

.notes {
  display: none;
}

/* Build steps. Fade only, no translate: the universal fade-and-rise is the
   most recognisable AI-design tell and the brief rules it out. */
[data-build] {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-build) linear;
}

[data-build].is-revealed {
  opacity: 1;
  visibility: visible;
}

/* 5  The march ----------------------------------------------------------- */

/* Stage stacking order: backdrops 0, slides 1 (2 while active), march 4,
   controls 5. The march needs to sit above the active slide or its markers
   cannot be clicked. */
.march {
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: 24px;
  z-index: 4;
  height: 64px;
  transition: opacity var(--dur-fadeout) linear;
}

.march[hidden] {
  display: none;
}

.march.is-dissolved {
  opacity: 0;
  pointer-events: none;
}

.march-track {
  position: absolute;
  left: 0;
  right: 0;
  top: 48px;
  height: 2px;
  background: var(--rule-strong);
}

.march-marker {
  position: absolute;
  top: 44px;
  left: 0;
  width: 10px;
  height: 10px;
  margin-left: -5px;
  border-radius: 50%;
  background: var(--cyan);
  transform: translateX(var(--march-x, 0px));
  transition: transform var(--dur-march) var(--ease-march);
}

.march-stop {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  font-family: var(--font-body);
  font-size: var(--fs-number);
  color: var(--steel);
  letter-spacing: 0.01em;
}

.march-stop::after {
  content: '';
  order: 2;
  width: 2px;
  height: 8px;
  background: var(--rule-strong);
}

.march-stop > span {
  order: 1;
  display: block;
  transform-origin: center bottom;
  transition:
    transform var(--dur-label) var(--ease-march),
    color var(--dur-label) linear;
}

.march-stop.is-arriving > span {
  color: var(--cream);
  transform: scale(1.14);
}

.march-stop.is-passed > span {
  color: color-mix(in srgb, var(--steel) 74%, var(--cream));
}

/* 6  Layout types -------------------------------------------------------- */

/* title and closing */
.slide[data-layout='title'],
.slide[data-layout='closing'] {
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-bottom: var(--pad-top);
}

.hero-title {
  font-size: var(--fs-hero);
  font-weight: 600;
  color: var(--cream);
  margin: 0;
}

.hero-subtitle {
  font-size: var(--fs-lede);
  color: var(--cream);
  margin-top: 24px;
}

.hero-meta {
  margin-top: 88px;
  color: var(--steel);
  font-size: var(--fs-meta);
}

.hero-meta span {
  display: block;
}

.closing-lines {
  display: grid;
  gap: 32px;
  max-width: 30em;
}

.closing-lines p {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 52px;
  line-height: var(--lh-display);
}

.closing-meta {
  margin-top: 72px;
  color: var(--steel);
  font-size: var(--fs-meta);
  display: grid;
  gap: 6px;
}

/* Slide 17 holds the question and the close on one slide. The question leads
   on build 1 at a size that suits a closing statement rather than a chapter of
   its own; on build 2 it steps back, dimmed and a little smaller, so the
   closing lines take over while the room can still read what it was asked.
   Presenting only: in the reading version and in print both simply stack. */
#s17 .big-question {
  font-size: 50px;
  max-width: 24em;
}

#s17 .chain {
  margin-top: 30px;
  font-size: 27px;
  gap: 12px;
}

#s17 .closing-lines {
  margin-top: 40px;
}

#s17 .closing-lines p {
  font-size: 46px;
}

#s17 .closing-meta {
  margin-top: 28px;
}

body[data-mode='present'] #s17 .closing-question {
  transition:
    opacity 700ms ease,
    transform 700ms ease;
  transform-origin: center top;
}

body[data-mode='present'] #s17:has(.closing-lines.is-revealed) .closing-question {
  opacity: 0.3;
  transform: scale(0.84);
}

/* Slide 10's agent loop sits beside the words, so it is held to a size that
   leaves room for the wins, the caveat and the pull line underneath. */
#s10 .figure {
  width: 420px;
  max-width: 100%;
  margin: 0 auto;
}

/* The caveat and the pull line finish above the source line, which sits at a
   fixed height on every slide. */
#s10 .slide-body {
  padding-bottom: 76px;
}

#s10 .caveat {
  margin-top: 30px;
}

#s10 .pull {
  margin-top: 18px;
  font-size: 38px;
}

/* bignum-pair */
.bignum-pair {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  gap: 0 72px;
  align-items: start;
}

.bignum-pair .divider {
  align-self: stretch;
  background: var(--rule);
}

.bignum {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-bignum);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--cyan);
}

.bignum-pair .bignum {
  /* Sized so "900 million" holds one line. Wrapped, it stood twice as tall as
     the 95% beside it and the pair stopped reading as a pair. */
  font-size: 120px;
}

/* Only the fixed stage can promise the room for one line. Reading mode wraps. */
body[data-mode='present'] .bignum-pair .bignum {
  white-space: nowrap;
}

.bignum-label {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-bignum-label);
  line-height: 1.3;
  color: var(--cream);
  margin-top: 20px;
  max-width: 14em;
}

.roadmap {
  margin-top: 64px;
}

.roadmap + .caveat {
  margin-top: 32px;
}

/* rows: label, result, attribution */
.rows {
  display: grid;
  gap: 0;
}

.row {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 40px;
  align-items: baseline;
  padding: 26px 0;
  border-top: 1px solid var(--rule);
}

.row:first-child {
  border-top: 0;
  padding-top: 0;
}

.row-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-card-head);
  color: var(--cyan);
}

.row-body {
  font-size: var(--fs-body);
}

.row-body em {
  font-style: normal;
  color: var(--steel);
  font-size: 0.72em;
  display: block;
  margin-top: 8px;
}

.footer-line {
  margin-top: 40px;
  font-size: var(--fs-meta);
  color: var(--steel);
  max-width: 50em;
}

.pull {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-pull);
  line-height: var(--lh-display);
  color: var(--cream);
  max-width: 24em;
}

/* cards */
.cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gutter);
}

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 32px 36px;
}

.card h3 {
  font-size: var(--fs-card-head);
  color: var(--cyan);
  margin-bottom: 16px;
}

/* Slide 7's logos. Brand marks are drawn for white paper, so each sits on its
   own small cream chip rather than straight on navy, where half of them would
   lose their colours. Height is fixed and width follows the mark. */
.card h3 {
  display: flex;
  align-items: center;
  gap: 16px;
}

.card-logo {
  display: inline-flex;
  align-items: center;
  height: 52px;
  padding: 8px 12px;
  background: var(--cream);
  border-radius: 4px;
}

.card-logo[hidden] {
  display: none;
}

.card-logo img {
  display: block;
  height: 100%;
  width: auto;
  max-width: 160px;
  object-fit: contain;
}

.card p,
.card li {
  font-size: var(--fs-card);
  line-height: 1.34;
}

.card ul {
  display: grid;
  gap: 8px;
}

/* rows-panel */
.rows-panel {
  display: grid;
  grid-template-columns: 1fr 640px;
  gap: 64px;
  align-items: start;
}

.panel {
  background: var(--surface-raised);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 40px;
}

.panel h3 {
  font-size: 36px;
  color: var(--cream);
  margin-bottom: 20px;
}

.panel p {
  font-size: var(--fs-card);
  line-height: 1.36;
}

.panel p + p {
  margin-top: 14px;
}

.panel .quote {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 34px;
  color: var(--cyan);
  line-height: 1.22;
}

/* split */
.split {
  display: grid;
  grid-template-columns: 46% 1fr;
  gap: 72px;
  align-items: center;
  flex: 1 1 auto;
  min-height: 0;
}

.split[data-flip='true'] {
  grid-template-columns: 1fr 46%;
}

.split .figure {
  min-width: 0;
}

.split[data-flip='true'] .figure {
  order: 2;
}

.split .prose {
  display: grid;
  gap: 28px;
  align-content: center;
}

.split .prose p {
  max-width: var(--measure);
}

/* bars */
.bars {
  display: grid;
  gap: 30px;
}

.bar-row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 32px;
  align-items: center;
}

.bar-figure {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 68px;
  line-height: 1;
  color: var(--cyan);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.bar-track {
  position: relative;
  height: 56px;
  background: var(--surface);
  border-radius: 2px;
}

.bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--bar-width, 0%);
  background: var(--cyan);
  border-radius: 2px;
  transition: width var(--dur-bar) ease-out;
}

.bar-caption {
  position: absolute;
  left: 24px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--fs-card);
  color: var(--navy-deep);
  font-weight: 700;
  font-family: var(--font-body);
}

.bar-caption[data-outside='true'] {
  left: auto;
  right: 24px;
  color: var(--cream);
  font-weight: 400;
}

.strip {
  margin-top: 44px;
  padding-top: 28px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 56px;
  font-size: var(--fs-meta);
  color: var(--steel);
}

.strip b {
  color: var(--cream);
  font-weight: 700;
}

/* checklist */
.checklist {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 36px 80px;
  counter-reset: none;
}

.check {
  display: grid;
  grid-template-columns: 62px 1fr;
  gap: 24px;
  align-items: start;
}

.check-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 44px;
  line-height: 1;
  color: var(--cyan);
  font-variant-numeric: tabular-nums;
}

.check-body {
  font-size: var(--fs-body);
  line-height: 1.32;
}

.check-body b {
  font-family: var(--font-display);
  font-weight: 600;
}

/* list-callout */
.list {
  display: grid;
  gap: 20px;
}

.list li {
  font-size: var(--fs-body);
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 20px;
  align-items: baseline;
}

.list li::before {
  content: '';
  width: 10px;
  height: 10px;
  margin-top: 0.55em;
  background: var(--cyan);
}

.callout {
  margin-top: 120px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fs-pull);
  color: var(--cream);
}

/* range-list */
.range {
  padding: 8px 0 0;
}

.range-list-foot {
  margin-top: 56px;
  padding-top: 36px;
  border-top: 1px solid var(--rule);
}

.range-list-foot h3 {
  font-size: var(--fs-card-head);
  color: var(--cyan);
  margin-bottom: 24px;
}

.tick-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px 72px;
  font-size: 30px;
}

.tick-list li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 18px;
  align-items: baseline;
}

.tick-list li::before {
  content: '';
  width: 8px;
  height: 8px;
  margin-top: 0.6em;
  background: var(--steel);
}

.tick-list b {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--cream);
}

/* two-col-quotes */
.two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 72px;
  align-items: start;
}

.col h3 {
  font-size: var(--fs-card-head);
  margin-bottom: 22px;
}

.col[data-tone='up'] h3 {
  color: var(--cyan);
}

.col[data-tone='down'] h3 {
  color: var(--cream);
}

.col ul {
  display: grid;
  gap: 14px;
  font-size: 30px;
  line-height: 1.3;
}

.col li {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 16px;
  align-items: baseline;
}

.col li::before {
  content: '';
  width: 8px;
  height: 8px;
  margin-top: 0.55em;
  background: var(--steel);
}

.col[data-tone='up'] li::before {
  background: var(--cyan);
}

.quotes {
  margin-top: 52px;
  padding-top: 36px;
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 72px;
}

.quotes blockquote {
  margin: 0;
  font-size: 30px;
  font-style: italic;
  line-height: 1.3;
  color: var(--cream);
}

.quotes cite {
  display: block;
  margin-top: 12px;
  font-style: normal;
  font-size: var(--fs-source);
  color: var(--steel);
}

/* bignum-takeover, slide 21 */
.slide[data-layout='bignum-takeover'] {
  justify-content: center;
}

.takeover {
  display: grid;
  gap: 64px;
  justify-items: start;
}

.takeover-figure {
  display: grid;
  gap: 8px;
}

.takeover-figure .bignum {
  font-size: 220px;
  color: var(--white);
}

.takeover-figure[data-signature='true'] .bignum {
  color: var(--cyan);
}

.takeover-figure .bignum-label {
  font-size: 36px;
  max-width: 24em;
}

.slide[data-layout='bignum-takeover'].is-compact .takeover {
  gap: 40px;
}

.slide[data-layout='bignum-takeover'].is-compact .takeover-strip {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 72px;
  justify-content: start;
  align-items: end;
}

.slide[data-layout='bignum-takeover'].is-compact .takeover-figure .bignum {
  font-size: 84px;
}

.slide[data-layout='bignum-takeover'].is-compact .takeover-figure .bignum-label {
  font-size: 22px;
  color: var(--steel);
  max-width: 16em;
  margin-top: 8px;
}

.takeover-strip {
  display: grid;
  gap: 64px;
}

.takeover-detail {
  display: grid;
  grid-template-columns: 1fr 720px;
  gap: 72px;
  align-items: start;
  width: 100%;
}

/* question-chain, slide 22 */
.slide[data-layout='question-chain'] {
  justify-content: center;
}

.big-question {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 62px;
  line-height: 1.18;
  color: var(--cream);
  max-width: 26em;
  margin: 0 auto;
  text-align: center;
}

.chain {
  margin: 64px auto 0;
  display: grid;
  gap: 16px;
  justify-items: center;
  font-size: 36px;
}

.chain li {
  font-family: var(--font-display);
  font-weight: 600;
}

.chain .lhs {
  color: var(--steel);
}

.chain .rhs {
  color: var(--cream);
}

.chain .joiner {
  color: var(--steel);
  font-family: var(--font-body);
  font-weight: 400;
}

.slide[data-layout='question-chain'] .vote {
  margin: 56px auto 0;
  max-width: 1180px;
  width: 100%;
}

/* spectrum-vote, slide 18 */
.spectrum {
  margin-bottom: 40px;
}

/* 7  Diagram primitives -------------------------------------------------- */

.figure svg,
.diagram {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.svg-line {
  fill: none;
  stroke: var(--rule-strong);
  stroke-width: 2;
}

.svg-line-accent {
  fill: none;
  stroke: var(--cyan);
  stroke-width: 3;
  stroke-linecap: round;
}

.svg-fill-surface {
  fill: var(--surface);
  stroke: var(--rule);
  stroke-width: 1;
}

.svg-dot {
  fill: var(--cyan);
}

.svg-dot-quiet {
  fill: var(--steel);
}

.svg-label {
  font-family: var(--font-body);
  font-size: 22px;
  fill: var(--cream);
}

.svg-label-small {
  font-family: var(--font-body);
  font-size: 19px;
  fill: var(--steel);
}

.svg-year {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 26px;
  fill: var(--cyan);
}

.svg-head {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 28px;
  fill: var(--cream);
}

.svg-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 34px;
  fill: var(--cyan);
}

/* Slide 10's agent loop draws itself once on entry, then stops. */
.draw-once {
  stroke-dasharray: var(--draw-len, 1000);
  stroke-dashoffset: var(--draw-len, 1000);
}

.is-active .draw-once {
  animation: draw var(--dur-draw) var(--ease-march) forwards;
}

@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .draw-once {
    stroke-dashoffset: 0;
  }

  .is-active .draw-once {
    animation: none;
  }
}

/* 8  Voting -------------------------------------------------------------- */

.vote {
  display: grid;
  gap: 18px;
}

.vote-question {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 34px;
  color: var(--cream);
}

.vote-options {
  display: grid;
  gap: 14px;
}

.vote-option {
  display: grid;
  grid-template-columns: 34px 1fr 150px;
  gap: 20px;
  align-items: center;
  width: 100%;
  text-align: left;
  padding: 6px 0;
}

.vote-key {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  color: var(--steel);
  font-variant-numeric: tabular-nums;
}

.vote-bar {
  position: relative;
  height: 46px;
  background: color-mix(in srgb, var(--cyan-muted) 26%, transparent);
  border-radius: 2px;
  overflow: hidden;
}

.vote-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--vote-width, 0%);
  background: var(--cyan);
  transition: width var(--dur-bar) ease-out;
}

.vote-bar-label {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 26px;
  color: var(--cream);
  white-space: nowrap;
}

.vote-tally {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 24px;
  color: var(--cyan);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.vote-hint {
  font-size: var(--fs-source);
  color: var(--steel);
}

/* With a live poll set up, each vote gains a QR code in a column of its own to
   the right of the bars, so the bars simply get a little shorter and nothing
   below them moves. Same code on all three vote slides. */
.vote.has-poll {
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 44px;
}

.vote.has-poll > :not(.vote-poll) {
  grid-column: 1;
}

.vote.has-poll > .vote-poll {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: center;
}

.vote-poll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 210px;
  text-align: center;
}

.vote-poll-caption {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  white-space: nowrap;
  color: var(--cream);
}

.vote-poll canvas,
.vote-poll .qr {
  width: 172px;
  height: 172px;
  image-rendering: pixelated;
  background: var(--cream);
  padding: 8px;
  border-radius: 4px;
}

.vote-poll-url {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
  color: var(--cyan);
  overflow-wrap: anywhere;
}

/* If the poll cannot be reached, the code stays up (phones may still get
   through on mobile data) but dims, so the presenter can see at a glance. */
.vote-poll[data-state='offline'] canvas {
  opacity: 0.45;
}

/* 9  Overlays ------------------------------------------------------------ */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--navy-deep) 94%, #000);
  padding: 48px;
  overflow: auto;
}

.overlay[hidden] {
  display: none;
}

.overlay h2 {
  font-size: 30px;
  margin-bottom: 28px;
  color: var(--cream);
}

.overlay-close {
  position: absolute;
  top: 28px;
  right: 36px;
  font-size: 17px;
  color: var(--steel);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 8px 14px;
}

/* overview grid */
.overview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(288px, 1fr));
  gap: 22px;
  font-size: 16px;
}

.thumb {
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--navy-deep);
  overflow: hidden;
  display: grid;
  gap: 0;
  text-align: left;
}

.thumb.is-current {
  border-color: var(--cyan);
}

.thumb-canvas {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--navy-deep);
}

.thumb-canvas > .stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
}

.thumb-canvas .slide {
  opacity: 1;
  visibility: visible;
  transition: none;
}

.thumb-canvas [data-build] {
  opacity: 1;
  visibility: visible;
}

.thumb-meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-top: 1px solid var(--rule);
  color: var(--steel);
  font-size: 15px;
}

.thumb-meta b {
  color: var(--cream);
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* help and sources */
.help-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 64px;
  font-size: 19px;
  max-width: 1200px;
}

.help-list div {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--rule);
}

.help-list kbd {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  color: var(--cyan);
}

.sources-panel {
  max-width: 1100px;
  font-size: 21px;
  line-height: 1.5;
  color: var(--cream);
}

.sources-panel .tag {
  display: inline-block;
  font-size: 15px;
  color: var(--steel);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 2px 8px;
  margin-right: 10px;
}

/* live region */
.announce {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* appendix, hidden in presenting mode */
.appendix {
  display: none;
}

.appendix h2 {
  font-size: 34px;
  margin-bottom: 24px;
}

.appendix h3 {
  font-size: 26px;
  color: var(--cyan);
  margin: 32px 0 14px;
}

.appendix .appendix-source {
  color: var(--steel);
  font-size: 17px;
  margin: 8px 0 24px;
}

.appendix li {
  font-size: 20px;
  line-height: 1.5;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}

.appendix .tag {
  display: inline-block;
  font-size: 14px;
  color: var(--steel);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 2px 8px;
  margin-right: 10px;
  vertical-align: 2px;
}

/* 10  Reading mode ------------------------------------------------------- */

body[data-mode='read'] {
  font-size: 18px;
}

body[data-mode='read'] .deck {
  position: static;
  display: block;
  overflow: visible;
}

body[data-mode='read'] .stage {
  /* Back into the flow. The stage is absolutely positioned for the letterbox
     on stage, which would otherwise leave the appendix running underneath it. */
  position: static;
  width: 100%;
  max-width: 820px;
  height: auto;
  margin: 0 auto;
  padding: 0 20px 96px;
  transform: none;
}

body[data-mode='read'] .slide {
  position: static;
  inset: auto;
  opacity: 1;
  visibility: visible;
  display: block;
  padding: 56px 0 8px;
  border-top: 1px solid var(--rule);
  /* Reading-mode type scale is a token swap, not a re-declaration. */
  --fs-hero: 34px;
  --fs-title: 27px;
  --fs-lede: 20px;
  --fs-bignum: 58px;
  --fs-bignum-label: 17px;
  --fs-pull: 24px;
  --fs-body: 18px;
  --fs-card: 17px;
  --fs-card-head: 19px;
  --fs-meta: 16px;
  --fs-source: 14px;
  --gap-title: 20px;
  --gutter: 14px;
  --measure: 34em;
  --pad-x: 0px;
}

body[data-mode='read'] .slide:first-of-type {
  border-top: 0;
}

body[data-mode='read'] .slide[data-layout='title'],
body[data-mode='read'] .slide[data-layout='closing'] {
  text-align: left;
}

body[data-mode='read'] [data-build] {
  opacity: 1;
  visibility: visible;
}

body[data-mode='read'] .is-stowed {
  display: revert;
}

body[data-mode='read'] .source,
body[data-mode='read'] .slide-number {
  position: static;
  right: auto;
  bottom: auto;
  left: auto;
  margin-top: 20px;
  display: block;
}

body[data-mode='read'] .slide-number {
  margin-top: 6px;
}

body[data-mode='read'] .march {
  position: sticky;
  position: -webkit-sticky;
  top: 0;
  left: auto;
  right: auto;
  bottom: auto;
  z-index: 30;
  display: flex;
  gap: 4px;
  height: auto;
  padding: 10px 20px;
  margin: 0;
  background: color-mix(in srgb, var(--navy-deep) 92%, #000);
  border-bottom: 1px solid var(--rule);
  justify-content: center;
  transition: none;
  opacity: 1;
}

body[data-mode='read'] .march.is-dissolved {
  opacity: 1;
  pointer-events: auto;
}

body[data-mode='read'] .march-track,
body[data-mode='read'] .march-marker {
  display: none;
}

body[data-mode='read'] .march-stop {
  position: static;
  transform: none;
  flex-direction: row;
  font-size: 13px;
  padding: 6px 10px;
  border-radius: 2px;
}

body[data-mode='read'] .march-stop::after {
  display: none;
}

body[data-mode='read'] .march-stop.is-current {
  background: color-mix(in srgb, var(--cyan) 18%, transparent);
}

body[data-mode='read'] .march-stop.is-current > span {
  color: var(--cyan);
  transform: none;
}

/* Layouts collapse to one column. Nothing scrolls sideways except wide
   tables, which scroll inside their own container. */
body[data-mode='read'] .split,
body[data-mode='read'] .split[data-flip='true'],
body[data-mode='read'] .bignum-pair,
body[data-mode='read'] .cards,
body[data-mode='read'] .rows-panel,
body[data-mode='read'] .checklist,
body[data-mode='read'] .two-col,
body[data-mode='read'] .quotes,
body[data-mode='read'] .tick-list,
body[data-mode='read'] .takeover-detail,
body[data-mode='read'] .row,
body[data-mode='read'] .bar-row,
body[data-mode='read'] .vote-option,
body[data-mode='read'] .check,
body[data-mode='read'] .slide[data-layout='bignum-takeover'].is-compact .takeover-strip {
  grid-template-columns: 1fr;
  gap: 16px;
}

body[data-mode='read'] .split .figure,
body[data-mode='read'] .split[data-flip='true'] .figure {
  order: 0;
}

body[data-mode='read'] .bignum-pair .divider {
  display: none;
}

body[data-mode='read'] .check {
  grid-template-columns: 40px 1fr;
  gap: 12px;
}

body[data-mode='read'] .check-num {
  font-size: 26px;
}

body[data-mode='read'] .vote-option {
  grid-template-columns: 24px 1fr;
  gap: 10px;
}

body[data-mode='read'] .vote-tally,
body[data-mode='read'] .vote-hint {
  display: none;
}

body[data-mode='read'] .vote-bar {
  height: 34px;
}

body[data-mode='read'] .vote-bar-fill {
  width: 0;
}

body[data-mode='read'] .vote-bar-label {
  font-size: 16px;
  white-space: normal;
  position: static;
  transform: none;
  display: flex;
  align-items: center;
  height: 100%;
  padding-left: 12px;
}

body[data-mode='read'] .row {
  padding: 18px 0;
}

body[data-mode='read'] .bar-figure {
  text-align: left;
  font-size: 40px;
}

body[data-mode='read'] .bar-track {
  height: 34px;
}

body[data-mode='read'] .bar-caption {
  font-size: 15px;
  left: 12px;
}

body[data-mode='read'] .bar-caption[data-outside='true'] {
  right: 12px;
}

body[data-mode='read'] .callout,
body[data-mode='read'] .takeover {
  margin-top: 28px;
  gap: 24px;
}

body[data-mode='read'] .big-question {
  font-size: 26px;
  text-align: left;
  margin: 0;
}

body[data-mode='read'] .chain {
  margin: 24px 0 0;
  justify-items: start;
  font-size: 19px;
  gap: 10px;
}

body[data-mode='read'] .closing-lines p {
  font-size: 24px;
}

body[data-mode='read'] .strip {
  flex-direction: column;
  gap: 8px;
}

body[data-mode='read'] .notes {
  display: block;
}

body[data-mode='read'] details.notes {
  margin: 24px 0 8px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 12px 16px;
  background: var(--surface);
  font-size: 16px;
  line-height: 1.5;
  color: var(--cream);
}

body[data-mode='read'] details.notes summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  color: var(--steel);
}

body[data-mode='read'] details.notes[open] summary {
  margin-bottom: 10px;
  color: var(--cyan);
}

body[data-mode='read'] .appendix {
  display: block;
  padding: 56px 0;
  border-top: 1px solid var(--rule);
}

body[data-mode='read'] .overlay,
body[data-mode='read'] .read-hint {
  display: none;
}

body[data-mode='read'] .wide-scroll {
  overflow-x: auto;
}

.read-hint {
  position: fixed;
  right: 18px;
  bottom: 14px;
  z-index: 20;
  font-size: 13px;
  color: var(--steel);
}

/* 11  Print -------------------------------------------------------------- */

@page {
  size: A4 landscape;
  margin: 0;
}

@media print {
  :root {
    --dur-slide: 0ms;
    --dur-build: 0ms;
    --dur-march: 0ms;
    --dur-label: 0ms;
    --dur-bar: 0ms;
    --dur-draw: 0ms;
    --dur-fadeout: 0ms;
  }

  body {
    background: var(--navy-deep);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .deck {
    position: static;
    display: block;
    overflow: visible;
  }

  .stage {
    width: 1920px;
    height: auto;
    transform: none;
  }

  .slide {
    position: relative;
    inset: auto;
    opacity: 1;
    visibility: visible;
    width: 1920px;
    height: 1080px;
    page-break-after: always;
    break-after: page;
    transform: scale(var(--print-scale, 1));
    transform-origin: top left;
  }

  [data-build] {
    opacity: 1;
    visibility: visible;
  }

  .march {
    opacity: 1;
  }

  .march.is-dissolved {
    opacity: 1;
  }

  .march-marker,
  .draw-once {
    animation: none;
    transition: none;
  }

  .overlay,
  .read-hint {
    display: none !important;
  }

  .notes {
    display: none;
  }

  body.print-notes .notes,
  body.print-notes details.notes {
    display: block;
    width: 1920px;
    padding: 40px 96px 80px;
    font-size: 26px;
    line-height: 1.5;
    color: var(--cream);
    page-break-after: always;
    break-after: page;
  }

  body.print-notes details.notes summary {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--steel);
    margin-bottom: 16px;
  }

  .appendix {
    display: block;
    width: 1920px;
    padding: 76px 96px;
  }
}

/* ==========================================================================
   Additions
   ========================================================================== */

/* For a slide that carries its sources in the S panel only, not on the slide.
   No slide uses it at the moment. */
.source-panel-only {
  display: none;
}

body[data-mode='read'] .source-panel-only {
  display: block;
}

/* Overview thumbnails are pictures of slides, never interactive copies. */
.thumb-canvas,
.thumb-canvas * {
  pointer-events: none;
}

/* The act label grows quickly on arrival and settles slowly. */
.march-stop > span {
  transition:
    transform 300ms var(--ease-march),
    color 300ms linear;
}

.march-stop.is-arriving > span {
  transition-duration: var(--dur-label);
}

@media (prefers-reduced-motion: reduce) {
  .march-stop > span {
    transition-duration: 0ms;
  }
}

/* ==========================================================================
   12  Presenter view (index.html?presenter=1)
   ========================================================================== */

body[data-view='presenter'] .deck,
body[data-view='presenter'] .read-hint,
body[data-view='presenter'] .appendix {
  display: none;
}

.presenter {
  position: fixed;
  inset: 0;
  display: grid;
  /* The preview column is exactly as wide as two stacked 16:9 frames can be in
     the height available, so the notes get every pixel left over. */
  grid-template-columns: min(calc((100vh - 192px) * 8 / 9), 62%) minmax(360px, 1fr);
  gap: 24px;
  padding: 24px;
  background: var(--navy-deep);
  font-size: 16px;
  line-height: 1.4;
  overflow: hidden;
}

.pv-previews {
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: 20px;
  min-width: 0;
  min-height: 0;
}

.pv-box {
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 8px;
  min-height: 0;
}

.pv-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  color: var(--steel);
  letter-spacing: 0.02em;
}

.pv-frame {
  /* Driven by the height the row gives it, so two previews plus their labels
     always fit the window rather than pushing each other off the bottom. */
  position: relative;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--navy-deep);
}

.pv-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 1920px;
  height: 1080px;
  transform-origin: top left;
}

.pv-inner .stage {
  transform: none;
  position: absolute;
  top: 0;
  left: 0;
}

.pv-inner .slide {
  transition: none;
}

.pv-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pv-side {
  display: grid;
  /* Notes get a floor: they are the one thing on this screen that must never
     be squeezed to nothing by a long vote panel. */
  grid-template-rows: auto auto minmax(180px, 1fr) auto auto;
  gap: 16px;
  min-height: 0;
  overflow: hidden;
}

.pv-clocks {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.pv-clock {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 12px 16px;
  display: grid;
  gap: 2px;
}

.pv-clock-label {
  font-size: 13px;
  color: var(--steel);
}

.pv-clock-value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 34px;
  line-height: 1.1;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

.pv-notes {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 8px;
  min-height: 0;
}

.pv-notes-text {
  overflow-y: auto;
  font-size: 21px;
  line-height: 1.45;
  color: var(--cream);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 16px 18px;
}

.pv-votes {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 14px 16px;
  display: grid;
  gap: 10px;
}

.pv-votes .vote-question {
  font-size: 17px;
}

.pv-votes .vote-option {
  grid-template-columns: 22px 1fr 92px;
  gap: 12px;
}

.pv-votes .vote-bar {
  height: 30px;
}

.pv-votes .vote-bar-label {
  font-size: 16px;
  left: 12px;
}

.pv-votes .vote-tally {
  font-size: 16px;
}

.pv-votes .vote-key {
  font-size: 15px;
}

.pv-votes .vote-hint {
  font-size: 13px;
}

.pv-controls {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.pv-button {
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  padding: 12px 10px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  color: var(--cream);
  background: var(--surface);
}

.pv-button:hover {
  border-color: var(--cyan);
}

.pv-button-primary {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--navy-deep);
}

@media (max-width: 1080px) {
  .presenter {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    overflow: auto;
  }

  .pv-previews {
    grid-template-rows: auto auto;
  }
}


/* Row label widths vary by slide. These are classes, not inline styles, so the
   reading-mode single-column override can actually win. */
.row-wide {
  grid-template-columns: 340px 1fr;
}

.row-wider {
  grid-template-columns: 380px 1fr;
}

.row-widest {
  grid-template-columns: 420px 1fr;
}

.bignum-feature {
  font-size: 150px;
}

.bignum-label-wide {
  max-width: 18em;
}

body[data-mode='read'] .row-wide,
body[data-mode='read'] .row-wider,
body[data-mode='read'] .row-widest {
  grid-template-columns: 1fr;
}

body[data-mode='read'] .bignum-feature {
  font-size: var(--fs-bignum);
}

/* Long numbers ("15.9 million") must fit a phone, so reading mode sizes them
   against the viewport rather than at a fixed px. */
body[data-mode='read'] .slide {
  --fs-bignum: clamp(30px, 8vw, 58px);
}

body[data-mode='read'] .bignum,
body[data-mode='read'] .bar-figure,
body[data-mode='read'] .closing-lines p,
body[data-mode='read'] .big-question,
body[data-mode='read'] .hero-title,
body[data-mode='read'] .chain li {
  overflow-wrap: break-word;
  hyphens: auto;
}

body[data-mode='read'] .diagram,
body[data-mode='read'] .figure svg {
  max-width: 100%;
}

/* Slide 16 hangs its numbers off `justify-items: start` on stage, which sizes
   them to max-content. On a phone that has to stretch instead. */
body[data-mode='read'] .takeover {
  justify-items: stretch;
}

body[data-mode='read'] .takeover-strip,
body[data-mode='read'] .takeover-figure,
body[data-mode='read'] .takeover-detail {
  width: 100%;
  min-width: 0;
}

body[data-mode='read'] .bignum-label {
  max-width: none;
}

body[data-mode='read'] .takeover-figure .bignum {
  font-size: var(--fs-bignum);
}

body[data-mode='read'] .takeover-figure .bignum-label {
  font-size: var(--fs-bignum-label);
}

/* The question-chain layout no longer belongs to a slide: slide 17 reuses its
   .big-question and .chain styles with its own sizes. These are kept for a
   slide that gives the question a page of its own again. */
.slide[data-layout='question-chain'] .big-question {
  font-size: 54px;
}

.slide[data-layout='question-chain'] .chain {
  margin-top: 44px;
  gap: 10px;
  font-size: 32px;
}

.slide[data-layout='question-chain'] .vote {
  margin-top: 40px;
  gap: 12px;
}

.slide[data-layout='question-chain'] .vote-options {
  gap: 10px;
}

.slide[data-layout='question-chain'] .vote-bar {
  height: 40px;
}

.slide[data-layout='question-chain'] .vote-question {
  font-size: 30px;
}

/* Diagram-led slides centre their figure in the space the title leaves, rather
   than hanging it from the top with a void underneath. */
.slide[data-layout='timeline'] .slide-body,
.slide[data-layout='staircase'] .slide-body,
.slide[data-layout='ridge'] .slide-body {
  justify-content: center;
}

/* Slide 16's three figures are read left to right, so they align on their
   numbers, not on the ragged bottoms of their labels. */
.slide[data-layout='bignum-takeover'].is-compact .takeover-strip {
  align-items: start;
}

/* Slide 16 centres its content in the space it has, in every build. The three
   full-slide numbers and the final compact layout share one vertical anchor. */
.slide[data-layout='bignum-takeover'] .slide-body {
  justify-content: center;
}

/* A stowed build is out of the layout entirely, not just invisible. Only the
   slide 21 takeover uses this; everywhere else hidden builds keep their space
   so that revealing a step never reflows the slide. */
.slide .is-stowed {
  display: none;
}

/* Before the strip collapses, each number owns the whole slide. */
body[data-mode='present'] .slide[data-layout='bignum-takeover']:not(.is-compact) .takeover-figure .bignum-label {
  font-size: 34px;
  max-width: 20em;
  margin-top: 28px;
}

/* Several components set an explicit display, which beats the user-agent rule
   for [hidden]. Without this, hiding a panel does nothing. */
[hidden] {
  display: none !important;
}

/* Slide 8 gives its bars the room to carry the 88-versus-6 gap. */
.slide[data-layout='bars'] .bars {
  gap: 44px;
}

.slide[data-layout='bars'] .bar-track {
  height: 76px;
}

.slide[data-layout='bars'] .bar-figure {
  font-size: 80px;
}

.slide[data-layout='bars'] .strip {
  margin-top: 64px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

/* Wide diagrams: full width on stage, a contained scroller when reading. */
.diagram-scroll {
  width: 100%;
}

.diagram-hint {
  display: none;
}

body[data-mode='read'] .diagram-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 6px;
}

body[data-mode='read'] .diagram-scroll .diagram {
  min-width: 760px;
}

body[data-mode='read'] .diagram-hint {
  display: block;
  margin-top: 6px;
  font-size: 13px;
  color: var(--steel);
}

/* Reading mode keeps the number louder than its caption. */
body[data-mode='read'] .slide {
  --fs-bignum: clamp(34px, 9vw, 58px);
  --fs-bignum-label: 19px;
}

/* The roadmap is a footer for slide 2, not a third item stacked under the
   numbers, so it sits at the foot of the content area. */
.slide[data-layout='bignum-pair'] .roadmap {
  margin-top: auto;
}

/* ==========================================================================
   13  Backdrops
   The cinematic layer. Everything here sits behind a scrim heavy enough that
   cream on navy keeps its contrast, and everything stops under reduced motion.
   ========================================================================== */

.backdrops {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.backdrop {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 600ms linear;
}

.backdrops[data-kind='aurora'] .backdrop-aurora,
.backdrops[data-kind='grid'] .backdrop-grid,
.backdrops[data-kind='particles'] .backdrop-particles,
.backdrops[data-kind='dots'] .backdrop-dots,
.backdrops[data-kind='video'] .backdrop-video {
  opacity: 1;
}

/* The scrim is what makes any of this safe to put text on. */
.backdrop-scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--navy-deep) 76%, transparent);
}

.backdrops[data-kind='none'] .backdrop-scrim {
  background: var(--navy-deep);
}

.backdrops[data-kind='video'] .backdrop-scrim {
  /* Footage is busier than a drawn backdrop, so it carries a heavier scrim. */
  background: color-mix(in srgb, var(--navy-deep) 86%, transparent);
}

/* Aurora: drifting colour fields, palette only, no third hue. */
.aurora-blob {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.7;
  background: var(--cyan-muted);
  animation: aurora-drift 26s var(--delay, 0s) ease-in-out infinite alternate;
}

.aurora-blob:nth-child(2) {
  background: var(--cyan);
  opacity: 0.42;
}

.aurora-blob:nth-child(3) {
  background: var(--navy-brand);
  opacity: 0.9;
}

.aurora-blob:nth-child(4) {
  background: var(--cyan-muted);
  opacity: 0.55;
}

@keyframes aurora-drift {
  from {
    transform: translate3d(-4%, -3%, 0) scale(1);
  }
  to {
    transform: translate3d(5%, 4%, 0) scale(1.15);
  }
}

/* Grid: a perspective floor running toward a horizon. */
.grid-floor {
  position: absolute;
  left: -60%;
  right: -60%;
  bottom: -10%;
  height: 78%;
  background-image:
    repeating-linear-gradient(
      to right,
      color-mix(in srgb, var(--cyan) 46%, transparent) 0 1px,
      transparent 1px 90px
    ),
    repeating-linear-gradient(
      to bottom,
      color-mix(in srgb, var(--cyan) 38%, transparent) 0 1px,
      transparent 1px 64px
    );
  transform: perspective(520px) rotateX(74deg);
  transform-origin: bottom center;
  animation: grid-run 9s linear infinite;
}

@keyframes grid-run {
  from {
    background-position:
      0 0,
      0 0;
  }
  to {
    background-position:
      0 0,
      0 64px;
  }
}

.grid-horizon {
  position: absolute;
  left: 0;
  right: 0;
  top: 18%;
  height: 46%;
  background: radial-gradient(
    ellipse at 50% 100%,
    color-mix(in srgb, var(--cyan) 42%, transparent),
    transparent 68%
  );
}

/* Particles: a canvas constellation, drawn only while it is on screen. */
.backdrop-particles,
.backdrop-dots {
  width: 100%;
  height: 100%;
  display: block;
}

/* The dot field carries a lighter scrim than the other backdrops: its dots are
   small and sparse, and at 76% they disappeared. */
.backdrops[data-kind='dots'] .backdrop-scrim {
  background: color-mix(in srgb, var(--navy-deep) 42%, transparent);
}

/* Video: full bleed, ambient, always behind the scrim. */
.backdrop-video video {
  /* Overscaled so a blurred video's soft edges never show against the stage. */
  width: 106%;
  height: 106%;
  margin: -3% 0 0 -3%;
  object-fit: cover;
  display: block;
  filter: saturate(0.55) contrast(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .aurora-blob,
  .grid-floor {
    animation: none;
  }

  .backdrop {
    transition: none;
  }
}

/* ==========================================================================
   14  On-screen controls
   ========================================================================== */

.controls {
  position: absolute;
  right: var(--pad-x);
  bottom: 92px;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* These stay at full opacity. Fading them when idle looked tidier on a
   projector and failed contrast outright, so they are kept quiet by colour and
   size instead: a small outline button, a steel counter, one cyan primary. */

/* The music button shows only on the title slide. .control-btn sets its own
   display, so the hidden attribute needs saying again here. */
.control-btn[hidden] {
  display: none;
}

.control-music .icon-muted,
.control-music[aria-pressed='true'] .icon-sound {
  display: none;
}

/* The deck opens muted: the Unmute button glows in cyan, over and over, until
   it is first pressed (js/music.js removes .is-inviting). For anyone who has
   asked for less motion it holds a steady cyan outline instead. */
.control-music.is-inviting {
  border-color: var(--cyan);
  color: var(--cyan);
  animation: music-invite 1600ms ease-in-out infinite;
}

@keyframes music-invite {
  0%,
  100% {
    background: var(--navy-brand);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--cyan) 0%, transparent);
  }
  50% {
    background: color-mix(in srgb, var(--cyan) 28%, var(--navy-brand));
    box-shadow: 0 0 18px 4px color-mix(in srgb, var(--cyan) 55%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .control-music.is-inviting {
    animation: none;
    box-shadow: 0 0 0 2px var(--cyan);
  }
}

.control-music[aria-pressed='true'] .icon-muted {
  display: block;
}

.control-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  background: var(--navy-brand);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  color: var(--cream);
  transition:
    border-color 160ms linear,
    background-color 160ms linear;
}

.control-btn svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.control-btn:hover {
  border-color: var(--cyan);
}

.control-btn[disabled] {
  opacity: 0.35;
  cursor: default;
}

.control-btn[disabled]:hover {
  border-color: var(--rule-strong);
}

/* Restart is a rewind, not a step, so it sits quieter than the other two. */
.control-btn-quiet {
  background: transparent;
  color: var(--steel);
}

.control-btn-quiet:hover {
  color: var(--cream);
}

.control-btn-primary {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--navy-deep);
}

.control-btn-primary:hover {
  background: var(--cream);
  border-color: var(--cream);
}

.control-meter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

.control-position {
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--steel);
  font-variant-numeric: tabular-nums;
  min-width: 54px;
  text-align: center;
}

/* A 2px line that empties as the next build step approaches. It is the only
   sign that the deck is running itself, and it is what separates a slide still
   unfolding from one that has finished and is waiting on the speaker. It sits
   at nothing when the clock is not running, so a paused deck shows no chrome. */
.control-clock {
  width: 54px;
  height: 2px;
  background: var(--cyan);
  transform: scaleX(0);
  transform-origin: left center;
}

.controls[data-auto='running'] .control-clock {
  animation: build-clock var(--step, 7000ms) linear forwards;
}

@keyframes build-clock {
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
}

body.is-blanked .controls,
body.is-blanked .backdrops {
  visibility: hidden;
}

body[data-mode='read'] .controls,
body[data-view='presenter'] .controls {
  display: none;
}

body[data-mode='read'] .backdrops {
  display: none;
}

@media print {
  .controls,
  .backdrops {
    display: none !important;
  }

  .slide-number {
    display: block;
  }

  .slide.is-leaving {
    display: none;
  }

  .slide.is-active,
  .slide {
    animation: none !important;
  }
}

/* ==========================================================================
   14b  Slide 2: the usage curve draws itself
   The curve is evidence for the 900 million above it, so it is keyed to that
   build step rather than to the slide: it draws when the number arrives, and
   each milestone lands as the line reaches it.
   ========================================================================== */

.usage-chart {
  --chart-dur: 2600ms;
}

/* Both columns carry a figure under their number, and they have to end level
   or the slide reads as lopsided. The label reserves three lines whether it
   needs them or not, so both figures start at the same height. */
.bignum-pair .bignum-label {
  min-height: 3.9em;
}

.bignum-pair .diagram {
  margin-top: 20px;
}

.chart-rule {
  stroke: var(--rule);
  stroke-width: 1;
}

.chart-base {
  stroke: var(--rule-strong);
  stroke-width: 1.5;
}

.chart-line {
  fill: none;
  stroke: var(--cyan);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* pathLength="1" in the markup makes the dash units fractions of the whole
     line, so it can be timed without measuring it. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

/* The reported approach to a billion, which OpenAI has not confirmed. Drawn
   as a dash so it reads as a different kind of claim from the rest of the line. */
.chart-forecast {
  fill: none;
  stroke: color-mix(in srgb, var(--cyan) 62%, transparent);
  stroke-width: 2.5;
  stroke-dasharray: 9 8;
  stroke-linecap: round;
  opacity: 0;
}

.chart-open {
  fill: var(--navy-deep);
  stroke: color-mix(in srgb, var(--cyan) 62%, transparent);
  stroke-width: 2;
  opacity: 0;
}

.chart-point {
  opacity: 0;
}

.usage-chart.is-revealed .chart-line {
  animation: chart-draw var(--chart-dur) ease-out forwards;
}

.usage-chart.is-revealed .chart-point {
  animation: chart-land 420ms ease-out calc(var(--at) * var(--chart-dur)) forwards;
}

.usage-chart.is-revealed .chart-forecast,
.usage-chart.is-revealed .chart-open {
  animation: chart-land 560ms linear calc(var(--chart-dur) + 160ms) forwards;
}

@keyframes chart-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes chart-land {
  to {
    opacity: 1;
  }
}

/* Anywhere the slide is shown rather than presented, the curve is simply
   finished. These overrides carry .is-revealed so they match the specificity of
   the rules above; without that the animation keeps running underneath them. */
.thumb-canvas .usage-chart.is-revealed .chart-line,
.pv-inner .usage-chart.is-revealed .chart-line,
body[data-mode='read'] .usage-chart.is-revealed .chart-line {
  stroke-dashoffset: 0;
  animation: none;
}

.thumb-canvas .usage-chart.is-revealed .chart-point,
.pv-inner .usage-chart.is-revealed .chart-point,
.thumb-canvas .usage-chart.is-revealed .chart-forecast,
.pv-inner .usage-chart.is-revealed .chart-forecast,
.thumb-canvas .usage-chart.is-revealed .chart-open,
.pv-inner .usage-chart.is-revealed .chart-open,
body[data-mode='read'] .usage-chart.is-revealed .chart-point,
body[data-mode='read'] .usage-chart.is-revealed .chart-forecast,
body[data-mode='read'] .usage-chart.is-revealed .chart-open {
  opacity: 1;
  animation: none;
}

@media print {
  .chart-line {
    stroke-dashoffset: 0 !important;
  }

  .chart-point,
  .chart-forecast,
  .chart-open {
    opacity: 1 !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .usage-chart.is-revealed .chart-line {
    stroke-dashoffset: 0;
    animation: none;
  }

  .usage-chart.is-revealed .chart-point,
  .usage-chart.is-revealed .chart-forecast,
  .usage-chart.is-revealed .chart-open {
    opacity: 1;
    animation: none;
  }
}

/* ==========================================================================
   14c  Slide 2: ninety five burst, five take root
   One hundred dots for one hundred pilots. The whole run is one shared timeline
   so the phases stay in step: the block arrives, the ninety five go red and
   burst outwards from the middle, and the five that are left turn green, fall
   clear of the wreckage and grow.

   Each element carries at most one animation per property. That is why a cell
   is a group that only ever animates transform, with a dot inside it that only
   ever animates fill and opacity: two animations competing for transform on the
   same element is how the entrance gets eaten by the burst.
   ========================================================================== */

.pop-field {
  /* One timeline, shared by every cell. --d rolls the wave outward from the
     centre of the block, so a dot's whole life is offset, entrance included. */
  --pop-dur: 7000ms;
}

.pop-cell {
  transform-origin: var(--ox) var(--oy);
  transform: scale(0);
}

.pop-dot {
  fill: color-mix(in srgb, var(--steel) 62%, transparent);
  opacity: 0;
}

.pop-ground {
  stroke: var(--rule-strong);
  stroke-width: 1.5;
  opacity: 0;
}

.pop-stem {
  fill: none;
  stroke: var(--shoot);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.pop-leaf {
  fill: color-mix(in srgb, var(--shoot) 82%, var(--navy-deep));
  transform-box: fill-box;
  transform-origin: left center;
  transform: scale(0);
}

.pop-field.is-revealed .pop-cell {
  animation: pop-cell var(--pop-dur) linear var(--d) both;
}

.pop-field.is-revealed .pop-seed {
  animation-name: pop-seed-cell;
}

.pop-field.is-revealed .pop-dot {
  animation: pop-dot var(--pop-dur) linear var(--d) both;
}

.pop-field.is-revealed .pop-seed .pop-dot {
  animation-name: pop-seed-dot;
}

.pop-field.is-revealed .pop-ground {
  animation: pop-fade 600ms linear 2800ms forwards;
}

.pop-field.is-revealed .pop-stem {
  animation: pop-grow 1600ms ease-out calc(4300ms + var(--sd)) forwards;
}

.pop-field.is-revealed .pop-leaf {
  animation: pop-unfurl 520ms ease-out calc(5100ms + var(--ld)) forwards;
}

/* The doomed ninety five. Percentages of the shared 7000ms run: in by 5 per
   cent, held, then out between 30 and 36. */
@keyframes pop-cell {
  0% {
    transform: scale(0);
  }

  5% {
    transform: scale(1);
  }

  30% {
    transform: scale(1.12);
  }

  36%,
  100% {
    transform: scale(2.1);
  }
}

@keyframes pop-dot {
  0% {
    opacity: 0;
    fill: color-mix(in srgb, var(--steel) 62%, transparent);
  }

  5%,
  20% {
    opacity: 1;
    fill: color-mix(in srgb, var(--steel) 62%, transparent);
  }

  26%,
  30% {
    opacity: 1;
    fill: var(--ember);
  }

  36%,
  100% {
    opacity: 0;
    fill: var(--ember);
  }
}

/* The five. They hold their place while the rest burst, then fall clear and
   settle, which is the part that has to look like weight rather than a slide. */
@keyframes pop-seed-cell {
  0% {
    transform: scale(0) translate(0, 0);
  }

  5%,
  36% {
    transform: scale(1) translate(0, 0);
    animation-timing-function: cubic-bezier(0.45, 0, 0.85, 0.5);
  }

  50% {
    transform: scale(1) translate(var(--dx), var(--dy));
    animation-timing-function: ease-out;
  }

  53% {
    transform: scale(1) translate(var(--dx), calc(var(--dy) - 14px));
  }

  57%,
  100% {
    transform: scale(1) translate(var(--dx), var(--dy));
  }
}

@keyframes pop-seed-dot {
  0% {
    opacity: 0;
    fill: color-mix(in srgb, var(--steel) 62%, transparent);
  }

  5%,
  20% {
    opacity: 1;
    fill: color-mix(in srgb, var(--steel) 62%, transparent);
  }

  26%,
  100% {
    opacity: 1;
    fill: var(--shoot);
  }
}

@keyframes pop-fade {
  to {
    opacity: 1;
  }
}

@keyframes pop-grow {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes pop-unfurl {
  to {
    transform: scale(1);
  }
}

/* Shown rather than presented. The animation is the argument, so the still has
   to make it without moving: all ninety five held red in the block, the five
   gone from it and grown at the foot. An empty frame with five seedlings in it
   would mean nothing to a reader who never saw the burst. The overrides carry
   .is-revealed to match the specificity of the rules above. */
.thumb-canvas .pop-field.is-revealed .pop-cell,
.pv-inner .pop-field.is-revealed .pop-cell,
body[data-mode='read'] .pop-field.is-revealed .pop-cell {
  animation: none;
  transform: scale(1);
}

.thumb-canvas .pop-field.is-revealed .pop-seed,
.pv-inner .pop-field.is-revealed .pop-seed,
body[data-mode='read'] .pop-field.is-revealed .pop-seed {
  transform: translate(var(--dx), var(--dy));
}

.thumb-canvas .pop-field.is-revealed .pop-dot,
.pv-inner .pop-field.is-revealed .pop-dot,
body[data-mode='read'] .pop-field.is-revealed .pop-dot {
  animation: none;
  /* Held back, so the block reads as what was there and the five that grew
     through it stay in front. At full strength the two compete. */
  opacity: 0.45;
  fill: var(--ember);
}

.thumb-canvas .pop-field.is-revealed .pop-seed .pop-dot,
.pv-inner .pop-field.is-revealed .pop-seed .pop-dot,
body[data-mode='read'] .pop-field.is-revealed .pop-seed .pop-dot {
  opacity: 1;
  fill: var(--shoot);
}

.thumb-canvas .pop-field.is-revealed .pop-ground,
.pv-inner .pop-field.is-revealed .pop-ground,
body[data-mode='read'] .pop-field.is-revealed .pop-ground,
.thumb-canvas .pop-field.is-revealed .pop-leaf,
.pv-inner .pop-field.is-revealed .pop-leaf,
body[data-mode='read'] .pop-field.is-revealed .pop-leaf {
  animation: none;
  opacity: 1;
  transform: scale(1);
}

.thumb-canvas .pop-field.is-revealed .pop-stem,
.pv-inner .pop-field.is-revealed .pop-stem,
body[data-mode='read'] .pop-field.is-revealed .pop-stem {
  animation: none;
  stroke-dashoffset: 0;
}

@media print {
  .pop-cell {
    animation: none !important;
    transform: scale(1) !important;
  }

  .pop-seed {
    transform: translate(var(--dx), var(--dy)) !important;
  }

  .pop-dot {
    animation: none !important;
    opacity: 0.45 !important;
    fill: var(--ember) !important;
  }

  .pop-seed .pop-dot {
    opacity: 1 !important;
    fill: var(--shoot) !important;
  }

  .pop-ground,
  .pop-leaf {
    animation: none !important;
    opacity: 1 !important;
    transform: scale(1) !important;
  }

  .pop-stem {
    animation: none !important;
    stroke-dashoffset: 0 !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pop-field.is-revealed .pop-cell {
    animation: none;
    transform: scale(1);
  }

  .pop-field.is-revealed .pop-seed {
    transform: translate(var(--dx), var(--dy));
  }

  .pop-field.is-revealed .pop-dot {
    animation: none;
    opacity: 0.45;
    fill: var(--ember);
  }

  .pop-field.is-revealed .pop-seed .pop-dot {
    opacity: 1;
    fill: var(--shoot);
  }

  .pop-field.is-revealed .pop-ground,
  .pop-field.is-revealed .pop-leaf {
    animation: none;
    opacity: 1;
    transform: scale(1);
  }

  .pop-field.is-revealed .pop-stem {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* ==========================================================================
   14d  Slide 4: the swarm walks up the staircase
   The canvas sits behind the diagram and matches it exactly, so a shape drawn
   at 1085, 452 in canvas units lands under step 3 in the diagram's units. It is
   decorative and aria-hidden: everything it says, the step beside it says in
   words. A cloned canvas is dead, so the thumbnails, the presenter previews,
   print and the reading version simply do without it.
   ========================================================================== */

.swarm-stage {
  position: relative;
}

.swarm-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.thumb-canvas .swarm-canvas,
.pv-inner .swarm-canvas,
body[data-mode='read'] .swarm-canvas {
  display: none;
}

/* Slide 5, last build: the drawn ridge fades as the dots blur it, so the
   frontier really does stop being visible. Presenting only; everywhere the
   canvas cannot run, the line stays. */
body[data-mode='present'] #s05 :is(.ridge-line, .ridge-glint-fade) {
  transition: opacity 1.6s ease;
}

body[data-mode='present'] #s05:has(.pull.is-revealed) .ridge-line {
  opacity: 0.12;
}

body[data-mode='present'] #s05:has(.pull.is-revealed) .ridge-glint-fade {
  opacity: 0;
}

/* The light that runs along the ridge: a short bright dash on the line,
   travelling left to right. Hidden until the line has drawn, and anywhere the
   animation does not run. */
.ridge-glint {
  fill: none;
  stroke: var(--cream);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 0.035 0.965;
  opacity: 0;
  filter: drop-shadow(0 0 6px var(--cyan));
}

@keyframes glint-run {
  from {
    stroke-dashoffset: 0.035;
  }
  to {
    stroke-dashoffset: -0.965;
  }
}

@keyframes glint-show {
  to {
    opacity: 1;
  }
}

@keyframes drop-in {
  from {
    opacity: 0;
    transform: translateY(-18px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Slide 5, build 2: the frontier draws itself left to right, the inside fills
   in beneath it, and the capabilities arrive along it: the ones AI cannot do
   yet drop in from above, the ones it can rise from below. Then the light
   starts running along the ridge. */
@media screen and (prefers-reduced-motion: no-preference) {
  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .ridge-line {
    stroke-dasharray: 1;
    animation: draw-line 1800ms ease-in-out both;
  }

  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .ridge-fill {
    animation: fade-in 900ms ease-out 900ms both;
  }

  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .frontier-out text {
    animation: drop-in 600ms var(--ease-march, ease-out) both;
  }

  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .frontier-in text {
    animation: rise-in 600ms var(--ease-march, ease-out) both;
  }

  body[data-mode='present']
    #s05
    .frontier-diagram.is-revealed
    :is(.frontier-out, .frontier-in)
    text:nth-child(1) {
    animation-delay: 500ms;
  }

  body[data-mode='present']
    #s05
    .frontier-diagram.is-revealed
    :is(.frontier-out, .frontier-in)
    text:nth-child(2) {
    animation-delay: 900ms;
  }

  body[data-mode='present']
    #s05
    .frontier-diagram.is-revealed
    :is(.frontier-out, .frontier-in)
    text:nth-child(3) {
    animation-delay: 1300ms;
  }

  body[data-mode='present']
    #s05
    .frontier-diagram.is-revealed
    :is(.frontier-out, .frontier-in)
    text:nth-child(4) {
    animation-delay: 1700ms;
  }

  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .frontier-key {
    animation: fade-in 700ms ease-out 1900ms both;
  }

  body[data-mode='present'] #s05 .frontier-diagram.is-revealed .ridge-glint {
    animation:
      glint-show 400ms ease-out 1900ms forwards,
      glint-run 3200ms linear 1900ms infinite;
  }
}

/* Thumbnails and presenter previews have no dots to take the line's place. */
.thumb-canvas .ridge-line,
.pv-inner .ridge-line {
  opacity: 1 !important;
}

@media print {
  .swarm-canvas {
    display: none !important;
  }
}

/* ==========================================================================
   14f  Slides 5 and 7 to 16: dots and builds that move
   Each of these slides gets a canvas of its own the size of the whole slide,
   made by the script, for the dot shapes. It sits above the words like slide
   10's does, and every shape is placed where no words are.

   The build animations below all key off .is-revealed, and all are written
   against the slide's id. Thumbnails and the presenter's previews are clones
   with their ids stripped, so none of this reaches them; reading mode and
   print are excluded by the mode and the media query; and nothing here hides
   anything by default, so wherever an animation does not run, the content is
   simply there.
   ========================================================================== */

.swarm-full {
  z-index: 2;
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes pop-in {
  from {
    transform: scale(0);
  }
  70% {
    transform: scale(1.25);
  }
  to {
    transform: scale(1);
  }
}

@keyframes draw-line {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes ping {
  0%,
  30%,
  100% {
    transform: scale(1);
  }
  8% {
    transform: scale(1.7);
  }
}

@keyframes glow {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

@media screen and (prefers-reduced-motion: no-preference) {
  /* Several things arriving on one build come in one after another. */
  body[data-mode='present'] #s11 .range-list-foot.is-revealed li,
  body[data-mode='present'] #s15 .col.is-revealed li,
  body[data-mode='present'] #s15 .quotes.is-revealed blockquote,
  body[data-mode='present'] #s17 .closing-lines.is-revealed p,
  body[data-mode='present'] #s17 .closing-meta.is-revealed,
  body[data-mode='present'] :is(#s13, #s16) .is-revealed .vote-option,
  body[data-mode='present'] #s13 .vote.is-revealed .vote-option {
    animation: rise-in 600ms var(--ease-march, ease-out) both;
  }

  body[data-mode='present'] :is(#s11, #s15) .is-revealed li:nth-child(2),
  body[data-mode='present'] #s15 .quotes.is-revealed blockquote:nth-child(2) {
    animation-delay: 350ms;
  }

  body[data-mode='present'] :is(#s11, #s15) .is-revealed li:nth-child(3) {
    animation-delay: 700ms;
  }

  body[data-mode='present'] :is(#s11, #s15) .is-revealed li:nth-child(4) {
    animation-delay: 1050ms;
  }

  body[data-mode='present'] :is(#s15) .is-revealed li:nth-child(5) {
    animation-delay: 1400ms;
  }

  body[data-mode='present'] :is(#s13, #s16) .is-revealed .vote-option:nth-child(2) {
    animation-delay: 120ms;
  }

  body[data-mode='present'] :is(#s13, #s16) .is-revealed .vote-option:nth-child(3) {
    animation-delay: 240ms;
  }

  body[data-mode='present'] :is(#s13, #s16) .is-revealed .vote-option:nth-child(4) {
    animation-delay: 360ms;
  }

  body[data-mode='present'] :is(#s13, #s16) .is-revealed .vote-option:nth-child(5) {
    animation-delay: 480ms;
  }

  /* Slide 17, build 2: the two closing lines, then the name. */
  body[data-mode='present'] #s17 .closing-lines.is-revealed p:nth-child(2) {
    animation-delay: 900ms;
  }

  body[data-mode='present'] #s17 .closing-meta.is-revealed {
    animation-delay: 1800ms;
  }

  /* Slides 9 and 11: each number lands as its item does. */
  body[data-mode='present'] :is(#s09, #s12) .check.is-revealed .check-num {
    animation: pop-in 500ms cubic-bezier(0.3, 1.5, 0.6, 1) both;
  }

  /* Slide 11: the range draws from cautious to bullish, each estimate popping
     up as the line reaches it, and Australia keeps pulsing. */
  body[data-mode='present'] #s11 .range.is-revealed .range-line {
    stroke-dasharray: 1;
    animation: draw-line 1400ms ease-in-out both;
  }

  body[data-mode='present'] #s11 .range.is-revealed circle {
    transform-box: fill-box;
    transform-origin: center;
    animation: pop-in 500ms cubic-bezier(0.3, 1.5, 0.6, 1) 120ms both;
  }

  body[data-mode='present'] #s11 .range.is-revealed .svg-dot {
    animation:
      pop-in 500ms cubic-bezier(0.3, 1.5, 0.6, 1) 620ms both,
      ping 3200ms ease-out 1600ms infinite;
  }

  body[data-mode='present'] #s11 .range.is-revealed circle:last-of-type {
    animation-delay: 1250ms;
  }

  body[data-mode='present'] #s11 .range.is-revealed text {
    animation: fade-in 700ms ease-out 900ms both;
  }

  /* Slide 13: the five futures come in slowest first. */
  body[data-mode='present'] #s13 .spectrum.is-revealed svg > g {
    animation: rise-in 600ms var(--ease-march, ease-out) both;
  }

  body[data-mode='present'] #s13 .spectrum.is-revealed svg > g:nth-of-type(2) {
    animation-delay: 300ms;
  }

  body[data-mode='present'] #s13 .spectrum.is-revealed svg > g:nth-of-type(3) {
    animation-delay: 600ms;
  }

  body[data-mode='present'] #s13 .spectrum.is-revealed svg > g:nth-of-type(4) {
    animation-delay: 900ms;
  }

  body[data-mode='present'] #s13 .spectrum.is-revealed svg > g:nth-of-type(5) {
    animation-delay: 1200ms;
  }

  /* Slide 14: the number lands. */
  body[data-mode='present'] #s14 .figure.is-revealed .bignum-feature {
    transform-origin: left bottom;
    animation: pop-in 800ms cubic-bezier(0.3, 1.3, 0.6, 1) both;
  }
}

/* ==========================================================================
   14g  Slide 6: what AI is good at, and the jagged frontier
   The old slides 6 and 7 on one page. Four short result cards across the top,
   each with room in its top right corner for a dot-drawn icon; below them the
   ridge, drawn shorter than it used to be; then the BCG finding either side of
   the line and the pull line. The icons' dots become the ridge on build 2.
   ========================================================================== */

.good-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}

.good-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 22px 18px;
  background: color-mix(in srgb, var(--navy-brand) 84%, transparent);
  border: 1px solid var(--rule);
  border-radius: 4px;
}

.good-card h3 {
  display: flex;
  align-items: center;
  min-height: 60px;
  padding-right: 78px;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 26px;
  line-height: 1.1;
  color: var(--cyan);
}

.good-card p {
  margin: 0;
  font-size: 24px;
  line-height: 1.3;
  color: var(--cream);
}

.good-card em {
  font-style: normal;
  font-size: 17px;
  color: var(--steel);
}

.frontier-diagram {
  margin-top: 28px;
}

.frontier-findings {
  margin-top: 20px;
}

.frontier-findings p {
  margin: 0;
  font-size: 26px;
  line-height: 1.35;
}

.slide[data-layout='frontier'] .pull {
  max-width: none;
  margin-top: 16px;
  font-size: 30px;
}

/* Two slides' sources in one line would run under the on-screen controls. */
.slide[data-layout='frontier'] .source {
  max-width: 1300px;
}

@media screen and (prefers-reduced-motion: no-preference) {
  body[data-mode='present'] #s05 .good-cards.is-revealed .good-card {
    animation: rise-in 600ms var(--ease-march, ease-out) both;
  }

  body[data-mode='present'] #s05 .good-cards.is-revealed .good-card:nth-child(2) {
    animation-delay: 300ms;
  }

  body[data-mode='present'] #s05 .good-cards.is-revealed .good-card:nth-child(3) {
    animation-delay: 600ms;
  }

  body[data-mode='present'] #s05 .good-cards.is-revealed .good-card:nth-child(4) {
    animation-delay: 900ms;
  }
}

/* ==========================================================================
   15  Slide 3: the timeline draws itself
   No build steps and no clicks. The axis draws left to right and each moment
   fades in as the line reaches it, so the crowding on the right is felt as
   acceleration rather than just seen.
   ========================================================================== */

.slide[data-layout='timeline'] {
  --tl-dur: 14s;
}

.tl-axis path {
  /* pathLength="1" in the markup means the dash units are fractions of the
     whole segment, so the three segments can be timed without measuring them. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.tl-event {
  opacity: 0;
}

.slide.is-active .tl-axis path {
  animation: tl-draw calc((var(--to) - var(--from)) * var(--tl-dur)) linear
    calc(var(--from) * var(--tl-dur)) forwards;
}

.slide.is-active .tl-event {
  animation: tl-appear 700ms ease-out calc(var(--at) * var(--tl-dur)) forwards;
}

@keyframes tl-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes tl-appear {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Anywhere the slide is a picture rather than a performance, it is simply
   finished: the overview thumbnails, the presenter previews, the reading
   version, print, and reduced motion.

   These carry `.is-active` because the rules that start the animation do, and
   without matching that specificity the animation keeps running underneath. In
   reading mode every slide is active, so the timeline was still playing itself
   halfway down a scrolling article. */
.thumb-canvas .slide.is-active .tl-event,
.pv-inner .slide.is-active .tl-event,
body[data-mode='read'] .slide.is-active .tl-event {
  opacity: 1;
  animation: none;
}

.thumb-canvas .slide.is-active .tl-axis path,
.pv-inner .slide.is-active .tl-axis path,
body[data-mode='read'] .slide.is-active .tl-axis path {
  stroke-dashoffset: 0;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .tl-event,
  .slide.is-active .tl-event {
    opacity: 1;
    animation: none;
  }

  .tl-axis path,
  .slide.is-active .tl-axis path {
    stroke-dashoffset: 0;
    animation: none;
  }
}

@media print {
  .tl-event {
    opacity: 1 !important;
    animation: none !important;
  }

  .tl-axis path {
    stroke-dashoffset: 0 !important;
    animation: none !important;
  }
}

/* ==========================================================================
   16  Reading text over footage
   Where a slide runs video, its own type has to hold against whatever is
   behind it. Dimming the video defeats the point of having it, so the type
   carries a halo instead and the video stays clear.
   ========================================================================== */

/* SVG labels get a painted-under stroke in the page colour. At these widths it
   reads as separation, not as an outline. */
.slide[data-backdrop='video'] .svg-label,
.slide[data-backdrop='video'] .svg-label-small,
.slide[data-backdrop='video'] .svg-year,
.slide[data-backdrop='video'] .svg-head {
  paint-order: stroke fill;
  stroke: var(--navy-deep);
  stroke-width: 5px;
  stroke-linejoin: round;
}

/* The band along the foot carries the source line, the slide number and the
   march. A short gradient protects it without touching the middle of the
   frame, where the footage is doing its work. */
.backdrops[data-kind='video']::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 190px;
  background: linear-gradient(
    to bottom,
    transparent,
    color-mix(in srgb, var(--navy-deep) 82%, transparent)
  );
  pointer-events: none;
}

/* A diagram drawn in steel at 38% is fine on flat navy and disappears over
   footage. On a video slide the whole diagram is pulled up: heavier halo,
   brighter rules and dots, and the smallest labels lifted off steel. */
.slide[data-backdrop='video'] .svg-label,
.slide[data-backdrop='video'] .svg-label-small,
.slide[data-backdrop='video'] .svg-year,
.slide[data-backdrop='video'] .svg-head {
  stroke-width: 6px;
}

.slide[data-backdrop='video'] .svg-line {
  stroke: color-mix(in srgb, var(--steel) 78%, transparent);
  stroke-width: 2.5;
}

.slide[data-backdrop='video'] .svg-line-accent {
  stroke-width: 4;
}

.slide[data-backdrop='video'] .svg-dot-quiet {
  fill: var(--steel);
}

/* Cream, not a steel mix. Over footage the smallest label needs the same
   weight as the rest; size still keeps it below them in the hierarchy. */
.slide[data-backdrop='video'] .svg-label-small {
  fill: var(--cream);
}

/* The source line sits in steel at 19px, which is the first thing to go over
   footage even with the gradient at the foot. */
.slide[data-backdrop='video'] .source {
  color: color-mix(in srgb, var(--steel) 60%, var(--cream));
}
