/* =========================================================================
   WEAR · RAE Showcase deck
   Full-viewport beats, no fixed stage. Tuned at 1920x1080; type is clamped so
   it still holds together at 1280 wide.
   ========================================================================= */

:root{
  --ink:#101528;
  --indigo:#1b2452;
  --indigo-deep:#111834;
  --peri:#6e77ac;
  --lilac:#c9cbe0;
  --paper:#e7e5e1;
  --violet:#5b4bc4;
  --violet-soft:#8b7ee0;
  --rule:rgba(16,21,40,.16);
  --rule-light:rgba(255,255,255,.24);

  /* The two gaps that bracket the funnel. They are one token because the
     funnel has to sit at the same distance from the strapline above it and
     from the comparison below it; two independent numbers drifted apart.
     --head-gap is what .head reserves under itself, so the pipe only needs to
     make up the difference. */
  --head-gap:calc(var(--frame-h) * 0.0260);
  --beat-gap:calc(var(--frame-h) * 0.052);

  /* The frame's inset lives in type.css with the rest of the frame tokens:
     one measure, used above the header and below the last block. */

  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.15' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}

*{box-sizing:border-box;}

html,body{
  margin:0;
  background:var(--paper);
  font-family:var(--mono);
  color:var(--ink);
  -webkit-font-smoothing:antialiased;
  cursor:default;
}

#deck{
  height:100vh;
  overflow-y:auto;
  overflow-x:hidden;

  scroll-snap-type:y proximity;
  scroll-behavior:smooth;
  scrollbar-width:none;
}
#deck::-webkit-scrollbar{display:none;}

/* ============================================================ slide ===== */
.slide{
  position:relative;
  min-height:100vh;
  display:flex;
  align-items:center;

  scroll-snap-align:start;
  background:var(--paper);
  color:var(--ink);
  overflow:hidden;
}.slide.dark{background:var(--indigo-deep);color:#fff;}
.slide.dark::before{
  content:"";position:absolute;inset:0;z-index:0;
  background-image:
    linear-gradient(102deg,rgba(10,14,32,.92) 0%,rgba(13,18,40,.74) 52%,rgba(10,14,32,.86) 100%),
    url("grain-dark.jpeg");
  background-size:cover,cover;
  background-position:center;
}
.slide::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background-image:var(--grain);
  opacity:.12;mix-blend-mode:multiply;
}
.slide.dark::after{opacity:.10;mix-blend-mode:overlay;}
.slide > *{position:relative;z-index:2;}
.slide.dark .rule{background:var(--rule-light);}

.wrap{
  width:min(100%, calc(var(--frame-h) * 1.4444));
  margin:0 auto;
  padding:var(--frame-inset) 0;
  display:flex;
  flex-direction:column;
}

/* A content beat pins its header inside the frame, and the frame is centred in
   the window — so on a taller aspect the space above and below is equal and the
   title still starts at the same height on every beat. Keyed on the header
   component itself rather than a class repeated on fifteen <section>s.
   The same inset pads the top and the bottom, so a beat that fills its frame
   has equal margins above the eyebrow and below its last block. */
.slide:has(> .wrap > .head){align-items:flex-start;}
.slide:has(> .wrap > .head) > .wrap{
  padding-top:calc(var(--frame-gap) + var(--frame-inset));
  padding-bottom:calc(var(--frame-gap) + var(--frame-inset));
}
/* The literature table is the one block that stretches (see .lit): it takes the
   height the frame has left and shares it between its rows, so that beat keeps
   the same margin above and below instead of trailing off under its last row.
   Scoped to the table's own beat — a full-height wrap would otherwise give the
   `margin-top:auto` closers on three other beats the floor to hang from. */
.slide:has(.lit) > .wrap{min-height:100vh;}

/* ============================================================ type ====== */
/* Sizes, line heights, tracking and the label colour all live in type.css.
   Nothing below this line declares a font-size of its own. */
h1,h2,h3,h4,p,ul,ol,figure,blockquote{margin:0;}

.dim{color:rgba(16,21,40,.6);}
.dark .dim{color:rgba(255,255,255,.62);}
.violet{color:var(--violet);}
.dark .violet{color:var(--violet-soft);}
em{font-style:italic;}
.note{font-style:italic;color:rgba(16,21,40,.5);}
.dark .note{color:rgba(255,255,255,.5);}

/* ============================================================ bits ====== */
.rule{height:1px;background:var(--rule);border:0;margin:0;}

.spark{
  width:1.1em;height:1.1em;background:currentColor;flex:none;
  clip-path:polygon(50% 0%,57% 43%,100% 50%,57% 57%,50% 100%,43% 57%,0% 50%,43% 43%);
}

/* Cards and callouts are components: their border, fill and padding live in
   components.css. This file only places them. */
.dark .card{background:rgba(255,255,255,.055);border-color:var(--rule-light);}

.stat .k{
  font-family:var(--display);font-size:var(--type-h2);
  line-height:1;color:var(--violet);
}
.dark .stat .k{color:var(--violet-soft);}
.stat .l{font-size:var(--type-label);color:rgba(16,21,40,.6);margin-top:.55em;line-height:1.4;}
.dark .stat .l{color:rgba(255,255,255,.6);}

/* The stage a statistic belongs to, read before the number rather than after
   it: on the results beat the reader has to know whether a figure is about
   applying or about being granted before the figure means anything. */
.stat .stage{
  font-size:var(--type-label);line-height:var(--lh-label);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--label);margin-bottom:.7em;
}

/* One statistic per beat at the size of the argument. The results numerals
   are the finding rather than a caption to it, so they take the title step —
   the one place in the deck where a number outranks the prose around it. The
   label drops a step so the numeral keeps the weight. */
.stat.xl .k{font-size:var(--type-stat);line-height:1.02;letter-spacing:var(--tr-title);}
.stat.xl .l{font-size:var(--type-dense);line-height:1.45;margin-top:.75em;}

/* The one content-beat header. Kicker, title, optional strapline — same three
   slots, same order, same spacing on every content slide, so the title lands
   on the same baseline no matter what sits underneath it. */
.head{
  display:flex;flex-direction:column;align-items:flex-start;
  margin:0 0 var(--head-gap);
}
.head > *{margin-top:0;}
.head .eyebrow{margin-bottom:.55em;}
/* The strapline is the first paragraph of the beat, so it takes the prose step
   and the prose ink like every other paragraph — the title above it is what
   carries the size, and colour is not what separates one strapline from the
   next. */
.head .lead{font-size:var(--type-body);line-height:var(--lh-body);
            color:var(--prose);margin-top:.5em;}

/* Dense beats run closer to their header — the table needs the height more
   than the gap does. */
.slide.dense .head{margin-bottom:calc(var(--frame-h) * 0.014);}
/* One prose measure for the whole deck, set in characters so it scales with
   type instead of fighting it. Paragraphs only — .sm and .xs are also used as
   layout wrappers and must not be capped. Headings use the full column.
   Anything that sits inside a bordered block (.why, .course, .strand, .void)
   is exempt: the box is already the measure, so text that stops half-way
   across it just looks cut off. Those exemptions need `.wrap` in the selector
   to beat the 64ch rule above. */
.wrap p{max-width:64ch;}
.wrap p.lead,.wrap p.prose,.wrap .callout p{max-width:none;}
.wrap h1,.wrap h2,.wrap h3{max-width:none;}

/* ============================================================ builds ==== */
.build{
  opacity:0;transform:translateY(10px);
  transition:opacity .5s ease,transform .5s cubic-bezier(.22,.9,.28,1);
}
.build.on{opacity:1;transform:none;}

