/* ===========================================================
   ORGAN ON A CHIP - exhibition panel
   v5 design language: "exhibit panel, not web app".

   The reference is a science museum wall panel and a good
   A-level textbook figure. That means:
     - two self hosted typefaces, a real modular scale
     - near square corners, 2px everywhere, no pills
     - white space organised by hairline rules, not by
       wash filled rounded cards
     - textbook figure conventions: leader lines, figure
       captions, a scale bar, one stroke weight discipline
     - blue at full strength only for interactive things and
       one primary action per screen. The coral anatomy and
       the diagram colours carry the warmth.

   Layout rule, unchanged since v3: NOTHING SCROLLS. Every
   screen is a fixed composition that fills the viewport.
   Primary target 1080x1920 portrait. Secondary 1920x1080.

   THEME BLOCK
   Everything visual is a token below. To make a theme
   variant, swap this one block and nothing else.
   =========================================================== */

/* -----------------------------------------------------------
   FONTS - self hosted, zero external requests at runtime.

   Archivo, Omnibus-Type. SIL Open Font Licence 1.1.
     https://github.com/Omnibus-Type/Archivo
   Atkinson Hyperlegible, Braille Institute of America.
     SIL Open Font Licence 1.1.
     https://www.brailleinstitute.org/freefont
   Both woff2 files were taken from Google Fonts at build time
   and now live in ./fonts. Full licence text in fonts/OFL.txt.

   Atkinson Hyperlegible was drawn for low vision readers, which
   is the right body face for a school exhibit that has to be
   read standing up, from a distance, by anyone.
   ----------------------------------------------------------- */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-display: block;
  src: url("./fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 600 800;
  font-display: block;
  src: url("./fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("./fonts/atkinson-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("./fonts/atkinson-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 700;
  font-display: block;
  src: url("./fonts/atkinson-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: normal;
  font-weight: 700;
  font-display: block;
  src: url("./fonts/atkinson-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  font-style: italic;
  font-weight: 400;
  font-display: block;
  src: url("./fonts/atkinson-400italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* =========================================================== */

:root {
  /* ---- type ----
     Two faces and nothing else. Archivo carries every heading,
     every button and every figure that has to be read as a
     number. Atkinson carries all the reading matter. */
  --font-display: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-body: "Atkinson Hyperlegible", Verdana, "Segoe UI", system-ui, sans-serif;

  /* ---- paper and ink ---- */
  --bg:          #e8e3dd;   /* behind the stage, only seen in a window */
  --surface:     #ffffff;   /* the panel itself */
  --wash:        #f2eeea;   /* the ONE wash tone, for inset figure stages */
  --ink:         #14191f;   /* headings, rules that carry weight */
  --ink-soft:    #454f59;   /* body copy */
  /* captions, sources, figure numbers. Dark enough to clear AA
     against the inset wash as well as against white, because the
     figure labels sit on the wash. */
  --ink-faint:   #616b75;
  --line:        #e2ddd7;   /* hairline */
  --line-strong: #bdb5ac;   /* a rule that has a job to do */

  /* ---- anatomy: unchanged, this is the house illustration style ---- */
  --body-skin:      #f8cfbd;
  --organ-base:     #d9866a;
  --organ-deep:     #bf6749;
  --organ-line:     #a44d32;
  --organ-pale:     #e8a68d;
  --organ-mid:      #e0937a;

  /* ---- interaction: blue, and only where a finger can act ---- */
  --accent:         #2440d6;
  --accent-line:    #17299a;
  --accent-soft:    #93a0ef;
  --accent-wash:    #eaedfd;
  --accent-ink:     #ffffff;

  /* ---- chip ---- */
  --chip-glass:     #eef5f9;
  --chip-edge:      #b9ccd8;
  --chip-inner:     #d5e5ee;
  --chamber-fill:   #ffffff;
  --chamber-line:   #b9ccd8;
  --chamber-label:  #5d6b76;

  /* ---- the colour convention: blue = air, red/pink = blood ---- */
  --air:        #2f9ed4;
  --air-wash:   #ddeff9;
  --blood:      #e04166;
  --blood-wash: #fbdde4;

  /* ---- cell layers in the cross section ---- */
  --epithelium:      #8e7ce0;
  --epithelium-line: #6753c4;
  --endothelium:     #ef7f9c;
  --endothelium-line:#c94f70;
  --membrane:        #55616e;

  /* ---- things that live and move inside the cross section ---- */
  --rbc:        #f2a5b5;
  --rbc-line:   #d8546f;
  --wbc:        #fdfefe;
  --wbc-line:   #5d6a78;
  --wbc-nuc:    #a3b0bd;
  --germ:       #1f9d63;
  --germ-glow:  #9fe3bf;
  --alarm:      #e8912a;
  --alarm-wash: #fbe6c8;
  --leak:       #ead98a;
  --leak-line:  #c2a83c;

  /* ---- outcome states ----
     Two versions of each. The bright one is for drawings, where
     contrast rules do not apply. The -ink one is the only one
     ever allowed to carry text, and every one of them clears
     WCAG AA against white. */
  --ok:        #1f9d63;
  --warn:      #c4700c;
  --bad:       #d32f3c;
  --neutral:   #8a949e;

  /* the text-safe half of the pair. Every --tone-* below clears
     WCAG AA against white, and these are the only ones the lane
     labels, verdict labels and diagram labels are allowed to
     use. The bright versions above stay in the drawings. */
  --tone-ok:     #0f6b41;
  --tone-warn:   #8a4f07;
  --tone-bad:    #b31f2b;
  --tone-blood:  #b62445;
  --tone-air:    #15678f;
  --tone-accent: #2440d6;
  --blood-ink:   #b62445;
  --air-ink:     #15678f;

  /* ---- capsules on the comparison screen ---- */
  --pill-top:     #f4efe9;
  --pill-bottom:  #e08a70;
  --pill-line:    #cf9a85;
  --pill-win-top: #dfe4fd;
  --pill-win-bot: #2440d6;
  --dot-off:      #ded7cf;
  --pill-dim:     #ece5df;
  --pill-dim-line:#d9d0c8;

  /* ---- shape: near square, one value, no exceptions ---- */
  --radius: 2px;

  /* ---- motion ----
     One duration for every change of screen, so the panel always
     behaves the same way whichever route a visitor takes through
     it. Long enough to read as a move, short enough that a queue
     of people never waits for it. */
  --t-screen: 320ms;         /* the whole move, start to finish */
  --t-screen-out: 170ms;     /* the one leaving */
  --t-screen-in: 150ms;      /* the one arriving */
  --t-screen-in-delay: 170ms;/* it waits for the other to be gone, so two
                                screens are never half visible at once:
                                the panel hands over, it does not ghost */
  --t-screen-ease: cubic-bezier(.22,.9,.28,1);

  /* ---- the base unit, and the one thing that makes this
     composition responsive ----

     Everything below is a multiple of --k. It used to be 1vmin,
     which was composed against exactly 1080x1920 and 1920x1080
     and fell apart on anything else. A 3:4 screen is wide for
     its height, so a vmin scale made the type bigger exactly
     where there was least room for it, and a 9:21 screen got
     small type marooned in empty space.

     What a block of text actually costs in height goes as the
     square of the type size over the width of its column, so
     the size a panel can carry tracks the square root of its
     area, not its shorter side. sqrt() is not available on CSS
     lengths, so this is the straight line fit to it across
     every shape from 1:1 to 21:9, either way up, and it is
     within two per cent of the true geometric mean throughout.

     It lands on exactly 1vmin at 16:9 and 9:16, so both of the
     original targets are unchanged to the pixel.

     The 1vh cap is the second half of the rule. Plenty of a
     screen is furniture whose height does not fall when the
     columns get wider: a masthead, a row of meters, a button,
     a list of one line rows. On anything wider than 16:9 that
     furniture, not the prose, is what runs out of room. So a
     very wide panel holds the type size of a 16:9 one and
     spends the extra width on air instead. */
  --k: min(calc(0.467 * 1vmin + 0.3 * 1vmax), 1vh);

  /* ---- type scale: a 1.26 modular scale on --k ---- */
  --fs-caption: clamp(10px, calc(1.32 * var(--k)), 20px);
  --fs-micro:   clamp(10px, calc(1.42 * var(--k)), 21px);
  --fs-small:   clamp(12px, calc(1.58 * var(--k)), 23px);
  --fs-body:    clamp(13px, calc(1.80 * var(--k)), 26px);
  --fs-lead:    clamp(14px, calc(2.20 * var(--k)), 32px);
  --fs-h3:      clamp(15px, calc(2.45 * var(--k)), 35px);
  --fs-h2:      clamp(21px, calc(3.55 * var(--k)), 52px);
  --fs-h1:      clamp(28px, calc(5.60 * var(--k)), 80px);
  --fs-stat:    clamp(28px, calc(6.20 * var(--k)), 90px);
  --fs-display: clamp(34px, calc(10.20 * var(--k)), 150px);

  --sp-1: calc(0.75 * var(--k));
  --sp-2: calc(1.50 * var(--k));
  --sp-3: calc(2.40 * var(--k));
  --sp-4: calc(3.40 * var(--k));
  /* the air between the two halves of a screen. Wider than any
     gap inside them, because that gap is what makes a panel
     read as a panel rather than as a page. */
  --gutter: clamp(18px, calc(5.0 * var(--k)), 96px);
  --pad:  clamp(14px, calc(2.8 * var(--k)), 52px);
  --tap:  clamp(38px, calc(5.0 * var(--k)), 72px);
  /* the widest a column of reading matter is ever allowed to be.
     Without it an ultrawide panel sets 200 character lines. */
  --measure: 72ch;

  /* ---- one stroke discipline for every figure ----
     Rendered pixels, not user units, so a hairline on the body
     drawing is the same hairline on the chip and on the cross
     section however differently those three are scaled. */
  --sw-hair:   1.25px;
  --sw-line:   1.75px;
  --sw-heavy:  3px;
}


/* =========================================================== */

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

html, body {
  height: 100%;
  overflow: hidden;              /* the page itself never scrolls */
  background: var(--bg);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}
body { display: flex; align-items: center; justify-content: center; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: var(--accent); }

/* ---------- stage ---------- */
#stage {
  position: relative;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
}

/* ===========================================================
   MASTHEAD - quiet exhibit furniture. Wordmark left, sources
   right, one hairline under the lot.
   =========================================================== */
.topbar {
  position: relative; z-index: 5; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: 0 var(--pad);
  border-bottom: 1px solid var(--line);
}
.wordmark {
  display: flex; align-items: center; gap: 0.7em;
  min-height: var(--tap);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--ink);
}
.wordmark .dot {
  width: 0.62em; height: 0.62em; min-width: 6px; min-height: 6px;
  border-radius: 0; background: var(--accent);
}
.src-btn {
  display: inline-flex; align-items: center; gap: 0.55em;
  min-height: var(--tap); padding: 0 calc(1.4 * var(--k));
  border: 0; border-radius: 0;
  font-family: var(--font-display);
  font-size: var(--fs-small); font-weight: 700;
  color: var(--ink-soft);
  background: none;
}
.src-btn span:last-child { border-bottom: 1.5px solid var(--line-strong); padding-bottom: 0.1em; }
.src-btn:active { color: var(--accent); }
.src-btn:active span:last-child { border-bottom-color: var(--accent); }
.info-i {
  display: grid; place-items: center;
  width: 1.45em; height: 1.45em;
  border: 1.5px solid currentColor; border-radius: 50%;
  font-family: var(--font-display); font-weight: 700;
  font-size: 0.8em; line-height: 1;
}

/* ---------- screens ---------- */
/* the screens live inside their own clip, so the few pixels a
   screen lifts through on its way in or out can never add to the
   scrollable area of anything above it */
.screens { position: relative; z-index: 2; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.screen {
  position: absolute; inset: 0;
  display: none; flex-direction: column;
  overflow: hidden;
  padding: 0 var(--pad) var(--sp-2);
}
/* one crossfade for every screen change. The outgoing screen keeps
   its place on the stage while it fades, so nothing reflows, nothing
   scrolls and the two never appear side by side. */
.screen.is-on  { display: flex; animation: screenIn var(--t-screen-in) var(--t-screen-ease) var(--t-screen-in-delay) both; }
.screen.is-out { display: flex; animation: screenOut var(--t-screen-out) ease both; pointer-events: none; }
@keyframes screenIn  { from { opacity: 0; transform: translateY(1vmin); } to { opacity: 1; transform: none; } }
@keyframes screenOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-0.7vmin); } }
/* taps are refused while a screen is on its way in, so an eager
   second tap cannot land on whatever is arriving underneath it */
#stage.is-shifting .screens, #stage.is-shifting .nav, #stage.is-shifting .topbar { pointer-events: none; }

/* ===========================================================
   TYPE
   The one place letterspaced uppercase is allowed is .eyebrow.
   Everywhere else reads in sentence case, like print.
   =========================================================== */
.eyebrow {
  font-family: var(--font-display);
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-faint);
}
h1, h2, h3 {
  font-family: var(--font-display);
  color: var(--ink);
  text-wrap: balance;
}
h1 { font-size: var(--fs-h1); line-height: 0.96; letter-spacing: -0.032em; font-weight: 800; }
h2 { font-size: var(--fs-h2); line-height: 1.04; letter-spacing: -0.026em; font-weight: 800; }
h3 { font-size: var(--fs-h3); line-height: 1.16; letter-spacing: -0.014em; font-weight: 700; }
p  { font-size: var(--fs-body); line-height: 1.45; color: var(--ink-soft); }
p strong { color: var(--ink); font-weight: 700; }

/* figure captions: the single most useful bit of textbook
   furniture. "Fig. 2  The lung chip" and nothing more. */
.figcap {
  display: block;
  font-size: var(--fs-caption); line-height: 1.32;
  color: var(--ink-faint);
}
.figcap b {
  font-family: var(--font-display); font-weight: 700;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  margin-right: 0.45em;
}

/* source lines: one format, everywhere */
.src {
  display: block; margin-top: 0.5em;
  font-size: var(--fs-caption); line-height: 1.38;
  color: var(--ink-faint);
}

/* ===========================================================
   THE PANEL ENGINE

   Every numbered screen is the same two part composition: a
   figure half and a reading half. Nothing is a fixed split. The
   figure half takes the air that is going, the reading half
   takes what it needs, and --gutter holds them apart.

   Portrait stacks them, landscape sets them side by side with a
   rule between, exactly the way a printed panel divides its
   columns. Both are flex all the way down with min-height 0, so
   the composition shrinks rather than overflows whatever shape
   the screen turns out to be. Nothing ever scrolls.
   =========================================================== */
.panels {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  gap: var(--gutter);
  justify-content: center;
}
.panel {
  min-height: 0; min-width: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
}
/* The figure half is the shape of its drawing, not the shape of
   whatever room is left. A labelled figure in a 1080 wide column
   can only be so big before it runs out of width, so letting this
   half absorb every spare pixel just stranded the drawing in the
   middle of an empty box with its caption far below. It takes the
   height it needs, and .panels centres the whole composition, so
   the surplus falls as an even margin above and below it. */
.panel-figs  { flex: 0 1 auto; min-height: 0; justify-content: center; }
/* the reading half: only as tall as its words */
.panel-aside { flex: 0 0 auto; justify-content: flex-start; gap: var(--sp-3); }

@media (min-aspect-ratio: 1/1) {
  .panels { flex-direction: row; align-items: stretch; }
  .panel-figs  { flex: 1.1 1 0; }
  .panel-aside {
    flex: 1 1 0; min-height: 0; justify-content: center;
    padding-left: var(--gutter);
    border-left: 1px solid var(--line);
  }
}

/* reading matter never runs wider than a comfortable measure,
   whatever the panel does. This is what keeps a 21:9 screen from
   setting two hundred character lines. */
.scene-cap, .verdict p, .verdict h3, .story p, .payoff p,
.sum-foot, .kv dd, .lane-intro, .lane-cap { max-width: var(--measure); }
/* the working controls get a measure too, or a meter on a wide
   panel ends up with its label and its reading half a metre
   apart and the button reads as a banner */
.meters, .controls, .fix-list { max-width: min(100%, calc(56 * var(--k))); }

/* ---------- paired blocks of working matter ----------
   The three drug-test screens each carry a figure plus two blocks
   a visitor acts on: the controls, and the verdict that comes
   back. Stacking all three down one portrait column left a third
   of the panel blank on the right and, on a 3:4 screen, squeezed
   the figure down to nothing. So portrait spans the figure across
   the panel and sets the two blocks beside each other under it.

   Landscape folds the pair back into one reading column beside
   the figure, except on step 6, the heaviest screen, where
   display:contents dissolves the wrapper and the two blocks
   become columns of their own. */
/* shrink 0: when a screen is short, the figure is what gives way,
   never the words. A drawing that is 40px smaller is still a
   drawing; a paragraph clipped by 10px is a fault. */
.aside-pair {
  flex: 0 0 auto; min-height: 0; min-width: 0;
  display: flex; gap: var(--gutter);
}
.aside-pair > .panel { flex: 1 1 0; min-width: 0; }

@media (min-aspect-ratio: 1/1) {
  .aside-pair {
    flex: 1 1 0; flex-direction: column; justify-content: center;
    gap: var(--sp-3);
    padding-left: var(--gutter); border-left: 1px solid var(--line);
  }
  .aside-pair > .panel { flex: 0 0 auto; padding-left: 0; border-left: 0; }
  #s-fixes .aside-pair { display: contents; }
  #s-fixes .aside-pair > .panel {
    flex: 1 1 0; justify-content: center;
    padding-left: var(--gutter); border-left: 1px solid var(--line);
  }
}

