/* ---------------------------------------------------------------------------
   pathways.css — the chrome a step-through pathway lesson is made of.

   Shared by glycolysis-lab.html and krebs-lab.html, which were one sheet copied
   twice: the rail, the stage's lane plates and hotspots, the enzyme blob and
   its verdicts, the carrier tray, the bottom bar, the ledger, the closing tally
   and the modal card. Two pages agreeing on 240 rules by hand is two pages that
   drift.

   WHAT STAYS ON THE PAGE: the pathway's own colours, its --carw, and the tally
   grid — every one of them a count or a size that IS the lesson.

   LOAD ORDER: kodo.css, then this, then the page's <style>.
   Tokens come from kodo.css; nothing here declares a colour that is not a
   pathway's own.
   --------------------------------------------------------------------------- */
  /* THE PATHWAY PALETTE: the four things a step can spend or make. A lesson
     adds its own (the cycle adds --fad and --co2) in its own <style>. */
  :root{ --phos:#e07b1f; --nad:#2f7d5c; --atp:#c8452e; --carbon:#3a3a3a;
    /* THE CARRIER COLUMN'S WIDTH, DECLARED ONCE. It is read twice: by #carrier,
       which is the box kit/carriers.js fits the molecule into, and by
       trayReservePx(), which is how much of the stage's left edge the camera
       gives up. Those two have to be the same number — a column wider than the
       reserve draws the carrier through whatever is standing beside it — and a
       variable is the only way to say so once. Override per lesson. */
    --carw:400px;
    /* WHAT THE CAMERA GIVES UP on the left — #tray's min-width, which
       trayReservePx() reads off the stylesheet rather than the live box (the
       tray is empty at the intro and holds a different tile count per step,
       none of which may move the molecule). Defaults to the carrier column, so
       nothing is drawn outside the reserve; a page whose carrier is a drawn
       molecule ROOMIER than the strip it stands in sets this narrower and
       accepts the overhang. */
    --trayw:var(--carw); }

  /* TWO ROWS, TWO COLUMNS: rail across the top, stage takes the rest. */
  @media (min-width:921px){
    #app-sidebar{grid-template-rows:auto minmax(0,1fr); grid-template-columns:1fr 316px}
    #railbar{grid-column:1 / -1}
    /* STAGE TAKES THE WHOLE WIDTH, NOTES ON TOP: sticky notes, not a panel to
       make room for, so the molecule centres on the PAGE. */
    #stage{grid-column:1 / -1;grid-row:2}
    #side{grid-column:2;grid-row:2;z-index:9;pointer-events:none;
      /* no paper of its own — an opaque column would cut a rectangle out of
         the scene behind it */
      background:none}
    #side>*{pointer-events:auto}
  }
  #stage,#side{min-height:0}
  /* Scrolls as one column, so no piece needs a fixed height. Paper only when
     stacked — wide, it lies over the stage and takes the stage's. */
  #side{overflow-y:auto; padding:0 20px 30px; box-shadow:none; border:none}
  @media (max-width:920px){
    #side{background-color:var(--paper);
      background-image:radial-gradient(var(--paper-line) 1.4px, transparent 1.4px);
      background-size:26px 26px; background-position:13px 13px}
  }
  #side::before{display:none}
  /* The sidebar's one column of content. It comes here with the rest of the
     rail because a pathway page is the only thing that draws it. No `flex`:
     the sidebar scrolls, not the card, which `flex:none` further down says. */
  #card{display:flex;flex-direction:column;min-height:0}

  /* ---------- species labels: DOM over the canvas, one per lane ----------
     Placed every frame from the projected anchor (drawPlates) so they track
     the molecule as the camera orbits; the anchor is the label's bottom-middle.
     NO FULL-STAGE FLASH on a click: the button's own burst already says the
     click landed, and a page-wide wash reads as the page blinking rather than
     as the reaction running. */

  /* ---------- the hotspot: click the bond that breaks ----------
     A step whose content is "this bond comes apart" should be run BY coming
     apart; a button at the bottom of the screen is a slideshow. Pinned every
     frame to the bond's projected midpoint (drawSpots), like the name plates.
     TWO LAYERS, because the glow is BEHIND the molecule and the click cannot
     be: the canvas is drawn with alpha, so a disc underneath shows through
     wherever the molecule is not, but the canvas takes the pointer (orbit), so
     the target is a transparent button above. Same projected point. */
  #gl{position:relative;z-index:2}          /* over the glow, under everything */
  #spotglow{position:absolute;inset:0;z-index:1;pointer-events:none}
  /* THE SAME DISC, ON THE CARRIER: a kinase step's clickable bond is ATP's β–γ.
     Own container because #spotglow's children are lane spots hidden by index. */
  #cglow{position:absolute;inset:0;z-index:1;pointer-events:none;display:none}
  /* THE PROMPT GOES WITH THE MARK, not beside the carrier's name. Sized to
     the disc it covers, not the lanes' 74px: what is lit here is small. */
  #cspot{position:absolute;z-index:8;width:50px;height:50px}   /* over the tray */
  #cspot[hidden]{display:none}
  /* A FLAT DISC WITH AN EDGE, not a feathered glow: the edge reads as a target
     you can hit rather than as lighting. */
  /* CENTRED ON MARGINS, not on a transform, for the reason .enzblob is placed by
     its corner: the pulse below owns `transform`, and a centring that shares the
     property has to be restated in every keyframe — which an engine is then free
     to apply on top of the base rather than instead of it. Margins are layout
     and cannot be animated out from under the disc. */
  .sglow{position:absolute;width:50px;height:50px;border-radius:50%;
    margin:-25px 0 0 -25px;
    background:rgba(107,79,216,.30);
    animation:sglowPulse 2.4s ease-in-out infinite}
  @keyframes sglowPulse{
    0%,100%{transform:scale(.86);opacity:.75}
    50%    {transform:scale(1.12);opacity:1}}

  /* THE PROMPT IS A CALLOUT (annotate.js, see drawHint), never text beside the
     disc — so kit/hotspot.js's own hint span is always handed '' and hidden. */
  .shint:empty{display:none}

  /* NO DOT ON THE CALLOUT HERE. annotate.js's dot marks the anchor, but the
     purple disc under the bond already marks it, and two marks on one point
     read as two things. The leader still lands on the anchor. */
  .annot-dot{display:none}
  #spots{position:absolute;inset:0;z-index:6;pointer-events:none}
  /* target invisible: the glow underneath is the affordance; a ring on top
     would be a second one */
  .spot{position:absolute;transform:translate(-50%,-50%);pointer-events:auto;
    width:74px;height:74px;padding:0;border:none;border-radius:50%;
    background:none;cursor:pointer;display:block}
  .spot:focus-visible{outline:3px solid var(--ink);outline-offset:2px}
  @media (prefers-reduced-motion:reduce){ .sglow{animation:none;opacity:.85} }
  @media (max-width:920px){ .sglow{width:72px;height:72px;margin:-36px 0 0 -36px}
    .spot{width:60px;height:60px} }

  /* ---------- THE ENZYME, behind the sugar ----------
     The blob's own rules moved to `kit/enzyme-blob.css`, which the module that draws
     it ships with: its JS places the element by its CORNER and animates a
     wrapper, and both of those are agreements with that stylesheet rather than
     with this one. What stays here is the LAYER — where a pathway page puts the
     blobs relative to its own stage — because that is this file's subject.
     UNDER THE CANVAS (#gl is z-index 2 over it) so the molecule sits inside the
     enzyme rather than behind a wash. */
  #enzymes{position:absolute;inset:0;z-index:1;pointer-events:none}
  #enzymes[hidden]{display:none}

  /* The step's verdicts, with the enzyme's name under them, down the right of
     the stage. IN THE CARD'S BAND ON PURPOSE: the energy card (#couplebox)
     lands here and covers these words — they are its door, and a door beside
     the thing it opened is noise. The sidebar's stacking context (see #side)
     paints it over them. */
  #enzmeta{position:absolute;right:26px;top:96px;z-index:5;
    max-width:230px;text-align:right;display:flex;flex-direction:column;
    align-items:flex-end;gap:-10px;}