/* ============================================================ tables ==== */
table{border-collapse:collapse;width:100%;font-size:var(--type-dense);}
th,td{padding:.55em .7em;text-align:left;border-bottom:1px solid var(--rule);}
th{
  background:var(--indigo);color:#fff;font-weight:700;
  border-bottom:0;
}
td.n,th.n{text-align:right;font-variant-numeric:tabular-nums;}
tbody tr:last-child td{border-bottom:1px solid var(--rule);}
tbody tr.total td{border-top:1px solid var(--ink);font-weight:700;}
.sig{color:var(--violet);font-weight:700;}
.dark table th{background:rgba(255,255,255,.12);}

/* ============================================================ figures === */
.plate{
  background:#fff;padding:clamp(10px,1vw,18px);border:1px solid var(--rule);
  box-shadow:0 24px 48px -32px rgba(0,0,0,.7);
}
.plate img{display:block;width:100%;height:auto;}
.plate figcaption{padding-top:.6em;}

/* ============================================================ results === */
/* The results beats are the only ones that have to fill their frame: the
   numerals are the argument, so they take the height the header leaves rather
   than sitting in the top third with the bottom half of the slide empty. */
.slide--result > .wrap{min-height:100vh;}
/* The appendix table beat is the one beat that reads as a title card, so the
   block sits in the middle of the frame rather than hanging from the top with
   the spare height underneath it. The head drops its own bottom margin so the
   gap above the table is the same as the gap below. */
.slide--spec > .wrap{min-height:100vh;justify-content:center;}
.slide--spec .head{margin-bottom:0;}
.res-body{flex:1;display:flex;flex-direction:column;justify-content:center;}

/* The closing argument is one block of three statements, so it centres in the
   frame instead of hanging from the top of it. */
.slide--so-what > .wrap{min-height:100vh;}

/* Three headline figures stacked, not strung across. A row each gives every
   numeral the full measure, which is what lets it run at a step above the
   hero: in columns the widest of the three caps the size of all of them. The
   label sits to the right, so the numerals form one rail down the left. */
.res-stats{display:flex;flex-direction:column;}
.res-stats .stat{
  display:grid;grid-template-columns:42% 1fr;
  gap:0 2em;align-items:center;
  padding:.95em 0;
  border-top:1px solid var(--rule-light);
}
.res-stats .stat:last-child{border-bottom:1px solid var(--rule-light);}
.res-stats .stage{margin-bottom:.5em;}
/* The label pair sits against a numeral four times its size, so it takes the
   body step and the strapline's ink rather than the muted label grey — at the
   small size the grey read as a caption to a number it is actually explaining. */
.res-stats .stat.xl .l{
  font-size:var(--type-body);line-height:1.45;margin-top:0;
  color:var(--prose);
}

/* Two panels that have to be read against each other, so they share a width
   and a baseline rather than each finding its own. The figure title sits on
   top and centred: under a landscape plot it read as a caption to the axis,
   over it read as the plot's name. */
.fig-pair{
  display:grid;grid-template-columns:1fr 1fr;
  gap:calc(var(--frame-h) * 0.026);align-items:start;
}
.fig-pair .plate{display:flex;flex-direction:column;}
.fig-pair .plate figcaption{padding-top:0;padding-bottom:.9em;text-align:center;}
/* Matplotlib bakes its title into the bitmap, and the slide's own headline
   already says it. 1500x1125 → 1500x950 takes the title band and the bottom
   margin and nothing else: no part of the plot area is lost. */
.plate .crop{position:relative;overflow:hidden;aspect-ratio:1500/950;background:#fff;}
.plate .crop img{position:absolute;left:0;top:-11.05%;width:100%;}

/* Under the two panels: what was plotted, and what the other two outcomes do.
   The beat has to answer "is it just EBITDA?" in one glance, so the outcome
   names take the label tier and the verdicts the dense step — the same split
   as a chart legend, which is what this is. */
.outcomes{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:calc(var(--frame-h) * 0.028);
  margin-top:calc(var(--frame-h) * 0.026);
  padding-top:calc(var(--frame-h) * 0.018);
  border-top:1px solid var(--rule);
}
.outcomes .o-k{
  font-size:var(--type-label);line-height:var(--lh-label);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--label);margin-bottom:.6em;
}
.outcomes .o-v{font-size:var(--type-dense);line-height:1.45;color:var(--prose-dim);}

/* ============================================================== ledger === */
/* One row per claim, with label, verdict and detail on fixed columns. Two
   bullet lists of unequal length can never balance against each other; rows of
   the same shape always can, and the eye can run one column at a time instead
   of zig-zagging between two blocks of prose. Used twice: once for the other
   outcomes, once for the checks. */