/* a screen with only one lane on it centres that lane instead of
   letting it run the full width of a very wide panel */
.panels.one-lane { align-items: center; }
.panels.one-lane .panel-lane { width: 100%; max-width: min(100%, calc(96 * var(--k))); }

/* ===========================================================
   SCREEN 1 - the title panel
   =========================================================== */
#s-attract { justify-content: space-between; align-items: stretch; text-align: left; }
.attract-top {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-2) 0 0;
}
/* the standfirst sits under the title behind a heavy rule, the
   way the opening panel of an exhibition reads. It sits close to
   the rule above it and close to the hero below it: the space on
   this panel belongs to the drawing, not to the gaps. */
.attract-top .hook { margin-top: var(--sp-1); }
.attract-title {
  display: flex; flex-direction: column; align-items: flex-start; line-height: 0.82;
  padding-bottom: var(--sp-2);
  border-bottom: 3px solid var(--ink);
}
.attract-title .l1,
.attract-title .l3 {
  font-family: var(--font-display);
  font-size: var(--fs-display); font-weight: 800;
  letter-spacing: -0.042em; color: var(--ink);
}
.attract-title .l2 {
  font-family: var(--font-body); font-style: italic; font-weight: 400;
  font-size: calc(var(--fs-display) * 0.2);
  letter-spacing: 0; text-transform: none;
  color: var(--ink-faint);
  margin: 0.45em 0 0.3em;
}
.attract-hero {
  position: relative;
  flex: 1 1 auto; min-height: 0; width: 100%;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-1);
  padding: var(--sp-1) 0;
}
.attract-hero .hero-fig { height: 100%; min-height: 0; display: flex; align-items: center; justify-content: center; }
/* fixed shares rather than shrink-to-fit, so the body and the chip
   keep a constant size relationship whatever the screen does */