#enzmeta .estat{
 font-family:var(--font-marker);
font-size: 20px;
}
  #enzmeta[hidden]{display:none}
  /* THE ASTERISK over the pair — "there is a footnote here", which is what the
     two words are. MASKED, not an <img>: the mask paints currentColor through
     the shape, so one class tints it to the first word's colour.
     LONGHANDS, not the `mask:` shorthand — Safari parses the shorthand's
     size-after-slash inconsistently and drops the whole declaration. */
  .east{display:block;width:36px;height:38px;margin-bottom:2px;
    background-color:currentColor;
    -webkit-mask-image:url(../images/asterisk.svg); mask-image:url(../images/asterisk.svg);
    -webkit-mask-repeat:no-repeat;   mask-repeat:no-repeat;
    -webkit-mask-position:center;    mask-position:center;
    -webkit-mask-size:contain;       mask-size:contain}
  /* k- prefix ON PURPOSE: `.cpl` is the coupling FIGURE's class
     (`display:block;width:100%`), and a bare `.cpl` here matches these too —
     which renders the asterisk 150px wide and 0 tall, i.e. invisible.
     Same for the words below. */
  .east.k-cpl{color:var(--phos)} .east.k-irr{color:var(--atp)} .east.k-rev{color:var(--nad)}
  /* A WORD YOU CAN PRESS, not a badge: the energy card is the explanation, and
     this is the only thing that says it is there. Underlined for that reason. */
  .estat{font-family:var(--font-slab);font-weight:700;font-size:14px;
    letter-spacing:.3px;background:none;border:none;padding:0;cursor:pointer;
    text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1.5px;
    text-decoration-color:rgba(0,0,0,.18)}
  .estat.k-cpl{color:var(--phos)}
  .estat.k-irr{color:var(--atp)}
  .estat.k-rev{color:var(--nad)}
  /* ONE HOVER FOR THE PAIR. The two words are two readings of the same step
     and one door into the same card, so hovering either lights BOTH — hence
     the state on the container, never on .estat itself. Highlighter, not a
     button fill: they stay text. */
  #enzmeta:hover .estat{background:rgba(255,205,60,.34);
    box-shadow:0 0 0 3px rgba(255,205,60,.34);border-radius:2px;
    text-decoration-color:currentColor}
  .estat{transition:background .12s ease, box-shadow .12s ease}
  .estat[aria-pressed="true"]{text-decoration-color:currentColor}
  /* between the two verdicts and the enzyme's name. Handwritten, because it is
     the aside a tutor would add in the margin — not another readout. */
  /* ANCHORED BY ITS BOTTOM, just above the enzyme's name — the note is
     handwritten and runs to one, two or three lines, and a top anchor sets
     where it STARTS, so a long one grows down through the name under it. From
     the bottom it grows upward into empty stage instead. The 186px is the
     name's own offset less the gap between them; move #enzname and move this. */
  #enznote{position:absolute;right:30px;bottom:calc(70% - 180px);z-index:5;
    max-width:170px;text-align:right;pointer-events:none;
    font-family:var(--font-hand);font-size:24px;line-height:1.2;color:var(--ink);
    opacity:.85}
  #enznote[hidden]{display:none}
  @media (max-width:920px){ #enznote{right:12px;max-width:160px;font-size:15px} }
  #enzname{position:absolute;right:26px;top:calc(35% + 200px);z-index:5;max-width:210px;
    text-align:right;pointer-events:none}
  #enzname[hidden]{display:none}
  #enzname .ek{font-family:var(--font-slab);font-size:16px;
    letter-spacing:1.4px;text-transform:uppercase;color:var(--muted);margin-bottom:3px}
  #enzname .en{font-family:var(--font-slab);font-size:22px;
    line-height:1.2;color:#2f7fa8}
  @media (max-width:920px){ #enzname{right:12px;max-width:150px}
    #enzmeta{right:12px;max-width:150px} #enzname .en{font-size:14px} }

  #plates{position:absolute;inset:0;z-index:5;pointer-events:none}
  .plate{position:absolute;transform:translate(-50%,-100%);transform-origin:50% 100%;
    text-align:center;white-space:nowrap;line-height:1.15;
    transition:opacity .22s ease}
  /* THE NAME GOES WHILE THE MOLECULE IS BECOMING SOMETHING ELSE — mid-beat the
     thing on stage is between two molecules and the label is still the OLD
     name. Set in runStep, cleared when the product lands. Opacity, never
     display: the label's HEIGHT is the camera's top reserve (plateHeightPx),
     so dropping it from layout re-frames mid-animation. */
  #plates.mute .plate{opacity:0}
  /* A NAME ON SOMETHING IN FLIGHT — step 6's inorganic phosphate, which is the
     only molecule that arrives from off-stage rather than being on a lane.
     ITS OWN HOST, not a .plate in #plates, for two reasons: #plates is MUTED
     for a step's whole animation (see above), which is exactly when this has
     to be readable; and plateHeightPx() reserves the lane labels' height off
     the top of the camera, which a label on a moving object must not do. */
  #flyers{position:absolute;inset:0;z-index:5;pointer-events:none}
  .flyplate{position:absolute;transform:translate(-50%,-100%);text-align:center;
    white-space:nowrap;line-height:1.15;opacity:0;transition:opacity .25s ease}
  /* SIZED LIKE A LANE PLATE, not smaller: it is the only label on a MOVING
     object, competing with a molecule crossing the frame behind it, and
     anything quieter is unreadable. */
  .flyplate .fn{font-family:var(--font-slab);font-weight:700;font-size:22px;
    color:var(--phos);letter-spacing:.2px}
  .flyplate .ff{font-family:var(--font-slab);font-weight:500;font-size:15px;
    color:var(--muted)}
  @media (max-width:920px){
    .flyplate .fn{font-size:18px}.flyplate .ff{font-size:13px} }
  @media (prefers-reduced-motion:reduce){ .flyplate{transition:none} }
  .plate .pn{font-family:var(--font-slab);font-weight:700;font-size:19px;
    color:var(--ink);letter-spacing:.2px}
  .plate .pf{font-family:var(--font-slab);font-weight:500;font-size:14px;color:var(--muted)}
  @media (max-width:920px){
    .plate .pn{font-size:16px}.plate .pf{font-size:12.5px}
  }

  /* ---------- THE CLOSING TALLY ----------
     Sits over the stage on the end screen (the lanes are hidden under it, so a
     product is never on screen twice). Bottom inset clears the bar.
     THE GRID ITSELF IS THE LESSON'S, in its own <style>: how many things a
     pathway makes is the fact the screen exists to state, so the column count,
     the gap and the side padding are set per page. */
  #tally{position:absolute;left:0;right:0;top:0;bottom:118px;z-index:6;
    display:grid;pointer-events:none}
  #tally[hidden]{display:none}
  .tcell{position:relative;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:2px;min-height:0}
  /* the molecule's box: the slot kit/carriers.js projects into. Takes the
     slack, so the count and name sit tight under the drawing. */
  .tcell .tvis{flex:1;width:100%;min-height:0;display:flex}
  .tcell .tslot{flex:1;min-width:0}
  /* NAME THEN COUNT, both in the heading face — the cell reads as a caption,
     not as a label with a footnote. ONE colour across every count: a page's
     multipliers are one fact, and per-product colours would say each number
     came from somewhere different. */
  .tname{font-family:var(--font-slab);font-weight:700;font-size:19px;
    line-height:1.1;color:var(--ink);letter-spacing:.2px}
  .tcount{font-family:var(--font-slab);font-weight:700;font-size:23px;
    line-height:1.1;color:var(--nad);font-variant-numeric:tabular-nums}
  /* …and a line under one count that says what it MEANS. Sized well under the
     count so it reads as a caption on that cell, not as a fifth product. */
  .tnote{font-family:var(--font-sans);font-size:10px;letter-spacing:.5px;
    text-transform:uppercase;color:var(--muted);line-height:1.3;margin-top:3px;
    max-width:22ch}
  /* the ledger here is a cell, not the sidebar's sticky rail */
  #tledger{pointer-events:auto}
  @media (max-width:920px){
    #tally{bottom:104px;padding:8px 4% 0;gap:6px 12px}
    .tcount{font-size:19px} .tname{font-size:16px}
  }

  /* ---------- the bottom bar: heading, action button, step caption ----------
     ONE CONTAINER, ONE FIXED HEIGHT — typed, not measured. The camera reserves
     exactly --barh along the bottom edge, so nothing here can re-frame the shot
     by growing: hence `visibility` on #actbtn/#stephead, and #rxmsg out of flow
     above the bar since its height genuinely varies. Grow the button or heading
     → change --barh. Centred like the frustum, so the two agree. */
  #bottombar{--barh:88px;
    position:absolute;left:0;right:0;bottom:18px;z-index:8;height:var(--barh);
    display:flex;flex-direction:column;align-items:center;justify-content:flex-end;
    gap:11px;pointer-events:none}
  #bottombar>*{pointer-events:auto}
  /* the step's heading: which of the ten, plus the hook or that it is done. */
  #stephead{visibility:hidden;min-height:15px;
    font-family:var(--font-marker);font-weight:700;font-size:20px;
    letter-spacing:1.6px;text-transform:uppercase;color:var(--ink); margin-bottom: 16px;}
  #stephead.on{visibility:visible}

  /* ---------- the caption, on demand ----------
     THE TOOLTIP CHANGES NO LAYOUT: out of flow in a zero-width wrapper,
     opening upward over the stage. */
  .infowrap{position:relative;display:inline-block;width:0;height:0;
    vertical-align:middle}
  .infobtn{position:absolute;left:7px;top:50%;transform:translateY(-50%);
    width:17px;height:17px;padding:0;border-radius:50%;cursor:help;
    border:1.5px solid var(--muted);background:none;color:var(--muted);
    font-family:var(--font-sans);font-weight:700;font-size:10.5px;line-height:1;
    display:grid;place-items:center;transition:color .15s,border-color .15s}
  .infobtn:hover,.infobtn:focus{color:var(--ink);border-color:var(--ink);outline:none}
  #steptip{position:absolute;bottom:calc(100% + 14px);left:50%;
    transform:translateX(-50%) translateY(4px);
    width:min(520px,86%);padding:13px 17px;border-radius:12px;
    background:var(--panel);border:1.5px solid var(--hair);
    box-shadow:0 14px 34px -14px rgba(60,50,28,.55);
    font-family:var(--font-sans);font-size:14.5px;line-height:1.5;
    letter-spacing:0;text-transform:none;color:#5a5346;text-align:center;
    /* never a click target: cannot swallow a press meant for the stage, cannot
       hold itself open once the pointer leaves the icon */
    pointer-events:none;opacity:0;visibility:hidden;
    transition:opacity .16s ease, transform .16s ease}
  /* icon and tooltip are not siblings, so the band does the asking. */
  #bottombar:has(.infobtn:hover) #steptip,
  #bottombar:has(.infobtn:focus) #steptip{
    opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)}
  #steptip b{font-weight:700;color:var(--ink)}
  #steptip .p{color:var(--phos)} #steptip .n{color:var(--nad)}
  #steptip:empty{display:none}
  @media (prefers-reduced-motion:reduce){ #steptip{transition:none} }
  /* review beat's shared entry — #couplebox.in's timing, so verdict, button
     and figure arrive together, after the molecule lands */
  .revin{animation:revin .42s cubic-bezier(.2,.9,.3,1) 1.5s backwards}
  @keyframes revin{
    from{opacity:0;transform:translateY(8px)}
    to  {opacity:1;transform:none}}
  @media (prefers-reduced-motion:reduce){ .revin{animation:none} }
  #stephead em{font-family: var(--font-marker); font-style:normal;color:var(--muted)}

  /* THE STEP CAPTION. Never times out — replaced by the next step's. Only the
     intro and closing screen carry text.
     OUT OF FLOW, ABOVE THE BAR: its height is the only varying one here, and
     inside the bar it would change the camera's bottom reserve, framing intro
     and closing differently from the ten steps between. Length is a legibility
     budget only: it opens upward, so a long one hits the molecule.
     --caph is the reserve while showing (capReservePx) — typed, like --barh.
     Two lines at this size, plus the gap. */
  /* CENTRED ON AUTO MARGINS, not on a transform: `left:0;right:0` gives the
     percentage width the same containing block to resolve against, and the auto
     margins do the centring — which leaves `transform` to the animation alone.
     Sharing it with `translateX(-50%)` means every keyframe has to restate the
     centring, and an engine that composes an animation onto the base transform
     rather than replacing it then applies it twice (see .enzblob, where that
     painted the shape half a box off its own footprint in Safari). */
  #rxmsg{--caph:70px;
    position:absolute;bottom:calc(100% + 6px);left:0;right:0;
    margin-inline:auto;
    width:min(680px,64%);text-align:center;
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    font-family:var(--font-sans);font-size:15.5px;line-height:1.55;
    color:#5a5346;letter-spacing:0;opacity:0;pointer-events:none}
  #rxmsg p{margin:0}

  #rxmsg b{font-weight:700;color:var(--ink)}
  #rxmsg .p{color:var(--phos)}#rxmsg .n{color:var(--nad)}
  #rxmsg.show{opacity:1}
  #rxmsg.in{animation:rxIn .5s ease-out}
  @keyframes rxIn{from{opacity:0;transform:translateY(5px)}
    to{opacity:1;transform:none}}
  @media (max-width:920px){ #rxmsg{width:88%;font-size:14px} }

  /* THE PAPER IS A CLASS, the position is the id. A page that wants a second
     note — the cycle's results screen puts up three — takes `.sticky` and says
     where it goes itself, rather than copying a gradient and a shadow. */
  .sticky{position:relative;
    background:linear-gradient(168deg,#fdf0a8 0%,#fbe88c 62%,#f6de79 100%);
    padding:13px 15px 15px;transform:rotate(-1.2deg);transform-origin:top right;
    box-shadow:0 1px 1px rgba(0,0,0,.16), 0 10px 18px -10px rgba(0,0,0,.42)}
  #ledger{position:sticky;top:0;z-index:6;flex:none;margin:0 0 16px}
  /* ledger is not on the opening screen*/
  #ledger.hid{display:none}
  #ledger.pin{animation:pinup .5s cubic-bezier(.2,.9,.3,1) backwards}
  @keyframes pinup{
    from{opacity:0;transform:rotate(-7deg) translateY(-16px) scale(.96)}
    to  {opacity:1;transform:rotate(-1.2deg)}}
  @media (prefers-reduced-motion:reduce){ #ledger.pin{animation:none} }
  /* the lifted bottom-left corner: a triangle of shade folding off the page */
  .sticky::after{content:'';position:absolute;left:0;bottom:0;width:22px;height:18px;
    background:linear-gradient(48deg,rgba(0,0,0,.13) 0%,rgba(0,0,0,.04) 55%,transparent 56%);
    pointer-events:none}
  /* Type warms up to sit on yellow: --muted and --hair are mixed against cream
     and go muddy here, so heading and rules are ink at low alpha. Meaningful
     colours stay — red net, grey carbons. */
  .sticky .lhead{font-family:var(--font-slab);font-weight:700;font-size:10px;letter-spacing:1.3px;
    text-transform:uppercase;color:rgba(60,50,28,.62);margin-bottom:9px}
  .sticky .row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
    font-family:var(--font-slab);font-size:14px;color:#4a4232;padding:2.5px 0}
  .sticky .row .v{font-weight:700;font-size:16px;color:var(--ink);font-variant-numeric:tabular-nums}
  .sticky .row.net{border-top:1.5px dashed rgba(60,50,28,.3);margin-top:7px;padding-top:7px}
  .sticky .row.net .v{font-size:20px;color:var(--atp)}
  .sticky .row.prod{border-top:1.5px dashed rgba(60,50,28,.3);margin-top:7px;padding-top:7px}
  /* page colours: green for the NAD⁺/NADH carrier, ink for pyruvate */
  .sticky .row.prod .v{color:var(--nad)}
  .sticky .row.carb{border-top:1.5px dashed rgba(60,50,28,.3);margin-top:7px;padding-top:7px}
  .sticky .row.carb .v{font-size:15px;color:var(--carbon);letter-spacing:.5px}
  .sticky .bump{animation:bump .7s ease}
  @keyframes bump{0%{color:var(--phos);transform:scale(1.32)}100%{transform:scale(1)}}
  /* NET ATP SWELLS AND SETTLES: other rows are counts incrementing, this one
     is the arithmetic changing its mind — the beat the whole page is for. */
  .sticky .row.net .v.bump{animation:netpulse .78s cubic-bezier(.3,.8,.3,1)}
  @keyframes netpulse{
    0%  {transform:scale(1)}
    32% {transform:scale(1.55);color:var(--phos)}
    100%{transform:scale(1)}}

  /* ---------- the coupling box, under the ledger ----------
     The step's energy picture — A SHEET OF PAPER */
  #couplebox{display:none;position:relative;margin:0 0 18px;padding:13px 15px 14px;
    background:#fdfaf2;
    /* the faint ruling of the sheet — the sidebar's own grid, one shade softer */
    background-image:linear-gradient(rgba(120,105,75,.09) 1px,transparent 1px),
      linear-gradient(90deg,rgba(120,105,75,.09) 1px,transparent 1px);
    background-size:22px 22px;
    border:none;border-radius:2px;
    transform:rotate(.7deg);transform-origin:top left;
    box-shadow:0 1px 1px rgba(0,0,0,.14), 0 10px 18px -10px rgba(0,0,0,.4)}
  /* the lifted corner, bottom-RIGHT — the ledger lifts its bottom-left */
  #couplebox::after{content:'';position:absolute;right:0;bottom:0;width:22px;height:18px;
    background:linear-gradient(-48deg,rgba(0,0,0,.12) 0%,rgba(0,0,0,.04) 55%,transparent 56%);
    pointer-events:none}
  #couplebox .nbody:last-child,#couplebox .minfo:last-child{margin-bottom:0}
  /* CLOSE. The card is opened from the stage (#enzmeta) and has to be
     dismissible from itself — hunting back for the word you pressed is not a
     close button. Quiet: it is chrome on a sheet of notes. */
  #couplebox .cx{position:absolute;top:6px;right:8px;z-index:1;
    background:none;border:none;padding:2px 4px;line-height:1;font-size:17px;
    color:var(--muted);cursor:pointer}
  #couplebox .cx:hover{color:var(--ink)}
  /* room for it, so a heading never runs under the ✕ */
  #couplebox .nhead:first-of-type{padding-right:20px}
  /* arrives with the molecule it explains (drawCouple). Both keyframes carry
     the sheet's .7° tilt: `transform:none` would animate it flat and leave it
     there, the end state winning over the rule above. The delay lets the
     molecule finish arriving — glow and settle still play when `reviewing`
     flips. `backwards` holds the opening keyframe through the wait, so the box
     never pops in at full strength first. */
  #couplebox.in{animation:cplin .42s cubic-bezier(.2,.9,.3,1) .8s backwards}
  @keyframes cplin{
    from{opacity:0;transform:translateY(10px) scale(.985) rotate(.7deg)}
    to  {opacity:1;transform:rotate(.7deg)}}
  @media (prefers-reduced-motion:reduce){ #couplebox.in{animation:none} }

  /* ---------- reagent tray, LEFT EDGE: the cell's pools ----------
     THE FIVE CARRIERS, NOT A MENU. All five stay on screen, so the one a step
     reaches for is read against the four it does not — and only ONE is ever a
     button: card, border, pulse and lift belong to the live one alone.
     A COLUMN ON THE LEFT — the bottom edge belongs to the step caption, which
     needs the width; five short rows do not.
     THE WIDTH IS RESERVED, ALWAYS. usable() measures this element for how far
     right the lesson sits, and the tray shows only the carriers this step
     touches — without a floor its width would change with the step and slide
     the stage sideways. 152px is the tile width; keep the two in step. */
  /* z-4: OVER the canvas (#gl is 2) so the carrier's molecule and cards draw on
     the stage, UNDER #spots (6) so a lane's marked bond stays clickable where
     the 400px-wide carrier column reaches over it — which it does below about
     1170px. The carrier's own target (#cspot, z-8) is above either way. */
  #tray{position:absolute;left:26px;top:50%;transform:translateY(-50%);z-index:4;
    min-width:var(--trayw);
    display:flex;flex-direction:column;align-items:stretch;gap:3px}
  .tstrip{display:flex;flex-direction:column;gap:2px}
  /* ---------- ATP/ADP drawn, not carded ----------
     ATP and ADP are ONE object that changes — three phosphates to two, and back
     at steps 7 and 10 — so the student sees what the step did to the carrier
     rather than one card swapping for another. Drawn in full, never
     schematically — see THE ATP/ADP CARRIER in the script.
     NOT A CARD: drawn on the stage, at stage scale, beside the sugar — leaving
     the name, the prompt and the space the camera insets (usable()).
     THE BOX TAKES NO POINTER unless it is the button (steps 7/10, `.live`). At
     z-7 over the whole left column, an interactive box swallows every click
     aimed at the molecule behind it — including the marked bond. */
  #carrier{width:var(--carw);position:relative;pointer-events:none;
    padding:7px 8px 6px;border:2.5px solid transparent;border-radius:9px;
    background:none;text-align:left;user-select:none}
  /* No card treatment when live: the thing to click is the γ phosphate, so a
     pulsing box round the NAME points at the wrong object. The cue is the
     `need` prompt plus the disc behind the bond (#cglow). */
  #carrier.live{cursor:pointer;pointer-events:auto}
  #carrier .need{position:absolute;left:calc(100% + 9px);top:50%;transform:translateY(-50%);
    font-family:var(--font-hand);font-size:15px;color:var(--phos);white-space:nowrap;display:none}
  #carrier.live .need{display:block}
  #carrier .cname{font-family:var(--font-slab);font-weight:700;font-size:14px;
    color:var(--ink);line-height:1.15}
  #carrier .csub{font-family:var(--font-sans);font-size:9.5px;letter-spacing:.6px;
    text-transform:uppercase;color:var(--muted);line-height:1.2;margin-bottom:5px}
  /* The slot the molecule stands in. Fixed height whatever the step is doing —
     the camera's left inset measures off it — and takes no pointer: the only
     button on a spend step is the disc over the bond (#cspot). */
  #carrier .cvis{position:relative;height:470px;pointer-events:none;
    display:flex;flex-direction:column}
  /* ONE SLOT PER MOLECULE, stacked. Slots split the column's height, so two ATP
     come out half the size of one with no second constant — placeCarrierMol
     fits each to its own box and the printed scale is measured off it. */
  #carrier .cslot{position:relative;flex:1 1 0;min-height:0}
  /* The name goes on the SLOT once there is more than one: on a half-run step
     one is ATP and the other still ADP, so one heading can only be half right. */
  #carrier .clab{position:absolute;left:0;top:6px;
    font-family:var(--font-slab);font-weight:700;font-size:15px;color:var(--ink)}
  /* …and only then: with one molecule the column heading is already its name. */
  #carrier .clab{display:none}
  #carrier.multi .clab{display:block}
  #carrier.multi .cname,#carrier.multi .csub{display:none}
  #carrier .cname{font-size:17px}
  /* THE SHRINK, STATED. Fitted to the column, so NOT at the sugars' scale — a
     molecule quietly drawn small is a size claim nobody made. Written from the
     measured specs every frame, not typed. */
  #carrier .cscale{font-family:var(--font-sans);font-size:9.5px;
    color:var(--muted);line-height:1.25;margin-top:3px}


  /* the action button (Begin, and every no-reagent step), inside #bottombar.
     the site sheet's .pill, so the lesson's one invitation is the same object the
     rest of the site presses. Only the reserved-space trick is left here:
     `visibility` holds the row's height on the steps with no button. It comes
     out at 50px inside an 88px --barh, so nothing here can re-frame the shot.
     The blue here is the sketchbook's; kodo.css puts the button back to the
     site's own primary, which is the accent: see its §10. */
  #actbtn{visibility:hidden; --pill-fill:var(--action-blue)}
  /* the row that holds the action button and, at the end of a turn, the way
     past it. Centred, so a shorter secondary sits on the primary's axis. */
  #actrow{display:flex;align-items:center;gap:12px}
  /* the ink button is not always the first one written: `flip` puts the
     secondary on the left without either button knowing which it is. */
  #actrow.flip #actbtn{order:2}
  /* SECONDARY, AND IT HAS TO LOOK IT: .pill--ghost is that in the system. It
     is shorter than .pill by design, so the row centres the two rather than
     matching their boxes — which is what kept them looking straight before. */
  #actalt[hidden]{display:none}
  #actbtn i{font-size:15px;vertical-align:-1px;margin-left:2px}

  /* the press: a short flash in the DOM. Nothing chemical has happened yet, so
     it stays UI feedback, not an fx.js in-scene effect (SCIENCE.md §5). */
  #actbtn.burst{animation:actpress .3s ease-out}
  @keyframes actpress{
    0%  {transform:scale(1)}
    40% {transform:scale(.96)}
    100%{transform:scale(1)}}
  @media (prefers-reduced-motion:reduce){ #actbtn.burst{animation:none} }


  /* ---------- modal chrome ----------
     Shared by every modal a pathway page opens. A module shown INSIDE one
     brings its own styling (massaction.css, say); this supplies the card it
     sits in and nothing inside it.
     LOOK IS OURS, BEHAVIOUR IS kit/modal.js's — it reads `.mback`, `.mcard` and
     `.mclose` and toggles the `hidden` attribute, so the one rule it depends on
     is `.modal[hidden]{display:none}`. Restyle freely above that. */
  .modal{position:fixed;inset:0;z-index:60;display:flex;
    align-items:center;justify-content:center;padding:24px}
  .modal[hidden]{display:none}
  .modal .mback{position:absolute;inset:0;background:rgba(38,33,26,.52);
    backdrop-filter:blur(2px);animation:mfade .22s ease-out}
  /* A SHEET OF PAPER, all four of them: panel white, no inked border, depth
     from the shadow — a border would frame each modal as one more readout on a
     page already made of bordered stickers. */
  .modal .mcard{position:relative;width:min(660px,100%);max-height:100%;
    overflow:auto;background:var(--panel);border:none;
    border-radius:14px;padding:24px 26px 22px;
    box-shadow:0 30px 70px -24px rgba(0,0,0,.55);animation:mrise .26s cubic-bezier(.2,.9,.3,1)}
  @keyframes mfade{from{opacity:0}to{opacity:1}}
  @keyframes mrise{from{opacity:0;transform:translateY(14px) scale(.98)}
                   to  {opacity:1;transform:none}}
  .modal .mkick{font-family:var(--font-slab);font-weight:700;font-size:11.5px;
    letter-spacing:1.5px;text-transform:uppercase;color:#4f5bd5;margin:0 0 5px}
  .modal h2{font-family:var(--font-slab);font-weight:700;font-size:19px;
    margin:0 0 3px;color:var(--ink)}
  .modal .msub{font-family:var(--font-sans);font-size:12.5px;color:var(--muted);
    line-height:1.5;margin:0 0 14px}
  .modal .msub b{color:var(--ink);font-weight:600}
  .modal .mclose{position:absolute;top:10px;right:12px;font-size:22px;
    line-height:1;background:none;border:none;color:var(--muted);cursor:pointer;padding:4px}
  .modal .mclose:hover{color:var(--ink)}
  .modal .mtext{font-size:13px;line-height:1.55;color:#5a5346;margin:13px 0 0}
  .modal .mtext b{color:var(--ink)}

  /* ---------- PATHWAY RAIL, across the top ----------
     TEN NUMBERS, and nothing else: canonical textbook 1-10, the lit one is the
     step on stage, an already-run one clicks back. Names, carriers, one-way
     markers and stage blocking live in Show 10 steps, where they read as a
     list rather than squeezed into a strip. */
  #railbar{position:relative;display:flex;align-items:center;justify-content:center;
    padding:12px 22px 10px;z-index:7;
    /* same dotted paper as stage and sidebar — the numbers sit on the page, so
       the bar itself is not a surface */
    background-color:var(--paper);
    background-image:radial-gradient(var(--paper-line) 1.4px, transparent 1.4px);
    background-size:26px 26px; background-position:13px 13px}
  /* absolute so the rail stays centred however wide the word is. */
  #pagetitle{position:absolute;left:22px;top:50%;transform:translateY(-50%);
    margin:0;font-family:var(--font-marker);font-weight:700;font-size:24px;
    letter-spacing:.2px;color:var(--ink)}
  /* shown only in a frame, where lib/embed.js has put `bare` on <html> */
  .framed-only{display:none}
  html.bare .framed-only{display:block}
  #rail{display:flex;align-items:center;gap:10px}
  .rnum{width:30px;height:30px;padding:0;border-radius:50%;
    border:2px solid var(--hair);background:var(--panel);color:var(--muted);
    font-family:var(--font-slab);font-weight:700;font-size:14px;line-height:1;
    display:flex;align-items:center;justify-content:center;cursor:pointer;
    transition:opacity .12s, border-color .12s, color .12s, box-shadow .12s}
  /* hover everywhere the click goes somewhere — every number is reachable,
     forward included. cur and run are where you already are. */
  .rnum:not(.cur):not(.run):hover{opacity:1;border-color:var(--ink);
    color:var(--ink);background:rgba(43,39,35,.07);
    box-shadow:0 0 0 3px rgba(43,39,35,.09)}
  /* a step already run is filled — the trail you can count and click back into */
  .rnum.past{background:var(--ink);border-color:var(--ink);color:var(--panel);
    opacity:.45}
  .rnum.past:hover{color:var(--panel)}
  .rnum.cur{background:var(--ink);border-color:var(--ink);color:var(--panel);
    box-shadow:0 3px 10px -3px rgba(0,0,0,.5);cursor:default}
  /* THE STEP YOU ARE ON: awake but empty, from yours-to-run until it lands.
     Neither dim nor filled — happening, not yet made. */
  .rnum.run{opacity:1;border-color:var(--ink);color:var(--ink);border-width:2.5px;
    box-shadow:0 0 0 3px rgba(43,39,35,.1);cursor:default}
  /* not run yet: dim, but still clickable — jumping ahead hands over a
     molecule you never made, which the page allows. */
  .rnum.future{opacity:.4}
  /* END: the eleventh stop, and the only one that isn't a number — the screen
     after step 10, where the pathway is done and the closing question waits.
     A word, so it never reads as "step 11". */
  .rnum.rend{width:auto;padding:0 11px;border-radius:15px;font-size:12px;
    letter-spacing:.6px;text-transform:uppercase;font-family:var(--font-sans);
    margin-left:2px}

  /* The sidebar scrolls, not the card. */
  #card{overflow:visible;flex:none}
  #stepnote{padding-top:2px}
  .note .nhead{font-family:var(--font-slab);font-weight:700;font-size:14px;
    letter-spacing:1px;text-transform:uppercase;margin-bottom:7px}
  .note .nhead.irr{color:var(--atp)}
  .note .nhead.rev{color:var(--nad)}
  .note .nbody{font-size:13px;line-height:1.5;color:#5a5346;margin:0 0 9px}
  /* the door to the mass-action demo, under the reversibility verdict it backs
     up. The verdict is a heading and nothing else, so this link is its only
     explanation — the demo is the picture, a paragraph is not. */
  .note .minfo{font-family:var(--font-sans);font-weight:600;font-size:12px;
    letter-spacing:.2px;color:#4f5bd5;background:none;border:none;padding:0;
    cursor:pointer;text-decoration:underline;text-underline-offset:3px;
    text-decoration-thickness:1.5px;margin:-4px 0 9px}
  /* after a heading there is no descender to close up, so drop the -4px. */
  .note .nhead + .minfo{margin-top:0}
  .note .minfo:hover{color:#7b3fe4}
  .note .nbody:last-child{margin-bottom:0}
  /* THE FREE-ENERGY FIGURES ARE energy/energy.js's, and their rules went with
     them into energy/energy.css — the enzymes lesson wants the same card
     without the rail, the tray and the ledger around it. A pathway page loads
     energy.css after this one. */

  /* base sizing fits the sidebar; #pfkbody takes the modal's extra room. */
  .effrow{display:flex;gap:9px;margin:0 0 10px}
  #pfkbody .mtext{margin:0 0 14px}
  #pfkbody .effrow{margin:0 0 14px;gap:14px}
  #pfkbody .eff{padding:11px 13px}
  #pfkbody .eff .e{font-size:12.5px}
  .eff{flex:1;padding:8px 9px;border-radius:7px;background:rgba(0,0,0,.03)}
  .eff .eh{font-family:var(--font-slab);font-weight:700;font-size:10.5px;letter-spacing:.8px;
    text-transform:uppercase;margin-bottom:5px}
  .eff.on .eh{color:#1f6146}.eff.off .eh{color:#a8371f}
  .eff .e{font-size:12px;line-height:1.35;margin-bottom:5px;color:var(--muted)}
  .eff .e:last-child{margin-bottom:0}
  .eff .e b{display:block;color:var(--ink);font-weight:600}

  /* THE UTILITY CONTROLS SIT IN THE BOTTOM CORNERS, quiet. Restart throws work
     away and PNG is a side errand, so neither may be more inviting than the
     lesson's button between them — and the top of the stage is for the
     molecule's name. PNG takes #restart's muted treatment rather than the
     stage button's ink; the pathway map's door stacks above Restart. */
  #tools{position:fixed;right:26px;bottom:22px;z-index:20;
    display:flex;flex-direction:column;align-items:flex-end;gap:10px}
  /* THE TOGGLES ARE the site sheet's `.btn .btn--check` — the tick is the state and
     the chip around it is only a label, which is the rule that file argues.
     Nothing about them is written here any more; what stays is the corner they
     sit in. */
  #allsteps,#toggles{position:static}
  #restart,#savepng,#allsteps{z-index:20;
    font-family:var(--font-sans);font-weight:600;font-size:13px;letter-spacing:.5px;
    color:var(--muted);background:transparent;border:2px solid var(--hair);
    border-radius:var(--radius);padding:6px 15px;cursor:pointer;
    display:inline-flex;align-items:center;gap:6px}
  /* fixed: holds the SCREEN's corner past the readout column, and the scrolling
     sidebar cannot carry it away. The row owns the position; the two buttons
     inside it are static, so neither has to know the other's width. */
  #utils{position:fixed;left:26px;bottom:22px;z-index:20;display:flex;gap:10px}
  #restart,#savepng{position:static}
  /* room under the last readout so the buttons never land on it. Its own
     stacking context, above the stage: the energy card has to cover #enzmeta,
     which is absolutely positioned inside #stage. */
  #side{padding-bottom:110px;position:relative;z-index:9}
  #restart:hover,#savepng:hover,#allsteps:hover{background:var(--ink);color:var(--paper);border-color:var(--ink)}

  /* ---------- all ten steps, in one column ----------
     THE DETAIL LIVES HERE, since the rail is ten numbers: each step's product,
     the carrier it moved, the one-way marker, the star into PFK-1's regulation,
     under the four stage headings. Owns these styles outright. */
  #stepsbody .sgroup{margin-top:14px}
  #stepsbody .sgroup:first-child{margin-top:6px}
  #stepsbody .sghead{display:flex;align-items:baseline;gap:9px;
    padding:0 0 4px;border-bottom:1px solid var(--hair);margin-bottom:3px}
  #stepsbody .sghead .bt{font-family:var(--font-slab);font-weight:700;font-size:12px;
    letter-spacing:1.1px;text-transform:uppercase;color:var(--ink)}
  #stepsbody .sghead .bd{font-family:var(--font-slab);font-weight:700;
    font-size:12px;color:var(--muted)}

  #stepsbody .rrow{display:flex;align-items:center;gap:9px;min-height:34px;
    padding:2px 5px;border-radius:6px}
  /* nothing dimmed — the one place showing the whole pathway, so an unrun step
     is still fully legible */
  #stepsbody .rrow.cur{background:rgba(245,196,54,.34)}
  #stepsbody .rdotwrap{flex:none;display:flex;align-items:center}
  #stepsbody .rdot{width:24px;height:24px;border-radius:50%;
    border:2px solid var(--hair);background:var(--panel);
    font-family:var(--font-slab);font-weight:700;font-size:13px;
    display:flex;align-items:center;justify-content:center;color:var(--muted)}
  /* filled means RUN — the same reading the rail's numbers use */
  #stepsbody .rrow.done .rdot{background:var(--ink);border-color:var(--ink);
    color:var(--panel)}
  #stepsbody .rrow.cur .rdot{border-color:var(--ink);color:var(--ink);
    border-width:2.5px;box-shadow:0 0 0 3px rgba(43,39,35,.1)}
  #stepsbody .rlab{flex:1;min-width:0;font-family:var(--font-sans);font-size:14px;
    line-height:1.3;color:var(--ink)}
  /* only a COMPLETED row rewinds — the others are not places you can go */
  #stepsbody .rrow.done{cursor:pointer}
  #stepsbody .rrow.done:hover .rlab{color:var(--phos)}
  /* the one-way marker: only three steps carry it, and that is the lesson */
  #stepsbody .rirr{flex:none;padding-left:6px;font-size:14px;line-height:1;
    color:var(--phos);opacity:.75}
  /* THE CARRIER CHIP: which carrier this step spent or made, which way. Page
     colours, and the ledger's sign, so the chips visibly sum to the post-it. */
  #stepsbody .rchip{flex:none;margin-left:6px;padding:1px 5px;border-radius:4px;
    font-family:var(--font-slab);font-weight:700;font-size:11px;letter-spacing:.4px;
    white-space:nowrap}
  #stepsbody .rchip.spend{color:var(--phos);background:rgba(224,123,31,.13)}
  #stepsbody .rchip.atp  {color:var(--atp); background:rgba(200,69,46,.13)}
  #stepsbody .rchip.nadh {color:var(--nad); background:rgba(47,125,92,.13)}
  /* THE STAR IS A DOOR, not a status — one step carries it, the committed one */
  #stepsbody .rstar{flex:none;margin-left:4px;padding:2px 3px;border:none;
    background:none;cursor:pointer;font-size:14px;line-height:1;
    color:var(--phos);opacity:.85}
  #stepsbody .rstar:hover{opacity:1;transform:scale(1.18)}

  /* ---------- the ending card ----------
     TWO SHAPES, AND THE DIFFERENCE IS THE SCIENCE. Glycolysis ends on a FORK —
     three fates, three doors, and what picks one is oxygen. The cycle ends on a
     HAND-OFF: nothing branches, so it is one claim with the arithmetic under
     it. Both are the last thing their lesson says, so both are a sheet of
     paper rather than a boxed readout.

     THE THREE FATES ARE A MODAL, behind the last button.
     A SHEET OF PAPER, not a boxed card: this is the last thing the lesson
     says, not another readout. One centred title, three doors, nothing else. */
  #forkmodal .mcard{width:min(880px,100%);padding:30px 34px 32px}
  #forkmodal h2{font-size:34px;text-align:center;margin:0 0 8px;letter-spacing:.2px}
  #forkmodal .msub{text-align:center;font-size:13.5px;max-width:56ch;
    margin:0 auto 22px}
  #forkbody{display:flex;gap:16px;align-items:stretch}
  /* EACH DOOR CARRIES A COLOUR, its own, so the three fates are three things at
     a glance rather than one wall of text. `--c` is set per door in the markup
     (page palette: green O₂, red lactate, orange ethanol). */
  #forkbody .door{flex:1;padding:18px 18px 20px;border-radius:14px;
    background:color-mix(in srgb, var(--c) 9%, #fff);
    border:1.5px solid color-mix(in srgb, var(--c) 30%, transparent)}
  /* the condition is the kicker, the fate is the title — what picks one is O₂ */
  #forkbody .door .dk{font-family:var(--font-slab);font-weight:700;font-size:11px;
    letter-spacing:1.4px;text-transform:uppercase;color:var(--muted);margin-bottom:2px}
  #forkbody .door .dh{font-family:var(--font-slab);font-weight:700;font-size:22px;
    line-height:1.1;color:var(--c);margin-bottom:9px}
  #forkbody .door .db{font-family:var(--font-sans);font-size:13px;line-height:1.5;color:#5a5346}
  @media (max-width:920px){ #forkmodal .mcard{padding:22px 18px 24px}
    #forkmodal h2{font-size:26px} #forkbody{flex-direction:column} }

  /* …and the hand-off. Same paper, one column: a sum across the top, read as a
     line of arithmetic, then the prose that says what it means. */
  #handoffmodal .mcard{width:min(760px,100%);padding:30px 34px 32px}
  #handoffmodal h2{font-size:34px;text-align:center;margin:0 0 8px;letter-spacing:.2px}
  #handoffmodal .msub{text-align:center;font-size:14px;max-width:52ch;margin:0 auto 22px}
  #hobody .hosum{display:flex;align-items:center;justify-content:center;
    gap:10px;flex-wrap:wrap;margin-bottom:20px}
  /* each term carries its own colour, so the sum reads as the three things the
     tally just counted rather than as a formula */
  #hobody .hoq{display:flex;flex-direction:column;align-items:center;
    min-width:88px;padding:10px 14px 9px;border-radius:12px;
    font-family:var(--font-slab);font-weight:700;font-size:13px;color:var(--c);
    background:color-mix(in srgb, var(--c) 9%, #fff);
    border:1.5px solid color-mix(in srgb, var(--c) 30%, transparent)}
  #hobody .hoq b{font-size:26px;line-height:1}
  #hobody .hoq i{font-style:normal;font-family:var(--font-sans);font-weight:400;
    font-size:10.5px;letter-spacing:.5px;color:var(--muted);margin-top:3px}
  #hobody .hoop{font-family:var(--font-slab);font-size:20px;color:var(--muted)}
  #hobody .hott{display:flex;flex-direction:column;align-items:center;
    min-width:104px;padding:10px 16px 9px;border-radius:12px;
    font-family:var(--font-slab);font-weight:700;font-size:13px;
    color:var(--paper);background:var(--ink)}
  #hobody .hott b{font-size:28px;line-height:1}
  #hobody .hott i{font-style:normal;font-family:var(--font-sans);font-weight:400;
    font-size:10.5px;letter-spacing:.5px;color:var(--paper);opacity:.75;margin-top:3px}
  #hobody .hob{font-family:var(--font-sans);font-size:13.5px;line-height:1.6;
    color:#5a5346;margin:0 0 12px}
  @media (max-width:920px){ #handoffmodal .mcard{padding:22px 18px 24px}
    #handoffmodal h2{font-size:26px} }

  @media (max-width:920px){
    /* Stacked: rail, stage, readouts. Ten numbers fit across a phone, which is
       what makes the rail survive one. */
    #app-sidebar{grid-template-rows:auto 52vh 1fr}
    #railbar{grid-column:1;padding:10px 14px 8px;flex-wrap:wrap;gap:6px}
    /* narrow: the rail needs the full row, so the title sits above it in flow */
    #pagetitle{position:static;transform:none;flex:0 0 100%;font-size:17px}
    #side{padding:0 16px 24px}
    #ledger{padding:9px 12px}
    .sticky .row{font-size:12px;padding:1.5px 0}
    .sticky .lhead{margin-bottom:5px}
    #tray{left:12px;top:16px;transform:none}
    /* Both corners tighten in; the caption band between them is narrower here. */
    #restart,#savepng,#allsteps,.btn--check{padding:5px 12px;font-size:12px}
    #utils{left:14px;bottom:16px}
    #tools{right:14px;bottom:16px;gap:8px}
    #bottombar{bottom:18px}
  }