.ledger{display:flex;flex-direction:column;margin-top:calc(var(--frame-h) * 0.018);}
.ledger .row{
  display:grid;align-items:baseline;
  gap:0 1.1em;
  padding:.72em 0;
  border-top:1px solid var(--rule);
}
.ledger .row:last-child{border-bottom:1px solid var(--rule);}
.ledger .lbl,.ledger .vrd{
  font-size:var(--type-dense);line-height:1.42;
  font-weight:700;color:var(--ink);
}
.ledger .vrd{color:var(--violet);}
.ledger .vrd.no{color:#b4472f;}
.ledger .vrd.flat{color:rgba(16,21,40,.45);}
.ledger .txt{font-size:var(--type-dense);line-height:1.42;color:var(--prose-dim);}
.ledger .mark{
  font-family:var(--mono);font-weight:700;
  font-size:var(--type-dense);line-height:1.42;
  color:var(--violet);
}
.ledger .mark.no{color:#b4472f;}

/* Columns are em, so the row has to carry the same font size as its cells:
   otherwise a 30px label sits in a 136px column and wraps on a big screen while
   the prose column takes the rest of the frame. Padding and gap are em too, so
   the whole row scales with the type instead of only the words doing it. */
.ledger--outcome .row{
  grid-template-columns:minmax(max-content,13.4em) minmax(max-content,9.6em) minmax(0,1fr);
  gap:0 1.8em;padding:.60em 0;font-size:var(--type-body);
}
.ledger--outcome .lbl,
.ledger--outcome .vrd,
.ledger--outcome .txt{font-size:var(--type-body);line-height:1.45;}
.ledger--score .row{
  grid-template-columns:minmax(max-content,1.6em) minmax(max-content,15em) minmax(0,1fr);
  gap:0 1.6em;padding:.42em 0;font-size:var(--type-dense);
}

/* Groups inside one stack, so eight rows still read as two verdicts without
   needing a second column to carry the split. */
.ledger .group{
  font-size:var(--type-label);line-height:var(--lh-label);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--label);
  margin:calc(var(--frame-h) * 0.020) 0 .45em;
}
.ledger .group:first-child{margin-top:0;}
/* The break between the two verdicts is the one place in the stack where a
   gap is the message, so the second group gets a gap wide enough to read as a
   change of subject rather than as the next row. */
.ledger .group:not(:first-child){margin-top:calc(var(--frame-h) * 0.040);}
.ledger .group.no{color:#b4472f;}

/* ============================================================ gap map === */
.gapmap{
  display:grid;grid-template-columns:1fr 3.2em 1fr;
  grid-template-rows:repeat(3,1fr);gap:.5em 0;
}
.strand{
  border:1px solid var(--rule);background:rgba(255,255,255,.55);
  padding:clamp(12px,1.1vw,22px) clamp(16px,1.4vw,28px);
  display:flex;flex-direction:column;justify-content:center;
}
.strand h3{font-family:var(--display);font-size:var(--type-h3);font-weight:400;
           letter-spacing:.04em;text-transform:uppercase;margin:0 0 .35em;}
.strand p{font-size:var(--type-dense);line-height:1.45;color:rgba(16,21,40,.68);}
.strand .who{color:var(--violet);}
.arrow{
  display:flex;align-items:center;justify-content:center;
  font-style:normal;font-size:var(--type-h2);color:rgba(16,21,40,.34);
}
.void{
  border:1px dashed rgba(91,75,196,.6);background:rgba(91,75,196,.05);
  display:flex;flex-direction:column;justify-content:center;
  padding:clamp(20px,2vw,40px);
}
.void .eyebrow{color:var(--violet);}
.void h3{font-family:var(--display);font-weight:400;font-size:var(--type-h2);
         line-height:1.08;text-transform:uppercase;margin:.5em 0 0;}
.void p{font-size:var(--type-dense);line-height:1.5;color:rgba(16,21,40,.68);margin-top:.8em;}

/* Three questions side by side, each in its own card. Number and question
   share a line; the citation is pinned to the card floor so all three close
   on the same baseline. */
.course{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
        gap:var(--box-gap);align-items:stretch;
        margin-top:calc(var(--frame-h) * 0.010);}
.course > div{display:flex;flex-direction:column;}
.course .qh{display:flex;align-items:baseline;gap:.55em;}
.course .cno{font-family:var(--display);font-weight:400;font-size:var(--type-h3);
             letter-spacing:.04em;line-height:1;color:var(--violet);}
.course .q{font-family:var(--display);font-weight:400;font-size:var(--type-h3);
           letter-spacing:.05em;text-transform:uppercase;line-height:1.1;
           color:var(--ink);}
.course p{font-size:var(--type-body);line-height:1.55;color:var(--prose-dim);
          margin-top:.8em;max-width:none;}
.course b{color:var(--ink);font-weight:700;}
.course .cite{margin-top:auto;padding-top:1.1em;}
.dark .course p{color:rgba(255,255,255,.66);}
.dark .course b{color:#fff;}
.dark .course .cno{color:var(--violet-soft);}
.dark .course .q{color:#fff;}

/* Background prose under the heading, full width. */
.wrap p.prose{font-size:var(--type-body);line-height:1.6;
              color:rgba(16,21,40,.68);margin-top:0;}

/* "Why this matters" — a callout (components.css). Placement only: a callout
   that follows a row of cards or a table closes the beat, so it takes the same
   gap the header reserves under the title (--head-gap) rather than a sliver.
   At 0.016 the callout read as part of the block above it and the two beats
   ran together at the floor. */
.course + .callout,
.lit + .callout{margin-top:var(--head-gap);}

/* Footnotes. The marker sits in the prose, the source on the floor of the
   beat, so an evidence-heavy strapline stays about the argument rather than
   about who said it. Type is the citation tier — this only arranges it. */
.fn{
  display:flex;flex-wrap:wrap;
  gap:.15em 1.8em;
  margin-top:calc(var(--frame-h) * 0.010);
}
.fn sup{margin-right:.45em;top:-.4em;}


/* ============================================================ funnel ==== */
/* A squared funnel, not a trapezoid. Four rectangular slices, each narrower
   than the one above and stacked flush, so the silhouette carries the sample
   loss rather than a slope does. Records pour into a slice from its top edge,
   the slice fills from the bottom, and the count arrives once it is full. The
   pour runs once and stops — it should read as a thing that happened on entry,
   not as a loop running under a static slide. */
.pipe{
  display:flex;flex-direction:column;
  margin-top:calc(var(--beat-gap) - var(--head-gap));
}
/* Rows are stretched, not centred: the band takes the height of whichever is
   taller, itself or its label. At body size the longer labels run to two
   lines, and a fixed band height would leave a hairline seam between every
   slice — which reads as a rendering fault, not as a stacked funnel. */
.pipe .stage{display:grid;grid-template-columns:minmax(0,29%) 1fr;align-items:stretch;
             gap:calc(var(--frame-h) * 0.052);}
.pipe .band{
  position:relative;overflow:hidden;
  width:var(--w,100%);margin-inline:auto;
  min-height:clamp(52px,calc(var(--frame-h) * 0.070),86px);
  background:rgba(16,21,40,.09);
  display:flex;align-items:center;justify-content:center;
  color:#fff;
}
/* The liquid itself: it rises from the floor of the slice. Two crest ellipses
   at different speeds ride the waterline, so the surface interferes with
   itself and drifts rather than cutting a straight edge across the block. The
   crest is a tint of the slice colour, never white — white on paper reads as a
   hole in the slide, not as water. */
.pipe .fill{
  position:absolute;left:0;right:0;bottom:0;height:0;
  background:var(--fill,var(--indigo));
}
.pipe .fill::before,
.pipe .fill::after{
  content:"";position:absolute;background:var(--crest,var(--fill,var(--indigo)));
  border-radius:50%;
}
.pipe .fill::before{
  left:-30%;right:-30%;top:-.34em;height:.8em;
  animation:pipe-swell 6.5s ease-in-out infinite alternate;
}
.pipe .fill::after{
  left:-22%;right:-22%;top:-.22em;height:.55em;
  animation:pipe-swell 4.2s ease-in-out infinite alternate-reverse;
}
@keyframes pipe-swell{
  from{transform:translateX(-1.8%) scaleY(.86);}
  to{transform:translateX(1.8%) scaleY(1.14);}
}
.pipe .num{
  position:relative;
  font-family:var(--display);font-size:var(--type-h3);
  letter-spacing:.03em;line-height:1;
  opacity:0;transform:translateY(.3em);
}
.slide.active .pipe .fill{
  animation:pipe-fill .95s cubic-bezier(.36,.08,.28,1) forwards;
  animation-delay:calc(var(--s,0) * 420ms + 120ms);
}
.slide.active .pipe .num{
  animation:pipe-num .5s cubic-bezier(.22,.9,.28,1) forwards;
  animation-delay:calc(var(--s,0) * 420ms + 1050ms);
}
@keyframes pipe-fill{from{height:0;}to{height:100%;}}
@keyframes pipe-num{to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){
  .pipe .fill{height:100%;animation:none;}
  .pipe .fill::before,.pipe .fill::after{animation:none;}
  .pipe .num{opacity:1;transform:none;animation:none;}
  .pipe .lab{opacity:1;transform:none;animation:none;}
}
/* The label is one line, and it arrives with its own slice: opacity is keyed
   to the same --s delay as the fill, so the source name lands as the liquid
   starts rising rather than the whole funnel announcing itself up front. */
.pipe .lab{font-size:var(--type-body);line-height:1.4;color:var(--prose-dim);
           align-self:center;
           opacity:0;transform:translateY(.3em);}
.pipe .lab b{
  font-family:var(--display);font-weight:400;color:var(--ink);
  text-transform:uppercase;letter-spacing:.03em;
}
.pipe .lab b::after{content:"\2014";margin:0 .4em;color:rgba(16,21,40,.3);}
.slide.active .pipe .lab{
  animation:pipe-num .55s cubic-bezier(.22,.9,.28,1) forwards;
  animation-delay:calc(var(--s,0) * 420ms + 120ms);
}

/* ============================================================== data ==== */
/* The join, said once, under the funnel: the rows above are sources, this is
   what was done to them, and the method beat that follows carries the detail. */
.wrap p.join{
  max-width:none;
  font-size:var(--type-body);line-height:1.55;color:var(--prose);
  margin-top:calc(var(--frame-h) * 0.024);
}

/* The unmatched comparison, under the join. Back on white cards: the numbers
   are the payoff of the beat, and the card is what the deck already uses to
   say "this is a measured thing". Colour carries the group everywhere on the
   row — number, swatch, label — so no card has to repeat whose number it is. */
.comparewrap{padding-top:var(--beat-gap);}
.comparehead{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:2em;margin-bottom:.85em;
}
.legend{display:flex;gap:1.7em;}
.legend .sw{
  font-size:var(--type-label);line-height:1.3;
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.legend .sw::before{
  content:"";display:inline-block;
  width:.66em;height:.66em;margin-right:.6em;background:currentColor;
}
.legend .a{color:var(--violet);}
.legend .b{color:var(--ink);}

/* The strapline takes the same step and the same ink as every other piece of
   prose on the deck. Size is not what separates a claim from its qualifier
   here — colour is. */
.wrap p.comparelead{
  max-width:none;margin:0;
  font-size:var(--type-body);line-height:1.55;color:var(--prose);
}

.compare{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--box-gap);
  margin-top:calc(var(--frame-h) * 0.026);
}
.compare .var{
  font-family:var(--display);font-weight:400;font-size:var(--type-dense);
  letter-spacing:.04em;text-transform:uppercase;color:rgba(16,21,40,.66);
}
.compare .vals{
  display:flex;align-items:baseline;gap:.5em;
  margin-top:.5em;font-family:var(--display);line-height:1;
}
.compare .vals .a,.compare .vals .b{font-size:var(--type-h2);}
.compare .vals .a{color:var(--violet);}
.compare .vals .b{color:var(--ink);}
.compare .vals .vs{
  font-family:var(--mono);font-size:var(--type-label);
  letter-spacing:.14em;text-transform:uppercase;color:rgba(16,21,40,.36);
}

/* ============================================================ qdef ===== */
/* The question beat has to say what it means by "firm performance" before the
   method beat asks anyone to read a symbol for it. Two definitions, set as a
   margin note under the statement rather than a data block, so the beat keeps
   its single line of argument. */
.qdef{
  margin-top:clamp(22px,calc(var(--frame-h) * .0500),112px);
  border-top:1px solid var(--rule-light);
  padding-top:clamp(11px,calc(var(--frame-h) * .0165),37px);
}
.qdef-row{margin-top:clamp(15px,calc(var(--frame-h) * .0250),56px);}
.qdef-row:first-child{margin-top:0;}
.qdef .k{
  font-family:var(--display);font-size:var(--type-label);line-height:var(--lh-label);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--violet-soft);
}
.qdef p{
  margin-top:.55em;font-size:var(--type-dense);line-height:var(--lh-dense);
  color:rgba(255,255,255,.72);text-wrap:pretty;max-width:none;
}
.qdef b{color:#fff;font-weight:700;}

/* ============================================================ chain ===== */
.chain{display:flex;align-items:center;gap:.55em;margin-bottom:clamp(18px,2.4vh,34px);}
.chain i{
  width:.62em;height:.62em;border-radius:50%;background:rgba(16,21,40,.18);
  font-style:normal;
}
.dark .chain i{background:rgba(255,255,255,.24);}
.chain i.on{background:var(--violet);}
.dark .chain i.on{background:var(--violet-soft);}

/* ============================================================ hud ======= */
#bar{
  position:fixed;left:0;top:0;height:5px;width:0;
  background:var(--violet);z-index:90;
  will-change:width;
}
#count{
  position:fixed;right:1.4vw;bottom:1.6vh;z-index:90;
  color:rgba(16,21,40,.4);transition:color .3s ease;
}
#count.dark{color:rgba(255,255,255,.42);}
#count .stp{opacity:.7;}

#hint{
  position:fixed;left:50%;bottom:1.4vh;transform:translateX(-50%);z-index:90;
  color:rgba(16,21,40,.34);white-space:nowrap;
  transition:opacity .8s ease;
}
#hint.dark{color:rgba(255,255,255,.4);}
.hide{opacity:0 !important;}

/* ============================================================ overview == */
/* A contents, not an app. Two groups — the deck, then the appendix — built from
   one card, so the appendix is not a lesser citizen with styling of its own.
   The groups sit at the top and the foot of the frame, and the air between them
   is the only thing separating the talk from its evidence. */
#overview{
  position:fixed;inset:0;z-index:95;display:none;
  background:#080b16;color:#fff;overflow:hidden;
  padding:clamp(16px,3.2vh,44px) clamp(18px,4vw,80px);
}
#overview.open{display:flex;flex-direction:column;}