.attract-hero .hero-fig      { flex: 0 0 61%; }
.attract-hero .hero-fig.chip { flex: 0 0 34%; }
.attract-hero .hero-fig svg { height: 100%; width: 100%; }
.attract-hero .link-dots {
  width: calc(2.9 * var(--k)); height: 1px; flex: 0 0 auto;
  background: repeating-linear-gradient(to right, var(--line-strong) 0 6px, transparent 6px 12px);
}
.attract-bottom {
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2);
  width: 100%;
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line);
}
.hook {
  max-width: 44ch;
  font-size: var(--fs-lead); line-height: 1.34; color: var(--ink); font-weight: 400;
}
/* the one action on the panel, sized for a fifty inch screen read
   from a few steps away: a full width blue rule of a button under
   the hairline, not a small block in the corner */
.cta {
  width: 100%;
  min-height: calc(var(--tap) * 1.55);
  padding: 0 var(--sp-3);
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  font-family: var(--font-display);
  font-size: var(--fs-h3); font-weight: 800; letter-spacing: -0.02em;
  color: var(--accent-ink); background: var(--accent);
  border: 1.5px solid var(--accent);
  border-radius: var(--radius);
  transition: background .14s ease, transform .08s ease;
}
.cta:active { background: var(--accent-line); border-color: var(--accent-line); transform: translateY(1px); }
.tap-hint { font-size: var(--fs-caption); color: var(--ink-faint); align-self: center; }

/* ---- the detail inset ----
   Twenty seconds untouched and this fades up inside the title
   composition, playing the one beat worth stopping for. It is a
   plate set into the panel, on the same wash and with the same
   figure caption as every other drawing in the app, so it reads
   as part of the composition rather than something that popped
   up. It sits over the part of the figure that is dissolving
   anyway. Any touch clears it. */
.tease {
  position: absolute; right: 0; bottom: 0; z-index: 2;
  width: 40%; max-width: calc(46 * var(--k));
  opacity: 0; visibility: hidden;
  transition: opacity .55s ease, visibility 0s linear .55s;
}
.tease[data-on="1"] { opacity: 1; visibility: visible; transition: opacity .7s ease, visibility 0s; }
.tease-fig {
  background: var(--wash);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: var(--sp-1);
}
.tease-fig svg { display: block; width: 100%; height: auto; }
.tease .figcap { margin-top: 0.4em; }
/* at inset size the apparatus round the outside is noise, so the
   detail is cropped to the two channels and the sandwich */
.xsec.is-mini .side-chamber, .xsec.is-mini .arrow-faint { display: none; }
@media (min-aspect-ratio: 1/1) {
  /* the id has to carry .is-on too, or it would beat the
     .screen display rule and show through every other screen */
  #s-attract.is-on, #s-attract.is-out {
    display: grid;
    grid-template-columns: 1fr 1.2fr;
    grid-template-rows: 1fr 1fr;
    column-gap: var(--sp-4);
    align-content: stretch;
  }
  .attract-top { grid-column: 1; grid-row: 1; align-self: end; }
  .attract-bottom { grid-column: 1; grid-row: 2; align-self: start; border-top: 0; padding-top: var(--sp-3); }
  /* landscape has no spare room inside the hero, and the drawings
     are not for covering up. So the inset goes to the foot of the
     text column instead, where the panel is empty anyway. That
     means the hero cannot be the thing it is positioned against. */
  .attract-hero { grid-column: 2; grid-row: 1 / span 2; padding: var(--sp-1) 0; max-height: 100%; position: static; }
  .tease {
    left: var(--pad); right: auto; bottom: var(--sp-3);
    width: min(calc(42 * var(--k)), 40%); max-width: none;
  }
  .hook { max-width: 40ch; }
  .cta { max-width: 34ch; }
  .tap-hint { align-self: flex-start; }
}