#ov-head{
  flex:none;display:flex;align-items:baseline;justify-content:space-between;
  gap:2em;padding-bottom:.95em;
  border-bottom:1px solid rgba(255,255,255,.16);
}
#overview h3{color:rgba(255,255,255,.5);}
.ov-keys{color:rgba(255,255,255,.34);white-space:nowrap;}
.ov-keys b{color:var(--violet-soft);font-weight:400;}

#ov-grid{
  flex:1 1 auto;min-height:0;
  display:flex;flex-direction:column;
  gap:clamp(14px,2.4vh,38px);
  padding-top:clamp(12px,2vh,30px);
  overflow:auto;scrollbar-width:none;
}
#ov-grid::-webkit-scrollbar{display:none;}

/* The two groups share the frame in proportion to their rows — three of the
   deck to two of the appendix — so every row on the screen is the same height
   and neither group leaves a hole under itself. */
.ov-group{display:flex;flex-direction:column;min-height:0;}
.ov-group:first-child{flex:3 1 0;}
.ov-group:last-child{flex:2 1 0;}

.ov-label{
  flex:none;display:flex;align-items:baseline;gap:.7em;
  font-family:var(--display);color:rgba(255,255,255,.42);
  padding:0 0 .8em;
}
/* One hairline is what separates the talk from its evidence. */
.ov-group + .ov-group .ov-label{
  border-top:1px solid rgba(255,255,255,.1);
  padding-top:clamp(9px,1.4vh,18px);
}
.ov-label .n{color:var(--violet-soft);}

.ov-cards{
  flex:1 1 auto;min-height:0;
  display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  grid-auto-rows:1fr;
  gap:clamp(6px,.75vw,13px);
}

/* One card: the number at the top, the title at the foot, and the beat's own
   air between them. The same box in both groups. */
.ov-card{
  min-height:52px;max-height:calc(var(--frame-h) * .1850);overflow:hidden;
  display:flex;flex-direction:column;justify-content:space-between;gap:.7em;
  padding:clamp(8px,1.2vh,18px) clamp(9px,.85vw,16px);
  border:1px solid rgba(255,255,255,.13);
  background:rgba(255,255,255,.028);
  color:inherit;font:inherit;text-align:left;cursor:pointer;
  transition:background .2s ease,border-color .2s ease;
}
.ov-card:hover,
.ov-card.is-cursor{background:rgba(255,255,255,.075);border-color:rgba(139,126,224,.5);}
.ov-card.is-current{border-color:rgba(255,255,255,.72);background:rgba(139,126,224,.16);}
.ov-card .ov-n{flex:none;color:var(--violet-soft);}
.ov-card.is-current .ov-n{color:#fff;}
.ov-card .ov-t{
  font-family:var(--display);font-weight:400;
  font-size:var(--type-dense);line-height:1.2;letter-spacing:.05em;
  text-transform:uppercase;color:rgba(255,255,255,.82);
}
.ov-card.is-current .ov-t{color:#fff;}

@media (max-width:900px){
  .ov-cards{grid-template-columns:repeat(4,minmax(0,1fr));}
}
@media (max-width:700px){
  .ov-cards{grid-template-columns:repeat(3,minmax(0,1fr));}
}

/* ============================================================ motion ==== */
.reveal{opacity:0;transform:translateY(12px);}
.slide.active .reveal{
  opacity:1;transform:none;
  transition:opacity .6s ease,transform .6s cubic-bezier(.22,.9,.28,1);
  transition-delay:calc(var(--i,0) * 80ms + 100ms + var(--lag,0ms));
}

@media (prefers-reduced-motion:reduce){
  #deck{scroll-behavior:auto;}
  .slide,.reveal,.build{transition:none !important;}
  .reveal{opacity:1;transform:none;}
}


/* ============================================================ points ==== */
/* bullet list with a bold lead-in; replaces prose blocks on dense beats */
.points{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:1.15em;}
.points li{
  position:relative;padding-left:1.5em;
  font-size:var(--type-body);line-height:1.5;color:var(--prose-dim);
}
.points li::before{
  content:"";position:absolute;left:0;top:.62em;
  width:.42em;height:.42em;background:var(--violet);border-radius:1px;
}
.points b{color:var(--ink);font-weight:700;}

/* ========================================================= literature === */
/* One row per study: who, what it finds, what it cannot say. Three columns so
   the design detail and the limitation sit side by side — the reader can see
   which half of the question each study answers.
   The table takes whatever height the beat has left, and shares it evenly
   between its rows, so the block always runs from the header to the closing
   callout instead of leaving a hole under its last row. */
.lit{display:flex;flex-direction:column;flex:1;margin-top:calc(var(--frame-h) * 0.014);}
/* Directly under a header the header's own margin is the gap; a second one
   would read as a hole between the title and the table. The dense header
   reserves 0.014 of the deck's 0.026 --head-gap, so this makes up the
   difference: the strapline has to read as a statement of its own, not as the
   table's caption. */
.head + .lit{margin-top:calc(var(--head-gap) - var(--frame-h) * 0.014);}
.lit-head,.lit-row{
  display:grid;
  grid-template-columns:minmax(0,24%) minmax(0,1fr) minmax(0,1fr);
  gap:clamp(16px,2vw,40px);
}
.lit-head{padding-bottom:.35em;}
/* Row padding is a ratio of the frame, not an em: on a 16:10 window the type
   shrinks with --frame-h but fixed padding does not, and the last row then runs
   into the bottom of the frame. It is also the only slack in the beat: the
   table is the block that stretches, so every pixel here is a pixel the gaps
   above and below it cannot have. Rows stretch to fill the height they are
   given, so this floor can come down without the rows looking tighter. */
.lit-row{
  flex:1;
  padding:calc(var(--frame-h) * 0.0020) 0;
  border-top:1px solid var(--rule);align-items:center;
}
.lit-who h3{
  font-family:var(--display);font-weight:400;font-size:var(--type-dense);
  letter-spacing:.05em;text-transform:uppercase;margin:0;color:var(--ink);line-height:1.2;
}
.lit-who .cite{margin-top:.35em;}
/* Column 2 is a statement, column 3 is the list of what the design cannot do.
   Prose on the left, bullets on the right: the finding carries the weight, the
   limits are scanned. Both take the table step, like every other table on the
   deck — the study names, the findings and the limits are one block of type. */
.lit-does,.lit-cant{margin:0;padding:0;}
.lit-does{
  font-size:var(--type-dense);line-height:var(--lh-dense);
  color:var(--prose);
}
.lit-cant{list-style:none;}
.lit-cant li{
  position:relative;padding-left:1.1em;
  font-size:var(--type-dense);line-height:var(--lh-dense);
  color:var(--prose-dim);
}
.lit-cant li+li{margin-top:.26em;}
.lit-cant li::before{
  content:"";position:absolute;left:0;top:.55em;
  width:5px;height:5px;background:rgba(16,21,40,.3);
}

/* The paper's answer to the gap stated in the strapline. One block, full
   width — splitting it into columns put it in competition with the table
   above rather than reading as its conclusion. */
.contrib ul{margin:0;padding:0;list-style:none;}
/* Type comes from the callout itself, so the contribution box speaks at the
   same size and ink as any other callout. Only the list mechanics live here. */
.contrib li{
  position:relative;padding-left:1.1em;max-width:none;
}
.contrib li+li{margin-top:.2em;}
.contrib li::before{
  content:"";position:absolute;left:0;top:.62em;width:5px;height:5px;background:var(--violet);
}
.contrib b{color:var(--ink);}

/* ======================================================= methodology ==== */
/* A lens, not four cards. The panels never reorder — they stay 01 02 03 04
   left to right and the open one takes the width, so advancing reads as a
   lens travelling along the sequence rather than tiles reshuffling. A closed
   panel is a spine: number and title share one writing mode, so the sliver
   reads as one object rather than a number the wrong way round above a
   rotated label.

   Above the lens sits the estimating equation from the paper. It never
   changes; each move lights the term it is about, and carries its own
   definition inside the card. Wired with :has() on the panel state, which
   deck.js already sets. */

/* ---- the estimating equation -------------------------------------------- */
/* White card, near-black equation: this is the one block on the beat that has
   to be legible from the back of a room. The label sits in its own rail on the
   left so the equation can run the rest of the width at a size that reads
   rather than shouts. */
.spec-card{
  position:relative;
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  align-items:center;
  column-gap:clamp(16px,calc(var(--frame-h) * .0280),60px);
  border:1px solid rgba(16,21,40,.2);
  background:#fff;
  padding:clamp(10px,calc(var(--frame-h) * .0140),32px) clamp(14px,calc(var(--frame-h) * .0190),44px);
  margin-bottom:clamp(10px,calc(var(--frame-h) * .0180),42px);
}
.spec-card .spec-kick{
  display:block;
  padding-right:clamp(10px,calc(var(--frame-h) * .0220),48px);
  border-right:1px solid rgba(16,21,40,.12);
  font-family:var(--display);font-size:var(--type-label);line-height:1.45;
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--violet);
  white-space:nowrap;
}
.spec-card .spec-legends{display:grid;margin-top:.35em;}
.spec-card .spec-legend{
  grid-area:1/1;opacity:0;transition:opacity .45s ease;
  font-family:var(--mono);font-size:var(--type-micro);line-height:1.3;
  letter-spacing:.06em;text-transform:none;color:var(--violet);
}
.slide:has(.anatomy > .panel:nth-child(1).is-open) .spec-legend[data-legend="0"],
.slide:has(.anatomy > .panel:nth-child(2).is-open) .spec-legend[data-legend="1"],
.slide:has(.anatomy > .panel:nth-child(3).is-open) .spec-legend[data-legend="2"],
.slide:has(.anatomy > .panel:nth-child(4).is-open) .spec-legend[data-legend="3"]{
  opacity:1;
}

.eq--base{
  font-size:var(--type-eq);line-height:1.45;
  color:rgba(16,21,40,.88);
}
.eq--base sub{color:inherit;font-size:var(--type-sub);}
.eq--base sup{color:inherit;font-size:var(--type-sub);}
/* The letters nobody's card owns: Y, i, t, s, c, j, X. Each move decodes the
   method symbols it lights; this row decodes the data ones, and stays up the
   whole way through so the equation never reads as a wall of Greek. */
.eq-decode{
  grid-column:1 / -1;
  display:flex;flex-wrap:wrap;
  gap:.2em clamp(14px,calc(var(--frame-h) * .0230),50px);
  margin-top:clamp(7px,calc(var(--frame-h) * .0092),21px);
  padding-top:clamp(7px,calc(var(--frame-h) * .0085),19px);
  border-top:1px solid rgba(16,21,40,.1);
  font-family:var(--mono);font-size:var(--type-micro);line-height:1.4;
  letter-spacing:.02em;color:rgba(16,21,40,.56);
}
.eq-decode b{font-weight:700;color:rgba(16,21,40,.82);}
.eq--base .tok{transition:color .45s ease;}
/* The term each move is about takes the accent and bold. Roboto Mono is
   monospaced across weights, so bolding a term does not move the rest of the
   equation by a pixel — the weight comes free. Propensity matching has no
   entry: the score never appears in the regression, it decides who is in it. */
.slide:has(.anatomy > .panel:nth-child(1).is-open) .eq--base .t-fe,
.slide:has(.anatomy > .panel:nth-child(1).is-open) .eq--base .t-beta,
.slide:has(.anatomy > .panel:nth-child(3).is-open) .eq--base .t-cohort,
.slide:has(.anatomy > .panel:nth-child(4).is-open) .eq--base .t-obs{
  color:var(--violet);
  font-weight:700;
}

/* ---- the lens ------------------------------------------------------------
   The beat is a fixed-height column: the header and the equation card take
   what they need and the lens fills the rest. That keeps the margins identical
   to every other beat at any window size, and lets the diagrams grow with the
   frame instead of sitting in a capped box. */
.slide.slide--method{align-items:stretch;height:100vh;min-height:0;}
.slide.slide--method > .wrap{height:100%;}
.anatomy{
  display:flex;gap:clamp(6px,calc(var(--frame-h) * .0090),20px);
  flex:1 1 auto;height:auto;min-height:0;
}

.panel{
  --pad:clamp(12px,calc(var(--frame-h) * .0205),46px);
  position:relative;overflow:hidden;
  flex:0 1 clamp(48px,calc(var(--frame-h) * .0700),156px);
  display:flex;flex-direction:column;
  padding:var(--pad);
  border:1px solid var(--rule);
  background:rgba(255,255,255,.42);
  text-align:left;font:inherit;color:inherit;cursor:pointer;
  transition:flex-grow .62s cubic-bezier(.22,.9,.28,1),
             flex-basis .62s cubic-bezier(.22,.9,.28,1),
             background .4s ease,border-color .4s ease;
}
.panel:not(.is-open):hover{background:rgba(255,255,255,.74);}
/* Lifted, not tinted: a violet wash under body copy costs more contrast than
   the border gains. The open card reads as paper raised off the page. */
.panel.is-open{
  flex:1 1 0;
  background:rgba(255,255,255,.8);
  border-color:rgba(91,75,196,.45);
  cursor:default;
}

/* The closed panel, set as a spine. Number and title share one writing mode,
   so nothing is reading at odds with anything else. */
.panel .spine{
  margin-block:auto;max-height:100%;
  writing-mode:vertical-rl;transform:rotate(180deg);
  white-space:nowrap;overflow:hidden;
  font-family:var(--display);font-size:var(--type-dense);line-height:1.05;
  letter-spacing:.07em;text-transform:uppercase;color:rgba(16,21,40,.42);
  transition:opacity .28s ease;
}
.panel .spine .snum{
  display:inline-block;margin-inline-start:.6em;
  font-size:var(--type-h3);letter-spacing:.02em;color:rgba(16,21,40,.24);
}
.panel.is-open .spine{opacity:0;}

/* The open body, out of the flex flow so collapsing never reflows it. */
.panel .pbody{
  position:absolute;inset:0;
  padding:var(--pad);
  display:flex;flex-direction:column;
  opacity:0;transform:translateY(10px);pointer-events:none;
  transition:opacity .18s ease,transform .3s ease;
}
.panel.is-open .pbody{
  opacity:1;transform:none;transition-delay:.42s;pointer-events:auto;
}

.panel .pk{
  display:block;
  font-family:var(--display);font-size:var(--type-label);line-height:1.3;
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--violet);
  flex:none;
}
.panel .pt{
  font-family:var(--display);font-weight:400;font-size:var(--type-h3);
  line-height:1.12;text-transform:uppercase;letter-spacing:.03em;
  flex:none;margin-top:.35em;
}
/* Full card width — the 64ch prose measure belongs to running prose, not to
   a one-line caption under a title. */