/* ===========================================================
   SHARED BLOCKS
   =========================================================== */
/* one idea per screen means the heading can carry more weight and
   the standfirst can be set at reading size, the way the opening
   paragraph of a wall panel is */
.screen-head {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: calc(0.35 * var(--k));
  padding: var(--sp-3) 0 var(--sp-2);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--gutter);
}
.screen-head h2 { margin-top: 0.15em; max-width: 24ch; }
.screen-head p {
  max-width: min(100%, var(--measure));
  margin-top: 0.35em;
  font-size: var(--fs-lead); line-height: 1.36;
}

/* a surface only when something genuinely needs containing */
.card {
  border: 0;
  border-top: 1px solid var(--line-strong);
  border-radius: 0;
  background: none;
  padding: var(--sp-2) 0 0;
}

/* ---------- the body and the chip, side by side ----------
   Each plate gets a stage the shape of its own drawing, which is
   what keeps a Fig. caption against its figure instead of leaving
   it stranded at the foot of whatever height the screen had going
   spare. The chip is the taller of the two, so the pair is set on
   a common baseline and the captions line up with each other. */
.chiprow {
  flex: 0 1 auto; min-height: 0;
  display: flex; gap: var(--sp-4); justify-content: center; align-items: flex-end;
}
.chiprow .body-col, .chiprow .chip-col {
  flex: 1 1 0; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-2);
}
.fig {
  flex: 0 1 auto; min-height: 0; min-width: 0;
  width: 100%; max-height: 100%;
  display: flex;
}
/* the two labelled viewBoxes, so the stage is the drawing's shape */
.body-col .fig { aspect-ratio: 540 / 452; }
.chip-col .fig { aspect-ratio: 440 / 508; }
svg.body, svg.chip { flex: 1 1 auto; min-height: 0; min-width: 0; width: 100%; height: 100%; display: block; }

/* ===========================================================
   FIGURE CRAFT
   One hairline weight, one line weight, one heavy weight, all
   in rendered pixels so the three figures agree with each other
   however differently they are scaled.
   =========================================================== */
/* Outlines are measured in rendered pixels. Organs drawn AS
   thick strokes (trachea, colon, vessels) and the channel beds
   are shapes rather than outlines, so they are left to scale
   with their drawing and are not in this list. */
.org, .org-hair, .org-fold, .org-shape,
.chip-slab, .chip-inner, .port-ring, .cham-rect, .side-chamber,
.ch-air, .ch-blood, .epi, .endo, .membrane, .membrane-pores,
.rbc, .wbc-b, .pore-gap, .leak-fluid,
.alarm-spikes line, .arrow-faint, .arrow-epi,
.leader, .scalebar {
  vector-effect: non-scaling-stroke;
}

/* ---------- anatomy ---------- */
.sk       { fill: var(--body-skin); }
.org      { fill: var(--organ-base); stroke: var(--organ-line); stroke-width: var(--sw-hair); stroke-linejoin: round; }
.org.deep { fill: var(--organ-deep); }
.org.pale { fill: var(--organ-pale); }
.org.mid  { fill: var(--organ-mid); }
.org-stroke { fill: none; stroke: var(--organ-base); stroke-linecap: round; stroke-linejoin: round; }
.org-stroke.deep { stroke: var(--organ-deep); }
.org-stroke.mid  { stroke: var(--organ-mid); }
.org-stroke.pale { stroke: var(--organ-pale); }
.org-hair { fill: none; stroke: var(--organ-line); stroke-width: var(--sw-hair); stroke-linecap: round; opacity: 0.5; }
.org-edge { fill: none; stroke: var(--organ-line); stroke-linecap: round; stroke-linejoin: round; }
.org-fold { fill: none; stroke: var(--organ-deep); stroke-width: var(--sw-line); stroke-linecap: round; }

/* interactive organs */
.organ { cursor: pointer; }
.organ .org-shape {
  fill: var(--organ-base); stroke: var(--organ-line); stroke-width: var(--sw-line); stroke-linejoin: round;
  transition: fill .18s ease, stroke .18s ease;
}
.organ[data-organ="lungs"] .org-shape { fill: var(--organ-pale); }
.organ[data-organ="lungs"] { transform-box: fill-box; transform-origin: 50% 8%; }
@media (hover: hover) {
  .organ[data-hov="1"] .org-shape { fill: var(--accent-soft); stroke: var(--accent-line); }
}
.organ:focus { outline: none; }
.organ:focus-visible .org-shape { stroke: var(--accent-line); stroke-width: var(--sw-heavy); }
.organ[data-sel="1"] .org-shape { fill: var(--accent); stroke: var(--accent-line); }

/* ---------- the invitation to the first touch ----------
   Nobody knows a drawing is tappable until they are told. So
   until the first touch, the outline of the two things a finger
   can act on breathes slowly in the interactive blue: an outline,
   never a glow, on a three second cycle that is visible from
   across a room and calm enough to live on a wall all day. It
   stops at the first tap and does not come back until the kiosk
   resets itself. */
@media (prefers-reduced-motion: no-preference) {
  .invite .organ[data-organ="lungs"] .org-shape { animation: inviteOrgan 2.9s ease-in-out infinite; }
  .invite .chamber .cham-rect { animation: inviteChamber 2.9s ease-in-out infinite; }
}
@keyframes inviteOrgan {
  0%, 100% { stroke: var(--organ-line);  stroke-width: var(--sw-line); }
  45%      { stroke: var(--accent);      stroke-width: var(--sw-heavy); }
}
@keyframes inviteChamber {
  0%, 100% { stroke: var(--chamber-line); stroke-width: var(--sw-line); }
  45%      { stroke: var(--accent);       stroke-width: var(--sw-heavy); }
}
/* no movement allowed: the same invitation, held still */
@media (prefers-reduced-motion: reduce) {
  .invite .organ[data-organ="lungs"] .org-shape,
  .invite .chamber .cham-rect { stroke: var(--accent-soft); stroke-width: var(--sw-heavy); }
}

/* ---------- leader lines: the textbook label ----------
   A hairline out to the margin, a small solid dot on the thing
   being named, and the name set small in the body face. */
.leader {
  fill: none; stroke: var(--ink-faint); stroke-width: var(--sw-hair);
  stroke-linecap: round; stroke-linejoin: round;
  transition: stroke .18s ease;
}
.leader-dot { fill: var(--ink-faint); transition: fill .18s ease; }
.leader-label {
  font-family: var(--font-body); font-weight: 400;
  fill: var(--ink-soft);
  transition: fill .18s ease;
}
.leader-label.key { font-weight: 700; fill: var(--ink); }
/* the lungs are the one organ a visitor can act on, so their
   leader follows the selection */
[data-sel="1"] .leader { stroke: var(--accent); }
[data-sel="1"] .leader-dot { fill: var(--accent); }
[data-sel="1"] .leader-label { fill: var(--accent); font-weight: 700; }

/* the scale bar under the chip: the one honest way to say how
   big the real thing is */
.scalebar { fill: none; stroke: var(--ink-soft); stroke-width: var(--sw-line); stroke-linecap: butt; }
.scale-label {
  font-family: var(--font-display); font-weight: 700;
  fill: var(--ink-soft); font-variant-numeric: tabular-nums;
}