.panel .pleft{
  flex:none;margin-top:.75em;padding-top:.65em;max-width:none;
  border-top:1px solid rgba(16,21,40,.1);
  font-size:var(--type-label);line-height:1.5;letter-spacing:.02em;
  color:rgba(16,21,40,.55);
}
.panel .pleft b{
  font-family:var(--display);font-weight:400;
  text-transform:uppercase;letter-spacing:var(--tr-label);color:var(--violet);
}

/* A footnote belongs to the card that needs it — card 02 carries the two terms
   the deck has not yet defined, and the sources for the claims above them. */
.panel .pfoot{
  flex:none;margin-top:.55em;padding-top:.45em;
  border-top:1px solid rgba(16,21,40,.08);
  font-size:var(--type-micro);line-height:1.5;letter-spacing:.01em;
  color:var(--source);max-width:none;
}
.panel .pfoot b{color:rgba(16,21,40,.72);font-weight:700;}
.panel .pfoot .src{
  display:block;margin-top:.25em;letter-spacing:.04em;
  color:rgba(16,21,40,.36);
}

/* Diagram left, bullets right, equal halves. */
.panel .psplit{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(16px,calc(var(--frame-h) * .0305),68px);
  flex:1 1 auto;align-content:center;
  margin-top:.8em;padding-top:.8em;
  border-top:1px solid rgba(16,21,40,.1);
  min-height:0;
}
.panel .plist{
  list-style:none;margin:0;padding:0;
  display:flex;flex-direction:column;justify-content:center;gap:.65em;
  overflow:auto;
}
.panel .plist li{
  position:relative;padding-left:1.05em;
  font-size:var(--type-dense);line-height:1.45;color:rgba(16,21,40,.8);
  max-width:none;
}
.panel .plist b{color:var(--ink);}
.panel .plist li::before{
  content:"";position:absolute;left:0;top:.6em;
  width:5px;height:5px;background:rgba(91,75,196,.6);
}

/* The move's own definition, directly under the title. */
.panel .pest{
  flex:none;margin-top:.7em;
}
.panel .plab{
  display:block;
  font-family:var(--display);font-size:var(--type-label);line-height:1.3;
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--violet);
  margin-bottom:.2em;
}
.panel .peq{
  font-size:var(--type-dense);line-height:1.6;color:rgba(16,21,40,.86);
}

/* ---- diagrams ----------------------------------------------------------- */
.panel .pfig{
  margin:0;display:flex;flex-direction:column;justify-content:center;min-width:0;min-height:0;
  /* keeps the diagram from collapsing to nothing before the bullets agree to
     scroll on a very short window */
  min-height:clamp(80px,calc(var(--frame-h) * .150),280px);
}
.panel .pfig svg{
  width:100%;height:auto;max-height:100%;display:block;
}
.panel .pfig figcaption{
  padding-top:.5em;font-size:var(--type-label);line-height:1.3;
  letter-spacing:.08em;text-transform:uppercase;color:rgba(16,21,40,.4);
}
.pfig svg text{
  font-family:var(--mono);font-size:9px;letter-spacing:.05em;
  text-transform:uppercase;fill:rgba(16,21,40,.45);
}
.pfig .ax{fill:none;stroke:rgba(16,21,40,.16);stroke-width:1.5;}
.pfig .mark{fill:none;stroke:rgba(16,21,40,.28);stroke-width:1.5;stroke-dasharray:3 4;}
.pfig .band{fill:rgba(91,75,196,.05);stroke:none;}
.pfig .band--deep{fill:rgba(91,75,196,.2);}
.pfig .key{fill:rgba(91,75,196,.2);stroke:rgba(91,75,196,.34);stroke-width:1.2;}
.pfig .ln{fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;}
.pfig .ln.trt,.pfig .sw.trt{stroke:var(--violet);}
.pfig .ln.ctrl,.pfig .sw.ctrl{stroke:var(--peri);}
.pfig .ln.cf{stroke:rgba(16,21,40,.34);stroke-width:2;}
.pfig .sw{fill:none;stroke-width:2.5;stroke-linecap:round;}
.pfig .dot.trt{fill:var(--violet);stroke:none;}
.pfig .dot.ctrl{fill:rgba(255,255,255,.92);stroke:var(--peri);stroke-width:2;}
.pfig .cells rect{fill:rgba(91,75,196,.34);stroke:rgba(91,75,196,.7);stroke-width:1.4;}
.pfig .cells rect.pre{fill:none;stroke:rgba(16,21,40,.22);stroke-dasharray:2 3;}
.pfig .w{fill:var(--violet);}
.pfig .ticks path{stroke:rgba(16,21,40,.26);stroke-width:1.5;}

/* Draw-on and pop-in, keyed to the panel opening. */
.panel .pfig .draw{
  stroke-dasharray:var(--len,560);stroke-dashoffset:var(--len,560);
  transition:stroke-dashoffset 1s cubic-bezier(.4,.1,.2,1);
}
.panel.is-open .pfig .draw{stroke-dashoffset:0;transition-delay:.42s;}
.panel .pfig .ln.cf.draw{
  stroke-dasharray:4 5;stroke-dashoffset:0;opacity:0;
  transition:opacity .5s ease .95s;
}
.panel.is-open .pfig .ln.cf.draw{opacity:1;}

.panel .pfig .pop{
  opacity:0;transform:scale(.55);
  transform-box:fill-box;transform-origin:center;
  transition:opacity .4s ease,transform .5s cubic-bezier(.22,.9,.28,1);
}
.panel.is-open .pfig .pop{
  opacity:1;transform:none;
  transition-delay:calc(.42s + var(--d,0) * 70ms);
}

.panel .pfig .cells{opacity:0;transition:opacity .5s ease;}
.panel.is-open .pfig .cells{opacity:1;transition-delay:.45s;}

/* ---- the training loop --------------------------------------------------
   Three panels, one at a time. The network trains, then the hyperparameters
   are tuned, then the fitted scores are matched — each fills the diagram on
   its own, so at no point is more than one thing on screen. Ten-second cycle,
   all CSS keyframes, paused until the card is open. */
.pfig .st{opacity:0;animation:10s ease infinite;animation-play-state:paused;}
.pfig .st--1{animation-name:stOne;}
.pfig .st--2{animation-name:stTwo;}
.pfig .st--3{animation-name:stThree;}
.panel.is-open .pfig .st{animation-play-state:running;}

@keyframes stOne{0%{opacity:0}3%{opacity:1}40%{opacity:1}44%,100%{opacity:0}}
@keyframes stTwo{0%,44%{opacity:0}47%{opacity:1}64%{opacity:1}68%,100%{opacity:0}}
@keyframes stThree{0%,68%{opacity:0}71%{opacity:1}95%{opacity:1}99%,100%{opacity:0}}

.pfig .st-lab{fill:var(--violet);letter-spacing:.12em;}

/* stage 1 — the network, one route at a time */
.pfig .nn-edges line{stroke:rgba(16,21,40,.16);stroke-width:.8;}
.pfig .nn-nodes circle{fill:rgba(16,21,40,.32);}

.pfig .rt{opacity:0;animation:10s ease infinite;animation-play-state:paused;}
.pfig .rt--a{animation-name:rtA;}
.pfig .rt--b{animation-name:rtB;}
.panel.is-open .pfig .rt{animation-play-state:running;}

.pfig .rline{
  fill:none;stroke:var(--violet);stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:200;stroke-dashoffset:200;
  animation:10s cubic-bezier(.4,.1,.2,1) infinite;animation-play-state:paused;
}
.pfig .rt--a .rline{animation-name:rtDrawA;}
.pfig .rt--b .rline{animation-name:rtDrawB;}
.panel.is-open .pfig .rline{animation-play-state:running;}

.pfig .seen{opacity:0;animation:10s ease infinite;animation-play-state:paused;}
.pfig .rt--a .seen{animation-name:rtSeenA;}
.pfig .rt--b .seen{animation-name:rtSeenB;}
.pfig .seen circle{fill:var(--violet-soft);}
.panel.is-open .pfig .seen{animation-play-state:running;}

@keyframes rtA{0%,2%{opacity:0}5%{opacity:1}17%{opacity:1}21%,100%{opacity:0}}
@keyframes rtB{0%,21%{opacity:0}24%{opacity:1}36%{opacity:1}40%,100%{opacity:0}}
@keyframes rtDrawA{0%{stroke-dashoffset:200}11%{stroke-dashoffset:0}100%{stroke-dashoffset:0}}
@keyframes rtDrawB{0%,21%{stroke-dashoffset:200}32%{stroke-dashoffset:0}100%{stroke-dashoffset:0}}
@keyframes rtSeenA{0%,6%{opacity:0}10%{opacity:1}17%{opacity:1}21%,100%{opacity:0}}
@keyframes rtSeenB{0%,25%{opacity:0}29%{opacity:1}36%{opacity:1}40%,100%{opacity:0}}

/* stage 2 — the Gaussian process, then its next candidate */
.pfig .gp-axis{fill:none;stroke:rgba(16,21,40,.16);stroke-width:1;}
.pfig .gp-band{fill:rgba(139,126,224,.28);}
.pfig .gp-curve{
  fill:none;stroke:var(--violet);stroke-width:1.8;stroke-linecap:round;
  stroke-dasharray:420;stroke-dashoffset:420;
  animation:gpDraw 10s cubic-bezier(.4,.1,.2,1) infinite;animation-play-state:paused;
}
.pfig .gp-pts circle{fill:rgba(16,21,40,.45);}
.pfig .gp-pick{
  fill:var(--violet);
  animation:gpPick 10s ease infinite;animation-play-state:paused;
}
.panel.is-open .pfig .gp-curve,
.panel.is-open .pfig .gp-pick{animation-play-state:running;}

@keyframes gpDraw{
  0%,46%{stroke-dashoffset:420}58%{stroke-dashoffset:0}100%{stroke-dashoffset:0}
}
@keyframes gpPick{
  0%,56%{transform:translate(0,0)}
  62%{transform:translate(50px,-22px)}
  100%{transform:translate(50px,-22px)}
}

/* stage 3 — the fitted scores, matched */
.pfig .nm-axis{fill:none;stroke:rgba(16,21,40,.2);stroke-width:1.2;}
.pfig .nm-f{fill:var(--violet);}
.pfig .nm-c{fill:rgba(255,255,255,.95);stroke:var(--peri);stroke-width:1.8;}
.pfig .nmatch{
  fill:none;stroke:rgba(91,75,196,.85);stroke-width:2;
  stroke-dasharray:120;stroke-dashoffset:120;
  animation:nmDraw 10s cubic-bezier(.4,.1,.2,1) infinite;animation-play-state:paused;
}
.panel.is-open .pfig .nmatch{animation-play-state:running;}

@keyframes nmDraw{
  0%,72%{stroke-dashoffset:120}86%{stroke-dashoffset:0}100%{stroke-dashoffset:0}
}

/* The global reduced-motion block kills .reveal and .build only, so the lens
   has to opt out of its own choreography. Everything lands in its end state. */
@media (prefers-reduced-motion:reduce){
  .panel,
  .panel .pbody,
  .panel .spine,
  .panel .pfig .draw,
  .panel .pfig .pop,
  .panel .pfig .cells,
  .eq--base .tok{transition:none !important;}
  .panel .pfig .draw{stroke-dashoffset:0 !important;}
  .panel .pfig .pop{opacity:1;transform:none;}
  .panel .pfig .ln.cf.draw{opacity:1;}
  .panel .pfig .cells{opacity:1;}
  .panel .pfig .st,
  .panel .pfig .rt,
  .panel .pfig .rline,
  .panel .pfig .seen,
  .panel .pfig .gp-curve,
  .panel .pfig .gp-pick,
  .panel .pfig .nmatch{animation:none !important;}
  .panel .pfig .st--1{opacity:1;}
  .panel .pfig .st--2,
  .panel .pfig .st--3{opacity:0;}
  .panel .pfig .rt{opacity:1;}
  .panel .pfig .seen{opacity:1;}
  .panel .pfig .rline{stroke-dashoffset:0;}
  .panel .pfig .gp-curve,
  .panel .pfig .nmatch{stroke-dashoffset:0;}
}