/* ---------- the chip ---------- */
.chip-slab  { fill: var(--chip-glass); stroke: var(--chip-edge); stroke-width: var(--sw-line); }
.chip-inner { fill: none; stroke: var(--chip-inner); stroke-width: var(--sw-hair); }
.bed-blood  { fill: none; stroke: var(--blood-wash); stroke-width: 9; stroke-linecap: round; }
.bed-air    { fill: none; stroke: var(--air-wash);   stroke-width: 7; stroke-linecap: round; }
.flow-blood {
  fill: none; stroke: var(--blood); stroke-width: 5; stroke-linecap: round;
  stroke-dasharray: 10 26; animation: flow 1.15s linear infinite;
}
.flow-air { fill: none; stroke: var(--air); stroke-width: 4; stroke-linecap: round; stroke-dasharray: 8 22; }
@keyframes flow { to { stroke-dashoffset: -36; } }
.port-ring { fill: var(--surface); stroke-width: var(--sw-line); }
.port-ring.air   { stroke: var(--air); }
.port-ring.blood { stroke: var(--blood); }
.port-core.air   { fill: var(--air); }
.port-core.blood { fill: var(--blood); }

.chamber { cursor: pointer; }
.chamber .cham-rect {
  fill: var(--chamber-fill); stroke: var(--chamber-line); stroke-width: var(--sw-line);
  transition: fill .2s ease, stroke .2s ease;
}
.chamber .cell { fill: var(--organ-pale); transition: fill .2s ease; }
@media (hover: hover) {
  .chamber[data-hov="1"] .cham-rect { stroke: var(--accent-soft); fill: var(--accent-wash); }
}
.chip-slab, .chip-inner, .bed-blood, .bed-air, .side-chamber,
.flow-blood, .flow-air, .port-ring, .port-core,
.leader, .leader-dot, .leader-label, .scalebar, .scale-label { pointer-events: none; }
.chamber[data-sel="1"] .cham-rect  { fill: var(--accent-wash); stroke: var(--accent); stroke-width: var(--sw-line); }
.chamber[data-sel="1"] .cell       { fill: var(--accent); }
.hit { fill: transparent; cursor: pointer; }

/* ---------- the organ note under the two figures ---------- */
.link-hint {
  flex: 0 0 auto;
  font-size: var(--fs-caption); font-style: italic; color: var(--ink-faint);
  transition: color .2s ease;
}
.link-hint[data-on="1"] { color: var(--accent); font-style: normal; font-weight: 700; }
.organ-card { flex: 0 0 auto; border-top: 2px solid var(--ink); }
.organ-card h3 { margin-bottom: 0.3em; }
.kv { display: grid; gap: var(--sp-2); margin-top: var(--sp-1); }
.kv dt {
  font-family: var(--font-display); font-size: var(--fs-caption); font-weight: 700;
  color: var(--ink-faint); margin-bottom: 0.25em;
}
.kv dd { font-size: var(--fs-small); line-height: 1.4; color: var(--ink-soft); }
@media (min-width: 700px) {
  .kv { grid-template-columns: 1fr 1fr; gap: var(--sp-2) var(--sp-4); }
  .kv .kvcell { min-width: 0; }
}
/* the three sourced lung facts, divided by rules rather than
   packed into little boxes. Three columns under the figure in
   portrait, a stacked column beside it in landscape. */
.facts { list-style: none; display: flex; gap: var(--gutter); width: 100%; }
.facts li {
  flex: 1 1 0; min-width: 0;
  font-size: var(--fs-small); line-height: 1.4; color: var(--ink);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--line-strong);
}
@media (min-aspect-ratio: 1/1) {
  .facts { flex-direction: column; gap: var(--sp-3); }
  .facts li { flex: 0 0 auto; }
}

/* ---------- the lung cross section ----------
   The one wash tone in the whole design is painted inside the
   drawing now, not on the box around it. So the box can be given
   whatever room a screen has going spare and the drawing simply
   centres in it: the leftover is white paper, which is the air
   this pass is about. */
/* the box is the shape of the drawing, so the Fig. caption under
   it sits against the drawing rather than floating at the foot of
   whatever height the screen had going spare */
.scene {
  flex: 0 1 auto; min-height: 0;
  width: 100%; aspect-ratio: 442 / 200; max-height: 100%;
  display: flex; align-items: center; justify-content: center;
}
.scene svg { width: 100%; height: 100%; max-height: 100%; display: block; }
.plate { fill: var(--wash); }
.side-chamber { fill: var(--surface); stroke: var(--line-strong); stroke-width: var(--sw-hair); }
.ch-air   { fill: var(--air-wash);   stroke: var(--air);   stroke-width: var(--sw-hair); }
.ch-blood { fill: var(--blood-wash); stroke: var(--blood); stroke-width: var(--sw-hair); }
.epi  { fill: var(--epithelium);  stroke: var(--epithelium-line);  stroke-width: var(--sw-hair); transition: fill .4s ease; }
.epi-n { fill: var(--epithelium-line); }
.endo { fill: var(--endothelium); stroke: var(--endothelium-line); stroke-width: var(--sw-hair); transition: fill .4s ease, stroke .4s ease; }
.endo-n { fill: var(--endothelium-line); }
/* the membrane is a structure, not an outline, so it carries the
   heavy weight. The pores are painted over it in the background
   colour, so they have to be a shade wider to punch right through */
.membrane { stroke: var(--membrane); stroke-width: var(--sw-heavy); }
.membrane-pores { stroke: var(--wash); stroke-width: 4.5px; stroke-dasharray: 2 7; }
.mini .membrane-pores { stroke: var(--surface); }
/* sizes are in the cross section's own user units, chosen so the
   labels render at about the same size as the labels on the body
   and the chip, which are scaled differently */
.svg-label { font-family: var(--font-body); font-weight: 700; font-size: 8px; }
.svg-label.faint { fill: var(--ink-faint); font-weight: 400; font-size: 7.4px; }
.svg-label.air   { fill: var(--air-ink); }
.svg-label.blood { fill: var(--blood-ink); }
.svg-label.epi   { fill: var(--epithelium-line); font-size: 7.4px; }
.breath-word {
  font-family: var(--font-display); font-weight: 700; font-size: 8.4px;
  fill: var(--air-ink);
}
.arrow-faint { fill: none; stroke: var(--ink-faint); stroke-width: var(--sw-line); stroke-linecap: round; stroke-linejoin: round; }
.arrow-epi   { fill: none; stroke: var(--epithelium-line); stroke-width: var(--sw-line); stroke-linecap: round; stroke-linejoin: round; }
.stretchable { transform-box: fill-box; transform-origin: center; }
.vac-l, .vac-r { transform-box: fill-box; transform-origin: center; }

.air-p { fill: var(--air); opacity: 0.55; }
.rbc   { fill: var(--rbc); stroke: var(--rbc-line); stroke-width: var(--sw-hair); }
.wbc-b { fill: var(--wbc); stroke: var(--wbc-line); stroke-width: var(--sw-line); }
.wbc-n { fill: var(--wbc-nuc); }
.wbc   { filter: drop-shadow(0 0 1.5px rgba(22,32,44,0.25)); }