/* ============================================================ narrow ==== */
/* The deck is designed at 16:9. It should still hold together when the window
   is not — stacking the three-across rows and giving the literature table one
   study per block, rather than letting columns squeeze into unreadable
   slivers. Everything above this line is layout-agnostic: sizes are ratios of
   one base, so they scale without a breakpoint. */
@media (max-width:1100px){
  .course,
  .g3,
  .compare{grid-template-columns:1fr;}
  .lit-head{display:none;}
  .lit-row{grid-template-columns:1fr;gap:.35em;padding:.7em 0;}
  .lit-who{display:flex;align-items:baseline;gap:.7em;flex-wrap:wrap;}
  .anatomy{flex-direction:column;height:auto;gap:6px;}
  .panel{
    flex:0 0 auto;min-height:4.4em;width:100%;
    flex-direction:row;align-items:center;gap:1em;
    padding:.9em 1.1em;
  }
  .panel.is-open{flex:0 0 auto;flex-direction:column;align-items:flex-start;}
  .panel .spine{
    writing-mode:horizontal-tb;transform:none;margin:0;white-space:normal;
    font-size:var(--type-dense);color:rgba(16,21,40,.72);
  }
  .panel .spine .snum{font-size:var(--type-h3);}
  .panel.is-open .pbody{
    position:static;padding:0;margin-top:.9em;
    opacity:1;transform:none;pointer-events:auto;
  }
  .panel .psplit{grid-template-columns:1fr;}
  .panel .pfig{max-width:22em;}
  .gapmap{grid-template-columns:1fr;grid-template-rows:none;}
  .arrow{display:none;}
}

/* Below this the deck is being read on a phone, not presented. */
@media (max-width:700px){
  .wrap{width:92vw;}
  .head{margin-bottom:1.2em;}
}

/* Short viewports used to need a stack of fixed overrides here: with the label
   tier pinned at 12px, breadcrumbs stood at 80% of body instead of 66%, every
   labelled row grew, and 1366x768 overflowed by 12-40px. The label step now
   rides the body down (see type.css), so the ratios hold at any height and
   there is nothing to give back. */

/* Below this the definition notes are the cheapest thing to lose, before the
   bullets have to start scrolling. */

/* Below this the footnote is the cheapest thing to lose — it is reading, not
   presenting, and the bullets matter more. */
@media (max-height:820px){
  .slide--method .panel .pfoot{display:none;}
}

/* ============================================================ print ==== */
@media print{
  #deck{height:auto;overflow:visible;}
  .slide{min-height:0;height:100vh;page-break-after:always;}
  #bar,#count,#hint,#overview{display:none;}
  .reveal,.build{opacity:1 !important;transform:none !important;}
}


/* #deck must be the offsetParent for scroll bookkeeping */
#deck{position:relative;}

/* ========================================================== appendix ==== */
/* Two diagnostics read as one exhibit. The figures are pinned to a single
   height so the plots sit against each other and both captions land on one
   baseline, whatever each bitmap's aspect ratio; the plate keeps the column's
   width and the image centres inside it. */
.fig-pair--match{align-items:stretch;}
.fig-pair--match .plate{align-items:center;}
.fig-pair--match .plate img{height:calc(var(--frame-h) * 0.46);width:auto;max-width:100%;}

/* The readings of the exhibit, under it: one paragraph per figure, in the same
   shape as .outcomes so the appendix looks like the rest of the deck. No rule
   here — the gap is the separation, and the figures already carry their own
   captions. */
.fig-notes{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--box-gap);
  margin-top:calc(var(--frame-h) * 0.026);
}
.fig-notes p{font-size:var(--type-dense);line-height:1.5;color:rgba(16,21,40,.72);max-width:none;}
.fig-notes b{color:var(--ink);}
.fig-notes--three{grid-template-columns:repeat(3,1fr);}

/* A specification is four things at once — when treatment starts, who is
   treated, who is the comparison, and what that pairing identifies — so it is
   read across a row, not down a column. Rules only: the table carries six rows
   and must not out-weigh the type above it. */
.spec-table{
  display:flex;flex-direction:column;flex:0 0 auto;
  margin-top:calc(var(--frame-h) * 0.014);
}
.head + .spec-table{margin-top:calc(var(--frame-h) * 0.026);}
.spec-table + .callout{margin-top:calc(var(--frame-h) * 0.026);}
.spec-table .row{
  display:grid;
  grid-template-columns:3.4em minmax(0,.72fr) minmax(0,.74fr) minmax(0,.78fr) minmax(0,1.35fr);
  gap:clamp(14px,1.6vw,36px);
  align-items:center;
  padding:calc(var(--frame-h) * 0.018) 0;
  border-top:1px solid var(--rule);
}
.spec-table .row:last-child{border-bottom:1px solid var(--rule);}
.spec-table .head-row{padding-top:0;padding-bottom:.45em;border-top:0;}
.spec-table .c{
  font-size:var(--type-dense);line-height:var(--lh-dense);color:var(--prose);
}
.spec-table .c.n{color:var(--ink);font-variant-numeric:tabular-nums;}
.spec-table .hd{
  font-family:var(--display);font-size:var(--type-label);line-height:1.3;
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--label);
}

/* The network's setting sheet: label on the left rail, value in the mono face
   the equations use. A hyperparameter reads as a setting rather than as prose,
   and the rules run edge to edge so the list reads as one block. */
.net{
  display:grid;grid-template-columns:auto 1fr;
  gap:var(--box-gap);align-items:center;
}
/* The card hugs the drawing rather than stretching to the column, so the
   diagram fills its own frame instead of floating in white space. The caption
   is given zero intrinsic width so it wraps inside the artwork rather than
   widening the card to its own length. */
.net .plate img{height:calc(var(--frame-h) * 0.34);width:auto;max-width:100%;}
/* The caption sits over the artwork rather than beside it, centred on the
   drawing so it reads as the diagram's own title. */
.net .plate figcaption{width:0;min-width:100%;padding-top:0;padding-bottom:.9em;text-align:center;}

/* The training aside closes the beat, under the exhibit it explains. Air on
   both sides rather than a rule: the gap says "note", the rule said "table". */
.net-note{margin-top:calc(var(--frame-h) * 0.034);}
.net-note p{
  font-size:var(--type-dense);line-height:1.5;color:rgba(16,21,40,.72);max-width:none;
}
.net-note b{color:var(--ink);}

/* One statement for the whole beat, full width: what the model is, and what it
   is doing here, in the same breath. */
.net-intro{margin-bottom:calc(var(--frame-h) * 0.034);}
.net-intro p{font-size:var(--type-body);line-height:1.5;color:var(--prose);max-width:none;}
.net-intro b{color:var(--ink);}
.spec-rows{display:flex;flex-direction:column;}
.spec-rows .r{
  display:grid;grid-template-columns:1fr auto;gap:0 1.4em;align-items:baseline;
  padding:.40em 0;border-top:1px solid var(--rule);
}
.spec-rows .r:last-child{border-bottom:1px solid var(--rule);}
.spec-rows .k{
  font-size:var(--type-label);line-height:1.3;letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--label);
}
.spec-rows .v{
  font-family:var(--mono);font-size:var(--type-dense);line-height:1.3;
  font-variant-numeric:tabular-nums;color:var(--ink);white-space:nowrap;
}