.germ-glow { fill: var(--germ-glow); animation: germPulse 1.6s ease-in-out infinite; }
.germ-core { fill: var(--germ); }
@keyframes germPulse { 0%,100% { opacity: 0.75; } 50% { opacity: 0.35; } }
.signal-d { fill: var(--epithelium); }
.alarm-spikes { opacity: 0; }
.alarm-spikes line { stroke: var(--alarm); stroke-width: var(--sw-line); stroke-linecap: round; }
.pore-gap   { fill: var(--wash); stroke: var(--membrane); stroke-width: var(--sw-hair); opacity: 0; }
[data-alert="1"] .epi { fill: #a494ea; }
[data-alarm="1"] .endo { fill: var(--alarm-wash); stroke: var(--alarm); stroke-width: var(--sw-line); }
[data-alarm="1"] .endo-n { fill: var(--alarm); }

.leak-fluid { fill: var(--leak); stroke: var(--leak-line); stroke-width: var(--sw-hair); opacity: 0.85; }
.leak-drop  { fill: var(--leak-line); opacity: 0; }

.xsec[data-still="1"] .svg-label.faint,
.xsec[data-still="1"] .svg-label.epi,
.xsec[data-still="1"] .arrow-epi,
.xsec[data-still="1"] .arrow-faint { opacity: 0.28; }
.xsec[data-still="1"] .breath-word { fill: var(--ink-faint); }

/* the caption strip that narrates each phase, with the sequence
   progress drawn as a rule along the bottom */
.scene-cap {
  position: relative; overflow: hidden;
  flex: 0 0 auto;
  display: flex; align-items: center;
  min-height: 3.6em;
  padding: var(--sp-1) 0 calc(var(--sp-1) + 3px);
  border-bottom: 1px solid var(--line);
  background: none;
  font-size: var(--fs-small); line-height: 1.4; color: var(--ink);
}
.scene-cap::after {
  content: ""; position: absolute; left: 0; bottom: -1px;
  height: 2px;
  width: calc(var(--p, 0) * 100%);
  background: var(--accent);
}
.scene-row { flex: 0 0 auto; display: flex; gap: var(--sp-2); }
.scene-row .btn { flex: 0 1 auto; min-width: min(100%, calc(42 * var(--k))); }

/* the slots the drug test fills in as the visitor works through
   it. They hold their place so nothing under them jumps. */
.controls { flex: 0 0 auto; display: flex; flex-direction: column; gap: var(--sp-2); }
.controls:empty { display: none; }
.story-slot, .payoff-slot { flex: 0 1 auto; min-height: 0; }
.story-slot:empty, .payoff-slot:empty { display: none; }
/* a reading column with nothing in it yet takes no room and draws
   no rule, so an untouched screen is empty paper rather than an
   empty box */
.panel-aside:not(:has(> :not(:empty))) { display: none; }

/* ===========================================================
   SCREEN 3 - the IL-2 drug test
   =========================================================== */
.meters { flex: 0 0 auto; display: flex; flex-direction: column; gap: var(--sp-1); }
.meter {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) 0;
  border-top: 1px solid var(--line);
}
.meter[hidden] { display: none; }
.meter .ml { flex: 1 1 auto; font-size: var(--fs-small); color: var(--ink-soft); }
.meter .mb {
  flex: 0 0 auto; width: 24%; height: 8px;
  border-radius: 0; background: var(--dot-off); overflow: hidden;
}
.meter .mb i { display: block; height: 100%; width: 100%; background: var(--ok); transition: width .5s ease, background .5s ease; }
.meter .mb i[data-low="mid"] { background: var(--warn); }
.meter .mb i[data-low="1"]   { background: var(--bad); }
.meter .mv {
  flex: 0 0 auto; min-width: 7em; text-align: right;
  font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700;
  color: var(--ink);
}

.fix-list { flex: 0 0 auto; display: flex; flex-direction: column; gap: var(--sp-2); }

/* the three things to try: quiet outlined rectangles, sized for
   a finger but not for a sofa */
.drug-card {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; text-align: left;
  min-height: var(--tap);
  padding: var(--sp-1) var(--sp-2);
  border: 1.5px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface);
  transition: border-color .18s ease, background .18s ease, opacity .3s ease;
}
@media (hover: hover) { .drug-card:hover { border-color: var(--accent); } }
.drug-card:active { border-color: var(--accent); background: var(--accent-wash); }
.drug-card .cap {
  flex: 0 0 auto; min-width: 1.5em;
  font-family: var(--font-display); font-weight: 800; font-size: var(--fs-lead);
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint); text-align: center;
}
.drug-card .nm { display: block; font-family: var(--font-display); font-size: var(--fs-body); font-weight: 700; letter-spacing: -0.014em; color: var(--ink); }
.drug-card .ds { display: block; font-size: var(--fs-small); color: var(--ink-soft); line-height: 1.35; margin-top: 0.1em; }
.drug-card[data-tried="1"] { opacity: 0.55; }
.drug-card .tried-mark {
  margin-left: auto; padding-left: var(--sp-2);
  font-size: var(--fs-caption); color: var(--ink-faint);
}

/* ---------- verdict blocks: a rule and a label, not a box ---------- */
.verdict {
  border: 0; border-top: 2px solid var(--vc, var(--line-strong));
  border-radius: 0; background: none;
  padding: var(--sp-1) 0 0;
}
.verdict.is-star { background: none; }
.verdict .vtag {
  display: inline-block;
  font-family: var(--font-display); font-size: var(--fs-caption); font-weight: 700;
  letter-spacing: 0.01em; text-transform: none;
  color: var(--vc);
  border: 0; border-radius: 0; padding: 0;
  margin-bottom: 0.35em;
}
.verdict h3 { color: var(--ink); margin-bottom: 0.3em; margin-top: 0.15em; }
.verdict p { font-size: var(--fs-small); }
.star-tag {
  display: inline-block; margin: 0;
  font-family: var(--font-display); font-size: var(--fs-caption); font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--accent);
  background: none; border-radius: 0; padding: 0;
}
.star-tag::before { content: "\2605\00a0"; }
.star-tag::after { content: "\00a0\00b7\00a0"; color: var(--line-strong); }
.story {
  margin-top: var(--sp-2); padding-top: var(--sp-1);
  background: none; border-radius: 0;
  border: 0; border-top: 1px solid var(--line);
}
.story p { font-size: var(--fs-small); line-height: 1.45; color: var(--ink-soft); }
.mini-row { display: flex; gap: var(--sp-4); margin: var(--sp-2) 0; }
.mini { flex: 1 1 0; }
.mini svg { width: 100%; height: auto; display: block; }
.mini figcaption {
  margin-top: 0.45em;
  font-size: var(--fs-caption); line-height: 1.3; color: var(--ink-faint);
}
.mini figcaption b {
  font-family: var(--font-display); font-weight: 700; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; margin-right: 0.4em;
}

/* ===========================================================
   BUTTONS
   Confident rectangles. One primary action per screen carries
   the solid blue. Everything else is a quiet outline.
   =========================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  min-height: var(--tap);
  padding: 0 var(--sp-3);
  border-radius: var(--radius);
  border: 1.5px solid var(--line-strong);
  font-family: var(--font-display);
  font-size: var(--fs-body); font-weight: 700; letter-spacing: -0.008em;
  color: var(--ink);
  background: var(--surface);
  transition: border-color .14s ease, background .14s ease, color .14s ease, transform .08s ease;
}
@media (hover: hover) { .btn:hover { border-color: var(--ink); } }
.btn:active { transform: translateY(1px); }
.btn-primary { border-color: var(--accent); color: var(--accent-ink); background: var(--accent); }
@media (hover: hover) { .btn-primary:hover { border-color: var(--accent-line); background: var(--accent-line); } }
.btn-primary:active { background: var(--accent-line); border-color: var(--accent-line); }
.btn[disabled] { opacity: 0.4; }
/* wide enough for a confident tap, capped so it never becomes a
   banner stretched across a fifty inch panel */
.btn-full { width: 100%; max-width: calc(52 * var(--k)); align-self: flex-start; }
.btn-row { flex: 0 0 auto; display: flex; gap: var(--sp-2); }
.btn-row .btn { flex: 1 1 0; }

/* ===========================================================
   STEPS 7 and 8 - chips vs animal tests
   One lane per screen, each headed by a rule in its own colour.
   No boxes.
   =========================================================== */
.panel-lane { flex: 1 1 auto; min-height: 0; }

.lane {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: var(--sp-1);
  border: 0; border-top: 3px solid var(--lc, var(--accent));
  border-radius: 0; background: none;
  /* the last 12px is the room the button's cue ring stands off
     into, so the ring never adds to the height of the panel */
  padding: var(--sp-2) 0 12px;
}
.lane-head { flex: 0 0 auto; }
.lane-tag {
  display: block;
  font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700;
  letter-spacing: -0.005em; text-transform: none;
  color: var(--lc);
}
.lane-intro { font-size: var(--fs-small); margin-top: 0.3em; min-height: 2.6em; }
/* the headline number: big, tabular, in ink. The colour coding
   lives in the rule above the lane and in the capsules, so the
   number does not have to shout it as well. */
.lane-metric { flex: 0 0 auto; display: flex; align-items: baseline; gap: 0.4em; margin: var(--sp-1) 0; }
.lane-metric .big {
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: var(--fs-stat); font-weight: 800; letter-spacing: -0.05em; line-height: 0.86;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.lane-metric .mcap { font-size: var(--fs-small); color: var(--ink-soft); line-height: 1.2; }
.lane-stage {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.lane-stage .verdict { width: 100%; background: none; }
.lane-cap { flex: 0 0 auto; min-height: 2.9em; font-size: var(--fs-small); color: var(--ink); }
.lane > .src { flex: 0 0 auto; margin-top: 0; min-height: 2.4em; }
.lane > .src:empty { min-height: 0; }
/* each lane owns its own primary action, so both are blue. They
   are sized for a finger rather than stretched across the panel,
   which keeps the blue down to two calm blocks. */
.lane-btn { position: relative; flex: 0 0 auto; width: min(100%, calc(48 * var(--k))); align-self: flex-start; }

/* ---------- the same invitation, for a lane nobody has run ----------
   A hairline rectangle grows out of the button and fades, once
   every three and a bit seconds. It is the drawn equivalent of
   someone pointing at it: no bounce, no wobble, nothing that moves
   the button itself. It stops for good once the lane has been run. */
.lane-btn[data-cue="1"]::after {
  content: ""; position: absolute;
  top: -2px; right: -2px; bottom: -2px; left: -2px;
  border: 1.5px solid var(--accent); border-radius: var(--radius);
  opacity: 0; pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .lane-btn[data-cue="1"]::after { animation: btnCue 3.4s ease-out infinite; }
}
@keyframes btnCue {
  0%   { top: -2px;  right: -2px;  bottom: -2px;  left: -2px;  opacity: 0; }
  16%  { opacity: 0.5; }
  55%  { top: -10px; right: -10px; bottom: -10px; left: -10px; opacity: 0; }
  100% { top: -10px; right: -10px; bottom: -10px; left: -10px; opacity: 0; }
}
/* no movement allowed: one held outline standing off the button */
@media (prefers-reduced-motion: reduce) {
  .lane-btn[data-cue="1"]::after {
    animation: none;
    top: -6px; right: -6px; bottom: -6px; left: -6px;
    opacity: 0.4;
  }
}

/* 100 capsules, 10 by 10. The rounded ends are the drawing of a
   capsule, not a UI shape, so they stay. */
.tray-wrap { flex: 0 0 auto; width: 100%; display: flex; flex-direction: column; align-items: center; }
.tray-row { width: 100%; display: flex; align-items: center; justify-content: center; gap: var(--sp-3); }
.pillgrid {
  display: grid; grid-template-columns: repeat(10, 1fr); gap: var(--gg, calc(0.55 * var(--k)));
  width: min(100%, var(--gw, calc(42 * var(--k))));
  /* the failing capsules fall into this padding and are clipped
     there, so they never draw over the caption below */
  padding-bottom: var(--fall, calc(2 * var(--k)));
  overflow: hidden;
}
.pillgrid i {
  display: block; width: 100%; aspect-ratio: 2.8 / 1;
  border-radius: 999px;
  border: 1px solid var(--pill-line);
  background: linear-gradient(to right, var(--pill-top) 0 50%, var(--pill-bottom) 50% 100%);
  transform-origin: center;
  transition: transform .55s cubic-bezier(.4,0,.6,1), opacity .55s ease, background .35s ease, border-color .35s ease;
}
.pillgrid i.fail { opacity: 0; transform: translateY(var(--fall, calc(2 * var(--k)))) rotate(42deg) scale(0.6); }
.pillgrid i.win {
  border-color: var(--accent);
  background: linear-gradient(to right, var(--pill-win-top) 0 50%, var(--pill-win-bot) 50% 100%);
}
.pillgrid i.dim { border-color: var(--pill-dim-line); background: var(--pill-dim); }
/* ---------- the resting frame after a run ----------
   The survivors collect into a centred block with their count
   beside them. The grid keeps the exact footprint it had while
   they do it, so the caption under it and everything below hold
   still: only the capsules move. */
.pillgrid.is-tidy {
  grid-template-columns: repeat(var(--tcols, 5), 1fr);
  align-content: center; justify-content: center;
}
.pillgrid.is-tidy i.fail, .pillgrid.is-tidy i.dim { display: none; }
.tidy-count {
  flex: 0 0 auto; text-align: right;
  font-size: var(--fs-caption); line-height: 1.2; color: var(--ink-faint);
}
.tidy-count[hidden] { display: none; }
.tidy-count b {
  display: block;
  font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 800;
  letter-spacing: -0.03em; font-variant-numeric: tabular-nums; color: var(--ink);
}
.tray-label {
  display: block; margin-top: 0.5em; text-align: center;
  font-size: var(--fs-caption); line-height: 1.3; color: var(--ink-faint);
}
.tray-label b {
  font-family: var(--font-display); font-weight: 700; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; margin-right: 0.4em;
}

/* the comparison the visitor assembles as they go. It is the
   finale, so it sits under the chip lane in portrait and beside
   it in landscape. */
.summary {
  flex: 0 0 auto; min-width: 0;
  display: block;
  padding: var(--sp-2) 0 0;
  border: 0; border-top: 2px solid var(--ink); border-radius: 0;
  background: none;
}
.sum-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.sum-head h3 { color: var(--ink); }
.sum-hint { font-size: var(--fs-caption); font-style: italic; color: var(--ink-faint); }
.sum-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); margin-top: var(--sp-2); }
.sum-col { min-width: 0; border-top: 2px solid var(--lc); padding-top: var(--sp-1); }
.sum-ch {
  font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700;
  letter-spacing: -0.005em; text-transform: none; color: var(--lc);
}
.sum-col ul { list-style: none; margin-top: 0.4em; }
.sum-col li {
  position: relative; margin-top: 0.35em; padding-left: 1.3em; min-height: 1.9em;
  font-size: var(--fs-small); line-height: 1.32; color: var(--ink);
}
.sum-col li.got::before { content: "\2713"; position: absolute; left: 0; color: var(--lc); font-weight: 700; }
.sum-col li.todo i {
  display: block; height: 1px; margin-top: 0.6em; width: min(70%, 26ch);
  background: repeating-linear-gradient(to right, var(--line-strong) 0 7px, transparent 7px 14px);
}
.sum-col li .rs { font-size: var(--fs-caption); color: var(--ink-faint); margin-left: 0.5em; }
.sum-col li.always {
  padding-left: 0; margin-top: 0.7em; line-height: 1.4;
  font-size: var(--fs-caption); color: var(--ink-faint);
}
.sum-foot { margin-top: var(--sp-2); font-size: var(--fs-small); }

@media (min-aspect-ratio: 1/1) {
  .pillgrid { --gw: calc(50 * var(--k)); --gg: calc(0.5 * var(--k)); --fall: calc(2 * var(--k)); }
  .lane-cap { min-height: 3.8em; }
  .lane-intro { min-height: 3.2em; }
  .lane > .src { min-height: 4.4em; }
  .panel-lane { flex: 1.1 1 0; }
  .panels > .summary {
    flex: 1 1 0; align-self: stretch;
    padding-left: var(--gutter); border-left: 1px solid var(--line);
  }
  .sum-cols { grid-template-columns: 1fr; gap: var(--sp-3); }
  .sum-col li { min-height: 2.7em; }
}

.payoff {
  flex: 0 0 auto;
  padding: var(--sp-2) 0 0;
  border-radius: 0;
  border: 0; border-top: 2px solid var(--accent);
  background: none;
  text-align: left;
}
.payoff h3 { color: var(--ink); }
.payoff p { margin-top: 0.35em; font-size: var(--fs-small); }
#il2-payoff { margin-top: var(--sp-2); }

/* ===========================================================
   BOTTOM BAR
   Back / step indicator / Next. The indicator is a number and a
   rule, not a row of lozenges.
   =========================================================== */
.nav {
  position: relative; z-index: 5; flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-1) var(--pad) calc(var(--sp-1) + env(safe-area-inset-bottom, 0px));
  border-top: 2px solid var(--ink);
  background: var(--surface);
}
.nav.hidden { display: none; }
.nav-btn {
  flex: 0 0 auto;
  min-width: calc(var(--tap) * 2.1); min-height: var(--tap);
  display: grid; place-items: center;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius);
  font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700;
  color: var(--ink);
  background: var(--surface);
  transition: border-color .14s ease, background .14s ease, transform .08s ease;
}
.nav-btn:active { transform: translateY(1px); }
.nav-btn[disabled] { opacity: 0.3; }
.nav-btn.next { color: var(--accent-ink); border-color: var(--accent); background: var(--accent); }
.nav-btn.next:active { background: var(--accent-line); border-color: var(--accent-line); }

.stepbar { flex: 1 1 auto; display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.stepnum {
  flex: 0 0 auto;
  font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--ink-soft);
  white-space: nowrap;
}
.steps { flex: 1 1 auto; display: flex; align-items: stretch; gap: 6px; min-width: 0; }
.step {
  flex: 1 1 0; min-width: 0; height: var(--tap);
  display: flex; align-items: center;
  background: none; border: 0; padding: 0;
}
.step span {
  display: block; width: 100%; height: 2px;
  background: var(--dot-off); transition: background .3s ease, height .3s ease;
}
.step.seen span { background: var(--line-strong); }
.step[aria-current="true"] span { background: var(--accent); height: 4px; }

/* ===========================================================
   SOURCES - the one place scrolling is allowed
   =========================================================== */
.sheet {
  position: absolute; inset: 0; z-index: 40;
  display: none; flex-direction: column;
  background: var(--surface);
}
.sheet.is-on      { display: flex; animation: screenIn var(--t-screen-in) var(--t-screen-ease) var(--t-screen-in-delay) both; }
.sheet.is-closing { display: flex; animation: screenOut var(--t-screen-out) ease both; pointer-events: none; }
.sheet-head {
  flex: 0 0 auto; display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-3) var(--pad) var(--sp-2);
  border-bottom: 2px solid var(--ink);
}
.sheet-body { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-3) var(--pad) var(--sp-4); }
@media (min-aspect-ratio: 1/1) {
  .sheet-body { columns: 2; column-gap: var(--sp-4); }
  .cite { break-inside: avoid; }
}
.close-btn {
  flex: 0 0 auto;
  min-width: calc(var(--tap) * 1.4); min-height: var(--tap);
  display: grid; place-items: center;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius);
  font-family: var(--font-display); font-size: var(--fs-lead); font-weight: 700;
  color: var(--ink);
  background: var(--surface);
}
.close-btn:active { transform: translateY(1px); }
.cite { padding: var(--sp-2) 0; border-bottom: 1px solid var(--line); }
.cite .ct { font-family: var(--font-display); font-size: var(--fs-small); font-weight: 700; color: var(--ink); line-height: 1.24; }
.cite .cm { font-size: var(--fs-caption); color: var(--ink-soft); margin-top: 0.4em; line-height: 1.45; }
.cite a {
  display: inline-block; margin-top: 0.45em;
  font-size: var(--fs-caption);
  word-break: break-all; line-height: 1.45;
  text-decoration-thickness: 1px; text-underline-offset: 0.18em;
}
.note { font-size: var(--fs-small); color: var(--ink-soft); line-height: 1.5; }

/* ===========================================================
   PORTRAIT VERTICAL RHYTHM

   A portrait panel has more height than this content needs. The
   cross section is a 2.2:1 drawing and it is already as wide as
   the panel allows, so on a 9:16 screen it can only ever fill a
   third of the column. Centring the composition inside that
   left the surplus as two holes, one directly under the
   standfirst rule and one above the nav, with the figure, its
   readings and its buttons clumped in between.

   So in portrait the surplus is spent in a fixed order. The
   plate takes its share first, as a band that runs the full
   width of the screen with the drawing centred in it. What is
   left after that falls as an even margin above and below the
   whole composition, which is what .panels already does. The
   reading matter underneath does not move: it stays hard
   against the figure it belongs to.

   These rules are portrait only. Landscape is unchanged.
   =========================================================== */
@media (max-aspect-ratio: 1/1) {
  .scene {
    /* the stage is no longer the shape of the drawing. It is the
       drawing's own height plus a share of whatever the screen
       has going spare, and the taller the screen the bigger that
       share. The share is 0.45, which lands the plate's inside
       margin and the panel's margin around the plate on about
       the same figure, so the air reads as one rhythm rather
       than as two different accidents. */
    aspect-ratio: auto;
    height: calc(100vw / 2.21 + 0.45 * (100vh - 100vw));
    /* and never so tall that the wash swamps the drawing */
    max-height: calc(100vw / 1.15);
    /* full bleed. A band across the panel reads as architecture;
       an inset box with air around it reads as a box with air
       around it. It also buys the drawing the last of the
       width, which is the only way this figure gets any taller. */
    margin-left: calc(-1 * var(--pad));
    margin-right: calc(-1 * var(--pad));
    width: calc(100% + 2 * var(--pad));
    /* so the box and the drawing's own plate read as one field */
    background: var(--wash);
  }
  /* the tray is a figure too, and it sat at 42k wide in a panel
     of 96k. Taking the width is the one way it can take height
     as well, and it doubles the drawing while it is at it. */
  .pillgrid { --gw: 100%; --gg: calc(1.3 * var(--k)); }
  /* the lanes hold the same room open for the words a run puts
     into them as landscape does, so the stage is not the only
     part of the lane carrying the slack */
  .lane-intro { min-height: 3.2em; }
  .lane-cap { min-height: 3.8em; }
  .lane > .src { min-height: 4.4em; }
}

/* A tall portrait panel is a column, not a table. Below 7:10 the
   three sourced facts and the two notes under the chip read down
   it rather than across, which is also what makes them long
   enough to hold the foot of the panel. */
@media (max-aspect-ratio: 7/10) {
  .facts { flex-direction: column; gap: var(--sp-3); }
  .facts li { flex: 0 0 auto; }
  .kv { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
